Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-24 19:22:23 +00:00
parent 5bea0ff9ee
commit dda94bd107
+24 -9
View File
@@ -1,6 +1,7 @@
import { useRef, useState, forwardRef } from 'react'; import { useRef, useState, forwardRef } from 'react';
import { useFrame, useThree } from '@react-three/fiber'; import { useFrame, useThree } from '@react-three/fiber';
import { useXRInputSourceState } from '@react-three/xr'; import { useXRInputSourceState, useXR } from '@react-three/xr';
import { useNavigate } from 'react-router-dom';
import * as THREE from 'three'; import * as THREE from 'three';
import { Text } from '@react-three/drei'; import { Text } from '@react-three/drei';
import { XR3DButton, XR3DPanel } from './XRPanel3D'; import { XR3DButton, XR3DPanel } from './XRPanel3D';
@@ -56,7 +57,11 @@ export function XRHudInWorld(props: XRHudInWorldProps) {
const { camera } = useThree(); const { camera } = useThree();
const leftCtrl = useXRInputSourceState('controller', 'left'); const leftCtrl = useXRInputSourceState('controller', 'left');
const rightCtrl = useXRInputSourceState('controller', 'right'); const rightCtrl = useXRInputSourceState('controller', 'right');
const xrSession = useXR((s) => s.session);
const navigate = useNavigate();
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
/** Visibility of the small head-locked floating button group (toggled by X button). */
const [hudVisible, setHudVisible] = useState(true);
const [tab, setTab] = useState<Tab>('scene'); const [tab, setTab] = useState<Tab>('scene');
/** When pinned, the panel stays fixed in world space and does NOT follow the head. */ /** When pinned, the panel stays fixed in world space and does NOT follow the head. */
const [pinned, setPinned] = useState(true); const [pinned, setPinned] = useState(true);
@@ -77,12 +82,12 @@ export function XRHudInWorld(props: XRHudInWorldProps) {
const dragInitialized = useRef(false); const dragInitialized = useRef(false);
useFrame(() => { useFrame(() => {
// Toggle via left A button (Quest Touch buttons[4]) // Toggle the head-locked 3-button group via left X button (Quest Touch buttons[4])
const gp = leftCtrl?.inputSource?.gamepad; const gp = leftCtrl?.inputSource?.gamepad;
if (gp) { if (gp) {
const aBtn = gp.buttons[4]; const xBtn = gp.buttons[4];
const pressed = !!aBtn?.pressed; const pressed = !!xBtn?.pressed;
if (pressed && !lastABtn.current) setOpen((v) => !v); if (pressed && !lastABtn.current) setHudVisible((v) => !v);
lastABtn.current = pressed; lastABtn.current = pressed;
} }
@@ -166,15 +171,25 @@ export function XRHudInWorld(props: XRHudInWorldProps) {
return ( return (
<> <>
<WristToggle ref={wristRef} open={open} onToggle={() => setOpen((v) => !v)} /> <WristToggle ref={wristRef} open={open} onToggle={() => setOpen((v) => !v)} />
{/* Head-locked fallback: always-visible quick controls in lower-left of FOV */} {/* Head-locked fallback: floating quick-controls in lower-left of FOV.
<group ref={headLockRef} renderOrder={999}> Toggled on/off by the left controller X button. */}
<XR3DButton position={[0, 0.018, 0]} size={[0.07, 0.022]} <group ref={headLockRef} renderOrder={999} visible={hudVisible}>
<XR3DButton position={[0, 0.030, 0]} size={[0.07, 0.022]}
label={open ? '✕ Menu' : '☰ Menu'} active={open} label={open ? '✕ Menu' : '☰ Menu'} active={open}
onClick={() => { setOpen((v) => !v); reanchorRequested.current = true; }} onClick={() => { setOpen((v) => !v); reanchorRequested.current = true; }}
fontSize={0.0085} /> fontSize={0.0085} />
<XR3DButton position={[0, -0.008, 0]} size={[0.07, 0.022]} <XR3DButton position={[0, 0.004, 0]} size={[0.07, 0.022]}
label={showGrid ? 'Grid ON' : 'Grid OFF'} active={showGrid} label={showGrid ? 'Grid ON' : 'Grid OFF'} active={showGrid}
onClick={() => setShowGrid(!showGrid)} fontSize={0.0085} /> onClick={() => setShowGrid(!showGrid)} fontSize={0.0085} />
<XR3DButton position={[0, -0.022, 0]} size={[0.07, 0.022]}
label="⏏ Sair AR"
onClick={() => {
// End the XR session while preserving placement/state in the store,
// then return to the Viewer. Re-entering AR restores the same state.
try { xrSession?.end(); } catch (e) { console.warn('[XR] session.end failed', e); }
setTimeout(() => navigate('/viewer'), 50);
}}
fontSize={0.0085} />
<RecIndicator /> <RecIndicator />
</group> </group>
{open && ( {open && (