@pmndrs/xr
Version:
VR/AR for threejs
265 lines (264 loc) • 13.4 kB
JavaScript
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;
}