multimodalart HF Staff commited on
Commit
9f190d2
·
verified ·
1 Parent(s): e4d5d9b

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +133 -92
index.html CHANGED
@@ -65,7 +65,7 @@
65
  #viewer-container {
66
  position: relative;
67
  width: 100%;
68
- height: 65vh; /* Adjusted height for the viewer */
69
  }
70
  canvas {
71
  display: block;
@@ -91,10 +91,28 @@
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;
@@ -141,11 +159,14 @@
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">
@@ -155,7 +176,6 @@
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
 
@@ -165,32 +185,32 @@
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
  });
@@ -215,7 +235,7 @@
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 ---
@@ -230,56 +250,104 @@
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
@@ -287,7 +355,9 @@
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;
@@ -297,14 +367,14 @@
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);
@@ -315,7 +385,7 @@
315
  loadedCount++;
316
  if (loadedCount === totalFiles) onLoadingComplete();
317
  });
318
- return; // Exit onload as Draco is async
319
  }
320
 
321
  if (geometry) {
@@ -340,48 +410,34 @@
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();
@@ -391,41 +447,26 @@
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>
 
65
  #viewer-container {
66
  position: relative;
67
  width: 100%;
68
+ height: 65vh;
69
  }
70
  canvas {
71
  display: block;
 
91
  background: #45a049;
92
  }
93
  #loading {
94
+ display: none; /* Hidden by default */
95
+ padding: 15px;
96
+ }
97
+ #loading-text {
98
  color: #aaa;
99
  font-size: 18px;
100
+ margin-bottom: 10px;
101
+ }
102
+ #progress-container {
103
+ width: 80%;
104
+ max-width: 400px;
105
+ margin: 0 auto;
106
+ background-color: #555;
107
+ border-radius: 5px;
108
+ overflow: hidden;
109
+ display: none; /* Hidden by default, shown for web loads */
110
+ }
111
+ #progress-bar {
112
+ width: 0%;
113
+ height: 20px;
114
+ background-color: #4CAF50;
115
+ transition: width 0.1s linear;
116
  }
117
  #controls {
118
  position: absolute;
 
159
  </div>
160
  </div>
161
 
162
+ <div id="examples-container"></div>
 
 
163
 
164
+ <div id="loading">
165
+ <div id="loading-text">Loading...</div>
166
+ <div id="progress-container">
167
+ <div id="progress-bar"></div>
168
+ </div>
169
+ </div>
170
 
171
  <div id="viewer-container">
172
  <div id="controls">
 
176
  <div id="instructions">
177
  Controls: WASD to move, Mouse drag to look around
178
  </div>
 
179
  </div>
180
 
181
 
 
185
  <script>
186
  // --- DATA FOR EXAMPLES ---
187
  const baseURL = 'https://huggingface.co/datasets/multimodalart/HunyuanWorld-panoramas/resolve/main/';
 
188
  const examplesData = [
189
  { name: 'Cyberpunk', previewImage: 'cyberpunk/cyberpunk.webp', files: ['cyberpunk/mesh_layer0.ply', 'cyberpunk/mesh_layer1.ply'] },
190
  { name: 'European Town', previewImage: 'european/european.webp', files: ['european/mesh_layer0.ply', 'european/mesh_layer1.ply'] },
191
  { name: 'Italian Village', previewImage: 'italian/italian.webp', files: ['italian/mesh_layer0.ply', 'italian/mesh_layer1.ply', 'italian/mesh_layer2.ply', 'italian/mesh_layer3.ply'] },
192
  { name: 'Mountain', previewImage: 'mountain/mountain.webp', files: ['mountain/mesh_layer0.ply', 'mountain/mesh_layer1.ply'] },
193
+ { name: 'Windows XP', previewImage: 'wxp/wxp.webp', files: ['wxp/mesh_layer0.ply', 'wxp/mesh_layer1.ply', 'wxp/mesh_layer2.ply'] },
194
+ { name: 'Zelda', previewImage: 'zld/zld.webp', files: ['zld/mesh_layer0.ply', 'zld/mesh_layer1.ply'] }
195
  ];
 
 
196
  const examples = examplesData.map(ex => ({
197
  name: ex.name,
198
  previewImage: baseURL + ex.previewImage,
199
  files: ex.files.map(file => baseURL + file)
200
  }));
201
 
202
+ // --- UI & DOM ELEMENTS ---
203
  const examplesContainer = document.getElementById('examples-container');
204
+ const loadingDiv = document.getElementById('loading');
205
+ const loadingText = document.getElementById('loading-text');
206
+ const progressContainer = document.getElementById('progress-container');
207
+ const progressBar = document.getElementById('progress-bar');
208
+
209
+ // --- UI SETUP ---
210
  examples.forEach(example => {
211
  const card = document.createElement('div');
212
  card.className = 'example-card';
213
+ card.innerHTML = `<img src="${example.previewImage}" alt="${example.name}"><p>${example.name}</p>`;
 
 
 
214
  card.addEventListener('click', () => loadExample(example));
215
  examplesContainer.appendChild(card);
216
  });
 
235
  const keys = { w: false, a: false, s: false, d: false };
236
  let isMouseDown = false;
237
  let previousMousePosition = { x: 0, y: 0 };
238
+ let isRotating = false;
239
  let animationId = null;
240
 
241
  // --- SCENE HELPER FUNCTIONS ---
 
250
  }
251
 
252
  function onLoadingComplete() {
253
+ loadingDiv.style.display = 'none';
254
  positionCamera();
255
  isRotating = true;
256
  document.getElementById('rotate-toggle').textContent = 'Pause Rotation';
257
+ if (!animationId) animate();
 
 
258
  }
259
 
260
  function positionCamera() {
261
  scene.rotation.y = 0;
262
  camera.position.set(0, 0, 0);
263
+ camera.quaternion.set(0, 0, 0, 1);
264
  camera.lookAt(0, 0, -10);
265
  }
266
 
267
  // --- LOADING LOGIC ---
268
 
269
+ /**
270
+ * Fetches a file and reports progress via a callback.
271
+ * @param {string} url - The URL of the file to fetch.
272
+ * @param {function(number)} onProgress - Callback function that receives the size of each downloaded chunk.
273
+ * @returns {Promise<ArrayBuffer>} - A promise that resolves with the file's ArrayBuffer.
274
+ */
275
+ async function fetchWithProgress(url, onProgress) {
276
+ const response = await fetch(url);
277
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status} for ${url}`);
278
+ if (!response.body) throw new Error('Response body is null');
279
+
280
+ const reader = response.body.getReader();
281
+ const chunks = [];
282
+
283
+ while (true) {
284
+ const { done, value } = await reader.read();
285
+ if (done) break;
286
+ chunks.push(value);
287
+ onProgress(value.length);
288
+ }
289
+
290
+ let totalLength = 0;
291
+ chunks.forEach(chunk => totalLength += chunk.length);
292
+ const buffer = new Uint8Array(totalLength);
293
+ let offset = 0;
294
+ chunks.forEach(chunk => {
295
+ buffer.set(chunk, offset);
296
+ offset += chunk.length;
297
+ });
298
+
299
+ return buffer.buffer;
300
+ }
301
+
302
+ // Load pre-defined examples from server with progress
303
+ async function loadExample(example) {
304
+ loadingDiv.style.display = 'block';
305
+ progressContainer.style.display = 'block';
306
+ progressBar.style.width = '0%';
307
+ loadingText.textContent = 'Calculating size...';
308
  clearScene();
309
 
310
+ try {
311
+ // Step 1: Get total size of all files
312
+ const headPromises = example.files.map(url => fetch(url, { method: 'HEAD' }));
313
+ const responses = await Promise.all(headPromises);
314
+ const totalSize = responses.reduce((acc, res) => {
315
+ if (!res.ok) throw new Error(`Failed to get headers for ${res.url}`);
316
+ return acc + Number(res.headers.get('Content-Length'));
317
+ }, 0);
318
+
319
+ let loadedSize = 0;
320
+ loadingText.textContent = 'Loading... 0%';
321
+
322
+ // Step 2: Fetch files with progress tracking
323
+ const onProgress = (chunkSize) => {
324
+ loadedSize += chunkSize;
325
+ const percent = totalSize > 0 ? (loadedSize / totalSize) * 100 : 0;
326
+ progressBar.style.width = `${percent}%`;
327
+ loadingText.textContent = `Loading... ${Math.round(percent)}%`;
328
+ };
329
+
330
+ const contentPromises = example.files.map(url => fetchWithProgress(url, onProgress));
331
+ const buffers = await Promise.all(contentPromises);
332
 
333
+ // Step 3: Process downloaded files
334
+ loadingText.textContent = 'Processing files...';
335
+ buffers.forEach(buffer => {
336
+ const geometry = plyLoader.parse(buffer);
337
+ const material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, vertexColors: true });
338
+ const mesh = new THREE.Mesh(geometry, material);
339
+ mesh.rotateX(-Math.PI / 2);
340
+ mesh.rotateZ(-Math.PI / 2);
341
+ scene.add(mesh);
 
 
 
 
 
 
 
 
 
 
342
  });
343
+
344
+ onLoadingComplete();
345
+
346
+ } catch (error) {
347
+ console.error('Error loading example:', error);
348
+ alert('Failed to load example files. Check console for details.');
349
+ loadingDiv.style.display = 'none';
350
+ }
351
  }
352
 
353
  // Load custom files from user's computer
 
355
  const files = e.target.files;
356
  if (files.length === 0) return;
357
 
358
+ loadingDiv.style.display = 'block';
359
+ loadingText.textContent = 'Loading...';
360
+ progressContainer.style.display = 'none'; // Hide progress bar for local files
361
  clearScene();
362
 
363
  let loadedCount = 0;
 
367
  const reader = new FileReader();
368
  reader.onload = function(event) {
369
  try {
370
+ const buffer = event.target.result;
371
  let geometry;
372
  if (file.name.endsWith('.ply')) {
373
+ geometry = plyLoader.parse(buffer);
374
  } else if (file.name.endsWith('.drc')) {
375
+ dracoLoader.setDecoderConfig({ type: 'js' });
376
+ dracoLoader.parse(buffer, (decodedGeometry) => {
377
  geometry = decodedGeometry;
 
378
  if (!geometry.attributes.normal) geometry.computeVertexNormals();
379
  const material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, vertexColors: true });
380
  const mesh = new THREE.Mesh(geometry, material);
 
385
  loadedCount++;
386
  if (loadedCount === totalFiles) onLoadingComplete();
387
  });
388
+ return;
389
  }
390
 
391
  if (geometry) {
 
410
  isRotating = !isRotating;
411
  this.textContent = isRotating ? 'Pause Rotation' : 'Start Rotation';
412
  });
413
+ document.getElementById('reset-view').addEventListener('click', () => {
 
414
  positionCamera();
415
  if (!animationId) animate();
416
  });
 
417
  document.addEventListener('keydown', (event) => {
418
  if (event.key.toLowerCase() in keys) keys[event.key.toLowerCase()] = true;
419
  if (!animationId && Object.values(keys).some(k => k)) animate();
420
  });
 
421
  document.addEventListener('keyup', (event) => {
422
  if (event.key.toLowerCase() in keys) keys[event.key.toLowerCase()] = false;
423
  });
 
424
  renderer.domElement.addEventListener('mousedown', (event) => {
425
  isMouseDown = true;
426
  previousMousePosition = { x: event.clientX, y: event.clientY };
427
  event.preventDefault();
428
  });
 
429
  document.addEventListener('mouseup', () => { isMouseDown = false; });
 
430
  document.addEventListener('mousemove', (event) => {
431
  if (isMouseDown) {
432
+ const deltaMove = { x: event.clientX - previousMousePosition.x, y: event.clientY - previousMousePosition.y };
 
 
 
 
433
  const up = new THREE.Vector3(0, 1, 0);
434
  const right = new THREE.Vector3(1, 0, 0);
 
435
  camera.rotateOnWorldAxis(up, -deltaMove.x * 0.002);
436
  camera.rotateOnAxis(right, -deltaMove.y * 0.002);
 
437
  previousMousePosition = { x: event.clientX, y: event.clientY };
438
  }
439
  });
 
440
  renderer.domElement.addEventListener('contextmenu', (event) => event.preventDefault());
 
441
  window.addEventListener('resize', function() {
442
  camera.aspect = viewerContainer.clientWidth / viewerContainer.clientHeight;
443
  camera.updateProjectionMatrix();
 
447
  // --- ANIMATION LOOP ---
448
  function animate() {
449
  animationId = requestAnimationFrame(animate);
 
 
450
  if (keys.w || keys.a || keys.s || keys.d) {
451
  const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
452
  const right = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion);
 
453
  forward.y = 0; right.y = 0;
454
  forward.normalize(); right.normalize();
 
455
  const movement = new THREE.Vector3();
456
  if (keys.w) movement.add(forward);
457
  if (keys.s) movement.sub(forward);
458
  if (keys.a) movement.sub(right);
459
  if (keys.d) movement.add(right);
 
460
  if (movement.length() > 0) {
461
  movement.normalize().multiplyScalar(moveSpeed);
462
  camera.position.add(movement);
 
463
  }
464
  }
465
+ if (camera.position.length() > maxDistance) camera.position.setLength(maxDistance);
466
+ if (isRotating && scene.children.some(c => c instanceof THREE.Mesh)) scene.rotation.y += 0.0005;
 
 
 
 
 
 
 
 
467
  renderer.render(scene, camera);
468
  }
469
+ animate();
 
470
  </script>
471
  </body>
472
  </html>