dmpsuzuki commited on
Commit
a82dc04
·
1 Parent(s): 391a3ba

Initial Upload

Browse files
Files changed (5) hide show
  1. index.html +20 -17
  2. src/config.js +9 -0
  3. src/controls.js +84 -0
  4. src/viewer.js +56 -0
  5. style.css +23 -22
index.html CHANGED
@@ -1,19 +1,22 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
19
  </html>
 
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.0" />
6
+ <title>3DGS Viewer</title>
7
+ <link rel="stylesheet" href="style.css" />
8
+ <script type="importmap">
9
+ {
10
+ "imports": {
11
+ "three": "https://esm.sh/three@0.179.0",
12
+ "three/addons/": "https://esm.sh/three@0.179.0/examples/jsm/",
13
+ "@sparkjsdev/spark": "https://esm.sh/@sparkjsdev/spark@2?deps=three@0.179.0"
14
+ }
15
+ }
16
+ </script>
17
+ </head>
18
+ <body>
19
+ <div id="loading">loading...</div>
20
+ <script type="module" src="src/viewer.js"></script>
21
+ </body>
22
  </html>
src/config.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export const SPLAT_URL =
2
+ "https://huggingface.co/datasets/dynamic-maps/hard-intersection-multimodal-sample/resolve/main/assets/jp_tokyo_takanawadai-lod.rad"
3
+ export const config = {
4
+ position: [0, 0, 0],
5
+ quaternion: [0, 0, 0, 1],
6
+ scale: 1,
7
+ camera: { position: [0, 0, 10], lookAt: [0, 0, 0] },
8
+ background: "#000000",
9
+ };
src/controls.js ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as THREE from "three";
2
+
3
+ const LOOK_SPEED = 0.003;
4
+ const _yawQ = new THREE.Quaternion();
5
+ const _pitQ = new THREE.Quaternion();
6
+ const raycaster = new THREE.Raycaster();
7
+ const _ndc = new THREE.Vector2();
8
+
9
+ function raycastAt(mesh, camera, clientX, clientY) {
10
+ _ndc.set((clientX / innerWidth) * 2 - 1, -(clientY / innerHeight) * 2 + 1);
11
+ raycaster.setFromCamera(_ndc, camera);
12
+ const hits = [];
13
+ mesh.raycast(raycaster, hits);
14
+ hits.sort((a, b) => a.distance - b.distance);
15
+ return hits[0] ?? null;
16
+ }
17
+
18
+ export function setupControls(domElement, camera, mesh) {
19
+ let dragging = false,
20
+ dragButton = 0,
21
+ lastX = 0,
22
+ lastY = 0;
23
+
24
+ domElement.addEventListener("pointerdown", (e) => {
25
+ dragging = true;
26
+ dragButton = e.button;
27
+ lastX = e.clientX;
28
+ lastY = e.clientY;
29
+ domElement.setPointerCapture(e.pointerId);
30
+ });
31
+
32
+ domElement.addEventListener("pointermove", (e) => {
33
+ if (!dragging) return;
34
+ const dx = e.clientX - lastX,
35
+ dy = e.clientY - lastY;
36
+ lastX = e.clientX;
37
+ lastY = e.clientY;
38
+
39
+ if (dragButton === 0) {
40
+ // Left-drag: look around
41
+ const camUp = new THREE.Vector3(0, 1, 0).applyQuaternion(
42
+ camera.quaternion,
43
+ );
44
+ camera.quaternion.premultiply(
45
+ _yawQ.setFromAxisAngle(camUp, -dx * LOOK_SPEED),
46
+ );
47
+ const camRight = new THREE.Vector3(1, 0, 0).applyQuaternion(
48
+ camera.quaternion,
49
+ );
50
+ camera.quaternion.premultiply(
51
+ _pitQ.setFromAxisAngle(camRight, -dy * LOOK_SPEED),
52
+ );
53
+ camera.quaternion.normalize();
54
+ } else if (dragButton === 2) {
55
+ // Right-drag: pan
56
+ const hit = raycastAt(mesh, camera, lastX, lastY);
57
+ const panSpeed = (hit ? hit.distance : 5) * 0.001;
58
+ const right = new THREE.Vector3(1, 0, 0).applyQuaternion(
59
+ camera.quaternion,
60
+ );
61
+ const up = new THREE.Vector3(0, 1, 0).applyQuaternion(camera.quaternion);
62
+ camera.position.addScaledVector(right, -dx * panSpeed);
63
+ camera.position.addScaledVector(up, dy * panSpeed);
64
+ }
65
+ });
66
+
67
+ domElement.addEventListener("pointerup", () => {
68
+ dragging = false;
69
+ });
70
+ domElement.addEventListener("contextmenu", (e) => e.preventDefault());
71
+
72
+ domElement.addEventListener(
73
+ "wheel",
74
+ (e) => {
75
+ const hit = raycastAt(mesh, camera, e.clientX, e.clientY);
76
+ const dist = hit ? hit.distance : 10;
77
+ camera.position.addScaledVector(
78
+ raycaster.ray.direction,
79
+ -(dist * e.deltaY * 0.001),
80
+ );
81
+ },
82
+ { passive: true },
83
+ );
84
+ }
src/viewer.js ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as THREE from "three";
2
+ import { SparkRenderer, SplatMesh } from "@sparkjsdev/spark";
3
+ import { SPLAT_URL, config } from "./config.js";
4
+ import { setupControls } from "./controls.js";
5
+
6
+ const loadingEl = document.getElementById("loading");
7
+
8
+ const scene = new THREE.Scene();
9
+ scene.background = new THREE.Color(config.background);
10
+
11
+ const camera = new THREE.PerspectiveCamera(
12
+ 75,
13
+ innerWidth / innerHeight,
14
+ 0.01,
15
+ 1000000,
16
+ );
17
+ camera.position.set(...config.camera.position);
18
+ camera.lookAt(...config.camera.lookAt);
19
+
20
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
21
+ renderer.setPixelRatio(devicePixelRatio);
22
+ renderer.setSize(innerWidth, innerHeight);
23
+ document.body.appendChild(renderer.domElement);
24
+
25
+ const spark = new SparkRenderer({
26
+ renderer,
27
+ pagedExtSplats: true,
28
+ coneFov0: 70.0,
29
+ coneFov: 120.0,
30
+ behindFoveate: 0.2,
31
+ coneFoveate: 0.4,
32
+ });
33
+ scene.add(spark);
34
+
35
+ const mesh = new SplatMesh({ url: SPLAT_URL, paged: true, raycastable: true });
36
+ mesh.quaternion.set(...config.quaternion);
37
+ mesh.position.set(...config.position);
38
+ mesh.scale.setScalar(config.scale);
39
+ scene.add(mesh);
40
+
41
+ setupControls(renderer.domElement, camera, mesh);
42
+
43
+ window.addEventListener("resize", () => {
44
+ camera.aspect = innerWidth / innerHeight;
45
+ camera.updateProjectionMatrix();
46
+ renderer.setSize(innerWidth, innerHeight);
47
+ });
48
+
49
+ let firstFrame = true;
50
+ renderer.setAnimationLoop(() => {
51
+ renderer.render(scene, camera);
52
+ if (firstFrame) {
53
+ firstFrame = false;
54
+ loadingEl.classList.add("done");
55
+ }
56
+ });
style.css CHANGED
@@ -1,28 +1,29 @@
 
 
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
  }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
 
 
 
 
 
 
16
  }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
  }
 
1
+ * {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ }
5
  body {
6
+ background: #111;
7
+ overflow: hidden;
8
  }
9
+ canvas {
10
+ display: block;
11
+ touch-action: none;
 
12
  }
13
 
14
+ #loading {
15
+ position: fixed;
16
+ inset: 0;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ background: #111;
21
+ color: rgba(255, 255, 255, 0.4);
22
+ font: 12px monospace;
23
+ z-index: 100;
24
+ transition: opacity 0.4s;
25
  }
26
+ #loading.done {
27
+ opacity: 0;
28
+ pointer-events: none;
 
 
 
 
 
 
 
 
29
  }