Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | import { useCallback, useEffect, useMemo, useState } from 'react';
import { useApp } from '@playcanvas/react/hooks';
import { CameraComponent, XRSPACE_LOCALFLOOR, XRTYPE_AR, XRTYPE_VR } from 'playcanvas';
export type XrType = 'VR' | 'AR';
interface UseXrOptions {
onError?: (error: Error) => void;
}
const XR_TYPES: Record<XrType, string> = {
VR: XRTYPE_VR,
AR: XRTYPE_AR,
};
export const useXr = ({ onError }: UseXrOptions = {}) => {
const app = useApp();
const [available, setAvailable] = useState<Record<XrType, boolean>>({ VR: false, AR: false });
const [isCurrentlyInXr, setIsCurrentlyInXr] = useState(app.xr?.active ?? false);
useEffect(() => {
// We subscribe to the manager directly because `app` doesn't update when XR's availability changes.
const handleAvailable = (type: string, isAvailable: boolean) => {
if (type === XRTYPE_VR) {
setAvailable((prev) => ({ ...prev, VR: isAvailable }));
} else if (type === XRTYPE_AR) {
setAvailable((prev) => ({ ...prev, AR: isAvailable }));
}
};
setAvailable({
VR: app.xr?.isAvailable(XRTYPE_VR) ?? false,
AR: app.xr?.isAvailable(XRTYPE_AR) ?? false,
});
app.xr?.on('available', handleAvailable);
return () => {
app.xr?.off('available', handleAvailable);
};
}, [app]);
useEffect(() => {
const handleStart = () => setIsCurrentlyInXr(true);
const handleEnd = () => setIsCurrentlyInXr(false);
setIsCurrentlyInXr(app.xr?.active ?? false);
app.xr?.on('start', handleStart);
app.xr?.on('end', handleEnd);
return () => {
app.xr?.off('start', handleStart);
app.xr?.off('end', handleEnd);
};
}, [app]);
const isXrAvailable = useCallback((type: XrType) => available[type], [available]);
const startXr = useCallback(
(type: XrType) => {
const camera = app.root.findComponent('camera') as CameraComponent;
app.xr?.start(camera, XR_TYPES[type], XRSPACE_LOCALFLOOR, {
callback: (err: Error | null) => {
if (err) {
onError?.(err);
app.xr?.end();
}
},
});
},
[app, onError]
);
const endXr = useCallback(() => {
app.xr?.end();
}, [app]);
return useMemo(
() => ({ isXrAvailable, startXr, endXr, isCurrentlyInXr }),
[isXrAvailable, startXr, endXr, isCurrentlyInXr]
);
};
export default useXr;
|