@react-three/xr
Version:
VR/AR for react-three-fiber
145 lines (144 loc) • 6.66 kB
JavaScript
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]);
}