UNPKG

@react-three/xr

Version:

VR/AR for react-three-fiber

145 lines (144 loc) 6.66 kB
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { CombinedPointer as CombinedPointerImpl, Pointer, createGrabPointer, createLinesPointer, createRayPointer, createTouchPointer, } from '@pmndrs/pointer-events'; import { PointerCursorMaterial, PointerRayMaterial, bindPointerXRInputSourceEvent, updatePointerCursorModel, updatePointerRayModel, } from '@pmndrs/xr/internals'; import { createPortal, useFrame, useStore, useThree } from '@react-three/fiber'; import { forwardRef, useContext, useEffect, useImperativeHandle, useMemo, useRef } from 'react'; import { combinedPointerContext } from './contexts.js'; import { useXR } from './xr.js'; //for checking if `event.pointerState` is from an xr input source export { isXRInputSourceState } from '@pmndrs/xr/internals'; /** * Component for combining multiple Pointers into one so that only one pointer is active at a time * * @param props * @param props.children? - `Pointer` components to combine */ export function CombinedPointer({ children }) { const pointer = useMemo(() => new CombinedPointerImpl(false), []); useSetupPointer(pointer); return _jsx(combinedPointerContext.Provider, { value: pointer, children: children }); } function clearObject(object) { for (const key of Object.keys(object)) { delete object[key]; } } /** * Hook for creating a grab pointer */ export function useGrabPointer(spaceRef, pointerState, currentOptions, pointerType) { const options = useMemo(() => ({}), []); clearObject(options); Object.assign(options, currentOptions); const store = useStore(); const pointer = useMemo(() => createGrabPointer(() => store.getState().camera, spaceRef, pointerState, options, pointerType), [store, spaceRef, pointerState, options, pointerType]); useSetupPointer(pointer, currentOptions?.makeDefault); return pointer; } /** * Hook for creating a ray pointer */ export function useRayPointer(spaceRef, pointerState, currentOptions, pointerType) { const options = useMemo(() => ({}), []); clearObject(options); Object.assign(options, currentOptions); const store = useStore(); const pointer = useMemo(() => createRayPointer(() => store.getState().camera, spaceRef, pointerState, options, pointerType), [store, spaceRef, pointerState, options, pointerType]); useSetupPointer(pointer, currentOptions?.makeDefault); return pointer; } /** * Hook for creating a ray pointer */ export function useLinesPointer(spaceRef, pointerState, currentOptions, pointerType) { const options = useMemo(() => ({}), []); clearObject(options); Object.assign(options, currentOptions); const store = useStore(); const pointer = useMemo(() => createLinesPointer(() => store.getState().camera, spaceRef, pointerState, options, pointerType), [store, spaceRef, pointerState, options, pointerType]); useSetupPointer(pointer, currentOptions?.makeDefault); return pointer; } /** * Hook for creating a touch pointer */ export function useTouchPointer(spaceRef, pointerState, currentOptions, pointerType) { const options = useMemo(() => ({}), []); clearObject(options); Object.assign(options, currentOptions); const store = useStore(); const pointer = useMemo(() => createTouchPointer(() => store.getState().camera, spaceRef, pointerState, options, pointerType), [store, spaceRef, pointerState, options, pointerType]); useSetupPointer(pointer, currentOptions?.makeDefault); return pointer; } /** * Component for rendering a ray for a pointer * @param props * #### `materialClass` - Material to use for the ray * #### `pointer` - Pointer to use for the ray * #### `renderOrder` - Render order for the ray * @function */ export const PointerRayModel = forwardRef((props, ref) => { const material = useMemo(() => { const MaterialClass = props.materialClass ?? PointerRayMaterial; return new MaterialClass(); }, [props.materialClass]); const internalRef = useRef(null); useImperativeHandle(ref, () => internalRef.current, []); useFrame(() => internalRef.current != null && updatePointerRayModel(internalRef.current, material, props.pointer, props)); return (_jsx("mesh", { matrixAutoUpdate: false, renderOrder: props.renderOrder ?? 2, ref: internalRef, material: material, children: _jsx("boxGeometry", {}) })); }); /** * Component for rendering a cursor as a pointer * * @param props * #### `materialClass` - Class of the material to use for the cursor * #### `pointer` - Pointer to use for the cursor * #### `renderOrder` - Render order for the cursor * @function */ export const PointerCursorModel = forwardRef((props, ref) => { const material = useMemo(() => { const MaterialClass = props.materialClass ?? PointerCursorMaterial; return new MaterialClass(); }, [props.materialClass]); const internalRef = useRef(null); const groupRef = useRef(null); useImperativeHandle(ref, () => internalRef.current, []); useFrame(() => internalRef.current != null && groupRef.current != null && updatePointerCursorModel(groupRef.current, internalRef.current, material, props.pointer, props)); const scene = useThree((s) => s.scene); return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef }), createPortal(_jsx("mesh", { renderOrder: props.renderOrder ?? 1, ref: internalRef, matrixAutoUpdate: false, material: material, children: _jsx("planeGeometry", {}) }), scene)] })); }); /** * Hook for binding the xr session events such as `selectstart` to the provided pointer down/up events */ export function usePointerXRInputSourceEvents(pointer, inputSource, event, missingEvents) { const session = useXR((xr) => xr.session); useEffect(() => { if (session == null) { return; } return bindPointerXRInputSourceEvent(pointer, session, inputSource, event, missingEvents); }, [event, inputSource, pointer, session, missingEvents]); } function useSetupPointer(pointer, makeDefault = false) { const combinedPointer = useContext(combinedPointerContext); if (combinedPointer == null) { throw new Error(`xr pointers can only be used inside the XR component`); } useEffect(() => { const unregister = combinedPointer.register(pointer, makeDefault); return () => { unregister(); }; }, [combinedPointer, pointer, makeDefault]); useEffect(() => { if (!(pointer instanceof Pointer)) { return; } return () => pointer.exit({ timeStamp: performance.now() }); }, [pointer]); }