Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-12 11:28:19 +00:00
parent ac377315a7
commit 4699676514
+92
View File
@@ -0,0 +1,92 @@
/**
* DEVKIT — In-world expanded HUD anchored to left controller (or fake left).
*
* Replaces the production XRDebugHud while devkit is on. Shows inputs, FPS,
* model transform, plane count — readable inside the headset.
*/
import { useRef } from 'react';
import { useFrame, useThree } from '@react-three/fiber';
import { Text } from '@react-three/drei';
import * as THREE from 'three';
import { sceneStats } from './sceneStatsStore';
import { fakeInput, isFakeActive } from './fakeInputStore';
const _m = new THREE.Matrix4();
const _p = new THREE.Vector3();
const _q = new THREE.Quaternion();
const _s = new THREE.Vector3();
const _off = new THREE.Vector3(0.10, 0.05, -0.05); // above wrist, away from face
export function XRDevHud() {
const groupRef = useRef<THREE.Group>(null);
const textRef = useRef<{ text: string } & THREE.Object3D>(null);
const { camera, gl } = useThree();
useFrame((_state, _dt, frame: XRFrame | undefined) => {
if (!groupRef.current) return;
let anchored = false;
if (isFakeActive()) {
_m.copy(fakeInput.left.gripWorld);
anchored = true;
} else if (frame) {
const ref = gl.xr.getReferenceSpace();
if (ref) {
for (const src of frame.session.inputSources) {
if (src.handedness === 'left' && src.gripSpace) {
const pose = frame.getPose(src.gripSpace, ref);
if (pose) { _m.fromArray(pose.transform.matrix); anchored = true; break; }
}
}
}
}
if (anchored) {
_m.decompose(_p, _q, _s);
const off = _off.clone().applyQuaternion(_q);
groupRef.current.position.copy(_p).add(off);
groupRef.current.quaternion.copy(_q);
} else {
// fallback: HUD floats in front of camera
const off = new THREE.Vector3(-0.18, -0.10, -0.5).applyQuaternion(camera.quaternion);
groupRef.current.position.copy(camera.position).add(off);
groupRef.current.quaternion.copy(camera.quaternion);
}
if (textRef.current) {
const lines = [
`FPS ${sceneStats.fps} XR ${sceneStats.xrMode ? 'on' : 'off'} fake:${isFakeActive() ? '1' : '0'}`,
`inputs:${sceneStats.xrInputCount} planes:${sceneStats.xrPlanesCount}`,
`meshes:${sceneStats.meshCount} tris:${sceneStats.triCount}`,
`pos ${sceneStats.modelGroupPos.map((v) => v.toFixed(2)).join(' ')}`,
`rot ${sceneStats.modelGroupRotDeg.map((v) => v.toFixed(0)).join(' ')}`,
`bbox ${sceneStats.modelBboxSize.map((v) => v.toFixed(2)).join(' ')}`,
`L g${fakeInput.left.gripValue.toFixed(2)} t${fakeInput.left.triggerValue.toFixed(2)}`,
`R g${fakeInput.right.gripValue.toFixed(2)} t${fakeInput.right.triggerValue.toFixed(2)}`,
];
(textRef.current as unknown as { text: string }).text = lines.join('\n');
}
});
return (
<group ref={groupRef}>
<mesh position={[0.07, 0, 0]}>
<planeGeometry args={[0.18, 0.10]} />
<meshBasicMaterial color="#000000" transparent opacity={0.65} depthWrite={false} />
</mesh>
<Text
ref={textRef as unknown as React.Ref<THREE.Mesh>}
fontSize={0.0075}
color="#22c55e"
anchorX="left"
anchorY="top"
position={[-0.018, 0.045, 0.001]}
maxWidth={0.17}
lineHeight={1.3}
>
{' '}
</Text>
</group>
);
}