diff --git a/modules/rsplat/components/folk-splat-viewer.ts b/modules/rsplat/components/folk-splat-viewer.ts
index af66f97..ce7d8fa 100644
--- a/modules/rsplat/components/folk-splat-viewer.ts
+++ b/modules/rsplat/components/folk-splat-viewer.ts
@@ -249,7 +249,7 @@ export class FolkSplatViewer extends HTMLElement {
✨
- Upload a single image to generate a 3D Gaussian splat using AI (SAM 3D)
+ Upload a single image to generate a 3D model using AI (Hunyuan3D)
or browse to select an image
@@ -489,15 +489,15 @@ export class FolkSplatViewer extends HTMLElement {
let hiddenAt = 0;
const phases = [
{ t: 0, msg: "Staging image..." },
- { t: 2, msg: "Uploading to SAM 3D..." },
- { t: 5, msg: "Segmenting scene..." },
- { t: 10, msg: "Generating Gaussian splats..." },
- { t: 18, msg: "Finalizing model..." },
- { t: 30, msg: "Almost there..." },
+ { t: 3, msg: "Uploading to Hunyuan3D..." },
+ { t: 8, msg: "Reconstructing 3D geometry..." },
+ { t: 20, msg: "Generating mesh and textures..." },
+ { t: 45, msg: "Finalizing model..." },
+ { t: 75, msg: "Almost there..." },
];
- // Realistic progress curve — typical SAM 3D takes 5-30s
- const EXPECTED_SECONDS = 20;
+ // Realistic progress curve — Hunyuan3D typically takes 30-60s
+ const EXPECTED_SECONDS = 45;
const progressBar = progress.querySelector(".splat-generate__progress-bar") as HTMLElement;
const estimatePercent = (elapsed: number): number => {
@@ -618,7 +618,7 @@ export class FolkSplatViewer extends HTMLElement {
body: JSON.stringify({
url: this._generatedUrl,
title: this._generatedTitle || "AI Generated Model",
- description: "AI-generated 3D model via SAM 3D",
+ description: "AI-generated 3D model via Hunyuan3D",
}),
});
@@ -786,7 +786,7 @@ export class FolkSplatViewer extends HTMLElement {
body: JSON.stringify({
url: this._generatedUrl,
title: this._generatedTitle || "AI Generated Model",
- description: "AI-generated 3D model via SAM 3D",
+ description: "AI-generated 3D model via Hunyuan3D",
}),
});
diff --git a/modules/rsplat/mod.ts b/modules/rsplat/mod.ts
index 6cf92da..a3484ea 100644
--- a/modules/rsplat/mod.ts
+++ b/modules/rsplat/mod.ts
@@ -717,7 +717,7 @@ routes.get("/", async (c) => {
`,
scripts: `
`,
});
diff --git a/server/index.ts b/server/index.ts
index 811afd7..45a6ca6 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -1019,7 +1019,7 @@ app.post("/api/image-stage", async (c) => {
return c.json({ url: `${PUBLIC_ORIGIN}/data/files/generated/${filename}` });
});
-// Image-to-3D via fal.ai SAM 3D
+// Image-to-3D via fal.ai Hunyuan3D v2.1
app.post("/api/3d-gen", async (c) => {
if (!FAL_KEY) return c.json({ error: "FAL_KEY not configured" }, 503);
@@ -1029,17 +1029,13 @@ app.post("/api/3d-gen", async (c) => {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 120_000); // 120s server-side timeout
- const res = await fetch("https://fal.run/fal-ai/sam-3/3d-objects", {
+ const res = await fetch("https://fal.run/fal-ai/hunyuan3d-v21", {
method: "POST",
headers: {
Authorization: `Key ${FAL_KEY}`,
"Content-Type": "application/json",
},
- body: JSON.stringify({
- image_url,
- box_prompts: [{ x_min: 0, y_min: 0, x_max: 9999, y_max: 9999 }],
- export_textured_glb: true,
- }),
+ body: JSON.stringify({ input_image_url: image_url, textured_mesh: true, octree_resolution: 256 }),
signal: controller.signal,
});
clearTimeout(timeout);
@@ -1060,10 +1056,7 @@ app.post("/api/3d-gen", async (c) => {
}
const data = await res.json();
- // SAM 3D: prefer Gaussian splat (.ply), fallback to GLB mesh
- const splatUrl = data.gaussian_splat?.url;
- const glbUrl = data.model_glb?.url || data.glb_url || data.model_mesh?.url;
- const modelUrl = splatUrl || glbUrl;
+ const modelUrl = data.model_glb?.url || data.model_glb_pbr?.url;
if (!modelUrl) return c.json({ error: "No 3D model returned" }, 502);
// Download the model file
@@ -1071,12 +1064,11 @@ app.post("/api/3d-gen", async (c) => {
if (!modelRes.ok) return c.json({ error: "Failed to download model" }, 502);
const modelBuf = await modelRes.arrayBuffer();
- const ext = splatUrl ? "ply" : "glb";
- const filename = `splat-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.${ext}`;
+ const filename = `splat-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.glb`;
const dir = resolve(process.env.FILES_DIR || "./data/files", "generated");
await Bun.write(resolve(dir, filename), modelBuf);
- return c.json({ url: `/data/files/generated/${filename}`, format: ext });
+ return c.json({ url: `/data/files/generated/${filename}`, format: "glb" });
} catch (e: any) {
if (e.name === "AbortError") {
console.error("[3d-gen] server-side timeout after 120s");