UNPKG

@pmndrs/xr

Version:
265 lines (264 loc) 13.4 kB
import { CombinedPointer, createGrabPointer, createRayPointer, createTouchPointer, createLinesPointer, } from '@pmndrs/pointer-events'; import { Group } from 'three'; import { XRHandModel } from './hand.js'; import { PointerRayModel, PointerCursorModel } from './pointer.js'; import { XRSpace } from './space.js'; import { buildTeleportTargetFilter, createTeleportRayLine, syncTeleportPointerRayGroup, TeleportPointerRayModel, } from '../internals.js'; import { XRControllerModel } from './controller.js'; import { onXRFrame } from './utils.js'; import { bindPointerXRInputSourceEvent, defaultGrabPointerOpacity, defaultRayPointerOpacity, defaultTouchPointerOpacity, } from '../pointer/index.js'; export function createDefaultXRInputSourceRayPointer(scene, getCamera, space, state, session, options, combined, makeDefault) { //the space must be created before the pointer to make sure that the space is updated before the pointer const raySpace = new XRSpace(state.inputSource.targetRaySpace); const pointer = createRayPointer(getCamera, { current: raySpace }, state, options); const unregister = combined.register(pointer, makeDefault); const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, 'select', state.events); space.add(raySpace); let undoAddRayModel; const { rayModel: rayModelOptions = true, cursorModel: cursorModelOptions = true } = options ?? {}; if (rayModelOptions !== false) { const rayModel = new PointerRayModel(pointer, { opacity: defaultRayPointerOpacity, ...spreadable(rayModelOptions) }); raySpace.add(rayModel); undoAddRayModel = () => raySpace.remove(rayModel); } let undoAddCursorModel; if (cursorModelOptions !== false) { const cursorModel = new PointerCursorModel(raySpace, pointer, { opacity: defaultRayPointerOpacity, ...spreadable(cursorModelOptions), }); scene.add(cursorModel); undoAddCursorModel = () => scene.remove(cursorModel); } return () => { pointer.exit({ timeStamp: performance.now() }); space.remove(raySpace); undoAddRayModel?.(); undoAddCursorModel?.(); unbind(); unregister(); }; } export function createDefaultXRInputSourceTeleportPointer(scene, getCamera, space, state, session, options, combined, makeDefault) { //the space must be created before the pointer to make sure that the space is updated before the pointer const raySpace = new XRSpace(state.inputSource.targetRaySpace); space.add(raySpace); const teleportPointerRayGroup = new Group(); scene.add(teleportPointerRayGroup); onXRFrame((_, delta) => syncTeleportPointerRayGroup(raySpace, teleportPointerRayGroup, delta)); const linePoints = createTeleportRayLine(); const pointer = createLinesPointer(getCamera, { current: teleportPointerRayGroup }, state, { ...options, filter: buildTeleportTargetFilter(options), linePoints }, 'teleport'); const unregister = combined.register(pointer, makeDefault); const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, 'select', state.events); let undoAddRayModel; const { rayModel: rayModelOptions = true, cursorModel: cursorModelOptions = true } = options ?? {}; if (rayModelOptions !== false) { const rayModel = new TeleportPointerRayModel(linePoints); rayModel.options = { opacity: defaultRayPointerOpacity, ...spreadable(rayModelOptions) }; onXRFrame(() => rayModel.update(pointer)); teleportPointerRayGroup.add(rayModel); undoAddRayModel = () => teleportPointerRayGroup.remove(rayModel); } let undoAddCursorModel; if (cursorModelOptions !== false) { const cursorModel = new PointerCursorModel(raySpace, pointer, { opacity: defaultRayPointerOpacity, ...spreadable(cursorModelOptions), }); onXRFrame(() => (cursorModel.visible = pointer.getEnabled() && pointer.getButtonsDown().size > 0)); scene.add(cursorModel); undoAddCursorModel = () => scene.remove(cursorModel); } return () => { pointer.exit({ timeStamp: performance.now() }); space.remove(raySpace); scene.add(teleportPointerRayGroup); undoAddRayModel?.(); undoAddCursorModel?.(); unbind(); unregister(); }; } export function createDefaultXRInputSourceGrabPointer(scene, getCamera, space, state, gripSpace, session, event, options, combined, makeDefault) { //the space must be created before the pointer to make sure that the space is updated before the pointer const gripSpaceObject = new XRSpace(gripSpace); const pointer = createGrabPointer(getCamera, { current: gripSpaceObject }, state, options); const unregister = combined.register(pointer, makeDefault); const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, event, state.events); space.add(gripSpaceObject); let undoAddCursorModel; if (options?.cursorModel !== false) { const cursorModel = new PointerCursorModel(gripSpaceObject, pointer, { opacity: defaultGrabPointerOpacity, }); scene.add(cursorModel); undoAddCursorModel = () => scene.remove(cursorModel); } return () => { unregister(); pointer.exit({ timeStamp: performance.now() }); space.remove(gripSpaceObject); undoAddCursorModel?.(); unbind(); }; } export function createDefaultXRHandTouchPointer(scene, getCamera, space, state, options, combined, makeDefault) { //the space must be created before the pointer to make sure that the space is updated before the pointer const touchSpaceObject = new XRSpace(state.inputSource.hand.get('index-finger-tip')); const pointer = createTouchPointer(getCamera, { current: touchSpaceObject }, state, options); const unregister = combined.register(pointer, makeDefault); space.add(touchSpaceObject); let undoAddCursorModel; const { cursorModel: cursorModelOptions = true } = options ?? {}; if (cursorModelOptions !== false) { const cursorModel = new PointerCursorModel(touchSpaceObject, pointer, { opacity: defaultTouchPointerOpacity, ...spreadable(cursorModelOptions), }); scene.add(cursorModel); undoAddCursorModel = () => scene.remove(cursorModel); } return () => { unregister(); pointer.exit({ timeStamp: performance.now() }); space.remove(touchSpaceObject); undoAddCursorModel?.(); }; } export function createDefaultXRHand(scene, getCamera, space, state, session, { grabPointer: grabPointerOptions = true, rayPointer: rayPointerOptions = true, teleportPointer: teleportPointerOptions = false, model: modelOptions = true, touchPointer: touchPointerOptions = true, } = {}, combined) { const combinedPointer = new CombinedPointer(false); const unregisterPointer = combined.register(combinedPointer); let destroyRayPointer; if (rayPointerOptions !== false) { const rayPointerRayModelOptions = spreadable(rayPointerOptions)?.rayModel; destroyRayPointer = createDefaultXRInputSourceRayPointer(scene, getCamera, space, state, session, { minDistance: 0.2, ...spreadable(rayPointerOptions), rayModel: rayPointerRayModelOptions === false ? false : { maxLength: 0.2, ...spreadable(rayPointerRayModelOptions), }, }, combinedPointer, true); } const destroyTeleportPointer = teleportPointerOptions === false ? undefined : createDefaultXRInputSourceTeleportPointer(scene, getCamera, space, state, session, spreadable(teleportPointerOptions), combinedPointer); const destroyGrabPointer = grabPointerOptions === false ? undefined : createDefaultXRInputSourceGrabPointer(scene, getCamera, space, state, state.inputSource.hand.get('index-finger-tip'), session, 'select', spreadable(grabPointerOptions), combinedPointer); const destroyTouchPointer = touchPointerOptions === false ? undefined : createDefaultXRHandTouchPointer(scene, getCamera, space, state, spreadable(touchPointerOptions), combinedPointer); let removeModel; if (modelOptions !== false) { const model = new XRHandModel(state, spreadable(modelOptions)); space.add(model); removeModel = () => space.remove(model); } return () => { unregisterPointer(); destroyRayPointer?.(); destroyGrabPointer?.(); destroyTouchPointer?.(); destroyTeleportPointer?.(); removeModel?.(); }; } export function createDefaultXRController(scene, getCamera, space, state, session, { rayPointer: rayPointerOptions = true, grabPointer: grabPointerOptions = true, teleportPointer: teleportPointerOptions = false, model: modelOptions = true, } = {}, combined) { const combinedPointer = new CombinedPointer(true); const unregisterPointer = combined.register(combinedPointer); const destroyRayPointer = rayPointerOptions === false ? undefined : createDefaultXRInputSourceRayPointer(scene, getCamera, space, state, session, { minDistance: 0.2, ...spreadable(rayPointerOptions) }, combinedPointer, true); const destroyTeleportPointer = teleportPointerOptions === false ? undefined : createDefaultXRInputSourceTeleportPointer(scene, getCamera, space, state, session, spreadable(teleportPointerOptions), combinedPointer); const destroyGrabPointer = grabPointerOptions === false ? undefined : createDefaultXRInputSourceGrabPointer(scene, getCamera, space, state, state.inputSource.gripSpace, session, 'squeeze', spreadable(grabPointerOptions), combinedPointer); let removeModel; if (modelOptions !== false) { const model = new XRControllerModel(state, spreadable(modelOptions)); space.add(model); removeModel = () => space.remove(model); } return () => { unregisterPointer(); destroyTeleportPointer?.(); destroyRayPointer?.(); destroyGrabPointer?.(); removeModel?.(); }; } export function createDefaultXRTransientPointer(scene, getCamera, space, state, session, options, combined) { //the space must be created before the pointer to make sure that the space is updated before the pointer const raySpace = new XRSpace(state.inputSource.targetRaySpace); const pointer = createRayPointer(getCamera, { current: raySpace }, state, options); const unregister = combined.register(pointer); const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, 'select', state.events); space.add(raySpace); let undoAddCursorModel; const { cursorModel: cursorModelOptions = true } = options ?? {}; if (cursorModelOptions !== false) { const cursorModel = new PointerCursorModel(raySpace, pointer, { opacity: defaultRayPointerOpacity, ...spreadable(cursorModelOptions), }); scene.add(cursorModel); undoAddCursorModel = () => scene.remove(cursorModel); } return () => { unregister(); pointer.exit({ timeStamp: performance.now() }); space.remove(raySpace); undoAddCursorModel?.(); unbind(); }; } export function createDefaultXRGaze(scene, getCamera, space, state, session, options, combined) { //the space must be created before the pointer to make sure that the space is updated before the pointer const raySpace = new XRSpace(state.inputSource.targetRaySpace); const pointer = createRayPointer(getCamera, { current: raySpace }, state, options); const unregister = combined.register(pointer); const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, 'select', state.events); space.add(raySpace); let undoAddCursorModel; const { cursorModel: cursorModelOptions = true } = options ?? {}; if (cursorModelOptions !== false) { const cursorModel = new PointerCursorModel(raySpace, pointer, { opacity: defaultRayPointerOpacity, ...spreadable(cursorModelOptions), }); scene.add(cursorModel); undoAddCursorModel = () => scene.remove(cursorModel); } return () => { unregister(); pointer.exit({ timeStamp: performance.now() }); space.remove(raySpace); undoAddCursorModel?.(); unbind(); }; } export function createDefaultXRScreenInput(scene, getCamera, space, state, session, options, combined) { //the space must be created before the pointer to make sure that the space is updated before the pointer const raySpace = new XRSpace(state.inputSource.targetRaySpace); const pointer = createRayPointer(getCamera, { current: raySpace }, state, options); const unregister = combined.register(pointer); const unbind = bindPointerXRInputSourceEvent(pointer, session, state.inputSource, 'select', state.events); space.add(raySpace); return () => { unregister(); space.remove(raySpace); pointer.exit({ timeStamp: performance.now() }); unbind(); }; } function spreadable(value) { if (value === true) { return undefined; } return value; }