This commit is contained in:
gpt-engineer-app[bot]
2026-02-27 19:59:15 +00:00
parent 5b1b0cff57
commit 966379a14a
+23 -82
View File
@@ -10,26 +10,11 @@ import { ArrowLeft, Move, RotateCw, QrCode, Download, Crosshair } from 'lucide-r
import { Button } from '@/components/ui/button';
import { generateMarkerDownloadURL } from '@/lib/trackingMarker';
// --- XR Store singleton ---
let xrStoreInstance: ReturnType<typeof createXRStore> | null = null;
function getOrCreateXRStore() {
if (xrStoreInstance) return xrStoreInstance;
// Only emulate when on localhost AND no native XR support
const shouldEmulate =
typeof window !== 'undefined' &&
window.location.hostname === 'localhost' &&
!navigator.xr;
xrStoreInstance = createXRStore({
emulate: shouldEmulate ? 'metaQuest3' : false,
hand: { left: true, right: true },
controller: { left: true, right: true },
});
return xrStoreInstance;
}
// --- XR Store at MODULE level (required by Quest 3 browser) ---
const store = createXRStore({
hand: { left: true, right: true },
controller: { left: true, right: true },
});
// ─── XRModel ───────────────────────────────────────────
function XRModel({ url }: { url: string }) {
@@ -129,8 +114,8 @@ function ControllerFineTuning() {
const posStep = 0.001;
const rotStep = 0.1;
const deadzone = 0.15;
const store = useModelStore.getState();
const ft = { ...store.fineTuning };
const modelStore = useModelStore.getState();
const ft = { ...modelStore.fineTuning };
if (leftAxes) {
if (Math.abs(leftAxes[0]) > deadzone) ft.posX += leftAxes[0] * posStep;
@@ -150,47 +135,10 @@ function ControllerFineTuning() {
// ─── ImageTrackingAnchor ───────────────────────────────
function ImageTrackingAnchor({ children }: { children: React.ReactNode }) {
const groupRef = useRef<THREE.Group>(null);
const session = useXR((s) => s.session);
const { gl } = useThree();
const { setAnchorMode } = useModelStore();
const trackingActive = useRef(false);
useFrame((_, __, frame: any) => {
if (!frame || !session || !groupRef.current) return;
try {
const results = frame.getImageTrackingResults?.();
if (results && results.length > 0) {
for (const result of results) {
if (result.trackingState === 'tracked' || result.trackingState === 'emulated') {
const refSpace = gl.xr.getReferenceSpace();
if (!refSpace) continue;
const pose = frame.getPose(result.imageSpace, refSpace);
if (pose) {
const pos = pose.transform.position;
const ori = pose.transform.orientation;
groupRef.current.position.set(pos.x, pos.y, pos.z);
groupRef.current.quaternion.set(ori.x, ori.y, ori.z, ori.w);
if (!trackingActive.current) {
trackingActive.current = true;
setAnchorMode('tracking');
toast.success('QR Code detectado — Modelo ancorado!');
}
return;
}
}
}
}
} catch {
// getImageTrackingResults not available
}
if (trackingActive.current) return;
// Manual fallback — place 1.5m in front
useFrame(() => {
if (!groupRef.current) return;
// Place model 1.5m in front of the user
groupRef.current.position.set(0, 0, -1.5);
groupRef.current.quaternion.identity();
});
@@ -203,7 +151,6 @@ const XRSession = () => {
const navigate = useNavigate();
const { model, anchorMode, setAnchorMode } = useModelStore();
const [inXR, setInXR] = useState(false);
const [store] = useState(() => getOrCreateXRStore());
useEffect(() => {
if (!model) {
@@ -211,23 +158,22 @@ const XRSession = () => {
}
}, [model, navigate]);
const handleEnterAR = useCallback(async () => {
try {
const session = await store.enterAR();
if (session) {
// Listen for XR session state changes
useEffect(() => {
const unsubscribe = store.subscribe((state) => {
const session = state.session;
if (session && !inXR) {
setInXR(true);
setAnchorMode('manual');
toast.success('Sessão AR iniciada!');
session.addEventListener('end', () => {
setInXR(false);
setAnchorMode('manual');
});
setInXR(true);
setAnchorMode('manual');
toast.success('Sessão XR iniciada!');
}
} catch (err: any) {
console.error('XR error:', err);
toast.error(`Falha ao iniciar XR: ${err.message}`);
}
}, [store, setAnchorMode]);
});
return unsubscribe;
}, [inXR, setAnchorMode]);
const handleDownloadMarker = useCallback(async () => {
const url = await generateMarkerDownloadURL();
@@ -261,7 +207,8 @@ const XRSession = () => {
<Download className="h-3.5 w-3.5" />
<span className="font-mono text-xs">Marcador QR</span>
</Button>
<Button className="gap-2 glow-primary" onClick={handleEnterAR}>
{/* CRITICAL: Direct call without async wrapper to preserve user gesture */}
<Button className="gap-2 glow-primary" onClick={() => store.enterAR()}>
Iniciar Sessão AR
</Button>
</>
@@ -318,12 +265,6 @@ const XRSession = () => {
{/* Floating HUD overlay */}
<div className="absolute bottom-4 left-4 right-4 z-10 pointer-events-none">
<div className="pointer-events-auto inline-flex flex-wrap gap-3 rounded-lg border bg-card/90 backdrop-blur-sm p-3 shadow-lg">
<div className="flex items-center gap-2">
<QrCode className="h-3.5 w-3.5 text-primary" />
<span className="font-mono text-[10px] text-muted-foreground">
{anchorMode === 'tracking' ? '● QR Detectado' : '○ Buscando QR…'}
</span>
</div>
<div className="flex items-center gap-2">
<Move className="h-3.5 w-3.5 text-primary" />
<span className="font-mono text-[10px] text-muted-foreground">