@threlte/xr
Version:
Tools to more easily create VR and AR experiences with Threlte
104 lines (103 loc) • 3.79 kB
JavaScript
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);
}
});
};