UNPKG

@react-three/xr

Version:

VR/AR for react-three-fiber

223 lines (222 loc) 14.4 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { buildTeleportTargetFilter, createTeleportRayLine, defaultGrabPointerOpacity, defaultRayPointerOpacity, defaultTouchPointerOpacity, syncTeleportPointerRayGroup, } from '@pmndrs/xr/internals'; import { createPortal, useFrame, useThree } from '@react-three/fiber'; import { Suspense, useContext, useMemo, useRef } from 'react'; import { xrInputSourceStateContext } from './contexts.js'; import { XRControllerModel } from './controller.js'; import { XRHandModel } from './hand.js'; import { useXRInputSourceStateContext } from './input.js'; import { CombinedPointer, PointerCursorModel, PointerRayModel, useGrabPointer, useLinesPointer, usePointerXRInputSourceEvents, useRayPointer, useTouchPointer, } from './pointer.js'; import { XRSpace as XRSpaceImpl } from './space.js'; import { TeleportPointerRayModel } from './teleport.js'; export { defaultGrabPointerOpacity, defaultRayPointerOpacity, defaultTouchPointerOpacity, } from '@pmndrs/xr/internals'; function DefaultXRInputSourceGrabPointer(event, spaceType, options) { const state = useContext(xrInputSourceStateContext); if (state == null) { throw new Error(`DefaultXRInputSourceGrabPointer can only be used inside a XRInputSource`); } const ref = useRef(null); const pointer = useGrabPointer(ref, state, options); usePointerXRInputSourceEvents(pointer, state.inputSource, event, state.events); const cursorModelOptions = options.cursorModel; return (_jsx(XRSpaceImpl, { ref: ref, space: spaceType, children: cursorModelOptions !== false && (_jsx(PointerCursorModel, { pointer: pointer, opacity: defaultGrabPointerOpacity, ...spreadable(cursorModelOptions) })) })); } /** * Grab pointer for the XRHand * * @param {DefaultXRInputSourceGrabPointerOptions} props * #### `clickThresholdMs` - Time in milliseconds between `pointerdown` and `pointerup` to trigger a click event * #### `dblClickThresholdMs` - Time in milliseconds between the first click and the second click to trigger a `dblclick` event * #### `contextMenuButton` - The button that triggers context menu events * #### `makeDefault` - Used the set the default pointer inside a combined pointer * #### `cursorModel` - Properties for configuring how the cursor should look * #### `radius` - The size of the intersection sphere * #### `customSort` - Overrides the default sort function to use for sorting the intersection results */ export const DefaultXRHandGrabPointer = DefaultXRInputSourceGrabPointer.bind(null, 'select', 'index-finger-tip'); /** * Grab pointer for the XRController * * @param {DefaultXRInputSourceGrabPointerOptions} props * #### `clickThresholdMs` - Time in milliseconds between `pointerdown` and `pointerup` to trigger a click event * #### `dblClickThresholdMs` - Time in milliseconds between the first click and the second click to trigger a `dblclick` event * #### `contextMenuButton` - The button that triggers context menu events * #### `makeDefault` - Used the set the default pointer inside a combined pointer * #### `cursorModel` - Properties for configuring how the cursor should look * #### `radius` - The size of the intersection sphere * #### `customSort` - Overrides the default sort function to use for sorting the intersection results */ export const DefaultXRControllerGrabPointer = DefaultXRInputSourceGrabPointer.bind(null, 'squeeze', 'grip-space'); /** * Ray pointer for the XRInputSource * * @param {DefaultXRInputSourceRayPointerOptions} props * #### `clickThresholdMs` - Time in milliseconds between pointerdown and pointerup to trigger a click event * #### `dblClickThresholdMs` - Time in milliseconds between the first click and the second click to trigger a dblclick event * #### `contextMenuButton` - The button that triggers contextmenu events * #### `makeDefault` - Used the set the default pointer inside a combined pointer * #### `radius` - The size of the intersection sphere * #### `minDistance` - Minimal distance to trigger interactions * #### `linePoints` - The points thay make up the shape of the ray if undefined the ray goes in a straight line * #### `direction` - The direction of the ray * #### `rayModel` - Properties for configuring how the ray should look * #### `cursorModel` - Properties for configuring how the cursor should look */ export function DefaultXRInputSourceRayPointer(props) { const state = useXRInputSourceStateContext(); const ref = useRef(null); const pointer = useRayPointer(ref, state, props); usePointerXRInputSourceEvents(pointer, state.inputSource, 'select', state.events); const rayModelOptions = props.rayModel; const cursorModelOptions = props.cursorModel; return (_jsxs(XRSpaceImpl, { ref: ref, space: "target-ray-space", children: [rayModelOptions !== false && (_jsx(PointerRayModel, { pointer: pointer, opacity: defaultRayPointerOpacity, ...spreadable(rayModelOptions) })), cursorModelOptions !== false && (_jsx(PointerCursorModel, { pointer: pointer, opacity: defaultRayPointerOpacity, ...spreadable(cursorModelOptions) }))] })); } /** * Touch pointer for the XRHand * * @param {DefaultXRHandTouchPointerOptions} props * #### `clickThresholdMs` - Time in milliseconds between `pointerdown` and `pointerup` to trigger a click event * #### `dblClickThresholdMs` - Time in milliseconds between the first click and the second click to trigger a `dblclick` event * #### `contextMenuButton` - The button that triggers context menu events * #### `makeDefault` - Used the set the default pointer inside a combined pointer * #### `cursorModel` - Properties for configuring how the cursor should look * #### `hoverRadius` - The size of the intersection sphere * #### `downRadius` - The distance to the touch center to trigger a `pointerdown` event * #### `button` - The id of the button that is triggered when touching */ export function DefaultXRHandTouchPointer(props) { const state = useXRInputSourceStateContext('hand'); const ref = useRef(null); const pointer = useTouchPointer(ref, state, props); const cursorModelOptions = props.cursorModel; return (_jsx(XRSpaceImpl, { ref: ref, space: state.inputSource.hand.get('index-finger-tip'), children: cursorModelOptions !== false && (_jsx(PointerCursorModel, { pointer: pointer, opacity: defaultTouchPointerOpacity, ...spreadable(cursorModelOptions) })) })); } /** * Default controller implementation with grab and ray pointers * * @param {DefaultXRControllerOptions} props * #### `model` - Options for configuring the controller apperance * #### `grabPointer` - Options for configuring the grab pointer * #### `rayPointer` - Options for configuring the ray pointer */ export function DefaultXRController(props) { const modelOptions = props.model; const grabPointerOptions = props.grabPointer; const rayPointerOptions = props.rayPointer; const teleportPointerOptions = props.teleportPointer ?? false; return (_jsxs(_Fragment, { children: [modelOptions !== false && (_jsx(Suspense, { children: _jsx(XRControllerModel, { ...spreadable(modelOptions) }) })), _jsxs(CombinedPointer, { children: [grabPointerOptions !== false && _jsx(DefaultXRControllerGrabPointer, { ...spreadable(grabPointerOptions) }), rayPointerOptions !== false && (_jsx(DefaultXRInputSourceRayPointer, { makeDefault: true, minDistance: 0.2, ...spreadable(rayPointerOptions) })), teleportPointerOptions !== false && (_jsx(DefaultXRInputSourceTeleportPointer, { ...spreadable(teleportPointerOptions) }))] })] })); } /** * Default hand implementation with touch, grab and ray pointers * * @param {DefaultXRHandOptions} props * #### `model` - Options for configuring the hand appearance * #### `grabPointer` - Options for configuring the grab pointer * #### `rayPointer` - Options for configuring the ray pointer * #### `touchPointer` - Options for configuring the touch pointer */ export function DefaultXRHand(props) { const modelOptions = props.model; const grabPointerOptions = props.grabPointer; const rayPointerOptions = props.rayPointer; const touchPointerOptions = props.touchPointer; const teleportPointerOptions = props.teleportPointer ?? false; const rayPointerRayModelOptions = rayPointerOptions === false ? false : spreadable(rayPointerOptions)?.rayModel; return (_jsxs(_Fragment, { children: [modelOptions !== false && (_jsx(Suspense, { children: _jsx(XRHandModel, { ...spreadable(modelOptions) }) })), _jsxs(CombinedPointer, { children: [grabPointerOptions !== false && _jsx(DefaultXRHandGrabPointer, { ...spreadable(grabPointerOptions) }), touchPointerOptions !== false && _jsx(DefaultXRHandTouchPointer, { ...spreadable(touchPointerOptions) }), rayPointerOptions !== false && (_jsx(DefaultXRInputSourceRayPointer, { makeDefault: true, minDistance: 0.2, ...spreadable(rayPointerOptions), rayModel: rayPointerRayModelOptions === false ? false : { maxLength: 0.2, ...spreadable(rayPointerRayModelOptions) } })), teleportPointerOptions !== false && (_jsx(DefaultXRInputSourceTeleportPointer, { ...spreadable(teleportPointerOptions) }))] })] })); } /** * Default transient-pointer implementation with ray pointer * * @param {DefaultXRTransientPointerOptions} props * #### `clickThresholdMs` - Time in milliseconds between `pointerdown` and `pointerup` to trigger a click event * #### `dblClickThresholdMs` - Time in milliseconds between the first click and the second click to trigger a `dblclick` event * #### `contextMenuButton` - The button that triggers context menu events * #### `minDistance` - Minimal distance to trigger interactions * #### `linePoints` - The points thay make up the shape of the ray if undefined the ray goes in a straight line * #### `direction` - The direction of the ray * #### `cursorModel` - Properties for configuring how the cursor should look */ export function DefaultXRTransientPointer(props) { return _jsx(DefaultXRInputSourceRayPointer, { ...props, rayModel: false }); } /** * Default gaze implementation with ray pointer * * @param {DefaultXRGazeOptions} props * #### `clickThresholdMs` - Time in milliseconds between `pointerdown` and `pointerup` to trigger a click event * #### `dblClickThresholdMs` - Time in milliseconds between the first click and the second click to trigger a `dblclick` event * #### `contextMenuButton` - The button that triggers context menu events * #### `minDistance` - Minimal distance to trigger interactions * #### `linePoints` - The points thay make up the shape of the ray if undefined the ray goes in a straight line * #### `direction` - The direction of the ray * #### `cursorModel` - Properties for configuring how the cursor should look */ export function DefaultXRGaze(props) { return _jsx(DefaultXRInputSourceRayPointer, { ...props, rayModel: false }); } /** * Default screen-input implementation with ray pointer * * @param {DefaultXRScreenInputOptions} props * #### `clickThresholdMs` - Time in milliseconds between `pointerdown` and `pointerup` to trigger a click event * #### `dblClickThresholdMs` - Time in milliseconds between the first click and the second click to trigger a `dblclick` event * #### `contextMenuButton` - The button that triggers context menu events * #### `minDistance` - Minimal distance to trigger interactions * #### `linePoints` - The points thay make up the shape of the ray if undefined the ray goes in a straight line * #### `direction` - The direction of the ray */ export function DefaultXRScreenInput(props) { return _jsx(DefaultXRInputSourceRayPointer, { ...props, cursorModel: false, rayModel: false }); } /** * Telport pointer for the XRInputSource. * Emits a downward bend ray that only interesects with meshes marked as teleportable * * @param {DefaultXRInputSourceTeleportPointerOptions} props * #### `clickThresholdMs` - Time in milliseconds between `pointerdown` and `pointerup` to trigger a click event * #### `dblClickThresholdMs` - Time in milliseconds between the first click and the second click to trigger a `dblclick` event * #### `contextMenuButton` - The button that triggers context menu events * #### `makeDefault` - Used the set the default pointer inside a combined pointer * #### `radius` - The size of the intersection sphere * #### `minDistance` - Minimal distance to trigger interactions * #### `direction` - The direction of the ray * #### `rayModel` - Properties for configuring how the ray should look * #### `cursorModel` - Properties for configuring how the cursor should look */ export function DefaultXRInputSourceTeleportPointer(props) { const state = useContext(xrInputSourceStateContext); if (state == null) { throw new Error(`DefaultXRInputSourceRayPointer can only be used inside a XRInputSource`); } const ref = useRef(null); const groupRef = useRef(null); const linePoints = useMemo(() => createTeleportRayLine(), []); const pointer = useLinesPointer(groupRef, state, { ...props, linePoints, filter: buildTeleportTargetFilter(props), }, 'teleport'); usePointerXRInputSourceEvents(pointer, state.inputSource, 'select', state.events); const rayModelOptions = props.rayModel; const cursorModelOptions = props.cursorModel; const scene = useThree((state) => state.scene); const cursorRef = useRef(null); useFrame((_, delta) => { if (cursorRef.current != null) { cursorRef.current.visible = pointer.getEnabled() && pointer.getButtonsDown().size > 0; } const target = groupRef.current; const source = ref.current; if (target == null || source == null) { return; } syncTeleportPointerRayGroup(source, target, delta * 1000); }); return (_jsxs(_Fragment, { children: [_jsx(XRSpaceImpl, { ref: ref, space: "target-ray-space" }), createPortal(_jsxs("group", { ref: groupRef, children: [rayModelOptions !== false && (_jsx(TeleportPointerRayModel, { linePoints: linePoints, pointer: pointer, opacity: defaultRayPointerOpacity, ...spreadable(rayModelOptions) })), cursorModelOptions !== false && (_jsx(PointerCursorModel, { ref: cursorRef, pointer: pointer, opacity: defaultRayPointerOpacity, ...spreadable(cursorModelOptions) }))] }), scene)] })); } function spreadable(value) { if (value === true) { return undefined; } return value; }