fix: prevent world 3d viewer loading forever
This commit is contained in:
parent
ccb57c095a
commit
fc73057935
@ -178,6 +178,7 @@ const SPARK_MINIMAL_FRAME_LOD_SPLAT_SCALE = 0.82;
|
||||
const SPARK_MINIMAL_FRAME_LOD_RENDER_SCALE = 1.6;
|
||||
const SPARK_MIN_SORT_INTERVAL_MS = 24;
|
||||
const SPARK_MINIMAL_FRAME_SORT_INTERVAL_MS = 40;
|
||||
const SPARK_SPLAT_LOAD_TIMEOUT_MS = 45_000;
|
||||
|
||||
const KEY_TO_MOVEMENT: Partial<Record<string, MovementCommand>> = {
|
||||
KeyW: "forward",
|
||||
@ -507,12 +508,14 @@ function WorldEmptyState({
|
||||
description,
|
||||
fillFrame = false,
|
||||
previewImageUrl,
|
||||
loading = true,
|
||||
}: {
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
title: string;
|
||||
description: string;
|
||||
fillFrame?: boolean;
|
||||
previewImageUrl?: string | null;
|
||||
loading?: boolean;
|
||||
}) {
|
||||
if (previewImageUrl && fillFrame) {
|
||||
return (
|
||||
@ -523,7 +526,13 @@ function WorldEmptyState({
|
||||
src={previewImageUrl}
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/10">
|
||||
<Loader2 className="h-7 w-7 animate-spin text-white/55 drop-shadow" />
|
||||
{loading ? (
|
||||
<Loader2 className="h-7 w-7 animate-spin text-white/55 drop-shadow" />
|
||||
) : (
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-full border border-white/15 bg-black/55 text-white/80 shadow-lg backdrop-blur-sm">
|
||||
<Icon className="h-4 w-4" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -549,7 +558,13 @@ function WorldEmptyState({
|
||||
src={previewImageUrl}
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/30 backdrop-blur-[1px]">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-white/40" />
|
||||
{loading ? (
|
||||
<Loader2 className="h-8 w-8 animate-spin text-white/40" />
|
||||
) : (
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-2xl border border-white/10 bg-black/55 text-white/80 shadow-xl">
|
||||
<Icon className="h-5 w-5" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@ -1173,40 +1188,63 @@ function WorldSplatScene({
|
||||
let cancelled = false;
|
||||
let idleCallbackId: number | null = null;
|
||||
let timeoutId: ReturnType<typeof globalThis.setTimeout> | null = null;
|
||||
const loadTimeoutId = globalThis.setTimeout(() => {
|
||||
if (cancelled) return;
|
||||
onLoadStateChange?.("error");
|
||||
}, SPARK_SPLAT_LOAD_TIMEOUT_MS);
|
||||
|
||||
const markReady = () => {
|
||||
if (cancelled || hasAppliedInitialViewRef.current) return;
|
||||
try {
|
||||
lastCameraPoseRef.current = positionCameraInsideWorld({
|
||||
camera,
|
||||
controls: orbitControlsRef.current,
|
||||
firstPerson,
|
||||
groundPlaneOffset,
|
||||
splatMesh,
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("[WorldSplatScene] Failed to position camera for SPZ", error);
|
||||
lastCameraPoseRef.current = null;
|
||||
}
|
||||
|
||||
hasAppliedInitialViewRef.current = true;
|
||||
onLoadStateChange?.("ready");
|
||||
|
||||
const buildParticleCloud = () => {
|
||||
if (cancelled || revealPhaseRef.current === "hidden") return;
|
||||
try {
|
||||
setParticleCloudData(buildSplatParticleCloudData(splatMesh));
|
||||
} catch (error) {
|
||||
console.warn("[WorldSplatScene] Failed to build SPZ particle cloud", error);
|
||||
}
|
||||
};
|
||||
|
||||
if ("requestIdleCallback" in window) {
|
||||
idleCallbackId = window.requestIdleCallback(buildParticleCloud, {
|
||||
timeout: 250,
|
||||
});
|
||||
} else {
|
||||
timeoutId = globalThis.setTimeout(buildParticleCloud, 160);
|
||||
}
|
||||
};
|
||||
|
||||
splatMesh.initialized
|
||||
.then(() => {
|
||||
if (cancelled) return;
|
||||
window.requestAnimationFrame(() => {
|
||||
if (cancelled || hasAppliedInitialViewRef.current) return;
|
||||
lastCameraPoseRef.current = positionCameraInsideWorld({
|
||||
camera,
|
||||
controls: orbitControlsRef.current,
|
||||
firstPerson,
|
||||
groundPlaneOffset,
|
||||
splatMesh,
|
||||
});
|
||||
hasAppliedInitialViewRef.current = true;
|
||||
onLoadStateChange?.("ready");
|
||||
|
||||
const buildParticleCloud = () => {
|
||||
if (cancelled || revealPhaseRef.current === "hidden") return;
|
||||
setParticleCloudData(buildSplatParticleCloudData(splatMesh));
|
||||
};
|
||||
|
||||
if ("requestIdleCallback" in window) {
|
||||
idleCallbackId = window.requestIdleCallback(buildParticleCloud, {
|
||||
timeout: 250,
|
||||
});
|
||||
} else {
|
||||
timeoutId = globalThis.setTimeout(buildParticleCloud, 160);
|
||||
}
|
||||
});
|
||||
globalThis.clearTimeout(loadTimeoutId);
|
||||
window.requestAnimationFrame(markReady);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) onLoadStateChange?.("error");
|
||||
.catch((error) => {
|
||||
globalThis.clearTimeout(loadTimeoutId);
|
||||
if (!cancelled) {
|
||||
console.warn("[WorldSplatScene] Failed to load SPZ", error);
|
||||
onLoadStateChange?.("error");
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
globalThis.clearTimeout(loadTimeoutId);
|
||||
if (idleCallbackId !== null && "cancelIdleCallback" in window) {
|
||||
window.cancelIdleCallback(idleCallbackId);
|
||||
}
|
||||
@ -1551,6 +1589,7 @@ function WorldSplatViewer({
|
||||
}
|
||||
fillFrame={minimalFrame}
|
||||
icon={AlertCircle}
|
||||
loading={false}
|
||||
previewImageUrl={previewImageUrl}
|
||||
title="World 视图加载失败"
|
||||
/>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user