@react-three/xr
Version:
VR/AR for react-three-fiber
206 lines (205 loc) • 8.89 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { createGetXRSpaceMatrix } from '@pmndrs/xr/internals';
import { useFrame } from '@react-three/fiber';
import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
import { xrSpaceContext } from './contexts.js';
import { useXRInputSourceStateContext } from './input.js';
import { useXR } from './xr.js';
/**
* Component that puts its children in the provided XRSpace (or reference space type)
*
* @param props
* #### `space` - [XRSpaceType](https://developer.mozilla.org/en-US/docs/Web/API/XRSpace) | [XRSpaceType](https://pmndrs.github.io/xr/docs/api/space.XRSpaceType)
* #### `children` - [ReactNode](https://reactjs.org/docs/introducing-jsx.html#react-jsx)
* @function
*/
export const XRSpace = forwardRef(({ space, children }, ref) => {
const internalRef = useRef(null);
// eslint-disable-next-line react-hooks/rules-of-hooks
const resolvedSpace = typeof space === 'string' ? useXRSpace(space) : space;
useImperativeHandle(ref, () => internalRef.current, []);
useApplyXRSpaceMatrix(internalRef, resolvedSpace);
const setRef = useCallback((group) => {
if (group != null) {
group.transformReady = false;
group.visible = false;
}
internalRef.current = group;
}, []);
return (_jsx("group", { xrSpace: resolvedSpace, matrixAutoUpdate: false, ref: setRef, children: resolvedSpace && _jsx(xrSpaceContext.Provider, { value: resolvedSpace, children: children }) }));
});
export function useXRSpace(type) {
switch (type) {
case 'grip-space':
// eslint-disable-next-line react-hooks/rules-of-hooks
return useXRInputSourceStateContext().inputSource.gripSpace;
case 'target-ray-space':
// eslint-disable-next-line react-hooks/rules-of-hooks
return useXRInputSourceStateContext().inputSource.targetRaySpace;
case 'wrist':
case 'thumb-metacarpal':
case 'thumb-phalanx-proximal':
case 'thumb-phalanx-distal':
case 'thumb-tip':
case 'index-finger-metacarpal':
case 'index-finger-phalanx-proximal':
case 'index-finger-phalanx-intermediate':
case 'index-finger-phalanx-distal':
case 'index-finger-tip':
case 'middle-finger-metacarpal':
case 'middle-finger-phalanx-proximal':
case 'middle-finger-phalanx-intermediate':
case 'middle-finger-phalanx-distal':
case 'middle-finger-tip':
case 'ring-finger-metacarpal':
case 'ring-finger-phalanx-proximal':
case 'ring-finger-phalanx-intermediate':
case 'ring-finger-phalanx-distal':
case 'ring-finger-tip':
case 'pinky-finger-metacarpal':
case 'pinky-finger-phalanx-proximal':
case 'pinky-finger-phalanx-intermediate':
case 'pinky-finger-phalanx-distal':
case 'pinky-finger-tip':
// eslint-disable-next-line react-hooks/rules-of-hooks
return useXRInputSourceStateContext('hand').inputSource.hand.get(type);
case 'root':
case 'hips':
case 'spine-lower':
case 'spine-middle':
case 'spine-upper':
case 'chest':
case 'neck':
case 'head':
case 'left-shoulder':
case 'left-scapula':
case 'left-arm-upper':
case 'left-arm-lower':
case 'left-hand-wrist-twist':
case 'right-shoulder':
case 'right-scapula':
case 'right-arm-upper':
case 'right-arm-lower':
case 'right-hand-wrist-twist':
case 'left-hand-palm':
case 'left-hand-wrist':
case 'left-hand-thumb-metacarpal':
case 'left-hand-thumb-phalanx-proximal':
case 'left-hand-thumb-phalanx-distal':
case 'left-hand-thumb-tip':
case 'left-hand-index-metacarpal':
case 'left-hand-index-phalanx-proximal':
case 'left-hand-index-phalanx-intermediate':
case 'left-hand-index-phalanx-distal':
case 'left-hand-index-tip':
case 'left-hand-middle-metacarpal':
case 'left-hand-middle-phalanx-proximal':
case 'left-hand-middle-phalanx-intermediate':
case 'left-hand-middle-phalanx-distal':
case 'left-hand-middle-tip':
case 'left-hand-ring-metacarpal':
case 'left-hand-ring-phalanx-proximal':
case 'left-hand-ring-phalanx-intermediate':
case 'left-hand-ring-phalanx-distal':
case 'left-hand-ring-tip':
case 'left-hand-little-metacarpal':
case 'left-hand-little-phalanx-proximal':
case 'left-hand-little-phalanx-intermediate':
case 'left-hand-little-phalanx-distal':
case 'left-hand-little-tip':
case 'right-hand-palm':
case 'right-hand-wrist':
case 'right-hand-thumb-metacarpal':
case 'right-hand-thumb-phalanx-proximal':
case 'right-hand-thumb-phalanx-distal':
case 'right-hand-thumb-tip':
case 'right-hand-index-metacarpal':
case 'right-hand-index-phalanx-proximal':
case 'right-hand-index-phalanx-intermediate':
case 'right-hand-index-phalanx-distal':
case 'right-hand-index-tip':
case 'right-hand-middle-metacarpal':
case 'right-hand-middle-phalanx-proximal':
case 'right-hand-middle-phalanx-intermediate':
case 'right-hand-middle-phalanx-distal':
case 'right-hand-middle-tip':
case 'right-hand-ring-metacarpal':
case 'right-hand-ring-phalanx-proximal':
case 'right-hand-ring-phalanx-intermediate':
case 'right-hand-ring-phalanx-distal':
case 'right-hand-ring-tip':
case 'right-hand-little-metacarpal':
case 'right-hand-little-phalanx-proximal':
case 'right-hand-little-phalanx-intermediate':
case 'right-hand-little-phalanx-distal':
case 'right-hand-little-tip':
case 'left-upper-leg':
case 'left-lower-leg':
case 'left-foot-ankle-twist':
case 'left-foot-ankle':
case 'left-foot-subtalar':
case 'left-foot-transverse':
case 'left-foot-ball':
case 'right-upper-leg':
case 'right-lower-leg':
case 'right-foot-ankle-twist':
case 'right-foot-ankle':
case 'right-foot-subtalar':
case 'right-foot-transverse':
case 'right-foot-ball':
// eslint-disable-next-line react-hooks/rules-of-hooks
return useXR((state) => state.body)?.get(type);
}
if (type == null) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const context = useContext(xrSpaceContext);
if (context == null) {
throw new Error(`XR objects must be placed inside the XROrigin`);
}
return context;
}
// eslint-disable-next-line react-hooks/rules-of-hooks
const [referenceSpace, setReferenceSpace] = useState(undefined);
// eslint-disable-next-line react-hooks/rules-of-hooks
const session = useXR((xr) => xr.session);
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (session == null) {
return;
}
let aborted = false;
session.requestReferenceSpace(type).then((space) => {
if (aborted) {
return;
}
setReferenceSpace(space);
});
return () => void (aborted = true);
}, [session, type]);
return referenceSpace;
}
/**
* Hook that returns a function to compute a matrix that contains the transformation of the provided xr space
*/
export function useGetXRSpaceMatrix(space) {
const localReferenceSpace = useContext(xrSpaceContext);
const referenceSpace = useXR((xr) => localReferenceSpace ?? xr.originReferenceSpace);
return useMemo(() => (space == null || referenceSpace == null ? undefined : createGetXRSpaceMatrix(space, referenceSpace)), [space, referenceSpace]);
}
/**
* Hook that applies the transformation of the provided xr space to the provided object reference
*
* @param onFrame Optional callback that gets executed after the matrix of the reference object was updated
* @param ref.current A react ref object that points to the object that the XRSpaceMatrix should be applied to
* @requires matrixAutoUpdate to be disabled for the referenced object
*/
export function useApplyXRSpaceMatrix(ref, space, onFrame) {
const getXRSpaceMatrix = useGetXRSpaceMatrix(space);
useFrame((state, delta, frame) => {
if (ref.current != null) {
ref.current.visible = ref.current.transformReady = getXRSpaceMatrix?.(ref.current.matrix, frame) ?? false;
}
onFrame?.(state, delta, frame);
//makes sure we update the frame before using the space transformation anywhere
}, -100);
}