UNPKG

@threlte/xr

Version:

Tools to more easily create VR and AR experiences with Threlte

104 lines (103 loc) 3.79 kB
import { Matrix4 } from 'three'; import { useThrelte, useTask } from '@threlte/core'; import { useController } from './useController.svelte.js'; import { useXROrigin } from './useXROrigin.svelte.js'; import { isPresenting, session } from '../internal/state.svelte.js'; import { fromStore } from 'svelte/store'; /** * Use this hook to perform a hit test per frame in an AR environment. * * ```ts * useHitTest((hitMatrix, hit) => { * mesh.matrix.copy(hitMatrix) * }, { * source: 'viewer' | 'leftInput' | 'rightInput' // Default 'viewer' * }) * ``` */ export const useHitTest = (hitTestCallback, options = {}) => { const source = options.source ?? 'viewer'; const { xr } = useThrelte().renderer; const xrOrigin = useXROrigin(); const hitMatrix = new Matrix4(); let hitTestSource = $state.raw(); if (source === 'viewer') { $effect.pre(() => { const currentSession = session.current; if (currentSession === undefined) return; let cancelled = false; let created; currentSession .requestReferenceSpace('viewer') .then((space) => currentSession.requestHitTestSource?.({ space })) .then((src) => { if (cancelled || src === undefined) { src?.cancel(); return; } created = src; hitTestSource = src; }) .catch(console.error); return () => { cancelled = true; created?.cancel(); if (hitTestSource === created) hitTestSource = undefined; }; }); } else { const controller = fromStore(useController(source === 'leftInput' ? 'left' : 'right')); $effect.pre(() => { const currentSession = session.current; const space = controller.current?.inputSource.targetRaySpace; if (currentSession === undefined || space === undefined) return; let cancelled = false; let created; currentSession .requestHitTestSource?.({ space }) ?.then((src) => { if (cancelled || src === undefined) { src?.cancel(); return; } created = src; hitTestSource = src; }) .catch(console.error); return () => { cancelled = true; created?.cancel(); if (hitTestSource === created) hitTestSource = undefined; }; }); } useTask(() => { const referenceSpace = xr.getReferenceSpace(); if (referenceSpace === null || hitTestSource === undefined) { return hitTestCallback(hitMatrix, undefined); } const [hit] = xr.getFrame().getHitTestResults(hitTestSource); const pose = hit?.getPose(referenceSpace); if (pose === undefined) { return hitTestCallback(hitMatrix, undefined); } hitMatrix.fromArray(pose.transform.matrix); const currentOrigin = xrOrigin.current; if (currentOrigin !== undefined) { currentOrigin.updateWorldMatrix(true, false); hitMatrix.premultiply(currentOrigin.matrixWorld); } hitTestCallback(hitMatrix, hit); }, { running: () => isPresenting.current && hitTestSource !== undefined }); $effect.pre(() => { if (hitTestSource === undefined) { // Execute callback one last time to inform consumers of no hits. hitTestCallback(hitMatrix, undefined); } }); };