bilca commited on
Commit
bb72c71
·
verified ·
1 Parent(s): f2e48ec

Update js_scripts/index.js

Browse files
Files changed (1) hide show
  1. js_scripts/index.js +125 -150
js_scripts/index.js CHANGED
@@ -24,14 +24,25 @@
24
  document.head.appendChild(linkEl);
25
  }
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  // Generate a unique identifier for this widget instance.
28
  const instanceId = Math.random().toString(36).substr(2, 8);
29
 
30
  // Read configuration values from the JSON file.
31
  const gifUrl = config.gif_url;
32
  const plyUrl = config.ply_url;
33
-
34
- // Camera constraint parameters
35
  const minZoom = parseFloat(config.minZoom || "0");
36
  const maxZoom = parseFloat(config.maxZoom || "20");
37
  const minAngle = parseFloat(config.minAngle || "0");
@@ -145,16 +156,6 @@
145
  `;
146
  }
147
 
148
- // Global variables to track state
149
- let SPLAT = null;
150
- let cameraInstance = null;
151
- let controlsInstance = null;
152
- let rendererInstance = null;
153
- let sceneInstance = null;
154
- let animFrameId = null;
155
- let isViewerInitialized = false;
156
- let resizeHandler = null;
157
-
158
  // If a gif_url is provided, set the preview image.
159
  // Otherwise, hide the preview container, show the viewer immediately,
160
  // and hide the "close" button since there's no preview to return to.
@@ -173,7 +174,6 @@
173
 
174
  // --- Button Event Handlers ---
175
  if (gifUrl) {
176
- // When GIF is clicked, hide it and initialize the viewer
177
  gifPreview.addEventListener('click', function() {
178
  console.log("GIF preview clicked, showing 3D viewer");
179
  gifPreview.style.display = 'none';
@@ -182,7 +182,46 @@
182
  });
183
  }
184
 
185
- // Close button handler - hide viewer and show GIF
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  closeBtn.addEventListener('click', function() {
187
  console.log("Close button clicked");
188
 
@@ -192,13 +231,12 @@
192
  document.exitFullscreen();
193
  }
194
  }
195
-
196
  if (widgetContainer.classList.contains('fake-fullscreen')) {
197
  widgetContainer.classList.remove('fake-fullscreen');
198
  fullscreenToggle.textContent = '⇱';
199
  }
200
 
201
- // Clean up the viewer resources
202
  cleanupViewer();
203
 
204
  // Hide viewer and show GIF
@@ -206,10 +244,8 @@
206
  gifPreview.style.display = 'block';
207
  });
208
 
209
- // Fullscreen toggle handler
210
  fullscreenToggle.addEventListener('click', function() {
211
  if (isIOS) {
212
- // iOS doesn't support proper fullscreen, so we use CSS
213
  if (!widgetContainer.classList.contains('fake-fullscreen')) {
214
  widgetContainer.classList.add('fake-fullscreen');
215
  } else {
@@ -218,7 +254,6 @@
218
  }
219
  fullscreenToggle.textContent = widgetContainer.classList.contains('fake-fullscreen') ? '⇲' : '⇱';
220
  } else {
221
- // Standard fullscreen API for other browsers
222
  if (!document.fullscreenElement) {
223
  if (widgetContainer.requestFullscreen) {
224
  widgetContainer.requestFullscreen();
@@ -252,57 +287,28 @@
252
  }
253
  });
254
 
255
- // Help toggle button
256
  helpToggle.addEventListener('click', function(e) {
257
  e.stopPropagation();
258
  menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
259
  });
260
 
261
- // Reset camera button
262
- resetCameraBtn.addEventListener('click', function() {
263
- console.log("Reset camera button clicked");
264
- resetCamera();
265
- });
266
-
267
- // Handle Escape key for fullscreen exit
268
- document.addEventListener('keydown', function(e) {
269
- if (e.key === 'Escape' || e.key === 'Esc') {
270
- let wasFullscreen = false;
271
- if (document.fullscreenElement === widgetContainer) {
272
- wasFullscreen = true;
273
- if (document.exitFullscreen) {
274
- document.exitFullscreen();
275
- }
276
- }
277
- if (widgetContainer.classList.contains('fake-fullscreen')) {
278
- wasFullscreen = true;
279
- widgetContainer.classList.remove('fake-fullscreen');
280
- fullscreenToggle.textContent = '⇱';
281
- }
282
- if (wasFullscreen) {
283
- resetCamera();
284
- }
285
- }
286
- });
287
-
288
  // --- Camera Reset Function ---
289
  function resetCamera() {
290
  console.log("Resetting camera to initial position");
291
-
292
- if (!SPLAT || !cameraInstance || !controlsInstance || !sceneInstance) {
293
- console.log("Cannot reset camera - SPLAT not loaded or camera/controls not initialized");
294
  return;
295
  }
296
 
297
  try {
298
- // Dispose the current controls
 
 
 
299
  if (controlsInstance && typeof controlsInstance.dispose === 'function') {
300
  controlsInstance.dispose();
301
  }
302
 
303
- // Reset camera to default position
304
- cameraInstance = new SPLAT.Camera();
305
-
306
  // Create new controls
307
  controlsInstance = new SPLAT.OrbitControls(
308
  cameraInstance,
@@ -329,77 +335,41 @@
329
  // Update controls
330
  controlsInstance.update();
331
 
332
- console.log("Camera reset complete");
333
-
334
  } catch (error) {
335
  console.error("Error resetting camera:", error);
336
  }
337
  }
338
 
339
- // Clean up all viewer resources
340
- function cleanupViewer() {
341
- console.log("Cleaning up viewer resources...");
342
-
343
- // Stop animation frame
344
- if (animFrameId) {
345
- cancelAnimationFrame(animFrameId);
346
- animFrameId = null;
347
- }
348
-
349
- // Remove resize handler
350
- if (resizeHandler) {
351
- window.removeEventListener('resize', resizeHandler);
352
- resizeHandler = null;
353
- }
354
-
355
- // Dispose controls
356
- if (controlsInstance && typeof controlsInstance.dispose === 'function') {
357
- try {
358
- controlsInstance.dispose();
359
- } catch (e) {
360
- console.warn("Error disposing controls:", e);
361
  }
362
- controlsInstance = null;
363
- }
364
-
365
- // Dispose renderer
366
- if (rendererInstance && typeof rendererInstance.dispose === 'function') {
367
- try {
368
- rendererInstance.dispose();
369
- } catch (e) {
370
- console.warn("Error disposing renderer:", e);
371
  }
372
- rendererInstance = null;
373
- }
374
-
375
- // Clear scene
376
- sceneInstance = null;
377
-
378
- // Clear camera
379
- cameraInstance = null;
380
-
381
- // Reset WebGL context
382
- if (canvas) {
383
- const ctx = canvas.getContext('webgl') || canvas.getContext('webgl2');
384
- if (ctx && ctx.getExtension('WEBGL_lose_context')) {
385
- try {
386
- ctx.getExtension('WEBGL_lose_context').loseContext();
387
- } catch (e) {
388
- console.warn("Error releasing WebGL context:", e);
389
- }
390
  }
391
  }
392
-
393
- // Mark viewer as not initialized
394
- isViewerInitialized = false;
395
-
396
- console.log("Viewer cleanup complete");
397
- }
398
 
399
  // --- Initialize the 3D PLY Viewer ---
400
  async function initializeViewer() {
401
  // Skip initialization if already initialized
402
- if (isViewerInitialized) {
403
  console.log("Viewer already initialized, skipping initialization");
404
  return;
405
  }
@@ -413,26 +383,26 @@
413
  if (!SPLAT) {
414
  console.log("Loading SPLAT library...");
415
  SPLAT = await import("https://bilca-gsplat-library.static.hf.space/dist/index.js");
416
- console.log("SPLAT library loaded successfully:", SPLAT);
417
  }
418
 
419
- // Create renderer
420
  console.log("Creating WebGL renderer...");
421
  rendererInstance = new SPLAT.WebGLRenderer(canvas);
422
- console.log("Renderer created:", rendererInstance);
423
 
424
- // Create scene
425
  console.log("Creating scene...");
426
  sceneInstance = new SPLAT.Scene();
427
- console.log("Scene created:", sceneInstance);
428
 
429
  // Create camera
430
  console.log("Creating camera...");
431
  cameraInstance = new SPLAT.Camera();
432
- console.log("Camera created:", cameraInstance);
 
 
 
433
 
434
  // Set canvas background
435
- console.log("Setting canvas background...");
436
  canvas.style.background = config.canvas_background || "#FEFEFD";
437
 
438
  // Create controls
@@ -460,73 +430,78 @@
460
  controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
461
 
462
  controlsInstance.update();
463
- console.log("Orbit controls created and configured:", controlsInstance);
464
 
465
  // Handle resize
466
- console.log("Setting up resize handler...");
467
  const handleResize = () => {
468
  if (rendererInstance) {
469
  rendererInstance.setSize(canvas.clientWidth, canvas.clientHeight);
470
  }
471
  };
472
 
473
- // Initial resize
474
  handleResize();
475
-
476
- // Add resize event listener
477
  resizeHandler = handleResize;
478
- window.addEventListener('resize', resizeHandler);
479
- console.log("Resize handler set up");
 
 
480
 
481
- // Load PLY file
482
- console.log("Loading PLY file:", plyUrl);
483
  try {
484
- await SPLAT.PLYLoader.LoadAsync(
 
 
485
  plyUrl,
486
  sceneInstance,
487
  (progress) => {
488
  progressIndicator.value = progress * 100;
489
- console.log(`Loading progress: ${Math.round(progress * 100)}%`);
490
  }
491
  );
492
- console.log("PLY file loaded successfully");
493
- progressDialog.style.display = 'none';
494
-
495
- // Check if scene has content
496
- console.log("Checking scene content...");
497
- if (sceneInstance && sceneInstance.getChildren) {
498
- const children = sceneInstance.getChildren();
499
- console.log("Scene children:", children);
500
- console.log("Scene children count:", children ? children.length : 0);
501
- }
502
-
503
  } catch (error) {
504
  console.error("Error loading PLY file:", error);
505
  progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`;
506
  return;
507
  }
508
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  // Start animation loop
510
  console.log("Starting animation loop...");
511
- const animate = () => {
512
- if (controlsInstance && sceneInstance && cameraInstance && rendererInstance) {
513
- controlsInstance.update();
514
- rendererInstance.render(sceneInstance, cameraInstance);
515
- animFrameId = requestAnimationFrame(animate);
516
  }
517
- };
 
 
 
 
518
 
 
519
  animFrameId = requestAnimationFrame(animate);
520
- console.log("Animation loop started with frame ID:", animFrameId);
521
 
522
  // Mark viewer as initialized
523
- isViewerInitialized = true;
524
  console.log("PLY viewer initialization complete");
525
 
526
  } catch (error) {
527
  console.error("Error initializing PLY viewer:", error);
528
  progressDialog.innerHTML = `<p style="color: red">Error initializing viewer: ${error.message}</p>`;
529
- cleanupViewer();
530
  }
531
  }
532
  })();
 
24
  document.head.appendChild(linkEl);
25
  }
26
 
27
+ // --- Outer scope variables for camera state ---
28
+ let cameraInstance = null;
29
+ let controlsInstance = null;
30
+ let initialCameraPosition = null;
31
+ let initialCameraRotation = null;
32
+ let rendererInstance = null;
33
+ let sceneInstance = null;
34
+ let SPLAT = null; // We'll save the imported SPLAT module here.
35
+ let animFrameId = null;
36
+ let resizeHandler = null;
37
+ let hasInitializedViewer = false;
38
+ let loadedPlyModel = null;
39
+
40
  // Generate a unique identifier for this widget instance.
41
  const instanceId = Math.random().toString(36).substr(2, 8);
42
 
43
  // Read configuration values from the JSON file.
44
  const gifUrl = config.gif_url;
45
  const plyUrl = config.ply_url;
 
 
46
  const minZoom = parseFloat(config.minZoom || "0");
47
  const maxZoom = parseFloat(config.maxZoom || "20");
48
  const minAngle = parseFloat(config.minAngle || "0");
 
156
  `;
157
  }
158
 
 
 
 
 
 
 
 
 
 
 
159
  // If a gif_url is provided, set the preview image.
160
  // Otherwise, hide the preview container, show the viewer immediately,
161
  // and hide the "close" button since there's no preview to return to.
 
174
 
175
  // --- Button Event Handlers ---
176
  if (gifUrl) {
 
177
  gifPreview.addEventListener('click', function() {
178
  console.log("GIF preview clicked, showing 3D viewer");
179
  gifPreview.style.display = 'none';
 
182
  });
183
  }
184
 
185
+ // Function to clean up the viewer
186
+ function cleanupViewer() {
187
+ console.log("Cleaning up viewer resources...");
188
+
189
+ // Stop animation frame
190
+ if (animFrameId) {
191
+ cancelAnimationFrame(animFrameId);
192
+ animFrameId = null;
193
+ }
194
+
195
+ // Remove resize event handler
196
+ if (resizeHandler) {
197
+ window.removeEventListener("resize", resizeHandler);
198
+ resizeHandler = null;
199
+ }
200
+
201
+ // Dispose controls
202
+ if (controlsInstance && typeof controlsInstance.dispose === 'function') {
203
+ try {
204
+ controlsInstance.dispose();
205
+ controlsInstance = null;
206
+ } catch (e) {
207
+ console.error("Error disposing controls:", e);
208
+ }
209
+ }
210
+
211
+ // Clear references but preserve loaded objects
212
+ // We don't fully dispose the renderer to preserve WebGL context
213
+ rendererInstance = null;
214
+ sceneInstance = null;
215
+ cameraInstance = null;
216
+ initialCameraPosition = null;
217
+ initialCameraRotation = null;
218
+
219
+ // Mark viewer as not initialized
220
+ hasInitializedViewer = false;
221
+
222
+ console.log("Viewer cleanup complete - renderer and PLY model data preserved for fast reload");
223
+ }
224
+
225
  closeBtn.addEventListener('click', function() {
226
  console.log("Close button clicked");
227
 
 
231
  document.exitFullscreen();
232
  }
233
  }
 
234
  if (widgetContainer.classList.contains('fake-fullscreen')) {
235
  widgetContainer.classList.remove('fake-fullscreen');
236
  fullscreenToggle.textContent = '⇱';
237
  }
238
 
239
+ // Clean up the viewer
240
  cleanupViewer();
241
 
242
  // Hide viewer and show GIF
 
244
  gifPreview.style.display = 'block';
245
  });
246
 
 
247
  fullscreenToggle.addEventListener('click', function() {
248
  if (isIOS) {
 
249
  if (!widgetContainer.classList.contains('fake-fullscreen')) {
250
  widgetContainer.classList.add('fake-fullscreen');
251
  } else {
 
254
  }
255
  fullscreenToggle.textContent = widgetContainer.classList.contains('fake-fullscreen') ? '⇲' : '⇱';
256
  } else {
 
257
  if (!document.fullscreenElement) {
258
  if (widgetContainer.requestFullscreen) {
259
  widgetContainer.requestFullscreen();
 
287
  }
288
  });
289
 
 
290
  helpToggle.addEventListener('click', function(e) {
291
  e.stopPropagation();
292
  menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
293
  });
294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  // --- Camera Reset Function ---
296
  function resetCamera() {
297
  console.log("Resetting camera to initial position");
298
+ if (!cameraInstance || !SPLAT) {
299
+ console.log("Cannot reset camera - missing camera instance");
 
300
  return;
301
  }
302
 
303
  try {
304
+ // Create a new camera to reset its position
305
+ cameraInstance = new SPLAT.Camera();
306
+
307
+ // Dispose previous controls if they exist
308
  if (controlsInstance && typeof controlsInstance.dispose === 'function') {
309
  controlsInstance.dispose();
310
  }
311
 
 
 
 
312
  // Create new controls
313
  controlsInstance = new SPLAT.OrbitControls(
314
  cameraInstance,
 
335
  // Update controls
336
  controlsInstance.update();
337
 
338
+ console.log("Camera reset successful");
 
339
  } catch (error) {
340
  console.error("Error resetting camera:", error);
341
  }
342
  }
343
 
344
+ resetCameraBtn.addEventListener('click', async function() {
345
+ console.log("Reset camera button clicked");
346
+ resetCamera();
347
+ });
348
+
349
+ document.addEventListener('keydown', function(e) {
350
+ if (e.key === 'Escape' || e.key === 'Esc') {
351
+ let wasFullscreen = false;
352
+ if (document.fullscreenElement === widgetContainer) {
353
+ wasFullscreen = true;
354
+ if (document.exitFullscreen) {
355
+ document.exitFullscreen();
356
+ }
 
 
 
 
 
 
 
 
 
357
  }
358
+ if (widgetContainer.classList.contains('fake-fullscreen')) {
359
+ wasFullscreen = true;
360
+ widgetContainer.classList.remove('fake-fullscreen');
361
+ fullscreenToggle.textContent = '⇱';
 
 
 
 
 
362
  }
363
+ if (wasFullscreen) {
364
+ resetCamera();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  }
366
  }
367
+ });
 
 
 
 
 
368
 
369
  // --- Initialize the 3D PLY Viewer ---
370
  async function initializeViewer() {
371
  // Skip initialization if already initialized
372
+ if (hasInitializedViewer) {
373
  console.log("Viewer already initialized, skipping initialization");
374
  return;
375
  }
 
383
  if (!SPLAT) {
384
  console.log("Loading SPLAT library...");
385
  SPLAT = await import("https://bilca-gsplat-library.static.hf.space/dist/index.js");
386
+ console.log("SPLAT library loaded successfully");
387
  }
388
 
389
+ // Create new renderer or clear existing one
390
  console.log("Creating WebGL renderer...");
391
  rendererInstance = new SPLAT.WebGLRenderer(canvas);
 
392
 
393
+ // Create new scene
394
  console.log("Creating scene...");
395
  sceneInstance = new SPLAT.Scene();
 
396
 
397
  // Create camera
398
  console.log("Creating camera...");
399
  cameraInstance = new SPLAT.Camera();
400
+
401
+ // Store initial camera state
402
+ initialCameraPosition = cameraInstance.position.clone();
403
+ initialCameraRotation = cameraInstance.rotation.clone();
404
 
405
  // Set canvas background
 
406
  canvas.style.background = config.canvas_background || "#FEFEFD";
407
 
408
  // Create controls
 
430
  controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
431
 
432
  controlsInstance.update();
 
433
 
434
  // Handle resize
 
435
  const handleResize = () => {
436
  if (rendererInstance) {
437
  rendererInstance.setSize(canvas.clientWidth, canvas.clientHeight);
438
  }
439
  };
440
 
441
+ // Set up resize handler
442
  handleResize();
 
 
443
  resizeHandler = handleResize;
444
+ window.addEventListener("resize", resizeHandler);
445
+
446
+ // Load PLY file if not already loaded or reload it
447
+ console.log("Loading or reusing PLY model...");
448
 
 
 
449
  try {
450
+ // Always load fresh PLY data
451
+ console.log(`Loading PLY file from ${plyUrl}`);
452
+ loadedPlyModel = await SPLAT.PLYLoader.LoadAsync(
453
  plyUrl,
454
  sceneInstance,
455
  (progress) => {
456
  progressIndicator.value = progress * 100;
 
457
  }
458
  );
459
+ console.log("PLY file loaded successfully, model:", loadedPlyModel);
 
 
 
 
 
 
 
 
 
 
460
  } catch (error) {
461
  console.error("Error loading PLY file:", error);
462
  progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`;
463
  return;
464
  }
465
 
466
+ // Hide progress dialog
467
+ progressDialog.style.display = 'none';
468
+
469
+ // Debug: Check scene content
470
+ console.log("Scene contents:", sceneInstance);
471
+ if (sceneInstance && typeof sceneInstance.getChildren === 'function') {
472
+ const children = sceneInstance.getChildren();
473
+ console.log("Scene children:", children);
474
+ if (children && children.length === 0) {
475
+ console.warn("Warning: Scene has no children!");
476
+ }
477
+ } else {
478
+ console.log("Cannot check scene children - getChildren method not available");
479
+ }
480
+
481
  // Start animation loop
482
  console.log("Starting animation loop...");
483
+ function animate() {
484
+ if (!controlsInstance || !sceneInstance || !cameraInstance || !rendererInstance) {
485
+ console.log("Animation stopped - resources cleaned up");
486
+ return;
 
487
  }
488
+
489
+ controlsInstance.update();
490
+ rendererInstance.render(sceneInstance, cameraInstance);
491
+ animFrameId = requestAnimationFrame(animate);
492
+ }
493
 
494
+ // Start the animation
495
  animFrameId = requestAnimationFrame(animate);
 
496
 
497
  // Mark viewer as initialized
498
+ hasInitializedViewer = true;
499
  console.log("PLY viewer initialization complete");
500
 
501
  } catch (error) {
502
  console.error("Error initializing PLY viewer:", error);
503
  progressDialog.innerHTML = `<p style="color: red">Error initializing viewer: ${error.message}</p>`;
504
+ hasInitializedViewer = false;
505
  }
506
  }
507
  })();