diff --git a/src/components/three/XRHudInWorld.tsx b/src/components/three/XRHudInWorld.tsx index 48f5bbe..d4d7276 100644 --- a/src/components/three/XRHudInWorld.tsx +++ b/src/components/three/XRHudInWorld.tsx @@ -63,6 +63,7 @@ export function XRHudInWorld(props: XRHudInWorldProps) { const panelRef = useRef(null); const wristRef = useRef(null); + const headLockRef = useRef(null); const targetPos = useRef(new THREE.Vector3()); const targetQuat = useRef(new THREE.Quaternion()); const lastABtn = useRef(false); @@ -144,11 +145,33 @@ export function XRHudInWorld(props: XRHudInWorldProps) { wristRef.current.visible = false; } } + + // Head-locked fallback toggle — always visible in lower-left of FOV, + // so the user can reopen the menu even without controllers or if the + // panel is positioned out of view. + if (headLockRef.current) { + const offset = new THREE.Vector3(-0.18, -0.18, -0.5).applyQuaternion(camera.quaternion); + headLockRef.current.position.copy(camera.position).add(offset); + headLockRef.current.quaternion.copy(camera.quaternion); + } }); + const showGrid = useModelStore((s) => s.showGrid); + const setShowGrid = useModelStore((s) => s.setShowGrid); + return ( <> setOpen((v) => !v)} /> + {/* Head-locked fallback: always-visible quick controls in lower-left of FOV */} + + { setOpen((v) => !v); reanchorRequested.current = true; }} + fontSize={0.0085} /> + setShowGrid(!showGrid)} fontSize={0.0085} /> + {open && ( ((set, get) => ({ renderMode: 'solid', setRenderMode: (renderMode) => set({ renderMode }), - showGrid: true, + showGrid: false, setShowGrid: (showGrid) => set({ showGrid }), gridY: -0.09,