diff --git a/app/api/canvas/3d/model-proxy/route.ts b/app/api/canvas/3d/model-proxy/route.ts index 2663cd60d..c266ab47f 100644 --- a/app/api/canvas/3d/model-proxy/route.ts +++ b/app/api/canvas/3d/model-proxy/route.ts @@ -4,6 +4,8 @@ import { buildThreeAssetUrl } from "@/lib/canvas/three-asset-url"; export const runtime = "nodejs"; export const maxDuration = 60; +const THREE_ASSET_BROWSER_CACHE_CONTROL = + "private, max-age=86400, stale-while-revalidate=604800"; function isAllowedModelHost(hostname: string) { const normalized = hostname.toLowerCase(); @@ -144,7 +146,13 @@ export async function GET(request: Request) { const acceptRanges = upstream.headers.get("accept-ranges"); if (acceptRanges) responseHeaders.set("accept-ranges", acceptRanges); - responseHeaders.set("cache-control", "private, no-store"); + const etag = upstream.headers.get("etag"); + if (etag) responseHeaders.set("etag", etag); + + const lastModified = upstream.headers.get("last-modified"); + if (lastModified) responseHeaders.set("last-modified", lastModified); + + responseHeaders.set("cache-control", THREE_ASSET_BROWSER_CACHE_CONTROL); if (shouldRewriteGltf) { const rewrittenPayload = rewriteGltfPayload(await upstream.text(), targetUrl); diff --git a/app/canvas/[id]/model3d/page.tsx b/app/canvas/[id]/model3d/page.tsx index 0db5cc4d2..08216c57d 100644 --- a/app/canvas/[id]/model3d/page.tsx +++ b/app/canvas/[id]/model3d/page.tsx @@ -91,6 +91,7 @@ import { cn, fetcher } from "@/lib/utils"; import { getSortedWorldLabsSplatEntries, pickBestArchivedWorldLabsSplatUrl, + pickPreferredInlineWorldLabsSplatUrl, } from "@/lib/worldlabs/splat-utils"; import { DEFAULT_PUBLIC_THREED_MODEL_ALIAS_CONFIG, @@ -2605,7 +2606,10 @@ export default function CanvasModel3DPage() { ? buildWorldShareUrl(currentTask.shareToken) : null; const currentSpzEntries = getSortedWorldLabsSplatEntries(currentWorldAsset?.spzUrls); - const currentPrimarySpzUrl = currentSpzEntries[0]?.[1] || null; + const currentPrimarySpzUrl = + pickPreferredInlineWorldLabsSplatUrl(currentWorldAsset?.spzUrls) || + currentSpzEntries[0]?.[1] || + null; const currentWorldImageUrl = currentWorldAsset?.panoUrl || currentWorldAsset?.thumbnailUrl || diff --git a/app/worlds/share/[token]/page.tsx b/app/worlds/share/[token]/page.tsx index d788c629e..1c215d96a 100644 --- a/app/worlds/share/[token]/page.tsx +++ b/app/worlds/share/[token]/page.tsx @@ -3,7 +3,7 @@ import { notFound } from "next/navigation"; import { WorldSharePage } from "@/components/canvas/world3d/WorldSharePage"; import { db } from "@/lib/db"; import { model3dAsset } from "@/lib/db/schema"; -import { pickBestArchivedWorldLabsSplatUrl } from "@/lib/worldlabs/splat-utils"; +import { pickPreferredInlineWorldLabsSplatUrl } from "@/lib/worldlabs/splat-utils"; export const dynamic = "force-dynamic"; @@ -86,7 +86,7 @@ export default async function SharedWorldPage({ previewImageUrl: asset.previewImageUrl || assetPayload?.thumbnailUrl || null, prompt: asset.prompt || null, semanticsMetadata: assetPayload?.semanticsMetadata || null, - splatUrl: pickBestArchivedWorldLabsSplatUrl(assetPayload?.spzUrls), + splatUrl: pickPreferredInlineWorldLabsSplatUrl(assetPayload?.spzUrls), title: assetPayload?.displayName || asset.prompt || `World ${asset.taskId.slice(0, 8)}`, worldUrl: assetPayload?.worldUrl || null, }; diff --git a/components/canvas/nodes/World3DNode.tsx b/components/canvas/nodes/World3DNode.tsx index b3de276b1..e94396297 100644 --- a/components/canvas/nodes/World3DNode.tsx +++ b/components/canvas/nodes/World3DNode.tsx @@ -35,6 +35,7 @@ import { import { isArchivedWorldLabsAssetUrl, pickBestArchivedWorldLabsSplatUrl, + pickPreferredInlineWorldLabsSplatUrl, } from "@/lib/worldlabs/splat-utils"; import { getNodeContentScale } from "@/lib/canvas/node-content-scale"; import { @@ -201,9 +202,17 @@ export function World3DNode({ const activeView = useMemo(() => resolveActiveView(data), [data]); const availableViews = useMemo(() => resolveAvailableViews(data), [data]); - const renderableSplatUrl = isArchivedWorldLabsAssetUrl(data.splatUrl) - ? data.splatUrl - : undefined; + const renderableSplatUrl = useMemo(() => { + const assetPayloadSplatUrl = pickPreferredInlineWorldLabsSplatUrl( + metadataAssetPayload.spzUrls as Record | null | undefined + ); + + if (isArchivedWorldLabsAssetUrl(assetPayloadSplatUrl)) { + return assetPayloadSplatUrl; + } + + return isArchivedWorldLabsAssetUrl(data.splatUrl) ? data.splatUrl : undefined; + }, [data.splatUrl, metadataAssetPayload.spzUrls]); const renderableMeshUrl = isArchivedWorldLabsAssetUrl(data.meshUrl) ? data.meshUrl : undefined; diff --git a/components/canvas/world3d/World3DViewer.tsx b/components/canvas/world3d/World3DViewer.tsx index dffbdc609..5b5bd5084 100644 --- a/components/canvas/world3d/World3DViewer.tsx +++ b/components/canvas/world3d/World3DViewer.tsx @@ -166,8 +166,10 @@ function lerpSparkRevealProfile( }; } -const SPLAT_REVEAL_DURATION_MS = 2600; -const SPLAT_PARTICLE_SAMPLE_LIMIT = 1800; +const SPLAT_REVEAL_DURATION_MS = 900; +const SPLAT_PARTICLE_SAMPLE_LIMIT = 720; +const SPLAT_WORLD_BOUNDS_SAMPLE_LIMIT = 1200; +const SPLAT_FAST_LOAD_SKIP_REVEAL_MS = 1200; const KEY_TO_MOVEMENT: Partial> = { KeyW: "forward", @@ -307,7 +309,7 @@ function getRobustSplatWorldBounds(splatMesh: SparkSplatMesh) { } splatMesh.updateMatrixWorld(true); - const targetCount = Math.min(totalSplats, 3200); + const targetCount = Math.min(totalSplats, SPLAT_WORLD_BOUNDS_SAMPLE_LIMIT); const sampleStep = Math.max(Math.floor(totalSplats / targetCount), 1); const point = new THREE.Vector3(); const xs: number[] = []; @@ -1063,16 +1065,16 @@ function WorldSplatScene({ onLoadStateChange?.("ready"); const buildParticleCloud = () => { - if (cancelled) return; + if (cancelled || revealPhaseRef.current === "hidden") return; setParticleCloudData(buildSplatParticleCloudData(splatMesh)); }; if ("requestIdleCallback" in window) { idleCallbackId = window.requestIdleCallback(buildParticleCloud, { - timeout: 900, + timeout: 250, }); } else { - timeoutId = globalThis.setTimeout(buildParticleCloud, 120); + timeoutId = globalThis.setTimeout(buildParticleCloud, 160); } }); }) @@ -1364,15 +1366,20 @@ function WorldSplatViewer({ const [loadState, setLoadState] = useState<"loading" | "ready" | "error">("loading"); const [overlayPhase, setOverlayPhase] = useState("loading"); const resolvedSplatUrl = useMemo(() => buildThreeAssetUrl(splatUrl), [splatUrl]); + const loadStartedAtRef = useRef(0); useEffect(() => { + loadStartedAtRef.current = performance.now(); setLoadState("loading"); setOverlayPhase("loading"); }, [resolvedSplatUrl]); useEffect(() => { if (loadState === "ready") { - setOverlayPhase("revealing"); + const elapsed = performance.now() - loadStartedAtRef.current; + setOverlayPhase( + elapsed <= SPLAT_FAST_LOAD_SKIP_REVEAL_MS ? "hidden" : "revealing" + ); } if (loadState === "loading") { @@ -1421,8 +1428,13 @@ function WorldSplatViewer({ , + targetScore: number +) { + if (!entries.length) return null; + + const scoredEntries = entries + .map((entry) => ({ + entry, + score: parseResolutionScore(entry[0]), + })) + .filter( + (item) => + Number.isFinite(item.score) && + item.score > 0 && + item.score !== Number.MAX_SAFE_INTEGER + ); + + if (!scoredEntries.length) { + return entries[0] || null; + } + + return scoredEntries + .slice() + .sort((left, right) => { + const distanceDelta = + Math.abs(left.score - targetScore) - Math.abs(right.score - targetScore); + if (distanceDelta !== 0) return distanceDelta; + return left.score - right.score; + })[0]?.entry || null; +} + const KNOWN_ARCHIVED_ASSET_HOSTS = [ "blob.vercel-storage.com", "s3.1080p.hk", @@ -129,3 +163,26 @@ export function pickBestArchivedWorldLabsSplatUrl( ) { return getArchivedWorldLabsSplatEntries(spzUrls)[0]?.[1] || null; } + +export function pickPreferredInlineWorldLabsSplatEntry( + spzUrls?: Record | null +) { + const archivedEntries = getArchivedWorldLabsSplatEntries(spzUrls); + if (archivedEntries.length > 0) { + return pickClosestResolutionEntry( + archivedEntries, + INLINE_WORLDLABS_SPLAT_TARGET_SCORE + ); + } + + return pickClosestResolutionEntry( + getSortedWorldLabsSplatEntries(spzUrls), + INLINE_WORLDLABS_SPLAT_TARGET_SCORE + ); +} + +export function pickPreferredInlineWorldLabsSplatUrl( + spzUrls?: Record | null +) { + return pickPreferredInlineWorldLabsSplatEntry(spzUrls)?.[1] || null; +}