UNPKG

@pmndrs/xr

Version:
117 lines (116 loc) 5.43 kB
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); }; }