@pmndrs/xr
Version:
VR/AR for threejs
117 lines (116 loc) • 5.43 kB
JavaScript
import { CombinedPointer } from '@pmndrs/pointer-events';
import { Group } from 'three';
import { resolveInputSourceImplementation } from '../store.js';
import { createDefaultXRController, createDefaultXRGaze, createDefaultXRHand, createDefaultXRScreenInput, createDefaultXRTransientPointer, } from './default.js';
import { XRSpace } from './space.js';
import { setupSyncIsVisible } from '../visible.js';
export function setupSyncXRElements(scene, getCamera, store, target, updatesList) {
const combined = new CombinedPointer(true);
const onFrame = () => combined.move(scene, { timeStamp: performance.now() });
updatesList.push(onFrame);
setupSyncIsVisible(store, (visible) => combined.setEnabled(visible, { timeStamp: performance.now() }));
const inputGroup = new Group();
const syncControllers = setupSyncInputSourceElements(createDefaultXRController, scene, getCamera, store, 'controller', inputGroup, updatesList, combined);
const syncGazes = setupSyncInputSourceElements(createDefaultXRGaze, scene, getCamera, store, 'gaze', inputGroup, updatesList, combined);
const syncHands = setupSyncInputSourceElements(createDefaultXRHand, scene, getCamera, store, 'hand', inputGroup, updatesList, combined);
const syncScreenInputs = setupSyncInputSourceElements(createDefaultXRScreenInput, scene, getCamera, store, 'screenInput', inputGroup, updatesList, combined);
const syncTransientPointers = setupSyncInputSourceElements(createDefaultXRTransientPointer, scene, getCamera, store, 'transientPointer', inputGroup, updatesList, combined);
const unsubscribe = store.subscribe((s, prev) => {
inputGroup.visible = s.visibilityState === 'visible';
syncControllers(s.session, s.inputSourceStates, prev.inputSourceStates, s.controller, prev.controller);
syncGazes(s.session, s.inputSourceStates, prev.inputSourceStates, s.gaze, prev.gaze);
syncHands(s.session, s.inputSourceStates, prev.inputSourceStates, s.hand, prev.hand);
syncScreenInputs(s.session, s.inputSourceStates, prev.inputSourceStates, s.screenInput, prev.screenInput);
syncTransientPointers(s.session, s.inputSourceStates, prev.inputSourceStates, s.transientPointer, prev.transientPointer);
});
target.add(inputGroup);
return () => {
const index = updatesList.indexOf(onFrame);
if (index === -1) {
return;
}
updatesList.splice(index, 1);
target.remove(inputGroup);
unsubscribe();
syncControllers(undefined, [], [], false, false);
syncGazes(undefined, [], [], false, false);
syncHands(undefined, [], [], false, false);
syncScreenInputs(undefined, [], [], false, false);
syncTransientPointers(undefined, [], [], false, false);
};
}
function setupSyncInputSourceElements(defaultCreate, scene, getCamera, store, key, target, updatesList, combined) {
return setupSync(key, (session, state, implementationInfo) => runInXRUpdatesListContext(updatesList, () => {
const implementation = resolveInputSourceImplementation(implementationInfo, state.inputSource.handedness, {});
if (implementation === false) {
return;
}
const spaceObject = new XRSpace(state.inputSource.targetRaySpace);
target.add(spaceObject);
const customCleanup = typeof implementation === 'object'
? defaultCreate(scene, getCamera, spaceObject, state, session, implementation, combined)
: implementation?.(store, spaceObject, state, session);
return () => {
target.remove(spaceObject);
customCleanup?.();
};
}));
}
function setupSync(key, create) {
let cleanupMap = new Map();
return (session, values, prevValues, impl, prevImpl) => {
if (values === prevValues && impl === prevImpl) {
return;
}
if (impl != prevImpl) {
cleanup(cleanupMap);
}
const newCleanupMap = new Map();
const valuesLength = values.length;
if (session != null) {
for (let i = 0; i < valuesLength; i++) {
const value = values[i];
if (value.type != key) {
continue;
}
let cleanup = cleanupMap.get(value);
const wasCreated = cleanupMap.delete(value);
if (!wasCreated) {
cleanup = create(session, value, impl);
}
newCleanupMap.set(value, cleanup);
}
}
cleanup(cleanupMap);
cleanupMap = newCleanupMap;
};
}
function cleanup(map) {
for (const cleanup of map.values()) {
cleanup?.();
}
map.clear();
}
export let xrUpdatesListContext;
function runInXRUpdatesListContext(updatesList, fn) {
const innerUpdatesList = [];
const update = (frame, delta) => {
const length = innerUpdatesList.length;
for (let i = 0; i < length; i++) {
innerUpdatesList[i](frame, delta);
}
};
updatesList.push(update);
const prev = xrUpdatesListContext;
xrUpdatesListContext = innerUpdatesList;
const cleanup = fn();
xrUpdatesListContext = prev;
return () => {
cleanup?.();
const index = updatesList.indexOf(update);
if (index === -1) {
return;
}
updatesList.splice(index, 1);
};
}