diff --git a/viewer/.gitignore b/.gitignore similarity index 100% rename from viewer/.gitignore rename to .gitignore diff --git a/viewer/.npmrc b/.npmrc similarity index 100% rename from viewer/.npmrc rename to .npmrc diff --git a/Dockerfile b/Dockerfile index b6706f9bc07cd11410e785c8727a8586203700b4..6a33d344d2fa17fb961562bc8e1f53265f65871d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ FROM node:alpine WORKDIR /app -COPY viewer/package.json package.json +COPY package.json package.json RUN npm install -COPY viewer/ /app +COPY / /app RUN npm run build EXPOSE 3000 -CMD ["npm", "start"] \ No newline at end of file +CMD ["npm", "start"] diff --git a/viewer/package-lock.json b/package-lock.json similarity index 99% rename from viewer/package-lock.json rename to package-lock.json index 4a77b36ad89f7d59c979775593c7135341b4f817..8d1fc44ae20380126a21f14f30b88fce49432b75 100644 --- a/viewer/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-node": "^1.3.1", "@sveltejs/kit": "^1.20.4", + "carbon-icons-svelte": "^12.8.0", "svelte": "^4.0.5", "svelte-check": "^3.4.3", "tslib": "^2.4.1", @@ -829,6 +830,12 @@ "node": ">=6" } }, + "node_modules/carbon-icons-svelte": { + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/carbon-icons-svelte/-/carbon-icons-svelte-12.8.0.tgz", + "integrity": "sha512-ops12PG2NucXc4fWaDP9ZmsN4oA4ofjwQvd3yJAbrRzLfrUw9uMyC5u7FVSujS6gLkPRIbdd+LekfZt+1kY/zg==", + "dev": true + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", diff --git a/viewer/package.json b/package.json similarity index 95% rename from viewer/package.json rename to package.json index 3ed0f2c582f31a110a37bc8bdc42aebc63f683ef..584ab0cc0d48b440bdf56d412cc6d9621384bd10 100644 --- a/viewer/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-node": "^1.3.1", "@sveltejs/kit": "^1.20.4", + "carbon-icons-svelte": "^12.8.0", "svelte": "^4.0.5", "svelte-check": "^3.4.3", "tslib": "^2.4.1", diff --git a/viewer/src/app.d.ts b/src/app.d.ts similarity index 100% rename from viewer/src/app.d.ts rename to src/app.d.ts diff --git a/viewer/src/app.html b/src/app.html similarity index 100% rename from viewer/src/app.html rename to src/app.html diff --git a/viewer/src/lib/index.ts b/src/lib/index.ts similarity index 100% rename from viewer/src/lib/index.ts rename to src/lib/index.ts diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..0ef3fe92b2046c135e3ce30d599f84ea239b5915 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,62 @@ +<script lang="ts"> + import Leaderboard from "./Leaderboard.svelte"; + import ModelDetails from "./ModelDetails.svelte"; + import Viewer from "./Viewer.svelte"; + + interface Scene { + name: string; + url: string; + thumbnail: string; + } + + let currentView: "Leaderboard" | "Vote" | "ModelDetails" | "Viewer" = "Leaderboard"; + let selectedEntry: { name: string; path: string } | null = null; + let selectedScene: Scene | null = null; + + function goHome() { + window.location.href = "/"; + } + + function showModelDetails(entry: { name: string; path: string }) { + selectedEntry = entry; + currentView = "ModelDetails"; + } + + function showScene(scene: Scene) { + selectedScene = scene; + currentView = "Viewer"; + } +</script> + +<div class="container"> + <div on:pointerdown={goHome} class="banner"> + <h1>IGF</h1> + <p>Generative 3D Leaderboard</p> + </div> + + {#if currentView === "Leaderboard" || currentView === "Vote"} + <div class="tabs"> + <button on:click={() => (currentView = "Vote")} class={currentView === "Vote" ? "active" : ""}>Vote</button> + <button on:click={() => (currentView = "Leaderboard")} class={currentView === "Leaderboard" ? "active" : ""} + >Leaderboard</button + > + </div> + {/if} + + {#if currentView === "Leaderboard"} + <Leaderboard onEntryClick={showModelDetails} /> + {:else if currentView === "Vote"} + <div class="loading-container"> + <div class="loading-text">Coming Soon</div> + </div> + {:else if currentView === "ModelDetails" && selectedEntry} + <ModelDetails + modelName={selectedEntry.name} + modelPath={selectedEntry.path} + onBack={() => (currentView = "Leaderboard")} + onSceneClick={showScene} + /> + {:else if currentView === "Viewer" && selectedScene && selectedEntry} + <Viewer modelName={selectedEntry.name} scene={selectedScene} onBack={() => (currentView = "ModelDetails")} /> + {/if} +</div> diff --git a/src/routes/Leaderboard.svelte b/src/routes/Leaderboard.svelte new file mode 100644 index 0000000000000000000000000000000000000000..5ffe015880453f515165aa762c51db26193a856c --- /dev/null +++ b/src/routes/Leaderboard.svelte @@ -0,0 +1,58 @@ +<script lang="ts"> + import { onMount } from "svelte"; + import { ProgressBarRound } from "carbon-icons-svelte"; + + interface Entry { + name: string; + path: string; + thumbnail: string; + } + + export let onEntryClick: (entry: Entry) => void; + + let leaderboard: Entry[] = []; + + const fetchLeaderboardData = async () => { + const baseUrl = "https://huggingface.co"; + const repoId = "dylanebert/3d-arena"; + const url = `${baseUrl}/api/datasets/${repoId}`; + const response = await fetch(url); + const data = await response.json(); + + const entries: Entry[] = []; + + for (const item of data.siblings) { + const filename = item.rfilename; + if (!filename.endsWith(".json")) continue; + + const directory = filename.split("/").slice(0, -1).join("/"); + const name = directory.split("/").pop(); + const thumbnail = `${baseUrl}/datasets/${repoId}/resolve/main/${directory}/thumbnail.png`; + + entries.push({ name, path: filename, thumbnail }); + } + + leaderboard = entries; + }; + + onMount(async () => { + await fetchLeaderboardData(); + }); +</script> + +{#if leaderboard.length > 0} + <div class="grid"> + {#each leaderboard as entry, index} + <button class="grid-item" on:click={() => onEntryClick(entry)}> + <img src={entry.thumbnail} alt={entry.name} class="thumbnail" /> + <div class="ranking">{index + 1}</div> + <div class="title">{entry.name}</div> + </button> + {/each} + </div> +{:else} + <div class="loading-container"> + <ProgressBarRound class="loading-icon" /> + <div class="loading-text">Loading...</div> + </div> +{/if} diff --git a/src/routes/ModelDetails.svelte b/src/routes/ModelDetails.svelte new file mode 100644 index 0000000000000000000000000000000000000000..f18f9f4e3cfb58accf43302c2fddc9cbb60ee93b --- /dev/null +++ b/src/routes/ModelDetails.svelte @@ -0,0 +1,73 @@ +<script lang="ts"> + import { onMount } from "svelte"; + import { ProgressBarRound, ArrowLeft } from "carbon-icons-svelte"; + + interface Scene { + name: string; + url: string; + thumbnail: string; + } + + export let modelName: string; + export let modelPath: string; + export let onBack: () => void; + export let onSceneClick: (scene: Scene) => void; + + let scenes: Scene[] = []; + + async function fetchScenes() { + scenes = []; + + const baseUrl = "https://huggingface.co"; + const repoId = "dylanebert/3d-arena"; + const url = `${baseUrl}/api/datasets/${repoId}`; + const response = await fetch(url); + const responseData = await response.json(); + + const extensions = ["obj", "glb", "ply", "splat"]; + const directory = modelPath.split("/").slice(0, -1).join("/"); + + scenes = responseData.siblings + .filter((scene: any) => { + const fileExtension = scene.rfilename.split(".").pop(); + return scene.rfilename.startsWith(directory) && extensions.includes(fileExtension); + }) + .reduce((acc: Scene[], scene: any) => { + const name = scene.rfilename.split("/").pop().split(".").slice(0, -1).join("."); + const url = `${baseUrl}/datasets/${repoId}/resolve/main/${scene.rfilename}`; + const thumbnail = url.replace(/\.[^.]+$/, ".png"); + acc.push({ name, url, thumbnail }); + return acc; + }, []); + + scenes = [...scenes]; + } + + onMount(fetchScenes); +</script> + +<div class="header"> + <div class="back" aria-label="Back" aria-hidden="true" on:click={onBack}> + <ArrowLeft size={24} /> + </div> + <div class="spacer" /> + <button class="title-button" on:click={fetchScenes}> + <h2 class="muted">{modelName}</h2> + </button> + <div class="spacer" /> +</div> +{#if scenes.length > 0} + <div class="grid"> + {#each scenes as scene} + <button class="grid-item" on:click={() => onSceneClick(scene)}> + <img src={scene.thumbnail} alt={scene.name} class="thumbnail" /> + <div class="title">{modelName}</div> + </button> + {/each} + </div> +{:else} + <div class="loading-container"> + <ProgressBarRound class="loading-icon" /> + <div class="loading-text">Loading...</div> + </div> +{/if} diff --git a/src/routes/Viewer.svelte b/src/routes/Viewer.svelte new file mode 100644 index 0000000000000000000000000000000000000000..f0b79d73e1be82bead6f171b4d8d3ef0a1dc31af --- /dev/null +++ b/src/routes/Viewer.svelte @@ -0,0 +1,76 @@ +<script lang="ts"> + import { onMount, onDestroy } from "svelte"; + import type { IViewer } from "./viewers/IViewer"; + import { createViewer } from "./viewers/ViewerFactory"; + import ArrowLeft from "carbon-icons-svelte/lib/ArrowLeft.svelte"; + + interface Scene { + name: string; + url: string; + thumbnail: string; + } + + export let modelName: string; + export let scene: Scene; + export let onBack: () => void; + + let container: HTMLDivElement; + let canvas: HTMLCanvasElement; + + let viewer: IViewer; + + async function loadScene() { + viewer = await createViewer(scene.url, canvas); + window.addEventListener("resize", handleResize); + window.addEventListener("keydown", handleKeyDown); + handleResize(); + } + + function handleResize() { + if (!canvas || !container) return; + const maxWidth = container.clientHeight * (16 / 9); + const maxHeight = container.clientWidth * (9 / 16); + canvas.width = Math.min(container.clientWidth, maxWidth); + canvas.height = Math.min(container.clientHeight, maxHeight); + } + + function handleKeyDown(e: KeyboardEvent) { + if (e.code === "KeyP") { + capture(); + } + } + + async function capture() { + const data = await viewer.capture(); + if (!data) { + console.error("Failed to capture screenshot"); + return; + } + const a = document.createElement("a"); + a.href = data; + a.download = "screenshot.png"; + a.click(); + } + + onMount(loadScene); + + onDestroy(() => { + viewer?.dispose(); + window.removeEventListener("resize", handleResize); + window.removeEventListener("keydown", handleKeyDown); + }); +</script> + +<div class="header"> + <div class="back" aria-label="Back" aria-hidden="true" on:click={onBack}> + <ArrowLeft size={24} /> + </div> + <button class="title-button" on:click={loadScene}> + <!-- svelte-ignore a11y-click-events-have-key-events --> + <!-- svelte-ignore a11y-no-static-element-interactions --> + <h2><span class="muted" on:click={onBack}>{modelName}/</span>{scene.name}</h2> + </button> +</div> +<div class="canvas-container" bind:this={container}> + <canvas bind:this={canvas} width={800} height={600} /> +</div> diff --git a/viewer/src/routes/store.js b/src/routes/store.js similarity index 100% rename from viewer/src/routes/store.js rename to src/routes/store.js diff --git a/viewer/src/routes/viewer/[slug]/BabylonViewer.ts b/src/routes/viewers/BabylonViewer.ts similarity index 100% rename from viewer/src/routes/viewer/[slug]/BabylonViewer.ts rename to src/routes/viewers/BabylonViewer.ts diff --git a/viewer/src/routes/viewer/[slug]/IViewer.ts b/src/routes/viewers/IViewer.ts similarity index 100% rename from viewer/src/routes/viewer/[slug]/IViewer.ts rename to src/routes/viewers/IViewer.ts diff --git a/viewer/src/routes/viewer/[slug]/SplatViewer.ts b/src/routes/viewers/SplatViewer.ts similarity index 84% rename from viewer/src/routes/viewer/[slug]/SplatViewer.ts rename to src/routes/viewers/SplatViewer.ts index e0721b8ca34334561c2fd65c3741b51d727336b2..1010c4294a7b2da297931b95a92e47773c8330b9 100644 --- a/viewer/src/routes/viewer/[slug]/SplatViewer.ts +++ b/src/routes/viewers/SplatViewer.ts @@ -18,14 +18,23 @@ export class SplatViewer implements IViewer { this.scene = new SPLAT.Scene(); this.camera = new SPLAT.Camera(); this.controls = new SPLAT.OrbitControls(this.camera, canvas); + this.controls.orbitSpeed = 3.0; this.handleResize = this.handleResize.bind(this); } async loadScene(url: string, loadingBarCallback?: (progress: number) => void) { - await SPLAT.Loader.LoadAsync(url, this.scene, (progress) => { - loadingBarCallback?.(progress); - }); + if (url.endsWith(".splat")) { + await SPLAT.Loader.LoadAsync(url, this.scene, (progress) => { + loadingBarCallback?.(progress); + }); + } else if (url.endsWith(".ply")) { + await SPLAT.PLYLoader.LoadAsync(url, this.scene, (progress) => { + loadingBarCallback?.(progress); + }); + } else { + throw new Error("Unsupported file format"); + } const frame = () => { this.controls.update(); diff --git a/src/routes/viewers/ViewerFactory.ts b/src/routes/viewers/ViewerFactory.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d4218eebc861ccaa66b436304a30d3970e36b89 --- /dev/null +++ b/src/routes/viewers/ViewerFactory.ts @@ -0,0 +1,19 @@ +import type { IViewer } from "./IViewer"; +import { BabylonViewer } from "./BabylonViewer"; +import { SplatViewer } from "./SplatViewer"; + +const meshFormats = ["obj", "stl", "gltf", "glb"]; +const splatFormats = ["splat", "ply"]; + +export async function createViewer(url: string, canvas: HTMLCanvasElement): Promise<IViewer> { + let viewer: IViewer; + if (meshFormats.some((format) => url.endsWith(format))) { + viewer = new BabylonViewer(canvas); + } else if (splatFormats.some((format) => url.endsWith(format))) { + viewer = new SplatViewer(canvas); + } else { + throw new Error("Unsupported file format"); + } + await viewer.loadScene(url); + return viewer; +} diff --git a/viewer/static/favicon.png b/static/favicon.png similarity index 100% rename from viewer/static/favicon.png rename to static/favicon.png diff --git a/viewer/static/global.css b/static/global.css similarity index 52% rename from viewer/static/global.css rename to static/global.css index 7fde503d1ee06102067befc5c162c167670c1e11..bd549bc907d67355c4803781f7d7f79969d982a6 100644 --- a/viewer/static/global.css +++ b/static/global.css @@ -1,6 +1,6 @@ html, body { - font-family: 'Roboto', sans-serif; + font-family: "Roboto", sans-serif; background-color: #1a1b1e; color: white; margin: 0; @@ -32,11 +32,45 @@ body { cursor: pointer; } +.muted { + color: #aaa; +} + +.loading-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 196px; +} + +.loading-icon { + width: 32px; + height: 32px; + animation: spin 0.5s linear infinite; + color: #aaa; +} + +.loading-text { + color: #aaa; + margin-top: 10px; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + .container { padding: 10px 15px 80px 15px; margin-left: auto; margin-right: auto; - max-height: 100vh; + height: 100vh; overflow-y: auto; position: relative; box-sizing: border-box; @@ -58,31 +92,47 @@ body { } } -.exit-button { +.canvas-container { + position: relative; + box-sizing: border-box; display: flex; + flex-direction: column; justify-content: center; align-items: center; + width: 100%; + max-height: 50%; + overflow: hidden; +} + +.header { + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.back { position: absolute; - top: 10px; - right: 16px; - width: 36px; - height: 36px; - color: #aaa; - font-size: 24px; + left: 0; cursor: pointer; - font-weight: bold; - transition: scale 0.2s ease; + transition: transform 0.1s; + padding: 16px; } -.exit-button:hover { - scale: 1.1; +.back:hover { + transform: scale(1.2); } -.header { - padding: 20px 0; - font-size: 24px; - font-weight: bold; - color: #aaa; +.spacer { + flex: 1; +} + +.title-button { + background: none; + border: none; + font: inherit; + color: inherit; + cursor: pointer; } .viewer { @@ -129,6 +179,12 @@ body { padding-top: 100%; } +@media (max-width: 575px) { + .grid-item::before { + padding-top: 56.25%; + } +} + .grid-item:hover { cursor: pointer; } @@ -153,18 +209,65 @@ body { height: 48px; } -.thumbnail { +.grid-item .ranking { position: absolute; - top: -10px; - ; + top: 0; left: 0; - width: 100%; - height: auto; + background-color: #1a1b1e; + color: #aaa; + padding: 10px; + box-sizing: border-box; + font-size: 16px; + overflow: hidden; + transition: background-color 0.2s ease; +} + +.grid-item:hover .ranking { + background-color: #444; +} + +.thumbnail { + position: absolute; + top: 50%; + left: 50%; + width: auto; + height: 100%; overflow: hidden; scale: 1; - transition: scale 0.2s ease; + transform: translate(-50%, -50%); + transition: transform 0.2s ease; } .grid-item:hover .thumbnail { - scale: 1.1; -} \ No newline at end of file + transform: translate(-50%, -50%) scale(1.1); +} + +.tabs { + display: flex; + justify-content: left; + margin-top: 10px; + margin-bottom: 10px; + gap: 10px; + width: 100%; +} + +.tabs button { + background-color: #1a1b1e; + color: #ddd; + border: none; + outline: none; + padding: 10px 10px 10px 10px; + font-family: "Roboto", sans-serif; + font-size: 14px; + font-weight: 600; + transition: background-color 0.2s ease; +} + +.tabs button:hover { + cursor: pointer; + background-color: #555; +} + +.tabs button.active { + background-color: #444; +} diff --git a/viewer/svelte.config.js b/svelte.config.js similarity index 100% rename from viewer/svelte.config.js rename to svelte.config.js diff --git a/viewer/tsconfig.json b/tsconfig.json similarity index 100% rename from viewer/tsconfig.json rename to tsconfig.json diff --git a/viewer/README.md b/viewer/README.md deleted file mode 100644 index 5c91169b0ca6508bb24301c957a9edea5abf2b01..0000000000000000000000000000000000000000 --- a/viewer/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# create-svelte - -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). - -## Creating a project - -If you're seeing this, you've probably already done this step. Congrats! - -```bash -# create a new project in the current directory -npm create svelte@latest - -# create a new project in my-app -npm create svelte@latest my-app -``` - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```bash -npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` - -## Building - -To create a production version of your app: - -```bash -npm run build -``` - -You can preview the production build with `npm run preview`. - -> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. diff --git a/viewer/src/lib/data/dataLoader.ts b/viewer/src/lib/data/dataLoader.ts deleted file mode 100644 index 6ebbde32211a6ea478cdfa4afda14f41b2690a5b..0000000000000000000000000000000000000000 --- a/viewer/src/lib/data/dataLoader.ts +++ /dev/null @@ -1,10 +0,0 @@ -import scenes from "$lib/data/scenes.json"; -import models from "$lib/data/models.json"; - -export async function getScenes() { - return scenes; -} - -export async function getModels() { - return models; -} diff --git a/viewer/src/lib/data/models.json b/viewer/src/lib/data/models.json deleted file mode 100644 index 2b5b6423c0ef6772dbd0f775f5bff7a47264d296..0000000000000000000000000000000000000000 --- a/viewer/src/lib/data/models.json +++ /dev/null @@ -1,137 +0,0 @@ -[ - { - "slug": "3dgs", - "title": "3D Gaussian Splatting", - "paper": "https://huggingface.co/papers/2308.04079", - "project": "https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/", - "code": "https://github.com/graphdeco-inria/gaussian-splatting", - "spaces": [] - }, - { - "slug": "sync-dreamer", - "title": "SyncDreamer", - "paper": "https://huggingface.co/papers/2309.03453", - "project": "https://liuyuan-pal.github.io/SyncDreamer/", - "code": "https://github.com/liuyuan-pal/SyncDreamer", - "spaces": [] - }, - { - "slug": "shap-e", - "title": "shap-e", - "paper": "https://arxiv.org/abs/2305.02463", - "code": "https://github.com/openai/shap-e", - "spaces": [ - "https://huggingface.co/spaces/hysts/Shap-E" - ] - }, - { - "slug": "dreamfusion", - "title": "DreamFusion", - "paper": "https://arxiv.org/abs/2209.14988", - "project": "https://dreamfusion3d.github.io/index.html", - "spaces": [] - }, - { - "slug": "mv-dream", - "title": "MVDream", - "paper": "https://huggingface.co/papers/2308.16512", - "project": "https://mv-dream.github.io/", - "code": "https://github.com/bytedance/MVDream", - "spaces": [] - }, - { - "slug": "wonder3d", - "title": "Wonder3D", - "paper": "https://huggingface.co/papers/2310.15008", - "project": "https://www.xxlong.site/Wonder3D/", - "code": "https://github.com/xxlong0/Wonder3D", - "spaces": [] - }, - { - "slug": "gaussian-dreamer", - "title": "GaussianDreamer", - "paper": "https://huggingface.co/papers/2310.08529", - "project": "https://taoranyi.com/gaussiandreamer/", - "code": "https://github.com/hustvl/GaussianDreamer", - "spaces": [] - }, - { - "slug": "dream-gaussian", - "title": "DreamGaussian", - "paper": "https://huggingface.co/papers/2309.16653", - "project": "https://dreamgaussian.github.io/", - "code": "https://github.com/dreamgaussian/dreamgaussian", - "spaces": [ - "https://huggingface.co/spaces/jiawei011/dreamgaussian" - ] - }, - { - "slug": "panic3d-anime-reconstruction", - "title": "PAniC-3D", - "paper": "https://arxiv.org/abs/2303.14587", - "project": "", - "code": "https://github.com/ShuhongChen/panic3d-anime-reconstruction", - "spaces": [] - }, - { - "slug": "eg3d", - "title": "EG3D", - "paper": "https://arxiv.org/abs/2112.07945", - "project": "https://nvlabs.github.io/eg3d/", - "code": "https://github.com/NVlabs/eg3d", - "spaces": [] - }, - { - "slug": "ag3d", - "title": "AG3D", - "paper": "https://zj-dong.github.io/AG3D/assets/paper.pdf", - "project": "https://zj-dong.github.io/AG3D/", - "code": "https://github.com/zj-dong/AG3D", - "spaces": [] - }, - { - "slug": "scene-dreamer", - "title": "SceneDreamer", - "paper": "https://arxiv.org/abs/2302.01330", - "project": "https://scene-dreamer.github.io/", - "code": "https://github.com/FrozenBurning/SceneDreamer", - "spaces": ["https://huggingface.co/spaces/FrozenBurning/SceneDreamer"] - }, - { - "slug": "ldm3d-vr", - "title": "LDM3D-VR", - "paper": "https://arxiv.org/abs/2311.03226", - "spaces": [] - }, - { - "slug": "d3ga", - "title": "D3GA", - "paper": "https://huggingface.co/papers/2311.08581", - "project": "https://zielon.github.io/d3ga/", - "spaces": [] - }, - { - "slug": "dmv3d", - "title": "DMV3D", - "paper": "https://huggingface.co/papers/2311.09217", - "project": "https://justimyhxu.github.io/projects/dmv3d/", - "spaces": [] - }, - { - "slug": "instant3d", - "title": "Instant3D", - "paper": "https://huggingface.co/papers/2311.08403", - "project": "https://instant-3d.github.io/", - "spaces": [] - }, - { - "slug": "one2345++", - "title": "One-2-3-45++", - "paper": "https://huggingface.co/papers/2311.07885", - "project": "https://one-2-3-45.github.io/", - "code": "https://github.com/One-2-3-45/One-2-3-45", - "spaces": [ - "https://huggingface.co/spaces/One-2-3-45/One-2-3-45" - ] - } -] diff --git a/viewer/src/lib/data/scenes.json b/viewer/src/lib/data/scenes.json deleted file mode 100644 index 5d9be8717c42b2d68021f51f930c564f84b4f5c8..0000000000000000000000000000000000000000 --- a/viewer/src/lib/data/scenes.json +++ /dev/null @@ -1,255 +0,0 @@ -[ - { - "slug": "3dgs-bicycle", - "model": "3dgs", - "title": "Bicycle", - "type": "splat", - "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/bicycle/bicycle-7k.splat", - "pipeline": [ - "Gaussian Splatting" - ] - }, - { - "slug": "3dgs-bonsai", - "model": "3dgs", - "title": "Bonsai", - "type": "splat", - "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/bonsai/bonsai-7k.splat", - "pipeline": [ - "Gaussian Splatting" - ] - }, - { - "slug": "3dgs-counter", - "model": "3dgs", - "title": "Counter", - "type": "splat", - "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/counter/counter-7k.splat", - "pipeline": [ - "Gaussian Splatting" - ] - }, - { - "slug": "3dgs-garden", - "model": "3dgs", - "title": "Garden", - "type": "splat", - "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/garden/garden-7k.splat", - "pipeline": [ - "Gaussian Splatting" - ] - }, - { - "slug": "3dgs-kitchen", - "model": "3dgs", - "title": "Kitchen", - "type": "splat", - "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/kitchen/kitchen-7k.splat", - "pipeline": [ - "Gaussian Splatting" - ] - }, - { - "slug": "3dgs-playroom", - "model": "3dgs", - "title": "Playroom", - "type": "splat", - "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/playroom/playroom-7k.splat", - "pipeline": [ - "Gaussian Splatting" - ] - }, - { - "slug": "3dgs-room", - "model": "3dgs", - "title": "Room", - "type": "splat", - "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/room/room-7k.splat", - "pipeline": [ - "Gaussian Splatting" - ] - }, - { - "slug": "3dgs-stump", - "model": "3dgs", - "title": "Stump", - "type": "splat", - "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/stump/stump-7k.splat", - "pipeline": [ - "Gaussian Splatting" - ] - }, - { - "slug": "sync-dreamer-armor", - "model": "sync-dreamer", - "title": "armor", - "type": "mesh", - "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/armor.glb", - "pipeline": [ - "Multi-view Diffusion", - "NeuS" - ] - }, - { - "slug": "sync-dreamer-deer", - "model": "sync-dreamer", - "title": "deer", - "type": "mesh", - "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/deer.glb", - "pipeline": [ - "Multi-view Diffusion", - "NeuS" - ] - }, - { - "slug": "sync-dreamer-drum", - "model": "sync-dreamer", - "title": "drum", - "type": "mesh", - "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/drum.glb", - "pipeline": [ - "Multi-view Diffusion", - "NeuS" - ] - }, - { - "slug": "sync-dreamer-forest", - "model": "sync-dreamer", - "title": "forest", - "type": "mesh", - "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/forest.glb", - "pipeline": [ - "Multi-view Diffusion", - "NeuS" - ] - }, - { - "slug": "sync-dreamer-monkey", - "model": "sync-dreamer", - "title": "monkey", - "type": "mesh", - "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/monkey.glb", - "pipeline": [ - "Multi-view Diffusion", - "NeuS" - ] - }, - { - "slug": "sync-dreamer-poro", - "model": "sync-dreamer", - "title": "poro", - "type": "mesh", - "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/poro.glb", - "pipeline": [ - "Multi-view Diffusion", - "NeuS" - ] - }, - { - "slug": "sync-dreamer-train", - "model": "sync-dreamer", - "title": "train", - "type": "mesh", - "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/train.glb", - "pipeline": [ - "Multi-view Diffusion", - "NeuS" - ] - }, - { - "slug": "dreamfusion-sweaterfrog", - "model": "dreamfusion", - "title": "sweater frog", - "type": "mesh", - "url": "https://dreamfusion3d.github.io/assets/meshes2/sweaterfrog_1step.glb", - "prompt": "frog wearing a sweater", - "pipeline": [ - "Multi-view Diffusion", - "NeuS" - ] - }, - { - "slug": "dreamfusion-chick", - "model": "dreamfusion", - "title": "chick", - "type": "mesh", - "url": "https://dreamfusion3d.github.io/assets/meshes2/44855521_sept18_hero16_047a_DSLR_photo_of_an_eggshell_broken_in_two_with_an_adorable_chick_standing_next_to_it_1step.glb", - "prompt": "eggshell broken in two with an adorable chick standing next to it", - "pipeline": [ - "Multi-view Diffusion", - "Marching Cubes" - ] - }, - { - "slug": "dreamfusion-ghost", - "model": "dreamfusion", - "title": "ghost", - "type": "mesh", - "url": "https://dreamfusion3d.github.io/assets/meshes2/44934035_sept18_hero19_113a_DSLR_photo_of_a_ghost_eating_a_hamburger_1step.glb", - "prompt": "ghost eating a hamburger", - "pipeline": [ - "Multi-view Diffusion", - "Marching Cubes" - ] - }, - { - "slug": "dreamfusion-pig", - "model": "dreamfusion", - "title": "pig", - "type": "mesh", - "url": "https://dreamfusion3d.github.io/assets/meshes2/44844973_sept18_hero14_076a_pig_wearing_a_backpack_1step.glb", - "prompt": "a pig wearing a backback", - "pipeline": [ - "Multi-view Diffusion", - "Marching Cubes" - ] - }, - { - "slug": "dreamfusion-eagle", - "model": "dreamfusion", - "title": "eagle", - "type": "mesh", - "url": "https://dreamfusion3d.github.io/assets/meshes2/44853505_sept18_hero15_145a_bald_eagle_carved_out_of_wood_1step.glb", - "prompt": "a bald eagle carved out of wood", - "pipeline": [ - "Multi-view Diffusion", - "Marching Cubes" - ] - }, - { - "slug": "dreamfusion-crab", - "model": "dreamfusion", - "title": "crab", - "type": "mesh", - "url": "https://dreamfusion3d.github.io/assets/meshes2/44930695_sept18_hero18_103a_crab,_low_poly_1step.glb", - "prompt": "a crab, low poly", - "pipeline": [ - "Multi-view Diffusion", - "Marching Cubes" - ] - }, - { - "slug": "dreamfusion-lemur", - "model": "dreamfusion", - "title": "lemur", - "type": "mesh", - "url": "https://dreamfusion3d.github.io/assets/meshes2/44853505_sept18_hero15_124a_lemur_taking_notes_in_a_journal_1step.glb", - "prompt": "a lemur taking notes in a journal", - "pipeline": [ - "Multi-view Diffusion", - "Marching Cubes" - ] - }, - { - "slug": "dreamfusion-corgi", - "model": "dreamfusion", - "title": "corgi", - "type": "mesh", - "url": "https://dreamfusion3d.github.io/assets/meshes2/44960400_sept18_hero20peter_117a_plush_toy_of_a_corgi_nurse_1step.glb", - "prompt": "a plush toy of a corgi nurse", - "pipeline": [ - "Multi-view Diffusion", - "Marching Cubes" - ] - } -] \ No newline at end of file diff --git a/viewer/src/lib/placeholder.png b/viewer/src/lib/placeholder.png deleted file mode 100755 index fb66a22baba4329aa49598425c75ebd92eeaac95..0000000000000000000000000000000000000000 Binary files a/viewer/src/lib/placeholder.png and /dev/null differ diff --git a/viewer/src/routes/+page.svelte b/viewer/src/routes/+page.svelte deleted file mode 100644 index 25c761a2e7e69ef90776d60a562ecf1dfcbb5d56..0000000000000000000000000000000000000000 --- a/viewer/src/routes/+page.svelte +++ /dev/null @@ -1,57 +0,0 @@ -<script lang="ts"> - import { activeTab } from "./store.js"; - import ModelsView from "./components/ModelsView.svelte"; - import ScenesView from "./components/ScenesView.svelte"; - - function goHome() { - window.location.href = "/"; - } -</script> - -<div class="container"> - <div on:pointerdown={goHome} class="banner"> - <h1>IGF</h1> - <p>Comparative 3D Research Browser</p> - </div> - <div class="tabs"> - <button on:click={() => activeTab.set("Models")} class={$activeTab === "Models" ? "active" : ""}>Models</button> - <button on:click={() => activeTab.set("Scenes")} class={$activeTab === "Scenes" ? "active" : ""}>Scenes</button> - </div> - {#if $activeTab === "Models"} - <ModelsView /> - {:else} - <ScenesView /> - {/if} -</div> - -<style> - .tabs { - display: flex; - justify-content: left; - margin-top: 10px; - margin-bottom: 10px; - gap: 10px; - width: 100%; - } - - .tabs button { - background-color: #1a1b1e; - color: #ddd; - border: none; - outline: none; - padding: 10px 10px 10px 10px; - font-family: "Roboto", sans-serif; - font-size: 14px; - font-weight: 600; - transition: background-color 0.2s ease; - } - - .tabs button:hover { - cursor: pointer; - background-color: #555; - } - - .tabs button.active { - background-color: #444; - } -</style> diff --git a/viewer/src/routes/components/ModelsView.svelte b/viewer/src/routes/components/ModelsView.svelte deleted file mode 100644 index 3e178a37dc5bd16f5357be30ffa58a1512014d8c..0000000000000000000000000000000000000000 --- a/viewer/src/routes/components/ModelsView.svelte +++ /dev/null @@ -1,42 +0,0 @@ -<script lang="ts"> - import { onMount } from "svelte"; - import { getModels, getScenes } from "$lib/data/dataLoader"; - import placeholderImage from "$lib/placeholder.png"; - - let models: any[] = []; - let sceneMap: any = {}; - - onMount(async () => { - models = await getModels(); - const scenes = await getScenes(); - for (let model of models) { - for (let scene of scenes) { - if (scene.model === model.slug) { - sceneMap[model.slug] = scene.slug; - break; - } - } - } - }); - - function handleImageError(event: Event) { - const image = event.currentTarget as HTMLImageElement; - image.src = placeholderImage; - } -</script> - -<div class="grid"> - {#each models as model} - {#if sceneMap[model.slug] !== undefined} - <a href={`/models/${model.slug}`} class="grid-item"> - <img - src={`/thumbnails/${sceneMap[model.slug]}.png`} - alt={model.title} - class="thumbnail" - on:error={(event) => handleImageError(event)} - /> - <div class="title">{model.title}</div> - </a> - {/if} - {/each} -</div> diff --git a/viewer/src/routes/components/ScenesView.svelte b/viewer/src/routes/components/ScenesView.svelte deleted file mode 100644 index 29b83c27926abbbe089b4182659f550030d313a6..0000000000000000000000000000000000000000 --- a/viewer/src/routes/components/ScenesView.svelte +++ /dev/null @@ -1,30 +0,0 @@ -<script lang="ts"> - import { onMount } from "svelte"; - import { getScenes } from "$lib/data/dataLoader"; - import placeholderImage from "$lib/placeholder.png"; - - let scenes: any[] = []; - - onMount(async () => { - scenes = await getScenes(); - }); - - function handleImageError(event: Event) { - const image = event.currentTarget as HTMLImageElement; - image.src = placeholderImage; - } -</script> - -<div class="grid"> - {#each scenes as scene} - <a href={`/viewer/${scene.slug}`} class="grid-item"> - <img - src={`/thumbnails/${scene.slug}.png`} - alt={scene.title} - class="thumbnail" - on:error={(event) => handleImageError(event)} - /> - <div class="title">{scene.title}</div> - </a> - {/each} -</div> diff --git a/viewer/src/routes/models/[slug]/+page.server.ts b/viewer/src/routes/models/[slug]/+page.server.ts deleted file mode 100644 index 7fa317fa2b9e5b0b8cc6f707da470e8360228077..0000000000000000000000000000000000000000 --- a/viewer/src/routes/models/[slug]/+page.server.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { getModels, getScenes } from "$lib/data/dataLoader"; - -export async function load({ params }) { - const models = await getModels(); - const scenes = await getScenes(); - - const model = models.find((model: any) => model.slug === params.slug); - const modelScenes = scenes.filter((scene: any) => scene.model === params.slug); - - return { - model: model, - scenes: modelScenes, - }; -} diff --git a/viewer/src/routes/models/[slug]/+page.svelte b/viewer/src/routes/models/[slug]/+page.svelte deleted file mode 100644 index 474f8b44b706a58d613f76027dfe4f0810c5fddd..0000000000000000000000000000000000000000 --- a/viewer/src/routes/models/[slug]/+page.svelte +++ /dev/null @@ -1,182 +0,0 @@ -<script lang="ts"> - import placeholderImage from "$lib/placeholder.png"; - - export let data: { - model: { - title: string; - paper: string; - project: string; - code: string; - }; - scenes: { - slug: string; - title: string; - }[]; - }; - - function handleImageError(event: Event) { - const image = event.currentTarget as HTMLImageElement; - image.src = placeholderImage; - } - - function goHome() { - window.location.href = "/"; - } -</script> - -<div class="container"> - <div on:pointerdown={goHome} class="banner"> - <h1>IGF</h1> - <p>Comparative 3D Research Browser</p> - </div> - <div class="header">{data.model.title}</div> - <div class="model-container"> - <div class="model-info"> - <p class="model-header">Info</p> - <table class="table"> - {#if data.model.paper} - <tr> - <td>Paper</td> - <td><a href={data.model.paper} target="_blank">{data.model.paper}</a></td> - </tr> - {/if} - {#if data.model.project} - <tr> - <td>Project</td> - <td><a href={data.model.project} target="_blank">{data.model.project}</a></td> - </tr> - {/if} - {#if data.model.code} - <tr> - <td>Code</td> - <td><a href={data.model.code} target="_blank">{data.model.code}</a></td> - </tr> - {/if} - </table> - </div> - <div class="grid-container"> - {#if data.scenes.length > 0} - <div class="grid"> - {#each data.scenes as scene} - <a href={`/viewer/${scene.slug}`} class="grid-item"> - <img - src={`/thumbnails/${scene.slug}.png`} - alt={scene.title} - class="thumbnail" - on:error={(event) => handleImageError(event)} - /> - <div class="title">{scene.title}</div> - </a> - {/each} - </div> - {:else} - <div class="grid"> - <div class="warning">No scenes found</div> - </div> - {/if} - </div> - </div> -</div> - -<style> - .model-header { - padding: 10px; - font-size: 16px; - color: #aaa; - margin: 0; - } - - .model-info { - border: 1px solid #333; - box-sizing: border-box; - width: 100%; - margin: 0; - - @media (min-width: 576px) { - width: 384px; - } - } - - .table { - table-layout: fixed; - width: 100%; - margin: 0; - padding: 0; - border-collapse: collapse; - } - - .table td { - width: 100%; - margin: 0; - padding: 10px; - border-top: 1px solid #333; - white-space: nowrap; - } - - .table td:first-child { - width: 128px; - background-color: #222; - border-right: 1px solid #333; - font-size: 14px; - font-weight: bold; - color: #aaa; - } - - .table td:last-child { - width: 100%; - font-size: 14px; - overflow: hidden; - } - - .table a { - color: #6d90b6; - } - - .model-container { - display: flex; - flex-wrap: wrap; - align-items: flex-start; - } - - .grid-container { - flex: 1; - display: flex; - flex-wrap: wrap; - justify-content: center; - } - - .grid { - margin-top: 10px; - margin-left: 0; - - @media (min-width: 576px) { - margin-top: 0; - margin-left: 10px; - } - } - - .grid-item { - @media (min-width: 576px) { - width: 100%; - } - - @media (min-width: 768px) { - width: calc(50% - 10px); - } - - @media (min-width: 992px) { - width: calc(33.333% - 10px); - } - - @media (min-width: 1200px) { - width: calc(25% - 10px); - } - } - - .warning { - width: 100%; - margin-top: 20px; - text-align: center; - color: #aaa; - } -</style> diff --git a/viewer/src/routes/viewer/[slug]/+page.server.ts b/viewer/src/routes/viewer/[slug]/+page.server.ts deleted file mode 100644 index 9a58a3bb31bf472899f81aeca3313d8031b42bc1..0000000000000000000000000000000000000000 --- a/viewer/src/routes/viewer/[slug]/+page.server.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { error } from "@sveltejs/kit"; -import { getModels, getScenes } from "$lib/data/dataLoader"; - -export async function load({ params }) { - const models = await getModels(); - const scenes = await getScenes(); - - const scene = scenes.find((scene: any) => scene.slug === params.slug); - const model = models.find((model: any) => model.slug === scene!.model); - - if (!scene) throw error(404); - - return { - scene: scene, - model: model, - }; -} diff --git a/viewer/src/routes/viewer/[slug]/+page.svelte b/viewer/src/routes/viewer/[slug]/+page.svelte deleted file mode 100644 index 0c3c305d75a85a58e3ffeac724f8bed32180f9b7..0000000000000000000000000000000000000000 --- a/viewer/src/routes/viewer/[slug]/+page.svelte +++ /dev/null @@ -1,374 +0,0 @@ -<script lang="ts"> - import { onMount, onDestroy } from "svelte"; - import type { IViewer } from "./IViewer"; - import { BabylonViewer } from "./BabylonViewer"; - import { SplatViewer } from "./SplatViewer"; - - export let data: { - scene: { - title: string; - model: string; - type: string; - url: string; - prompt: string; - pipeline: string[]; - }; - model: { - title: string; - }; - }; - - let stats: { name: string; value: any }[] = []; - - let viewer: IViewer; - let overlay: HTMLDivElement; - let container: HTMLDivElement; - let hudToggleBtn: HTMLButtonElement; - let canvas: HTMLCanvasElement; - let loadingBarFill: HTMLDivElement; - let collapsed = false; - - onMount(initViewer); - onDestroy(destroyViewer); - - async function initViewer() { - document.body.classList.add("viewer"); - if (data.scene.type == "mesh") { - viewer = new BabylonViewer(canvas); - } else if (data.scene.type == "splat") { - viewer = new SplatViewer(canvas); - } else { - console.error(`Unsupported scene type: ${data.scene.type}`); - } - handleMobileView(); - await loadScene(data.scene.url); - window.addEventListener("resize", () => { - updateCanvasSize(); - }); - updateStats(); - setInterval(updateStats, 1000); - } - - function destroyViewer() { - document.body.classList.remove("viewer"); - viewer.dispose(); - } - - function handleMobileView() { - const isMobile = window.innerWidth < 768; - if (isMobile) toggleHUD(); - } - - function toggleHUD() { - collapsed = !collapsed; - hudToggleBtn.textContent = collapsed ? ")" : "("; - if (collapsed) { - container.classList.remove("hud-expanded"); - } else { - container.classList.add("hud-expanded"); - } - } - - function setRenderMode(event: PointerEvent) { - const babylonViewer = viewer as BabylonViewer; - if (!babylonViewer) { - console.error("Can only set render mode for BabylonViewer"); - return; - } - - document.querySelectorAll(".mode-item").forEach((item) => { - item.classList.remove("active"); - }); - - const modeItem = event.currentTarget as HTMLElement; - modeItem.classList.add("active"); - - const mode = modeItem.dataset.mode as string; - babylonViewer.setRenderMode(mode); - } - - async function loadScene(url: string) { - overlay.style.display = "flex"; - await viewer.loadScene(url, (progress) => { - loadingBarFill.style.width = `${progress * 100}%`; - }); - updateCanvasSize(); - overlay.style.display = "none"; - } - - function updateCanvasSize() { - if (!canvas || !container) return; - canvas.width = container.clientWidth; - canvas.height = container.clientHeight; - } - - async function capture() { - const data = await viewer.capture(); - if (!data) { - console.error("Failed to capture screenshot"); - return; - } - const a = document.createElement("a"); - a.href = data; - a.download = "screenshot.png"; - a.click(); - } - - function updateStats() { - stats = viewer.getStats(); - } - - function exit() { - window.history.back(); - } -</script> - -<div bind:this={container} class="canvas-container hud-expanded"> - <div bind:this={overlay} class="loading-overlay"> - <div class="loading-bar"> - <div bind:this={loadingBarFill} class="loading-bar-fill" /> - </div> - </div> - <canvas bind:this={canvas} width="512" height="512" /> - <div class="exit-button" on:pointerdown={exit}>x</div> - <div class="hud" class:collapsed> - <button bind:this={hudToggleBtn} on:click={toggleHUD} class="hud-toggle-btn">(</button> - <div class="section"> - <div class="title">{data.scene.title}</div> - </div> - <div class="section"> - <div class="section-title">Model</div> - <div class="info-panel"> - {#if data.scene.model} - <a href={`/models/${data.scene.model}`} class="section-label">{data.model.title}</a> - {#if data.scene.pipeline} - <ol class="pipeline"> - {#each data.scene.pipeline as step} - <li>{step}</li> - {/each} - </ol> - {/if} - {:else} - <div class="section-label">None</div> - {/if} - </div> - </div> - {#if data.scene.prompt} - <div class="section"> - <div class="section-title">Prompt</div> - <div class="info-panel"> - <div class="section-label">{data.scene.prompt}</div> - </div> - </div> - {/if} - {#if stats.length > 0} - <div class="section"> - <div class="section-title">Stats</div> - <div class="info-panel"> - {#each stats as stat} - <div>{stat.name}: {stat.value}</div> - {/each} - </div> - </div> - {/if} - {#if data.scene.type === "mesh"} - <div class="section"> - <div class="section-title">Render Mode</div> - <div class="button-group mode-list"> - <div on:pointerdown={setRenderMode} class="hud-button mode-item active" data-mode="rendered"> - Rendered - </div> - <div on:pointerdown={setRenderMode} class="hud-button mode-item" data-mode="wireframe"> - Wireframe - </div> - </div> - </div> - {/if} - <div class="section"> - <div class="section-title">Actions</div> - <div class="button-group"> - <div class="hud-button" on:pointerdown={capture}>Capture</div> - </div> - </div> - </div> -</div> - -<style> - .canvas-container { - position: relative; - box-sizing: border-box; - transition: padding-left 0.2s ease; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - width: 100wh; - height: 100vh; - overflow: hidden; - } - - .canvas-container.hud-expanded { - padding-left: 256px; - } - - .loading-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: #1a1b1e; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - z-index: 100; - gap: 10px; - } - - .loading-overlay::before { - content: "Loading..."; - color: white; - font-size: 16px; - } - - .loading-bar { - position: relative; - width: 256px; - height: 20px; - border: 2px solid #aaa; - background-color: #1a1b1e; - } - - .loading-bar-fill { - position: absolute; - top: 0; - left: 0; - width: 0%; - height: 100%; - background-color: #555; - transition: width 0.2s ease; - } - - canvas { - max-width: 100%; - max-height: 100%; - } - - canvas:focus { - outline: none; - } - - .hud { - position: absolute; - top: 0; - left: 0; - width: 286px; - height: 100%; - box-sizing: border-box; - font-size: 14px; - background-color: #1a1b1e; - box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3); - transition: transform 0.2s ease; - margin: 0; - padding: 0 30px 0 0; - overflow-x: hidden; - overflow-y: auto; - - @media (max-width: 768px) { - width: 100%; - } - } - - .hud-toggle-btn { - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - background-color: #1a1b1e; - border: none; - color: #aaa; - font-size: 16px; - cursor: pointer; - outline: none; - width: 29px; - height: 100%; - box-sizing: border-box; - transition: background-color 0.2s ease; - border-left: 1px solid #444; - } - - .hud-toggle-btn:hover { - background-color: #444; - } - - .hud.collapsed { - transform: translateX(calc(-100% + 30px)); - } - - .section { - width: 100%; - padding: 10px; - box-sizing: border-box; - } - - .title { - font-size: 16px; - color: #aaa; - font-weight: bold; - padding: 4px; - padding-top: 10px; - } - - .section-title { - font-size: 11px; - font-weight: light; - text-transform: uppercase; - color: #aaa; - width: 100%; - padding: 4px; - } - - .section-label { - font-size: 14px; - color: #ddd; - } - - .info-panel { - padding: 6px 10px 0px 10px; - color: #ddd; - } - - .pipeline { - margin: 0; - padding: 6px 10px 0px 20px; - } - - .button-group { - border: none; - background-color: transparent; - box-sizing: border-box; - } - - .hud-button { - padding: 10px 15px; - cursor: pointer; - background-color: #1a1b1e; - border-bottom: 1px solid #444; - transition: background-color 0.2s ease; - box-sizing: border-box; - } - - .hud-button:last-child { - border-bottom: none; - } - - .hud-button:hover { - background-color: #555; - } - - .hud-button.active { - background-color: #444; - color: white; - } -</style> diff --git a/viewer/static/thumbnails/3dgs-bicycle.png b/viewer/static/thumbnails/3dgs-bicycle.png deleted file mode 100644 index f7388bf7ba3028485b6471872f1ad43727d58fb9..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/3dgs-bicycle.png and /dev/null differ diff --git a/viewer/static/thumbnails/3dgs-bonsai.png b/viewer/static/thumbnails/3dgs-bonsai.png deleted file mode 100644 index ee3f8c8a38506a3dcab0998f4d424dea72c19686..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/3dgs-bonsai.png and /dev/null differ diff --git a/viewer/static/thumbnails/3dgs-counter.png b/viewer/static/thumbnails/3dgs-counter.png deleted file mode 100644 index e76b13d8b4ac9444110f28871ac1a12ad267706a..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/3dgs-counter.png and /dev/null differ diff --git a/viewer/static/thumbnails/3dgs-flowers.png b/viewer/static/thumbnails/3dgs-flowers.png deleted file mode 100644 index 5eec184234037833d5adda804ceb5ae1e69dd26d..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/3dgs-flowers.png and /dev/null differ diff --git a/viewer/static/thumbnails/3dgs-garden.png b/viewer/static/thumbnails/3dgs-garden.png deleted file mode 100644 index 5861944c94c6a0bc2ef921cac60f8706b531d63d..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/3dgs-garden.png and /dev/null differ diff --git a/viewer/static/thumbnails/3dgs-kitchen.png b/viewer/static/thumbnails/3dgs-kitchen.png deleted file mode 100644 index 2b2b5a753dcb4db985e50a5c09a264663755c96e..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/3dgs-kitchen.png and /dev/null differ diff --git a/viewer/static/thumbnails/3dgs-playroom.png b/viewer/static/thumbnails/3dgs-playroom.png deleted file mode 100644 index 4140f796348cd83f322763351760ce23280be740..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/3dgs-playroom.png and /dev/null differ diff --git a/viewer/static/thumbnails/3dgs-room.png b/viewer/static/thumbnails/3dgs-room.png deleted file mode 100644 index 7b4b989b67a852c69ab30fe2f7fab1a8e1c34242..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/3dgs-room.png and /dev/null differ diff --git a/viewer/static/thumbnails/3dgs-stump.png b/viewer/static/thumbnails/3dgs-stump.png deleted file mode 100644 index 2b8738cecb6eacc9097d6cfef1d85e2725507298..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/3dgs-stump.png and /dev/null differ diff --git a/viewer/static/thumbnails/3dgs-treehill.png b/viewer/static/thumbnails/3dgs-treehill.png deleted file mode 100644 index 67a9a58186ca19c4ab5cf1d7ae88da9ec20b4c76..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/3dgs-treehill.png and /dev/null differ diff --git a/viewer/static/thumbnails/dreamfusion-chick.png b/viewer/static/thumbnails/dreamfusion-chick.png deleted file mode 100755 index 1eb78ac69741ad627e948ea0a45aa66bf7edb4f0..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/dreamfusion-chick.png and /dev/null differ diff --git a/viewer/static/thumbnails/dreamfusion-corgi.png b/viewer/static/thumbnails/dreamfusion-corgi.png deleted file mode 100755 index dcc4dd48c454c920cc95e8a692dbc90f35d739fa..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/dreamfusion-corgi.png and /dev/null differ diff --git a/viewer/static/thumbnails/dreamfusion-crab.png b/viewer/static/thumbnails/dreamfusion-crab.png deleted file mode 100755 index 872d3df5455eae65b968d09471033f95eeefe55d..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/dreamfusion-crab.png and /dev/null differ diff --git a/viewer/static/thumbnails/dreamfusion-eagle.png b/viewer/static/thumbnails/dreamfusion-eagle.png deleted file mode 100755 index 449502544db7674d6e5fb2bd363b846d88c63974..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/dreamfusion-eagle.png and /dev/null differ diff --git a/viewer/static/thumbnails/dreamfusion-ghost.png b/viewer/static/thumbnails/dreamfusion-ghost.png deleted file mode 100755 index 830e77d8b99f5bbdb302d7fd1490eb56f4485872..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/dreamfusion-ghost.png and /dev/null differ diff --git a/viewer/static/thumbnails/dreamfusion-lemur.png b/viewer/static/thumbnails/dreamfusion-lemur.png deleted file mode 100755 index 5699699fa60389f40d6aa04fdfb15b5427f36541..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/dreamfusion-lemur.png and /dev/null differ diff --git a/viewer/static/thumbnails/dreamfusion-pig.png b/viewer/static/thumbnails/dreamfusion-pig.png deleted file mode 100755 index 1141d746d06db70edb85bf2a1fc7de12a969135e..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/dreamfusion-pig.png and /dev/null differ diff --git a/viewer/static/thumbnails/dreamfusion-sweaterfrog.png b/viewer/static/thumbnails/dreamfusion-sweaterfrog.png deleted file mode 100755 index 3ce486afb77b3c5e7fa0f46957202590fd3a39cd..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/dreamfusion-sweaterfrog.png and /dev/null differ diff --git a/viewer/static/thumbnails/sync-dreamer-armor.png b/viewer/static/thumbnails/sync-dreamer-armor.png deleted file mode 100755 index 0955e38286edd486e88ebd6da6f3ade39aed32cc..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/sync-dreamer-armor.png and /dev/null differ diff --git a/viewer/static/thumbnails/sync-dreamer-deer.png b/viewer/static/thumbnails/sync-dreamer-deer.png deleted file mode 100755 index ec7825d799ce7ffadcccd5fc4ff03af6693265d3..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/sync-dreamer-deer.png and /dev/null differ diff --git a/viewer/static/thumbnails/sync-dreamer-drum.png b/viewer/static/thumbnails/sync-dreamer-drum.png deleted file mode 100755 index 3827f01a9aa8d133843c821092f9f14e5e674ff6..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/sync-dreamer-drum.png and /dev/null differ diff --git a/viewer/static/thumbnails/sync-dreamer-forest.png b/viewer/static/thumbnails/sync-dreamer-forest.png deleted file mode 100755 index 1b771914a1c71025f89851f7b8347008f19d8993..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/sync-dreamer-forest.png and /dev/null differ diff --git a/viewer/static/thumbnails/sync-dreamer-monkey.png b/viewer/static/thumbnails/sync-dreamer-monkey.png deleted file mode 100755 index c87b5148825eb42c137ee7abf190a52a53b9054c..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/sync-dreamer-monkey.png and /dev/null differ diff --git a/viewer/static/thumbnails/sync-dreamer-poro.png b/viewer/static/thumbnails/sync-dreamer-poro.png deleted file mode 100755 index 4a4e530d5c9ec3f7bd6fd4bc198c12b56ce60637..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/sync-dreamer-poro.png and /dev/null differ diff --git a/viewer/static/thumbnails/sync-dreamer-train.png b/viewer/static/thumbnails/sync-dreamer-train.png deleted file mode 100755 index f5be286dace6e591fa8ed1ad30935775815f8513..0000000000000000000000000000000000000000 Binary files a/viewer/static/thumbnails/sync-dreamer-train.png and /dev/null differ diff --git a/viewer/vite.config.ts b/vite.config.ts similarity index 100% rename from viewer/vite.config.ts rename to vite.config.ts