🚀 Auto-deploy: melhoria no snap e medição AR em 30/05/2026 11:48:45
This commit is contained in:
@@ -223,7 +223,11 @@ const Viewer = () => {
|
|||||||
toast.error("WebXR não é suportado neste navegador. Use o navegador do Meta Quest 3.");
|
toast.error("WebXR não é suportado neste navegador. Use o navegador do Meta Quest 3.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Entra diretamente no AR para não perder o clique do usuário!
|
||||||
|
import('@/stores/useXRStore').then(({ xrStore }) => {
|
||||||
|
xrStore.enterAR().catch(e => console.error('[Viewer] XR enterAR error:', e));
|
||||||
navigate("/xr");
|
navigate("/xr");
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEnterMeeting = () => {
|
const handleEnterMeeting = () => {
|
||||||
|
|||||||
+17
-48
@@ -2,7 +2,8 @@ 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, OrbitControls, Text } from '@react-three/drei';
|
import { useGLTF, Grid, Html, Line, OrbitControls, Text } from '@react-three/drei';
|
||||||
import { XR, createXRStore, useXR, XROrigin, useXRHitTest } from '@react-three/xr';
|
import { XR, useXR, XROrigin, useXRHitTest } from '@react-three/xr';
|
||||||
|
import { xrStore as store } from '@/stores/useXRStore';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { useModelStore } from '@/stores/useModelStore';
|
import { useModelStore } from '@/stores/useModelStore';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -45,39 +46,7 @@ if (navigator.xr) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lê flags de feature WebXR do localStorage (default: todas ON).
|
// `store` is now imported from `@/stores/useXRStore`
|
||||||
// O usuário liga/desliga pelo HUD AR (aba WebXR) para diagnosticar qual
|
|
||||||
// feature ativa o grid de segurança do Quest. Mudanças exigem sair e re-entrar do AR.
|
|
||||||
function loadXRFeatures() {
|
|
||||||
const defaults = {
|
|
||||||
handTracking: true, planeDetection: true, hitTest: true, domOverlay: true,
|
|
||||||
anchors: true, meshDetection: true, depthSensing: true, layers: true,
|
|
||||||
bodyTracking: true, lightEstimation: true,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem('xrFeatures');
|
|
||||||
if (raw) return { ...defaults, ...JSON.parse(raw) };
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('[XR] Falha ao ler xrFeatures do localStorage:', err);
|
|
||||||
}
|
|
||||||
return defaults;
|
|
||||||
}
|
|
||||||
const _xrf = loadXRFeatures();
|
|
||||||
console.log('[XR] Features:', _xrf);
|
|
||||||
|
|
||||||
const store = createXRStore({
|
|
||||||
hand: { left: true, right: true },
|
|
||||||
controller: { left: true, right: true },
|
|
||||||
handTracking: _xrf.handTracking,
|
|
||||||
planeDetection: _xrf.planeDetection,
|
|
||||||
hitTest: _xrf.hitTest,
|
|
||||||
domOverlay: _xrf.domOverlay,
|
|
||||||
anchors: _xrf.anchors,
|
|
||||||
meshDetection: _xrf.meshDetection,
|
|
||||||
depthSensing: _xrf.depthSensing,
|
|
||||||
layers: _xrf.layers,
|
|
||||||
bodyTracking: _xrf.bodyTracking,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ─── XRModel ───────────────────────────────────────────
|
// ─── XRModel ───────────────────────────────────────────
|
||||||
function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').SceneModel }) {
|
function XRModel({ sceneModel }: { sceneModel: import('@/stores/useModelStore').SceneModel }) {
|
||||||
@@ -1113,6 +1082,20 @@ const XRSession = () => {
|
|||||||
|
|
||||||
if (!model) return null;
|
if (!model) return null;
|
||||||
|
|
||||||
|
// Auto-return to viewer if user exits AR session
|
||||||
|
useEffect(() => {
|
||||||
|
// If we are mounted, and the session is completely absent/ended (inXR is false),
|
||||||
|
// we wait a brief moment to avoid returning during initial transition, then return.
|
||||||
|
if (!inXR) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (!store.getState().session) {
|
||||||
|
navigate('/viewer');
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [inXR, navigate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col bg-background">
|
<div className="flex h-screen flex-col bg-background">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -1130,20 +1113,6 @@ const XRSession = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{!inXR && (
|
|
||||||
<>
|
|
||||||
<Button variant="outline" size="sm" className="gap-1.5" onClick={handleDownloadMarker}>
|
|
||||||
<Download className="h-3.5 w-3.5" />
|
|
||||||
<span className="font-mono text-xs">Marcador</span>
|
|
||||||
</Button>
|
|
||||||
<Button className="gap-2 glow-primary" onClick={() => {
|
|
||||||
console.log('[XR] Botão AR clicado');
|
|
||||||
store.enterAR().catch((e) => console.error('[XR] enterAR rejeitado:', e));
|
|
||||||
}}>
|
|
||||||
Iniciar Sessão AR
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{inXR && (
|
{inXR && (
|
||||||
<span className="font-mono text-xs text-primary flex items-center gap-1.5">
|
<span className="font-mono text-xs text-primary flex items-center gap-1.5">
|
||||||
<span className="h-2 w-2 rounded-full bg-primary animate-pulse" />
|
<span className="h-2 w-2 rounded-full bg-primary animate-pulse" />
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { createXRStore } from '@react-three/xr';
|
||||||
|
|
||||||
|
function loadXRFeatures() {
|
||||||
|
const defaults = {
|
||||||
|
handTracking: true,
|
||||||
|
planeDetection: true,
|
||||||
|
hitTest: true,
|
||||||
|
domOverlay: true,
|
||||||
|
anchors: true,
|
||||||
|
meshDetection: true,
|
||||||
|
depthSensing: true,
|
||||||
|
layers: true,
|
||||||
|
bodyTracking: true,
|
||||||
|
lightEstimation: true,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem('xrFeatures');
|
||||||
|
if (raw) return { ...defaults, ...JSON.parse(raw) };
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[XR] Falha ao ler xrFeatures do localStorage:', err);
|
||||||
|
}
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _xrf = loadXRFeatures();
|
||||||
|
|
||||||
|
export const xrStore = createXRStore({
|
||||||
|
hand: { left: true, right: true },
|
||||||
|
controller: { left: true, right: true },
|
||||||
|
handTracking: _xrf.handTracking,
|
||||||
|
planeDetection: _xrf.planeDetection,
|
||||||
|
hitTest: _xrf.hitTest,
|
||||||
|
domOverlay: _xrf.domOverlay,
|
||||||
|
anchors: _xrf.anchors,
|
||||||
|
meshDetection: _xrf.meshDetection,
|
||||||
|
depthSensing: _xrf.depthSensing,
|
||||||
|
layers: _xrf.layers,
|
||||||
|
bodyTracking: _xrf.bodyTracking,
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user