Adicionou HUD debug XR

X-Lovable-Edit-ID: edt-a2cd647c-77d0-480c-b086-792055f18208
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-12 10:42:07 +00:00
+97 -21
View File
@@ -1,7 +1,7 @@
import { useEffect, useRef, useMemo, useCallback, useState } from 'react'; import { useEffect, useRef, useMemo, useCallback, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Canvas, useFrame, useThree } from '@react-three/fiber'; import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { useGLTF, Grid, Html, Line } from '@react-three/drei'; import { useGLTF, Grid, Html, Line, OrbitControls, Text } from '@react-three/drei';
import { XR, createXRStore, useXR, XROrigin } from '@react-three/xr'; import { XR, createXRStore, useXR, XROrigin } from '@react-three/xr';
import * as THREE from 'three'; import * as THREE from 'three';
import { useModelStore } from '@/stores/useModelStore'; import { useModelStore } from '@/stores/useModelStore';
@@ -338,6 +338,59 @@ function XRGrid() {
// ImageTrackingAnchor removed — replaced by XRHitTestPlacement // ImageTrackingAnchor removed — replaced by XRHitTestPlacement
// ─── XR Debug HUD (floats in front of viewer) ──────────
// Shows live grip/trigger values and isGrabbing state per hand.
// Essential for diagnosing grab issues without external monitoring.
function XRDebugHud() {
const session = useXR((s) => s.session);
const groupRef = useRef<THREE.Group>(null);
const { camera } = useThree();
const [text, setText] = useState('XR aguardando inputs...');
useFrame(() => {
if (!groupRef.current) return;
// Anchor the HUD to a fixed offset in front of the camera
const offset = new THREE.Vector3(-0.25, -0.18, -0.6).applyQuaternion(camera.quaternion);
groupRef.current.position.copy(camera.position).add(offset);
groupRef.current.quaternion.copy(camera.quaternion);
if (!session) return;
const sources = Array.from(session.inputSources);
const lines: string[] = [`inputs: ${sources.length}`];
for (const src of sources) {
const gp = src.gamepad;
const grip = gp?.buttons[2];
const trig = gp?.buttons[1];
const gv = grip ? (grip.value || (grip.pressed ? 1 : 0)) : 0;
const tv = trig ? (trig.value || (trig.pressed ? 1 : 0)) : 0;
const grabbing = gv > 0.7 ? 'GRAB' : gv > 0.3 ? '...' : ' ';
lines.push(`${src.handedness.padEnd(5)} grip:${gv.toFixed(2)} trg:${tv.toFixed(2)} ${grabbing}`);
}
setText(lines.join('\n'));
});
return (
<group ref={groupRef}>
<mesh position={[0, 0, -0.001]}>
<planeGeometry args={[0.32, 0.10]} />
<meshBasicMaterial color="#000000" transparent opacity={0.55} depthWrite={false} />
</mesh>
<Text
fontSize={0.012}
color="#22c55e"
anchorX="left"
anchorY="top"
position={[-0.155, 0.045, 0]}
maxWidth={0.30}
lineHeight={1.25}
>
{text}
</Text>
</group>
);
}
// ─── XRSession Page ──────────────────────────────────── // ─── XRSession Page ────────────────────────────────────
const XRSession = () => { const XRSession = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -422,7 +475,7 @@ const XRSession = () => {
<div className="flex-1 relative"> <div className="flex-1 relative">
<Canvas <Canvas
gl={{ antialias: true, alpha: true, powerPreference: 'high-performance', preserveDrawingBuffer: true }} gl={{ antialias: true, alpha: true, powerPreference: 'high-performance', preserveDrawingBuffer: true }}
camera={{ position: [0, 1.6, 0], fov: 50, near: 0.01, far: 100 }} camera={{ position: [1.5, 1.2, 1.5], fov: 50, near: 0.01, far: 100 }}
className="!bg-transparent" className="!bg-transparent"
onCreated={({ gl, invalidate }) => { onCreated={({ gl, invalidate }) => {
gl.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); gl.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
@@ -438,28 +491,51 @@ const XRSession = () => {
<XROrigin /> <XROrigin />
<XRHitTestPlacement {inXR ? (
placementMode={placementMode} <>
snapToPlanes={snapToPlanes} {/* In XR: hit-test placement → grab wrapper → model */}
onPlace={() => { <XRHitTestPlacement
setPlacementMode(false); placementMode={placementMode}
toast.success('Modelo posicionado na superfície!'); snapToPlanes={snapToPlanes}
}} onPlace={() => {
> setPlacementMode(false);
<XRGrabbable toast.success('Modelo posicionado na superfície!');
allowScale={allowScale} }}
onGrabStart={() => { >
if (placementMode) setPlacementMode(false); <XRGrabbable
}} allowScale={allowScale}
> onGrabStart={() => {
<XRModel url={model.url} /> if (placementMode) setPlacementMode(false);
</XRGrabbable> }}
</XRHitTestPlacement> >
<XRModel url={model.url} />
</XRGrabbable>
</XRHitTestPlacement>
<XRMeasurementOverlay />
<ControllerFineTuning freeMove={freeMove} />
<XRDebugHud />
</>
) : (
<>
{/* Desktop preview (no headset): show model with OrbitControls */}
<group position={[0, 0, 0]}>
<XRModel url={model.url} />
</group>
<XRMeasurementOverlay />
<OrbitControls
makeDefault
enableDamping
dampingFactor={0.1}
minDistance={0.05}
maxDistance={50}
target={[0, 0, 0]}
/>
</>
)}
<XRMeasurementOverlay />
<XRSnapHandler /> <XRSnapHandler />
<XRGrid /> <XRGrid />
<ControllerFineTuning freeMove={freeMove} />
</XR> </XR>
</Canvas> </Canvas>