Spaces:
Running
Running
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>
|