Simplified XR store setup
Refactored XRSession to remove marker bitmap preloading and lazy async store initialization. Replaced complex getXRStore flow with a single getOrCreateXRStore, removed manual bitmap handling, and updated AR start flow to use the centralized store. Simplified UX message on successful XR start. X-Lovable-Edit-ID: edt-a8250c81-169f-4cc7-8370-4f5068a2c078
This commit is contained in:
+7
-54
@@ -8,55 +8,15 @@ import { useModelStore } from '@/stores/useModelStore';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { ArrowLeft, Move, RotateCw, QrCode, Download, Crosshair } from 'lucide-react';
|
import { ArrowLeft, Move, RotateCw, QrCode, Download, Crosshair } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { generateTrackingMarker, generateMarkerDownloadURL } from '@/lib/trackingMarker';
|
import { generateMarkerDownloadURL } from '@/lib/trackingMarker';
|
||||||
|
|
||||||
// --- Marker bitmap singleton ---
|
// --- XR Store singleton ---
|
||||||
let markerBitmapPromise: Promise<ImageBitmap> | null = null;
|
|
||||||
function getMarkerBitmap(): Promise<ImageBitmap> {
|
|
||||||
if (!markerBitmapPromise) {
|
|
||||||
markerBitmapPromise = generateTrackingMarker();
|
|
||||||
}
|
|
||||||
return markerBitmapPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- XR Store singleton (lazy-initialized with marker) ---
|
|
||||||
let xrStoreInstance: ReturnType<typeof createXRStore> | null = null;
|
let xrStoreInstance: ReturnType<typeof createXRStore> | null = null;
|
||||||
|
|
||||||
async function getXRStore() {
|
function getOrCreateXRStore() {
|
||||||
if (xrStoreInstance) return xrStoreInstance;
|
if (xrStoreInstance) return xrStoreInstance;
|
||||||
|
|
||||||
const bitmap = await getMarkerBitmap();
|
|
||||||
|
|
||||||
// Only emulate when on localhost AND no native XR support
|
// 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 },
|
|
||||||
customSessionInit: {
|
|
||||||
optionalFeatures: ['image-tracking'],
|
|
||||||
// @ts-ignore — trackedImages is a WebXR extension not yet in TS types
|
|
||||||
trackedImages: [
|
|
||||||
{
|
|
||||||
image: bitmap,
|
|
||||||
widthInMeters: 0.15, // 15cm printed marker
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return xrStoreInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a synchronous fallback store for initial render
|
|
||||||
// (will be replaced once the async one is ready)
|
|
||||||
function getInitialXRStore() {
|
|
||||||
if (xrStoreInstance) return xrStoreInstance;
|
|
||||||
|
|
||||||
const shouldEmulate =
|
const shouldEmulate =
|
||||||
typeof window !== 'undefined' &&
|
typeof window !== 'undefined' &&
|
||||||
window.location.hostname === 'localhost' &&
|
window.location.hostname === 'localhost' &&
|
||||||
@@ -243,24 +203,17 @@ const XRSession = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { model, anchorMode, setAnchorMode } = useModelStore();
|
const { model, anchorMode, setAnchorMode } = useModelStore();
|
||||||
const [inXR, setInXR] = useState(false);
|
const [inXR, setInXR] = useState(false);
|
||||||
const [store, setStore] = useState(() => getInitialXRStore());
|
const [store] = useState(() => getOrCreateXRStore());
|
||||||
|
|
||||||
// Pre-load marker and rebuild store with customSessionInit
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!model) {
|
if (!model) {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
getXRStore().then((s) => {
|
|
||||||
// Update store ref if it was replaced with the marker-enabled one
|
|
||||||
if (s !== store) setStore(s);
|
|
||||||
});
|
|
||||||
}, [model, navigate]);
|
}, [model, navigate]);
|
||||||
|
|
||||||
const handleEnterAR = useCallback(async () => {
|
const handleEnterAR = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const s = await getXRStore();
|
const session = await store.enterAR();
|
||||||
const session = await s.enterAR();
|
|
||||||
if (session) {
|
if (session) {
|
||||||
session.addEventListener('end', () => {
|
session.addEventListener('end', () => {
|
||||||
setInXR(false);
|
setInXR(false);
|
||||||
@@ -268,13 +221,13 @@ const XRSession = () => {
|
|||||||
});
|
});
|
||||||
setInXR(true);
|
setInXR(true);
|
||||||
setAnchorMode('manual');
|
setAnchorMode('manual');
|
||||||
toast.success('Sessão XR iniciada — Buscando QR Code…');
|
toast.success('Sessão XR iniciada!');
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('XR error:', err);
|
console.error('XR error:', err);
|
||||||
toast.error(`Falha ao iniciar XR: ${err.message}`);
|
toast.error(`Falha ao iniciar XR: ${err.message}`);
|
||||||
}
|
}
|
||||||
}, [setAnchorMode]);
|
}, [store, setAnchorMode]);
|
||||||
|
|
||||||
const handleDownloadMarker = useCallback(async () => {
|
const handleDownloadMarker = useCallback(async () => {
|
||||||
const url = await generateMarkerDownloadURL();
|
const url = await generateMarkerDownloadURL();
|
||||||
|
|||||||
Reference in New Issue
Block a user