From 4699676514bd8a2735331356627acdb73ea5ab36 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 11:28:19 +0000 Subject: [PATCH] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/devkit/XRDevHud.tsx | 92 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/devkit/XRDevHud.tsx diff --git a/src/devkit/XRDevHud.tsx b/src/devkit/XRDevHud.tsx new file mode 100644 index 0000000..fbe72c8 --- /dev/null +++ b/src/devkit/XRDevHud.tsx @@ -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(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 ( + + + + + + } + fontSize={0.0075} + color="#22c55e" + anchorX="left" + anchorY="top" + position={[-0.018, 0.045, 0.001]} + maxWidth={0.17} + lineHeight={1.3} + > + {' '} + + + ); +}