UNPKG

@threlte/xr

Version:

Tools to more easily create VR and AR experiences with Threlte

92 lines (91 loc) 3.55 kB
class InputSourcesState { current = $state.raw([]); } export const inputSources = new InputSourcesState(); const subscribers = new Set(); /** * Registers callbacks with the module-level XR input-source dispatcher. * * This does not subscribe to a specific `XRInputSource`, `XRSession`, or * Three.js object. Instead, the subscriber is stored in the internal * `subscribers` set and receives events for whichever current input-source * state matches its `type` and `handedness`. * * For example, a `{ type: 'controller', handedness: 'left' }` subscriber will * receive forwarded events for the current left controller, even if the * underlying `XRInputSource` instance disconnects and reconnects. * * Returns a cleanup function that removes the subscriber from the dispatcher. */ export const addSubscriber = (sub) => { subscribers.add(sub); return () => { subscribers.delete(sub); }; }; export const dispatchEvent = (state, eventType, event) => { const key = `on${eventType}`; for (const sub of subscribers) { if (sub.type !== state.type) continue; if (sub.handedness !== state.handedness) continue; const cb = sub.callbacks[key]; cb?.(event); } }; const dispatchSpaceEvent = (state, event) => { state.targetRay.dispatchEvent(event); if (state.type === 'controller') { state.grip.dispatchEvent(event); } if (state.type === 'hand') { state.hand.dispatchEvent(event); } }; export const createInputSourceEvent = (state, type, extra = {}) => { return { type, data: state.inputSource, inputSource: state.inputSource, target: state.type === 'hand' ? state.hand : state.targetRay, ...extra }; }; export const dispatchInputSourceStateEvent = (state, eventType, event, options = {}) => { if (options.dispatchSpaces !== false) { dispatchSpaceEvent(state, event); } dispatchEvent(state, eventType, event); }; const getPreferredState = (predicate, options) => { if (options?.isPrimary !== undefined) { return inputSources.current.find((state) => state.isPrimary === options.isPrimary && predicate(state)); } return (inputSources.current.find((state) => state.isPrimary && predicate(state)) ?? inputSources.current.find(predicate)); }; export const getInputSourceState = (inputSource, options) => { if (options?.isPrimary !== undefined) { return inputSources.current.find((state) => state.inputSource === inputSource && state.isPrimary === options.isPrimary); } return inputSources.current.find((state) => state.inputSource === inputSource); }; export const getControllerState = (handedness, options) => { return getPreferredState((state) => state.type === 'controller' && state.handedness === handedness, options); }; export const getHandState = (handedness, options) => { return getPreferredState((state) => state.type === 'hand' && state.handedness === handedness, options); }; export const dispatchInputSourceEvent = (inputSource, eventType, event) => { const state = getInputSourceState(inputSource); if (state === undefined) return; dispatchInputSourceStateEvent(state, eventType, event); }; export const dispatchXRInputSourceEvent = (event) => { const state = getInputSourceState(event.inputSource); if (state === undefined) return; dispatchInputSourceStateEvent(state, event.type, createInputSourceEvent(state, event.type, { frame: event.frame })); };