Update index.html
Browse files- index.html +28 -31
index.html
CHANGED
@@ -256,7 +256,9 @@
|
|
256 |
positionCamera();
|
257 |
isRotating = true;
|
258 |
document.getElementById('rotate-toggle').textContent = 'Pause Rotation';
|
259 |
-
if (!animationId)
|
|
|
|
|
260 |
}
|
261 |
|
262 |
function positionCamera() {
|
@@ -293,20 +295,15 @@
|
|
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()));
|
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...';
|
@@ -316,40 +313,27 @@
|
|
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 |
-
|
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);
|
@@ -361,7 +345,7 @@
|
|
361 |
newMeshes.push(mesh);
|
362 |
});
|
363 |
|
364 |
-
modelCache.set(example.name, newMeshes);
|
365 |
onLoadingComplete();
|
366 |
|
367 |
} catch (error) {
|
@@ -372,7 +356,6 @@
|
|
372 |
}
|
373 |
}
|
374 |
|
375 |
-
// Load custom files from user's computer
|
376 |
document.getElementById('file-input').addEventListener('change', function(e) {
|
377 |
const files = e.target.files;
|
378 |
if (files.length === 0) return;
|
@@ -391,7 +374,6 @@
|
|
391 |
if (file.name.endsWith('.ply')) {
|
392 |
geometry = plyLoader.parse(buffer);
|
393 |
} else if (file.name.endsWith('.drc')) {
|
394 |
-
dracoLoader.setDecoderConfig({ type: 'js' });
|
395 |
dracoLoader.parse(buffer, (decodedGeometry) => {
|
396 |
geometry = decodedGeometry;
|
397 |
if (!geometry.attributes.normal) geometry.computeVertexNormals();
|
@@ -432,7 +414,13 @@
|
|
432 |
if (!animationId) animate();
|
433 |
});
|
434 |
document.addEventListener('keydown', (event) => {
|
435 |
-
if (event.key.toLowerCase() in keys)
|
|
|
|
|
|
|
|
|
|
|
|
|
436 |
});
|
437 |
document.addEventListener('keyup', (event) => {
|
438 |
if (event.key.toLowerCase() in keys) keys[event.key.toLowerCase()] = false;
|
@@ -462,7 +450,7 @@
|
|
462 |
|
463 |
// --- ANIMATION LOOP ---
|
464 |
function animate() {
|
465 |
-
|
466 |
if (keys.w || keys.a || keys.s || keys.d) {
|
467 |
const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
|
468 |
const right = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion);
|
@@ -479,9 +467,18 @@
|
|
479 |
}
|
480 |
}
|
481 |
if (camera.position.length() > maxDistance) camera.position.setLength(maxDistance);
|
|
|
|
|
482 |
if (isRotating && scene.children.some(c => c instanceof THREE.Mesh)) scene.rotation.y += 0.0005;
|
|
|
|
|
483 |
renderer.render(scene, camera);
|
|
|
|
|
|
|
484 |
}
|
|
|
|
|
485 |
animate();
|
486 |
</script>
|
487 |
</body>
|
|
|
256 |
positionCamera();
|
257 |
isRotating = true;
|
258 |
document.getElementById('rotate-toggle').textContent = 'Pause Rotation';
|
259 |
+
if (!animationId) {
|
260 |
+
animate();
|
261 |
+
}
|
262 |
}
|
263 |
|
264 |
function positionCamera() {
|
|
|
295 |
clearScene();
|
296 |
loadingDiv.style.display = 'block';
|
297 |
|
|
|
298 |
if (modelCache.has(example.name)) {
|
299 |
loadingText.textContent = 'Loading from cache...';
|
300 |
progressContainer.style.display = 'none';
|
|
|
301 |
const cachedMeshes = modelCache.get(example.name);
|
302 |
+
cachedMeshes.forEach(mesh => scene.add(mesh.clone()));
|
|
|
|
|
303 |
setTimeout(onLoadingComplete, 50);
|
304 |
return;
|
305 |
}
|
306 |
|
|
|
307 |
progressContainer.style.display = 'block';
|
308 |
progressBar.style.width = '0%';
|
309 |
loadingText.textContent = 'Calculating size...';
|
|
|
313 |
let progressAnimationId = null;
|
314 |
|
315 |
try {
|
|
|
316 |
const headPromises = example.files.map(url => fetch(url, { method: 'HEAD' }));
|
317 |
const responses = await Promise.all(headPromises);
|
318 |
+
totalSize = responses.reduce((acc, res) => acc + Number(res.headers.get('Content-Length') || 0), 0);
|
|
|
|
|
|
|
319 |
|
|
|
320 |
const updateProgressUI = () => {
|
321 |
+
const percent = totalSize > 0 ? (loadedSize / totalSize) * 100 : 0;
|
322 |
+
progressBar.style.width = `${percent}%`;
|
323 |
+
loadingText.textContent = `Downloading... ${Math.round(percent)}%`;
|
324 |
if (loadedSize < totalSize) {
|
|
|
|
|
|
|
325 |
progressAnimationId = requestAnimationFrame(updateProgressUI);
|
|
|
|
|
|
|
326 |
}
|
327 |
};
|
328 |
progressAnimationId = requestAnimationFrame(updateProgressUI);
|
329 |
|
330 |
+
const onProgress = (chunkSize) => { loadedSize += chunkSize; };
|
|
|
|
|
|
|
|
|
331 |
const contentPromises = example.files.map(url => fetchWithProgress(url, onProgress));
|
332 |
const buffers = await Promise.all(contentPromises);
|
333 |
|
|
|
334 |
cancelAnimationFrame(progressAnimationId);
|
335 |
+
loadingText.textContent = `Processing files...`;
|
336 |
|
|
|
337 |
const newMeshes = [];
|
338 |
buffers.forEach(buffer => {
|
339 |
const geometry = plyLoader.parse(buffer);
|
|
|
345 |
newMeshes.push(mesh);
|
346 |
});
|
347 |
|
348 |
+
modelCache.set(example.name, newMeshes);
|
349 |
onLoadingComplete();
|
350 |
|
351 |
} catch (error) {
|
|
|
356 |
}
|
357 |
}
|
358 |
|
|
|
359 |
document.getElementById('file-input').addEventListener('change', function(e) {
|
360 |
const files = e.target.files;
|
361 |
if (files.length === 0) return;
|
|
|
374 |
if (file.name.endsWith('.ply')) {
|
375 |
geometry = plyLoader.parse(buffer);
|
376 |
} else if (file.name.endsWith('.drc')) {
|
|
|
377 |
dracoLoader.parse(buffer, (decodedGeometry) => {
|
378 |
geometry = decodedGeometry;
|
379 |
if (!geometry.attributes.normal) geometry.computeVertexNormals();
|
|
|
414 |
if (!animationId) animate();
|
415 |
});
|
416 |
document.addEventListener('keydown', (event) => {
|
417 |
+
if (event.key.toLowerCase() in keys) {
|
418 |
+
keys[event.key.toLowerCase()] = true;
|
419 |
+
// BUG FIX: Ensure the animation loop is running when a key is pressed.
|
420 |
+
if (!animationId) {
|
421 |
+
animate();
|
422 |
+
}
|
423 |
+
}
|
424 |
});
|
425 |
document.addEventListener('keyup', (event) => {
|
426 |
if (event.key.toLowerCase() in keys) keys[event.key.toLowerCase()] = false;
|
|
|
450 |
|
451 |
// --- ANIMATION LOOP ---
|
452 |
function animate() {
|
453 |
+
// Process movement
|
454 |
if (keys.w || keys.a || keys.s || keys.d) {
|
455 |
const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
|
456 |
const right = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion);
|
|
|
467 |
}
|
468 |
}
|
469 |
if (camera.position.length() > maxDistance) camera.position.setLength(maxDistance);
|
470 |
+
|
471 |
+
// Process rotation
|
472 |
if (isRotating && scene.children.some(c => c instanceof THREE.Mesh)) scene.rotation.y += 0.0005;
|
473 |
+
|
474 |
+
// Render the scene
|
475 |
renderer.render(scene, camera);
|
476 |
+
|
477 |
+
// BUG FIX: Request the next frame at the *end* of the function for robustness.
|
478 |
+
animationId = requestAnimationFrame(animate);
|
479 |
}
|
480 |
+
|
481 |
+
// Start the initial animation loop
|
482 |
animate();
|
483 |
</script>
|
484 |
</body>
|