multimodalart HF Staff commited on
Commit
e4d5d9b
·
verified ·
1 Parent(s): 6abdf40

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +430 -18
index.html CHANGED
@@ -1,19 +1,431 @@
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>
3
+ <head>
4
+ <title>Hunyuan World Navigator</title>
5
+ <style>
6
+ body {
7
+ margin: 0;
8
+ font-family: Arial, sans-serif;
9
+ background: #1a1a1a;
10
+ color: white;
11
+ text-align: center;
12
+ }
13
+ #header {
14
+ padding: 20px;
15
+ background: #282828;
16
+ border-bottom: 1px solid #444;
17
+ }
18
+ #header h1 {
19
+ margin: 0 0 10px 0;
20
+ font-size: 2em;
21
+ }
22
+ #header p {
23
+ margin: 0 0 20px 0;
24
+ color: #ccc;
25
+ }
26
+ #header a {
27
+ color: #61dafb;
28
+ text-decoration: none;
29
+ }
30
+ #header a:hover {
31
+ text-decoration: underline;
32
+ }
33
+ #examples-container {
34
+ display: flex;
35
+ flex-wrap: wrap;
36
+ justify-content: center;
37
+ padding: 20px;
38
+ gap: 20px;
39
+ background: #222;
40
+ }
41
+ .example-card {
42
+ background: #333;
43
+ border-radius: 8px;
44
+ overflow: hidden;
45
+ width: 200px;
46
+ cursor: pointer;
47
+ transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
48
+ box-shadow: 0 4px 8px rgba(0,0,0,0.2);
49
+ }
50
+ .example-card:hover {
51
+ transform: scale(1.05);
52
+ box-shadow: 0 8px 16px rgba(0,0,0,0.3);
53
+ }
54
+ .example-card img {
55
+ width: 100%;
56
+ height: 120px;
57
+ object-fit: cover;
58
+ display: block;
59
+ }
60
+ .example-card p {
61
+ margin: 0;
62
+ padding: 15px;
63
+ font-weight: bold;
64
+ }
65
+ #viewer-container {
66
+ position: relative;
67
+ width: 100%;
68
+ height: 65vh; /* Adjusted height for the viewer */
69
+ }
70
+ canvas {
71
+ display: block;
72
+ width: 100%;
73
+ height: 100%;
74
+ }
75
+ #upload-container {
76
+ margin-top: 15px;
77
+ }
78
+ #file-input {
79
+ display: none;
80
+ }
81
+ .upload-btn {
82
+ background: #4CAF50;
83
+ color: white;
84
+ padding: 10px 15px;
85
+ border: none;
86
+ border-radius: 4px;
87
+ cursor: pointer;
88
+ font-size: 16px;
89
+ }
90
+ .upload-btn:hover {
91
+ background: #45a049;
92
+ }
93
+ #loading {
94
+ display: none;
95
+ margin-top: 10px;
96
+ color: #aaa;
97
+ font-size: 18px;
98
+ }
99
+ #controls {
100
+ position: absolute;
101
+ top: 10px;
102
+ left: 10px;
103
+ z-index: 10;
104
+ }
105
+ .control-btn {
106
+ padding: 8px 12px;
107
+ margin-right: 5px;
108
+ border: none;
109
+ border-radius: 4px;
110
+ cursor: pointer;
111
+ background: rgba(85, 85, 85, 0.8);
112
+ color: white;
113
+ }
114
+ .control-btn:hover {
115
+ background: rgba(102, 102, 102, 0.9);
116
+ }
117
+ #instructions {
118
+ position: absolute;
119
+ bottom: 10px;
120
+ left: 10px;
121
+ color: white;
122
+ background: rgba(0,0,0,0.5);
123
+ padding: 10px;
124
+ border-radius: 5px;
125
+ font-size: 14px;
126
+ z-index: 10;
127
+ }
128
+ </style>
129
+ </head>
130
+ <body>
131
+ <div id="header">
132
+ <h1>Hunyuan World Navigator</h1>
133
+ <p>
134
+ <a href="https://huggingface.co/tencent/HunyuanWorld-1" target="_blank" rel="noopener noreferrer">HunyuanWorld-1 on Hugging Face</a> |
135
+ <a href="https://github.com/camenduru/HunyuanWorld-1.0-jupyter" target="_blank" rel="noopener noreferrer">Generate your own on Google Colab</a>
136
+ </p>
137
+ <p>Click an example below or upload your own files to begin.</p>
138
+ <div id="upload-container">
139
+ <label for="file-input" class="upload-btn">Select Custom PLY/DRC Files</label>
140
+ <input id="file-input" type="file" accept=".ply,.drc" multiple>
141
+ </div>
142
+ </div>
143
+
144
+ <div id="examples-container">
145
+ <!-- Examples will be dynamically inserted here -->
146
+ </div>
147
+
148
+ <div id="loading">Loading...</div>
149
+
150
+ <div id="viewer-container">
151
+ <div id="controls">
152
+ <button id="rotate-toggle" class="control-btn">Pause Rotation</button>
153
+ <button id="reset-view" class="control-btn">Reset View</button>
154
+ </div>
155
+ <div id="instructions">
156
+ Controls: WASD to move, Mouse drag to look around
157
+ </div>
158
+ <!-- Canvas will be appended here by Three.js -->
159
+ </div>
160
+
161
+
162
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
163
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/PLYLoader.js"></script>
164
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/DRACOLoader.js"></script>
165
+ <script>
166
+ // --- DATA FOR EXAMPLES ---
167
+ const baseURL = 'https://huggingface.co/datasets/multimodalart/HunyuanWorld-panoramas/resolve/main/';
168
+
169
+ const examplesData = [
170
+ { name: 'Cyberpunk', previewImage: 'cyberpunk/cyberpunk.webp', files: ['cyberpunk/mesh_layer0.ply', 'cyberpunk/mesh_layer1.ply'] },
171
+ { name: 'European Town', previewImage: 'european/european.webp', files: ['european/mesh_layer0.ply', 'european/mesh_layer1.ply'] },
172
+ { name: 'Italian Village', previewImage: 'italian/italian.webp', files: ['italian/mesh_layer0.ply', 'italian/mesh_layer1.ply', 'italian/mesh_layer2.ply', 'italian/mesh_layer3.ply'] },
173
+ { name: 'Mountain', previewImage: 'mountain/mountain.webp', files: ['mountain/mesh_layer0.ply', 'mountain/mesh_layer1.ply'] },
174
+ { name: 'WXP', previewImage: 'wxp/wxp.webp', files: ['wxp/mesh_layer0.ply', 'wxp/mesh_layer1.ply', 'wxp/mesh_layer2.ply'] },
175
+ { name: 'ZLD', previewImage: 'zld/zld.webp', files: ['zld/mesh_layer0.ply', 'zld/mesh_layer1.ply'] }
176
+ ];
177
+
178
+ // Prepend the base URL to all example file paths
179
+ const examples = examplesData.map(ex => ({
180
+ name: ex.name,
181
+ previewImage: baseURL + ex.previewImage,
182
+ files: ex.files.map(file => baseURL + file)
183
+ }));
184
+
185
+ // --- UI SETUP ---
186
+ const examplesContainer = document.getElementById('examples-container');
187
+ examples.forEach(example => {
188
+ const card = document.createElement('div');
189
+ card.className = 'example-card';
190
+ card.innerHTML = `
191
+ <img src="${example.previewImage}" alt="${example.name}">
192
+ <p>${example.name}</p>
193
+ `;
194
+ card.addEventListener('click', () => loadExample(example));
195
+ examplesContainer.appendChild(card);
196
+ });
197
+
198
+ // --- THREE.JS INITIALIZATION ---
199
+ const viewerContainer = document.getElementById('viewer-container');
200
+ const scene = new THREE.Scene();
201
+ scene.background = new THREE.Color(0x222222);
202
+ const camera = new THREE.PerspectiveCamera(75, viewerContainer.clientWidth / viewerContainer.clientHeight, 0.1, 1000);
203
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
204
+ renderer.setSize(viewerContainer.clientWidth, viewerContainer.clientHeight);
205
+ viewerContainer.appendChild(renderer.domElement);
206
+
207
+ // --- LOADERS ---
208
+ const plyLoader = new THREE.PLYLoader();
209
+ const dracoLoader = new THREE.DRACOLoader();
210
+ dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/[email protected]/examples/js/libs/draco/');
211
+
212
+ // --- MOVEMENT & CONTROL VARIABLES ---
213
+ const moveSpeed = 0.01;
214
+ const maxDistance = 0.3;
215
+ const keys = { w: false, a: false, s: false, d: false };
216
+ let isMouseDown = false;
217
+ let previousMousePosition = { x: 0, y: 0 };
218
+ let isRotating = false; // Start paused
219
+ let animationId = null;
220
+
221
+ // --- SCENE HELPER FUNCTIONS ---
222
+ function clearScene() {
223
+ scene.children.slice().forEach(child => {
224
+ if (child instanceof THREE.Mesh) {
225
+ if (child.geometry) child.geometry.dispose();
226
+ if (child.material) child.material.dispose();
227
+ scene.remove(child);
228
+ }
229
+ });
230
+ }
231
+
232
+ function onLoadingComplete() {
233
+ document.getElementById('loading').style.display = 'none';
234
+ positionCamera();
235
+ isRotating = true;
236
+ document.getElementById('rotate-toggle').textContent = 'Pause Rotation';
237
+ if (!animationId) {
238
+ animate();
239
+ }
240
+ }
241
+
242
+ function positionCamera() {
243
+ scene.rotation.y = 0;
244
+ camera.position.set(0, 0, 0);
245
+ camera.quaternion.set(0, 0, 0, 1); // Reset camera rotation
246
+ camera.lookAt(0, 0, -10);
247
+ }
248
+
249
+ // --- LOADING LOGIC ---
250
+
251
+ // Load pre-defined examples from server
252
+ function loadExample(example) {
253
+ document.getElementById('loading').style.display = 'block';
254
+ clearScene();
255
+
256
+ const promises = example.files.map(url =>
257
+ fetch(url).then(res => {
258
+ if (!res.ok) throw new Error(`Failed to fetch ${url}`);
259
+ return res.arrayBuffer();
260
+ })
261
+ );
262
+
263
+ Promise.all(promises)
264
+ .then(buffers => {
265
+ buffers.forEach(buffer => {
266
+ const geometry = plyLoader.parse(buffer);
267
+ const material = new THREE.MeshBasicMaterial({
268
+ side: THREE.DoubleSide,
269
+ vertexColors: true,
270
+ });
271
+ const mesh = new THREE.Mesh(geometry, material);
272
+ mesh.rotateX(-Math.PI / 2);
273
+ mesh.rotateZ(-Math.PI / 2);
274
+ scene.add(mesh);
275
+ });
276
+ onLoadingComplete();
277
+ })
278
+ .catch(error => {
279
+ console.error('Error loading example:', error);
280
+ alert('Failed to load example files. Check console for details.');
281
+ document.getElementById('loading').style.display = 'none';
282
+ });
283
+ }
284
+
285
+ // Load custom files from user's computer
286
+ document.getElementById('file-input').addEventListener('change', function(e) {
287
+ const files = e.target.files;
288
+ if (files.length === 0) return;
289
+
290
+ document.getElementById('loading').style.display = 'block';
291
+ clearScene();
292
+
293
+ let loadedCount = 0;
294
+ const totalFiles = files.length;
295
+
296
+ Array.from(files).forEach(file => {
297
+ const reader = new FileReader();
298
+ reader.onload = function(event) {
299
+ try {
300
+ let geometry;
301
+ if (file.name.endsWith('.ply')) {
302
+ geometry = plyLoader.parse(event.target.result);
303
+ } else if (file.name.endsWith('.drc')) {
304
+ dracoLoader.setDecoderConfig({ type: 'js' }); // Ensure decoder is set
305
+ dracoLoader.parse(event.target.result, (decodedGeometry) => {
306
+ geometry = decodedGeometry;
307
+ // Draco decoding is async, handle mesh creation inside callback
308
+ if (!geometry.attributes.normal) geometry.computeVertexNormals();
309
+ const material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, vertexColors: true });
310
+ const mesh = new THREE.Mesh(geometry, material);
311
+ mesh.rotateX(-Math.PI / 2);
312
+ mesh.rotateZ(-Math.PI / 2);
313
+ scene.add(mesh);
314
+
315
+ loadedCount++;
316
+ if (loadedCount === totalFiles) onLoadingComplete();
317
+ });
318
+ return; // Exit onload as Draco is async
319
+ }
320
+
321
+ if (geometry) {
322
+ const material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, vertexColors: true });
323
+ const mesh = new THREE.Mesh(geometry, material);
324
+ mesh.rotateX(-Math.PI / 2);
325
+ mesh.rotateZ(-Math.PI / 2);
326
+ scene.add(mesh);
327
+ }
328
+ } catch (error) {
329
+ console.error('Error loading file:', file.name, error);
330
+ }
331
+ loadedCount++;
332
+ if (loadedCount === totalFiles) onLoadingComplete();
333
+ };
334
+ reader.readAsArrayBuffer(file);
335
+ });
336
+ });
337
+
338
+ // --- CONTROLS & EVENT LISTENERS ---
339
+ document.getElementById('rotate-toggle').addEventListener('click', function() {
340
+ isRotating = !isRotating;
341
+ this.textContent = isRotating ? 'Pause Rotation' : 'Start Rotation';
342
+ });
343
+
344
+ document.getElementById('reset-view').addEventListener('click', function() {
345
+ positionCamera();
346
+ if (!animationId) animate();
347
+ });
348
+
349
+ document.addEventListener('keydown', (event) => {
350
+ if (event.key.toLowerCase() in keys) keys[event.key.toLowerCase()] = true;
351
+ if (!animationId && Object.values(keys).some(k => k)) animate();
352
+ });
353
+
354
+ document.addEventListener('keyup', (event) => {
355
+ if (event.key.toLowerCase() in keys) keys[event.key.toLowerCase()] = false;
356
+ });
357
+
358
+ renderer.domElement.addEventListener('mousedown', (event) => {
359
+ isMouseDown = true;
360
+ previousMousePosition = { x: event.clientX, y: event.clientY };
361
+ event.preventDefault();
362
+ });
363
+
364
+ document.addEventListener('mouseup', () => { isMouseDown = false; });
365
+
366
+ document.addEventListener('mousemove', (event) => {
367
+ if (isMouseDown) {
368
+ const deltaMove = {
369
+ x: event.clientX - previousMousePosition.x,
370
+ y: event.clientY - previousMousePosition.y
371
+ };
372
+
373
+ const up = new THREE.Vector3(0, 1, 0);
374
+ const right = new THREE.Vector3(1, 0, 0);
375
+
376
+ camera.rotateOnWorldAxis(up, -deltaMove.x * 0.002);
377
+ camera.rotateOnAxis(right, -deltaMove.y * 0.002);
378
+
379
+ previousMousePosition = { x: event.clientX, y: event.clientY };
380
+ }
381
+ });
382
+
383
+ renderer.domElement.addEventListener('contextmenu', (event) => event.preventDefault());
384
+
385
+ window.addEventListener('resize', function() {
386
+ camera.aspect = viewerContainer.clientWidth / viewerContainer.clientHeight;
387
+ camera.updateProjectionMatrix();
388
+ renderer.setSize(viewerContainer.clientWidth, viewerContainer.clientHeight);
389
+ });
390
+
391
+ // --- ANIMATION LOOP ---
392
+ function animate() {
393
+ animationId = requestAnimationFrame(animate);
394
+
395
+ let hasMoved = false;
396
+ if (keys.w || keys.a || keys.s || keys.d) {
397
+ const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
398
+ const right = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion);
399
+
400
+ forward.y = 0; right.y = 0;
401
+ forward.normalize(); right.normalize();
402
+
403
+ const movement = new THREE.Vector3();
404
+ if (keys.w) movement.add(forward);
405
+ if (keys.s) movement.sub(forward);
406
+ if (keys.a) movement.sub(right);
407
+ if (keys.d) movement.add(right);
408
+
409
+ if (movement.length() > 0) {
410
+ movement.normalize().multiplyScalar(moveSpeed);
411
+ camera.position.add(movement);
412
+ hasMoved = true;
413
+ }
414
+ }
415
+
416
+ // Limit movement
417
+ if (camera.position.length() > maxDistance) {
418
+ camera.position.setLength(maxDistance);
419
+ }
420
+
421
+ if (isRotating && scene.children.some(c => c instanceof THREE.Mesh)) {
422
+ scene.rotation.y += 0.0005;
423
+ }
424
+
425
+ renderer.render(scene, camera);
426
+ }
427
+
428
+ animate(); // Start the animation loop
429
+ </script>
430
+ </body>
431
+ </html>