fix: fallback 3d world when splat stalls
This commit is contained in:
parent
4cc0977c9f
commit
f1ab6b6255
@ -185,7 +185,7 @@ 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_FETCH_TIMEOUT_MS = 18_000;
|
||||
const SPARK_SPLAT_LOAD_TIMEOUT_MS = 18_000;
|
||||
const SPARK_SPLAT_LOAD_TIMEOUT_MS = 8_000;
|
||||
|
||||
const KEY_TO_MOVEMENT: Partial<Record<string, MovementCommand>> = {
|
||||
KeyW: "forward",
|
||||
@ -1718,6 +1718,9 @@ function WorldSplatCaptureBridge({
|
||||
function WorldSplatViewer({
|
||||
splatUrl,
|
||||
splatUrls,
|
||||
fallbackMeshUrl,
|
||||
fallbackMeshSettings,
|
||||
fallbackMeshLayerVisibility,
|
||||
worldUrl,
|
||||
previewImageUrl,
|
||||
firstPerson,
|
||||
@ -1725,6 +1728,8 @@ function WorldSplatViewer({
|
||||
interactionMode,
|
||||
onPick,
|
||||
onHotspotSelect,
|
||||
onFallbackMeshLayersChange,
|
||||
onFallbackMeshStatsChange,
|
||||
semanticsMetadata,
|
||||
resetViewToken,
|
||||
minimalFrame = false,
|
||||
@ -1734,6 +1739,9 @@ function WorldSplatViewer({
|
||||
}: {
|
||||
splatUrl?: string | null;
|
||||
splatUrls?: string[] | null;
|
||||
fallbackMeshUrl?: string | null;
|
||||
fallbackMeshSettings?: Model3DViewerSettings;
|
||||
fallbackMeshLayerVisibility?: Record<string, boolean>;
|
||||
worldUrl?: string | null;
|
||||
previewImageUrl?: string | null;
|
||||
minimalFrame?: boolean;
|
||||
@ -1742,6 +1750,8 @@ function WorldSplatViewer({
|
||||
interactionMode?: "navigate" | "hotspot";
|
||||
onPick?: (pick: World3DPickResult) => void;
|
||||
onHotspotSelect?: (hotspotId: string) => void;
|
||||
onFallbackMeshLayersChange?: (layers: Model3DLayer[]) => void;
|
||||
onFallbackMeshStatsChange?: (stats: Model3DStats) => void;
|
||||
semanticsMetadata?: Record<string, unknown> | null;
|
||||
resetViewToken?: number;
|
||||
onCaptureReady?: (capture: (() => Promise<Blob | null>) | null) => void;
|
||||
@ -1758,6 +1768,7 @@ function WorldSplatViewer({
|
||||
const [prefetchedSplat, setPrefetchedSplat] = useState<PrefetchedSplatAsset | null>(
|
||||
null
|
||||
);
|
||||
const [useMeshFallback, setUseMeshFallback] = useState(false);
|
||||
const resolvedSplatUrls = useMemo(() => {
|
||||
const rawUrls = [
|
||||
splatUrl,
|
||||
@ -1775,13 +1786,16 @@ function WorldSplatViewer({
|
||||
}, [splatUrl, splatUrls]);
|
||||
const resolvedSplatSignature = resolvedSplatUrls.join("|");
|
||||
const resolvedSplatUrl = resolvedSplatUrls[activeSplatIndex] || "";
|
||||
const resolvedFallbackMeshUrl = String(fallbackMeshUrl || "").trim() || null;
|
||||
const hasMeshFallback = Boolean(resolvedFallbackMeshUrl);
|
||||
const loadStartedAtRef = useRef(0);
|
||||
const movementStateRef = useRef<MovementState>(createMovementState());
|
||||
|
||||
useEffect(() => {
|
||||
setActiveSplatIndex(0);
|
||||
setFailedSplatLoads([]);
|
||||
}, [resolvedSplatSignature]);
|
||||
setUseMeshFallback(false);
|
||||
}, [resolvedFallbackMeshUrl, resolvedSplatSignature]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!resolvedSplatUrl) return;
|
||||
@ -1834,6 +1848,9 @@ function WorldSplatViewer({
|
||||
const failedUrl = resolvedSplatUrl;
|
||||
const nextIndex = activeSplatIndex + 1;
|
||||
const nextUrl = resolvedSplatUrls[nextIndex] || "";
|
||||
const shouldUseMeshFallback =
|
||||
hasMeshFallback &&
|
||||
(message.includes("SPZ 加载超时") || !nextUrl);
|
||||
|
||||
if (failedUrl) {
|
||||
setFailedSplatLoads((items) => [
|
||||
@ -1842,6 +1859,20 @@ function WorldSplatViewer({
|
||||
]);
|
||||
}
|
||||
|
||||
if (shouldUseMeshFallback) {
|
||||
console.warn("[WorldSplatViewer] Switching to mesh fallback", {
|
||||
error: message,
|
||||
failedUrl,
|
||||
attemptedUrls: resolvedSplatUrls.slice(0, nextIndex),
|
||||
fallbackMeshUrl: resolvedFallbackMeshUrl,
|
||||
});
|
||||
setPrefetchedSplat(null);
|
||||
setLoadProgress(null);
|
||||
setOverlayPhase("hidden");
|
||||
setUseMeshFallback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextUrl) {
|
||||
console.warn("[WorldSplatViewer] Falling back to next SPZ asset", {
|
||||
error: message,
|
||||
@ -1864,11 +1895,17 @@ function WorldSplatViewer({
|
||||
setPrefetchedSplat(null);
|
||||
setLoadState("error");
|
||||
},
|
||||
[activeSplatIndex, resolvedSplatUrl, resolvedSplatUrls]
|
||||
[
|
||||
activeSplatIndex,
|
||||
hasMeshFallback,
|
||||
resolvedFallbackMeshUrl,
|
||||
resolvedSplatUrl,
|
||||
resolvedSplatUrls,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!resolvedSplatUrl) return;
|
||||
if (!resolvedSplatUrl || useMeshFallback) return;
|
||||
|
||||
const abortController = new AbortController();
|
||||
let cancelled = false;
|
||||
@ -1905,7 +1942,25 @@ function WorldSplatViewer({
|
||||
globalThis.clearTimeout(timeoutId);
|
||||
abortController.abort();
|
||||
};
|
||||
}, [handleSplatLoadStateChange, resolvedSplatUrl]);
|
||||
}, [handleSplatLoadStateChange, resolvedSplatUrl, useMeshFallback]);
|
||||
|
||||
if ((useMeshFallback || !resolvedSplatUrl) && resolvedFallbackMeshUrl) {
|
||||
return (
|
||||
<Model3DViewer
|
||||
emptyStateTitle="准备加载 3D Mesh"
|
||||
layerVisibility={fallbackMeshLayerVisibility}
|
||||
modelRotation={[Math.PI, 0, 0]}
|
||||
modelUrl={resolvedFallbackMeshUrl}
|
||||
onCaptureReady={onCaptureReady}
|
||||
onLayersChange={onFallbackMeshLayersChange}
|
||||
onStatsChange={onFallbackMeshStatsChange}
|
||||
previewImageUrl={previewImageUrl}
|
||||
resetViewToken={resetViewToken}
|
||||
settings={fallbackMeshSettings || DEFAULT_MESH_SETTINGS}
|
||||
showMovementControls={showMovementControls}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!resolvedSplatUrl) {
|
||||
return (
|
||||
@ -2108,8 +2163,13 @@ export function World3DViewer({
|
||||
<div className={cn("h-full w-full", className)}>
|
||||
<WorldSplatViewer
|
||||
firstPerson={firstPerson}
|
||||
fallbackMeshLayerVisibility={meshLayerVisibility}
|
||||
fallbackMeshSettings={meshSettings}
|
||||
fallbackMeshUrl={meshUrl}
|
||||
hotspots={hotspots}
|
||||
interactionMode={interactionMode}
|
||||
onFallbackMeshLayersChange={onMeshLayersChange}
|
||||
onFallbackMeshStatsChange={onMeshStatsChange}
|
||||
onCaptureReady={onCaptureReady}
|
||||
onHotspotSelect={onHotspotSelect}
|
||||
onPick={onPick}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user