multimodalart HF Staff commited on
Commit
fa7f101
·
verified ·
1 Parent(s): e32591c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +54 -41
index.html CHANGED
@@ -112,7 +112,8 @@
112
  width: 0%;
113
  height: 20px;
114
  background-color: #4CAF50;
115
- transition: width 0.1s linear;
 
116
  }
117
  #controls {
118
  position: absolute;
@@ -143,9 +144,6 @@
143
  font-size: 14px;
144
  z-index: 10;
145
  }
146
- #progress-container{
147
- display: none;
148
- }
149
  </style>
150
  </head>
151
  <body>
@@ -186,15 +184,16 @@
186
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/PLYLoader.js"></script>
187
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/DRACOLoader.js"></script>
188
  <script>
189
- // --- DATA FOR EXAMPLES ---
 
190
  const baseURL = 'https://huggingface.co/datasets/multimodalart/HunyuanWorld-panoramas/resolve/main/';
191
  const examplesData = [
192
  { name: 'Cyberpunk', previewImage: 'cyberpunk/cyberpunk.webp', files: ['cyberpunk/mesh_layer0.ply', 'cyberpunk/mesh_layer1.ply'] },
193
  { name: 'European Town', previewImage: 'european/european.webp', files: ['european/mesh_layer0.ply', 'european/mesh_layer1.ply'] },
194
  { name: 'Italian Village', previewImage: 'italian/italian.webp', files: ['italian/mesh_layer0.ply', 'italian/mesh_layer1.ply', 'italian/mesh_layer2.ply', 'italian/mesh_layer3.ply'] },
195
  { name: 'Mountain', previewImage: 'mountain/mountain.webp', files: ['mountain/mesh_layer0.ply', 'mountain/mesh_layer1.ply'] },
196
- { name: 'Windows XP', previewImage: 'wxp/wxp.webp', files: ['wxp/mesh_layer0.ply', 'wxp/mesh_layer1.ply', 'wxp/mesh_layer2.ply'] },
197
- { name: 'Zelda', previewImage: 'zld/zld.webp', files: ['zld/mesh_layer0.ply', 'zld/mesh_layer1.ply'] }
198
  ];
199
  const examples = examplesData.map(ex => ({
200
  name: ex.name,
@@ -268,73 +267,90 @@
268
  }
269
 
270
  // --- LOADING LOGIC ---
271
-
272
- /**
273
- * Fetches a file and reports progress via a callback.
274
- * @param {string} url - The URL of the file to fetch.
275
- * @param {function(number)} onProgress - Callback function that receives the size of each downloaded chunk.
276
- * @returns {Promise<ArrayBuffer>} - A promise that resolves with the file's ArrayBuffer.
277
- */
278
  async function fetchWithProgress(url, onProgress) {
279
  const response = await fetch(url);
280
  if (!response.ok) throw new Error(`HTTP error! status: ${response.status} for ${url}`);
281
  if (!response.body) throw new Error('Response body is null');
282
-
283
  const reader = response.body.getReader();
284
  const chunks = [];
285
-
286
  while (true) {
287
  const { done, value } = await reader.read();
288
  if (done) break;
289
  chunks.push(value);
290
  onProgress(value.length);
291
  }
292
-
293
- let totalLength = 0;
294
- chunks.forEach(chunk => totalLength += chunk.length);
295
  const buffer = new Uint8Array(totalLength);
296
  let offset = 0;
297
  chunks.forEach(chunk => {
298
  buffer.set(chunk, offset);
299
  offset += chunk.length;
300
  });
301
-
302
  return buffer.buffer;
303
  }
304
 
305
- // Load pre-defined examples from server with progress
306
  async function loadExample(example) {
 
307
  loadingDiv.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  progressContainer.style.display = 'block';
309
  progressBar.style.width = '0%';
310
  loadingText.textContent = 'Calculating size...';
311
- clearScene();
312
-
 
 
 
313
  try {
314
- // Step 1: Get total size of all files
315
  const headPromises = example.files.map(url => fetch(url, { method: 'HEAD' }));
316
  const responses = await Promise.all(headPromises);
317
- const totalSize = responses.reduce((acc, res) => {
318
  if (!res.ok) throw new Error(`Failed to get headers for ${res.url}`);
319
- return acc + Number(res.headers.get('Content-Length'));
320
  }, 0);
321
 
322
- let loadedSize = 0;
323
- loadingText.textContent = 'Loading... 0%';
 
 
 
 
 
 
 
 
 
 
 
324
 
325
- // Step 2: Fetch files with progress tracking
326
  const onProgress = (chunkSize) => {
327
  loadedSize += chunkSize;
328
- const percent = totalSize > 0 ? (loadedSize / totalSize) * 100 : 0;
329
- progressBar.style.width = `${percent}%`;
330
- loadingText.textContent = `Loading... ${Math.round(percent)}%`;
331
  };
332
 
333
  const contentPromises = example.files.map(url => fetchWithProgress(url, onProgress));
334
  const buffers = await Promise.all(contentPromises);
 
 
 
335
 
336
- // Step 3: Process downloaded files
337
- loadingText.textContent = 'Processing files...';
338
  buffers.forEach(buffer => {
339
  const geometry = plyLoader.parse(buffer);
340
  const material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, vertexColors: true });
@@ -342,13 +358,16 @@
342
  mesh.rotateX(-Math.PI / 2);
343
  mesh.rotateZ(-Math.PI / 2);
344
  scene.add(mesh);
 
345
  });
346
 
 
347
  onLoadingComplete();
348
 
349
  } catch (error) {
350
  console.error('Error loading example:', error);
351
  alert('Failed to load example files. Check console for details.');
 
352
  loadingDiv.style.display = 'none';
353
  }
354
  }
@@ -357,15 +376,12 @@
357
  document.getElementById('file-input').addEventListener('change', function(e) {
358
  const files = e.target.files;
359
  if (files.length === 0) return;
360
-
361
  loadingDiv.style.display = 'block';
362
  loadingText.textContent = 'Loading...';
363
- progressContainer.style.display = 'none'; // Hide progress bar for local files
364
  clearScene();
365
-
366
  let loadedCount = 0;
367
  const totalFiles = files.length;
368
-
369
  Array.from(files).forEach(file => {
370
  const reader = new FileReader();
371
  reader.onload = function(event) {
@@ -384,13 +400,11 @@
384
  mesh.rotateX(-Math.PI / 2);
385
  mesh.rotateZ(-Math.PI / 2);
386
  scene.add(mesh);
387
-
388
  loadedCount++;
389
  if (loadedCount === totalFiles) onLoadingComplete();
390
  });
391
  return;
392
  }
393
-
394
  if (geometry) {
395
  const material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, vertexColors: true });
396
  const mesh = new THREE.Mesh(geometry, material);
@@ -419,7 +433,6 @@
419
  });
420
  document.addEventListener('keydown', (event) => {
421
  if (event.key.toLowerCase() in keys) keys[event.key.toLowerCase()] = true;
422
- if (!animationId && Object.values(keys).some(k => k)) animate();
423
  });
424
  document.addEventListener('keyup', (event) => {
425
  if (event.key.toLowerCase() in keys) keys[event.key.toLowerCase()] = false;
 
112
  width: 0%;
113
  height: 20px;
114
  background-color: #4CAF50;
115
+ /* Smoother transition for the bar */
116
+ transition: width 0.2s ease-out;
117
  }
118
  #controls {
119
  position: absolute;
 
144
  font-size: 14px;
145
  z-index: 10;
146
  }
 
 
 
147
  </style>
148
  </head>
149
  <body>
 
184
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/PLYLoader.js"></script>
185
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/DRACOLoader.js"></script>
186
  <script>
187
+ // --- CACHE & DATA ---
188
+ const modelCache = new Map();
189
  const baseURL = 'https://huggingface.co/datasets/multimodalart/HunyuanWorld-panoramas/resolve/main/';
190
  const examplesData = [
191
  { name: 'Cyberpunk', previewImage: 'cyberpunk/cyberpunk.webp', files: ['cyberpunk/mesh_layer0.ply', 'cyberpunk/mesh_layer1.ply'] },
192
  { name: 'European Town', previewImage: 'european/european.webp', files: ['european/mesh_layer0.ply', 'european/mesh_layer1.ply'] },
193
  { name: 'Italian Village', previewImage: 'italian/italian.webp', files: ['italian/mesh_layer0.ply', 'italian/mesh_layer1.ply', 'italian/mesh_layer2.ply', 'italian/mesh_layer3.ply'] },
194
  { name: 'Mountain', previewImage: 'mountain/mountain.webp', files: ['mountain/mesh_layer0.ply', 'mountain/mesh_layer1.ply'] },
195
+ { name: 'WXP', previewImage: 'wxp/wxp.webp', files: ['wxp/mesh_layer0.ply', 'wxp/mesh_layer1.ply', 'wxp/mesh_layer2.ply'] },
196
+ { name: 'ZLD', previewImage: 'zld/zld.webp', files: ['zld/mesh_layer0.ply', 'zld/mesh_layer1.ply'] }
197
  ];
198
  const examples = examplesData.map(ex => ({
199
  name: ex.name,
 
267
  }
268
 
269
  // --- LOADING LOGIC ---
 
 
 
 
 
 
 
270
  async function fetchWithProgress(url, onProgress) {
271
  const response = await fetch(url);
272
  if (!response.ok) throw new Error(`HTTP error! status: ${response.status} for ${url}`);
273
  if (!response.body) throw new Error('Response body is null');
 
274
  const reader = response.body.getReader();
275
  const chunks = [];
 
276
  while (true) {
277
  const { done, value } = await reader.read();
278
  if (done) break;
279
  chunks.push(value);
280
  onProgress(value.length);
281
  }
282
+ let totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
 
 
283
  const buffer = new Uint8Array(totalLength);
284
  let offset = 0;
285
  chunks.forEach(chunk => {
286
  buffer.set(chunk, offset);
287
  offset += chunk.length;
288
  });
 
289
  return buffer.buffer;
290
  }
291
 
 
292
  async function loadExample(example) {
293
+ clearScene();
294
  loadingDiv.style.display = 'block';
295
+
296
+ // --- CACHE CHECK ---
297
+ if (modelCache.has(example.name)) {
298
+ loadingText.textContent = 'Loading from cache...';
299
+ progressContainer.style.display = 'none';
300
+
301
+ const cachedMeshes = modelCache.get(example.name);
302
+ cachedMeshes.forEach(mesh => scene.add(mesh.clone())); // Clone to avoid issues
303
+
304
+ // Use a short timeout to let the "Loading from cache..." message be visible
305
+ setTimeout(onLoadingComplete, 50);
306
+ return;
307
+ }
308
+
309
+ // --- DOWNLOAD & PROCESS (CACHE MISS) ---
310
  progressContainer.style.display = 'block';
311
  progressBar.style.width = '0%';
312
  loadingText.textContent = 'Calculating size...';
313
+
314
+ let loadedSize = 0;
315
+ let totalSize = 0;
316
+ let progressAnimationId = null;
317
+
318
  try {
319
+ // Step 1: Get total size
320
  const headPromises = example.files.map(url => fetch(url, { method: 'HEAD' }));
321
  const responses = await Promise.all(headPromises);
322
+ totalSize = responses.reduce((acc, res) => {
323
  if (!res.ok) throw new Error(`Failed to get headers for ${res.url}`);
324
+ return acc + Number(res.headers.get('Content-Length') || 0);
325
  }, 0);
326
 
327
+ // Step 2: Animate progress bar smoothly
328
+ const updateProgressUI = () => {
329
+ if (loadedSize < totalSize) {
330
+ const percent = totalSize > 0 ? (loadedSize / totalSize) * 100 : 0;
331
+ progressBar.style.width = `${percent}%`;
332
+ loadingText.textContent = `Downloading... ${Math.round(percent)}%`;
333
+ progressAnimationId = requestAnimationFrame(updateProgressUI);
334
+ } else {
335
+ progressBar.style.width = `100%`;
336
+ loadingText.textContent = `Processing files...`;
337
+ }
338
+ };
339
+ progressAnimationId = requestAnimationFrame(updateProgressUI);
340
 
341
+ // Step 3: Fetch files and update progress
342
  const onProgress = (chunkSize) => {
343
  loadedSize += chunkSize;
 
 
 
344
  };
345
 
346
  const contentPromises = example.files.map(url => fetchWithProgress(url, onProgress));
347
  const buffers = await Promise.all(contentPromises);
348
+
349
+ // Stop the progress animation loop
350
+ cancelAnimationFrame(progressAnimationId);
351
 
352
+ // Step 4: Process and cache downloaded files
353
+ const newMeshes = [];
354
  buffers.forEach(buffer => {
355
  const geometry = plyLoader.parse(buffer);
356
  const material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, vertexColors: true });
 
358
  mesh.rotateX(-Math.PI / 2);
359
  mesh.rotateZ(-Math.PI / 2);
360
  scene.add(mesh);
361
+ newMeshes.push(mesh);
362
  });
363
 
364
+ modelCache.set(example.name, newMeshes); // Store in cache
365
  onLoadingComplete();
366
 
367
  } catch (error) {
368
  console.error('Error loading example:', error);
369
  alert('Failed to load example files. Check console for details.');
370
+ if (progressAnimationId) cancelAnimationFrame(progressAnimationId);
371
  loadingDiv.style.display = 'none';
372
  }
373
  }
 
376
  document.getElementById('file-input').addEventListener('change', function(e) {
377
  const files = e.target.files;
378
  if (files.length === 0) return;
 
379
  loadingDiv.style.display = 'block';
380
  loadingText.textContent = 'Loading...';
381
+ progressContainer.style.display = 'none';
382
  clearScene();
 
383
  let loadedCount = 0;
384
  const totalFiles = files.length;
 
385
  Array.from(files).forEach(file => {
386
  const reader = new FileReader();
387
  reader.onload = function(event) {
 
400
  mesh.rotateX(-Math.PI / 2);
401
  mesh.rotateZ(-Math.PI / 2);
402
  scene.add(mesh);
 
403
  loadedCount++;
404
  if (loadedCount === totalFiles) onLoadingComplete();
405
  });
406
  return;
407
  }
 
408
  if (geometry) {
409
  const material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, vertexColors: true });
410
  const mesh = new THREE.Mesh(geometry, material);
 
433
  });
434
  document.addEventListener('keydown', (event) => {
435
  if (event.key.toLowerCase() in keys) keys[event.key.toLowerCase()] = true;
 
436
  });
437
  document.addEventListener('keyup', (event) => {
438
  if (event.key.toLowerCase() in keys) keys[event.key.toLowerCase()] = false;