Files changed (1) hide show
  1. static/viewer/viewer.html +0 -167
static/viewer/viewer.html DELETED
@@ -1,167 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <title>3DGS Viewer</title>
7
- <style>
8
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
- html, body { width: 100%; height: 100%; overflow: hidden;
10
- background: radial-gradient(ellipse at 50% 60%, #1a2035 0%, #080b12 100%); }
11
- canvas { display: block; }
12
- #hud {
13
- position: fixed; top: 12px; left: 16px;
14
- color: #94a3b8; font: 12px/1.5 system-ui, sans-serif;
15
- pointer-events: none; user-select: none;
16
- }
17
- #loading {
18
- position: fixed; inset: 0;
19
- display: flex; flex-direction: column;
20
- align-items: center; justify-content: center;
21
- color: #94a3b8; font: 14px system-ui, sans-serif;
22
- gap: 12px;
23
- }
24
- #spinner {
25
- width: 36px; height: 36px;
26
- border: 3px solid #334155;
27
- border-top-color: #60a5fa;
28
- border-radius: 50%;
29
- animation: spin 0.9s linear infinite;
30
- }
31
- @keyframes spin { to { transform: rotate(360deg); } }
32
- #error {
33
- display: none;
34
- position: fixed; inset: 0;
35
- align-items: center; justify-content: center;
36
- color: #f87171; font: 13px system-ui, sans-serif;
37
- padding: 32px; text-align: center; white-space: pre-wrap;
38
- }
39
- </style>
40
- </head>
41
- <body>
42
- <div id="loading">
43
- <div id="spinner"></div>
44
- <div id="loading-label">Loading splat…</div>
45
- </div>
46
- <div id="error"></div>
47
- <div id="hud">drag to orbit &nbsp;·&nbsp; scroll to zoom &nbsp;·&nbsp; right-drag to pan</div>
48
-
49
- <script type="importmap">
50
- {
51
- "imports": {
52
- "three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.180.0/three.module.js",
53
- "three/addons/": "https://unpkg.com/three@0.180.0/examples/jsm/",
54
- "@sparkjsdev/spark": "https://unpkg.com/@sparkjsdev/spark@2.0.0/dist/spark.module.js"
55
- }
56
- }
57
- </script>
58
-
59
- <script type="module">
60
- import * as THREE from "three";
61
- import { OrbitControls } from "three/addons/controls/OrbitControls.js";
62
- import { SparkRenderer, SplatMesh } from "@sparkjsdev/spark";
63
-
64
- const params = new URLSearchParams(location.search);
65
- const plyURL = params.get("ply");
66
-
67
- const loadingEl = document.getElementById("loading");
68
- const loadLabel = document.getElementById("loading-label");
69
- const errorEl = document.getElementById("error");
70
-
71
- function showError(msg) {
72
- loadingEl.style.display = "none";
73
- errorEl.style.display = "flex";
74
- errorEl.textContent = msg;
75
- }
76
-
77
- if (!plyURL) {
78
- showError("No ?ply= parameter provided.");
79
- } else {
80
- init(plyURL);
81
- }
82
-
83
- function init(url) {
84
- const scene = new THREE.Scene();
85
-
86
- const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 1000);
87
- camera.position.set(0, 0.3, 1.8);
88
-
89
- const renderer = new THREE.WebGLRenderer({ antialias: false });
90
- renderer.setPixelRatio(window.devicePixelRatio);
91
- renderer.setSize(window.innerWidth, window.innerHeight);
92
- document.body.appendChild(renderer.domElement);
93
-
94
- const spark = new SparkRenderer({ renderer });
95
- scene.add(spark);
96
-
97
- const controls = new OrbitControls(camera, renderer.domElement);
98
- controls.enableDamping = true;
99
- controls.dampingFactor = 0.07;
100
- controls.minDistance = 0.2;
101
- controls.maxDistance = 50;
102
- controls.target.set(0, 0, 0);
103
-
104
- const splat = new SplatMesh({ url });
105
- // DEG saves PLYs with -Y up and the front of the object facing roughly
106
- // along the horizontal axis. Re-orient for Three.js (+Y up, camera on
107
- // +Z looking toward -Z): a Group lets us apply a yaw first (to bring the
108
- // front-facing side to +Z) and then flip 180° around X to put up on +Y.
109
- const splatRoot = new THREE.Group();
110
- splatRoot.add(splat);
111
- splat.rotation.y = Math.PI / 2; // yaw the model so its front faces the camera
112
- splatRoot.rotation.x = Math.PI; // flip Y/Z so it stands upright
113
- scene.add(splatRoot);
114
-
115
- let framed = false;
116
- function tryFrame() {
117
- if (framed) return;
118
- const box = new THREE.Box3().setFromObject(splatRoot);
119
- if (box.isEmpty() || !isFinite(box.min.x)) return;
120
- framed = true;
121
- // const center = new THREE.Vector3();
122
- // const size = new THREE.Vector3();
123
- // box.getCenter(center);
124
- // box.getSize(size);
125
- // console.log(size);
126
- // const maxDim = Math.max(size.x, size.y, size.z);
127
- // // With a 45° FOV, half-fov tan ≈ 0.414, so the minimum distance to fit
128
- // // a sphere of diameter `maxDim` in view is maxDim / (2*0.414) ≈ 1.21*maxDim.
129
- // // Use a small margin (1.15) so the object nearly fills the viewport.
130
- // const dist = maxDim * 1.15;
131
- // camera.position.copy(center).add(new THREE.Vector3(0, maxDim * 0.15, dist));
132
- // controls.target.copy(center);
133
- // controls.update();
134
- loadingEl.style.display = "none";
135
- }
136
-
137
- fetch(url, { method: "HEAD" }).then(r => {
138
- if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
139
- const len = r.headers.get("content-length");
140
- if (len) loadLabel.textContent = `Loading splat (${(len / 1024 / 1024).toFixed(1)} MB)…`;
141
- }).catch(e => showError("Fetch failed: " + e.message));
142
-
143
- let checkFrames = 0;
144
- function checkLoaded() {
145
- checkFrames++;
146
- if (checkFrames < 5) return;
147
- tryFrame();
148
- }
149
-
150
- window.addEventListener("resize", () => {
151
- camera.aspect = window.innerWidth / window.innerHeight;
152
- camera.updateProjectionMatrix();
153
- renderer.setSize(window.innerWidth, window.innerHeight);
154
- });
155
-
156
- renderer.setAnimationLoop(() => {
157
- controls.update();
158
- renderer.render(scene, camera);
159
- checkLoaded();
160
- });
161
-
162
- // Fallback: hide loading after 4s regardless of bbox-readiness.
163
- setTimeout(() => { loadingEl.style.display = "none"; }, 4000);
164
- }
165
- </script>
166
- </body>
167
- </html>