From 91d600ed93a646bf3ab231c6d749a9e2fa57138c Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Sun, 22 Feb 2026 00:31:46 +0000 Subject: [PATCH] Fix rSplat viewer: add file extension to URL for format detection GaussianSplats3D infers format from URL file extension. Changed file serve route to /api/splats/:id/:filename so URLs end in .splat/.ply/.spz. Also fixed camera orientation (Y-up) and scale values in test data. Co-Authored-By: Claude Opus 4.6 --- modules/splat/components/folk-splat-viewer.ts | 28 +++++++++++-------- modules/splat/mod.ts | 5 ++-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/modules/splat/components/folk-splat-viewer.ts b/modules/splat/components/folk-splat-viewer.ts index b118d57..2425f40 100644 --- a/modules/splat/components/folk-splat-viewer.ts +++ b/modules/splat/components/folk-splat-viewer.ts @@ -263,12 +263,11 @@ export class FolkSplatViewer extends HTMLElement { try { // Dynamic import from CDN (via importmap) - const THREE = await import("three"); const GaussianSplats3D = await import("@mkkellogg/gaussian-splats-3d"); const viewer = new GaussianSplats3D.Viewer({ - cameraUp: [0, -1, 0], - initialCameraPosition: [1, 0.5, 1], + cameraUp: [0, 1, 0], + initialCameraPosition: [5, 3, 5], initialCameraLookAt: [0, 0, 0], rootElement: container, sharedMemoryForWorkers: false, @@ -276,21 +275,28 @@ export class FolkSplatViewer extends HTMLElement { this._viewer = viewer; - await viewer.addSplatScene(this._splatUrl, { + viewer.addSplatScene(this._splatUrl, { showLoadingUI: false, progressiveLoad: true, + }) + .then(() => { + viewer.start(); + if (loading) loading.classList.add("hidden"); + }) + .catch((e: Error) => { + console.error("[rSplat] Scene load error:", e); + if (loading) { + const text = loading.querySelector(".splat-loading__text"); + if (text) text.textContent = `Error: ${e.message}`; + const spinner = loading.querySelector(".splat-loading__spinner") as HTMLElement; + if (spinner) spinner.style.display = "none"; + } }); - - viewer.start(); - - if (loading) { - loading.classList.add("hidden"); - } } catch (e) { console.error("[rSplat] Viewer init error:", e); if (loading) { const text = loading.querySelector(".splat-loading__text"); - if (text) text.textContent = `Error loading splat: ${(e as Error).message}`; + if (text) text.textContent = `Error loading viewer: ${(e as Error).message}`; const spinner = loading.querySelector(".splat-loading__spinner") as HTMLElement; if (spinner) spinner.style.display = "none"; } diff --git a/modules/splat/mod.ts b/modules/splat/mod.ts index b4f26e1..401667f 100644 --- a/modules/splat/mod.ts +++ b/modules/splat/mod.ts @@ -143,7 +143,8 @@ routes.get("/api/splats/:id", async (c) => { }); // ── API: Serve splat file ── -routes.get("/api/splats/:id/file", async (c) => { +// Matches both /api/splats/:id/file and /api/splats/:id/:filename (e.g. rainbow-sphere.splat) +routes.get("/api/splats/:id/:filename", async (c) => { const id = c.req.param("id"); const rows = await sql.unsafe( @@ -351,7 +352,7 @@ routes.get("/view/:id", async (c) => { [splat.id] ); - const fileUrl = `/${spaceSlug}/splat/api/splats/${splat.slug}/file`; + const fileUrl = `/${spaceSlug}/splat/api/splats/${splat.slug}/${splat.slug}.${splat.file_format}`; const html = renderShell({ title: `${splat.title} | rSplat`,