bilca commited on
Commit
e2471b8
·
verified ·
1 Parent(s): c22c892

Update index_3.js

Browse files
Files changed (1) hide show
  1. index_3.js +51 -119
index_3.js CHANGED
@@ -21,8 +21,7 @@
21
  let controlsInstance = null;
22
  let initialCameraPosition = null;
23
  let initialCameraRotation = null;
24
- // We'll save the imported SPLAT module here so we can reuse it later.
25
- let SPLAT = null;
26
 
27
  // Generate a unique identifier for this widget instance.
28
  const instanceId = Math.random().toString(36).substr(2, 8);
@@ -37,50 +36,43 @@
37
  const minAzimuth = config.minAzimuth !== undefined ? parseFloat(config.minAzimuth) : -Infinity;
38
  const maxAzimuth = config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) : Infinity;
39
 
40
- // Read initial orbit parameters for desktop.
41
  const initAlphaDesktop = config.initAlpha !== undefined ? parseFloat(config.initAlpha) : 0.5;
42
  const initBetaDesktop = config.initBeta !== undefined ? parseFloat(config.initBeta) : 0.5;
43
  const initRadiusDesktop = config.initRadius !== undefined ? parseFloat(config.initRadius) : 5;
44
- // Read initial orbit parameters for phone.
45
  const initAlphaPhone = config.initAlphaPhone !== undefined ? parseFloat(config.initAlphaPhone) : initAlphaDesktop;
46
  const initBetaPhone = config.initBetaPhone !== undefined ? parseFloat(config.initBetaPhone) : initBetaDesktop;
47
  const initRadiusPhone = config.initRadiusPhone !== undefined ? parseFloat(config.initRadiusPhone) : initRadiusDesktop;
48
 
49
- // Detect if the device is iOS.
50
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
51
- // Also detect Android devices.
52
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
53
-
54
- // Choose the appropriate initial orbit values based on device type.
55
  const chosenInitAlpha = isMobile ? initAlphaPhone : initAlphaDesktop;
56
  const chosenInitBeta = isMobile ? initBetaPhone : initBetaDesktop;
57
  const chosenInitRadius = isMobile ? initRadiusPhone : initRadiusDesktop;
58
 
59
- // Inject CSS styles into the document head.
60
- // The container will be set to fill 100% of the parent block; its height will be explicitly updated in the script.
61
  const styleEl = document.createElement('style');
62
  styleEl.textContent = `
63
- /* Widget container styling - fill the entire parent block */
64
  #ply-widget-container-${instanceId} {
65
  position: relative;
66
  width: 100%;
67
  height: 100%;
68
  }
69
- /* When in fake fullscreen mode (iOS fallback) */
70
  #ply-widget-container-${instanceId}.fake-fullscreen {
71
  position: fixed !important;
72
  top: 0 !important;
73
  left: 0 !important;
74
  width: 100vw !important;
75
  height: 100vh !important;
76
- padding-bottom: 0 !important;
77
  z-index: 9999 !important;
78
  }
79
  /* GIF Preview styling */
80
  #gif-preview-container-${instanceId} {
81
  position: absolute;
82
- top: 0;
83
- left: 0;
84
  width: 100%;
85
  height: 100%;
86
  border: 1px solid #474558;
@@ -97,8 +89,7 @@
97
  #viewer-container-${instanceId} {
98
  display: none;
99
  position: absolute;
100
- top: 0;
101
- left: 0;
102
  width: 100%;
103
  height: 100%;
104
  background: #FEFEFD;
@@ -113,25 +104,22 @@
113
  height: 100%;
114
  display: block;
115
  }
116
- /* Progress dialog styling (as a centered div) */
117
  #progress-dialog-${instanceId} {
118
  position: absolute;
119
- top: 50%;
120
- left: 50%;
121
  transform: translate(-50%, -50%);
122
- border: none;
123
  background: rgba(255,255,255,0.9);
124
  padding: 20px;
125
  border-radius: 5px;
126
  z-index: 1000;
127
  display: none;
128
  }
129
- /* Menu (instructions) content styling */
130
  #menu-content-${instanceId} {
131
  display: none;
132
  position: absolute;
133
- top: 70px;
134
- right: 15px;
135
  background: #FFFEF4;
136
  border: 1px solid #474558;
137
  border-radius: 5px;
@@ -155,45 +143,22 @@
155
  align-items: center;
156
  justify-content: center;
157
  }
158
- /* Positions: Close at top-left, fullscreen at top-right, help and reset buttons */
159
- #close-btn-${instanceId} {
160
- top: 17px;
161
- left: 15px;
162
- font-family: sans-serif;
163
- }
164
- #fullscreen-toggle-${instanceId} {
165
- top: 17px;
166
- right: 15px;
167
- }
168
- #help-toggle-${instanceId} {
169
- top: 17px;
170
- right: 70px;
171
- font-size: 22px;
172
- }
173
- #reset-camera-btn-${instanceId} {
174
- top: 17px;
175
- right: 123px;
176
- font-size: 22px;
177
- line-height: normal;
178
- padding: 0;
179
- }
180
- /* Adjust the reset icon position: move it slightly upward */
181
- .reset-icon {
182
- display: inline-block;
183
- transform: translateY(-3px);
184
- }
185
  `;
186
  document.head.appendChild(styleEl);
187
 
188
- // Create the widget container and set its inner HTML.
189
  const widgetContainer = document.createElement('div');
190
  widgetContainer.id = 'ply-widget-container-' + instanceId;
191
  widgetContainer.innerHTML = `
192
- <!-- GIF Preview Container -->
193
  <div id="gif-preview-container-${instanceId}">
194
  <img id="preview-image-${instanceId}" alt="Preview" crossorigin="anonymous">
195
  </div>
196
- <!-- Viewer Container -->
197
  <div id="viewer-container-${instanceId}">
198
  <canvas id="canvas-${instanceId}"></canvas>
199
  <div id="progress-dialog-${instanceId}">
@@ -209,15 +174,21 @@
209
  </div>
210
  `;
211
  scriptTag.parentNode.appendChild(widgetContainer);
212
-
213
- // Ensure the widget container takes the full height of its parent.
 
214
  function updateWidgetSize() {
215
- const parentHeight = scriptTag.parentNode.offsetHeight;
216
- // If the parent height is defined, use it; otherwise fall back to a default height.
217
- widgetContainer.style.height = (parentHeight && parentHeight > 0) ? parentHeight + "px" : "300px";
218
  }
219
  updateWidgetSize();
220
- window.addEventListener("resize", updateWidgetSize);
 
 
 
 
 
221
 
222
  // Grab element references.
223
  const gifPreview = document.getElementById('gif-preview-container-' + instanceId);
@@ -232,7 +203,7 @@
232
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
233
  const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
234
 
235
- // Set help instructions based on device type.
236
  if (isMobile) {
237
  menuContent.innerHTML = `
238
  - Pour vous déplacer, glissez deux doigts sur l'écran.<br>
@@ -247,16 +218,13 @@
247
  `;
248
  }
249
 
250
- // If a gif_url is provided, set the preview image.
251
- // Otherwise, hide the preview container, show the viewer immediately,
252
- // and hide the "close" button since there's no preview to return to.
253
  if (gifUrl) {
254
  previewImage.src = gifUrl;
255
  } else {
256
  gifPreview.style.display = 'none';
257
  viewerContainer.style.display = 'block';
258
  closeBtn.style.display = 'none';
259
- // Start the viewer immediately.
260
  initializeViewer();
261
  }
262
 
@@ -270,15 +238,12 @@
270
  }
271
 
272
  closeBtn.addEventListener('click', function() {
273
- if (document.fullscreenElement === widgetContainer) {
274
- if (document.exitFullscreen) {
275
- document.exitFullscreen();
276
- }
277
  }
278
  if (widgetContainer.classList.contains('fake-fullscreen')) {
279
  widgetContainer.classList.remove('fake-fullscreen');
280
  fullscreenToggle.textContent = '⇱';
281
- // For fake-fullscreen, reset camera immediately.
282
  resetCamera();
283
  }
284
  viewerContainer.style.display = 'none';
@@ -291,7 +256,6 @@
291
  widgetContainer.classList.add('fake-fullscreen');
292
  } else {
293
  widgetContainer.classList.remove('fake-fullscreen');
294
- // Reset camera when exiting fake-fullscreen.
295
  resetCamera();
296
  }
297
  fullscreenToggle.textContent = widgetContainer.classList.contains('fake-fullscreen') ? '⇲' : '⇱';
@@ -306,15 +270,12 @@
306
  } else if (widgetContainer.msRequestFullscreen) {
307
  widgetContainer.msRequestFullscreen();
308
  }
309
- } else {
310
- if (document.exitFullscreen) {
311
- document.exitFullscreen();
312
- }
313
  }
314
  }
315
  });
316
 
317
- // Listen for native fullscreen changes. When exiting fullscreen, reset camera.
318
  document.addEventListener('fullscreenchange', function() {
319
  if (document.fullscreenElement === widgetContainer) {
320
  fullscreenToggle.textContent = '⇲';
@@ -331,30 +292,22 @@
331
 
332
  // --- Camera Reset Function ---
333
  function resetCamera() {
334
- console.log("Resetting camera to initial position.");
335
  if (cameraInstance && initialCameraPosition && initialCameraRotation) {
336
- // Reset camera position and rotation.
337
  cameraInstance.position = initialCameraPosition.clone();
338
  cameraInstance.rotation = initialCameraRotation.clone();
339
  if (typeof cameraInstance.update === 'function') {
340
  cameraInstance.update();
341
  }
342
- // Dispose of the current controls.
343
  if (controlsInstance && typeof controlsInstance.dispose === 'function') {
344
  controlsInstance.dispose();
345
  }
346
- // Re-create OrbitControls with the original chosen parameters.
347
  controlsInstance = new SPLAT.OrbitControls(
348
  cameraInstance,
349
  canvas,
350
- 0.5, // default alpha fallback
351
- 0.5, // default beta fallback
352
- 5, // default radius fallback
353
- true,
354
  new SPLAT.Vector3(),
355
- chosenInitAlpha,
356
- chosenInitBeta,
357
- chosenInitRadius
358
  );
359
  controlsInstance.maxZoom = maxZoom;
360
  controlsInstance.minZoom = minZoom;
@@ -367,21 +320,17 @@
367
  }
368
  }
369
 
370
- // Modified reset button now calls the resetCamera function.
371
- resetCameraBtn.addEventListener('click', async function() {
372
  console.log("Reset camera button clicked.");
373
  resetCamera();
374
  });
375
 
376
- // --- Add keydown listener to exit fullscreen with Esc (or Echap) ---
377
  document.addEventListener('keydown', function(e) {
378
  if (e.key === 'Escape' || e.key === 'Esc') {
379
  let wasFullscreen = false;
380
- if (document.fullscreenElement === widgetContainer) {
381
  wasFullscreen = true;
382
- if (document.exitFullscreen) {
383
- document.exitFullscreen();
384
- }
385
  }
386
  if (widgetContainer.classList.contains('fake-fullscreen')) {
387
  wasFullscreen = true;
@@ -396,34 +345,24 @@
396
 
397
  // --- Initialize the 3D PLY Viewer ---
398
  async function initializeViewer() {
399
- // Import SPLAT and store it globally.
400
  SPLAT = await import("https://bilca-gsplat-library.static.hf.space/dist/index.js");
401
  progressDialog.style.display = 'block';
402
  const renderer = new SPLAT.WebGLRenderer(canvas);
403
  const scene = new SPLAT.Scene();
404
-
405
- // Construct the camera.
406
  const camera = new SPLAT.Camera();
407
 
408
- // Construct OrbitControls with the chosen initial orbit parameters.
409
  controlsInstance = new SPLAT.OrbitControls(
410
  camera,
411
  canvas,
412
- 0.5, // default alpha fallback
413
- 0.5, // default beta fallback
414
- 5, // default radius fallback
415
- true,
416
  new SPLAT.Vector3(),
417
- chosenInitAlpha,
418
- chosenInitBeta,
419
- chosenInitRadius
420
  );
421
-
422
  cameraInstance = camera;
423
- // Save the initial camera state (after controls are created)
424
  initialCameraPosition = camera.position.clone();
425
  initialCameraRotation = camera.rotation.clone();
426
-
427
  canvas.style.background = "#FEFEFD";
428
  controlsInstance.maxZoom = maxZoom;
429
  controlsInstance.minZoom = minZoom;
@@ -432,38 +371,31 @@
432
  controlsInstance.minAzimuth = minAzimuth;
433
  controlsInstance.maxAzimuth = maxAzimuth;
434
  controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
435
-
436
  controlsInstance.update();
437
-
438
  try {
439
  await SPLAT.PLYLoader.LoadAsync(
440
  plyUrl,
441
  scene,
442
- (progress) => {
443
- progressIndicator.value = progress * 100;
444
- }
445
  );
446
  progressDialog.style.display = 'none';
447
  } catch (error) {
448
  console.error("Error loading PLY file:", error);
449
  progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`;
450
  }
451
-
452
  const frame = () => {
453
  controlsInstance.update();
454
  renderer.render(scene, camera);
455
  requestAnimationFrame(frame);
456
  };
457
-
458
  const handleResize = () => {
459
  renderer.setSize(canvas.clientWidth, canvas.clientHeight);
460
  };
461
-
462
  handleResize();
463
  window.addEventListener("resize", handleResize);
464
  requestAnimationFrame(frame);
465
  }
466
-
467
- // If a gif_url exists, the viewer is started on preview click;
468
- // otherwise, it was already started above.
469
  })();
 
21
  let controlsInstance = null;
22
  let initialCameraPosition = null;
23
  let initialCameraRotation = null;
24
+ let SPLAT = null; // Will store the imported SPLAT module
 
25
 
26
  // Generate a unique identifier for this widget instance.
27
  const instanceId = Math.random().toString(36).substr(2, 8);
 
36
  const minAzimuth = config.minAzimuth !== undefined ? parseFloat(config.minAzimuth) : -Infinity;
37
  const maxAzimuth = config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) : Infinity;
38
 
39
+ // Read initial orbit parameters.
40
  const initAlphaDesktop = config.initAlpha !== undefined ? parseFloat(config.initAlpha) : 0.5;
41
  const initBetaDesktop = config.initBeta !== undefined ? parseFloat(config.initBeta) : 0.5;
42
  const initRadiusDesktop = config.initRadius !== undefined ? parseFloat(config.initRadius) : 5;
 
43
  const initAlphaPhone = config.initAlphaPhone !== undefined ? parseFloat(config.initAlphaPhone) : initAlphaDesktop;
44
  const initBetaPhone = config.initBetaPhone !== undefined ? parseFloat(config.initBetaPhone) : initBetaDesktop;
45
  const initRadiusPhone = config.initRadiusPhone !== undefined ? parseFloat(config.initRadiusPhone) : initRadiusDesktop;
46
 
47
+ // Detect device type.
48
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
 
49
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
 
 
50
  const chosenInitAlpha = isMobile ? initAlphaPhone : initAlphaDesktop;
51
  const chosenInitBeta = isMobile ? initBetaPhone : initBetaDesktop;
52
  const chosenInitRadius = isMobile ? initRadiusPhone : initRadiusDesktop;
53
 
54
+ // Inject CSS styles.
 
55
  const styleEl = document.createElement('style');
56
  styleEl.textContent = `
57
+ /* Widget container styling - height/width set to 100% (we will adjust height in script) */
58
  #ply-widget-container-${instanceId} {
59
  position: relative;
60
  width: 100%;
61
  height: 100%;
62
  }
63
+ /* Fake fullscreen (for iOS) */
64
  #ply-widget-container-${instanceId}.fake-fullscreen {
65
  position: fixed !important;
66
  top: 0 !important;
67
  left: 0 !important;
68
  width: 100vw !important;
69
  height: 100vh !important;
 
70
  z-index: 9999 !important;
71
  }
72
  /* GIF Preview styling */
73
  #gif-preview-container-${instanceId} {
74
  position: absolute;
75
+ top: 0; left: 0;
 
76
  width: 100%;
77
  height: 100%;
78
  border: 1px solid #474558;
 
89
  #viewer-container-${instanceId} {
90
  display: none;
91
  position: absolute;
92
+ top: 0; left: 0;
 
93
  width: 100%;
94
  height: 100%;
95
  background: #FEFEFD;
 
104
  height: 100%;
105
  display: block;
106
  }
107
+ /* Progress dialog styling */
108
  #progress-dialog-${instanceId} {
109
  position: absolute;
110
+ top: 50%; left: 50%;
 
111
  transform: translate(-50%, -50%);
 
112
  background: rgba(255,255,255,0.9);
113
  padding: 20px;
114
  border-radius: 5px;
115
  z-index: 1000;
116
  display: none;
117
  }
118
+ /* Menu content styling */
119
  #menu-content-${instanceId} {
120
  display: none;
121
  position: absolute;
122
+ top: 70px; right: 15px;
 
123
  background: #FFFEF4;
124
  border: 1px solid #474558;
125
  border-radius: 5px;
 
143
  align-items: center;
144
  justify-content: center;
145
  }
146
+ /* Button positions */
147
+ #close-btn-${instanceId} { top: 17px; left: 15px; }
148
+ #fullscreen-toggle-${instanceId} { top: 17px; right: 15px; }
149
+ #help-toggle-${instanceId} { top: 17px; right: 70px; font-size: 22px; }
150
+ #reset-camera-btn-${instanceId} { top: 17px; right: 123px; font-size: 22px; }
151
+ .reset-icon { display: inline-block; transform: translateY(-3px); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  `;
153
  document.head.appendChild(styleEl);
154
 
155
+ // Create widget container and inner HTML.
156
  const widgetContainer = document.createElement('div');
157
  widgetContainer.id = 'ply-widget-container-' + instanceId;
158
  widgetContainer.innerHTML = `
 
159
  <div id="gif-preview-container-${instanceId}">
160
  <img id="preview-image-${instanceId}" alt="Preview" crossorigin="anonymous">
161
  </div>
 
162
  <div id="viewer-container-${instanceId}">
163
  <canvas id="canvas-${instanceId}"></canvas>
164
  <div id="progress-dialog-${instanceId}">
 
174
  </div>
175
  `;
176
  scriptTag.parentNode.appendChild(widgetContainer);
177
+
178
+ // Update widget container height based on its parent's height using ResizeObserver if available.
179
+ const parentElem = scriptTag.parentNode;
180
  function updateWidgetSize() {
181
+ const rect = parentElem.getBoundingClientRect();
182
+ // Set the widget's height to match the parent's height.
183
+ widgetContainer.style.height = rect.height ? rect.height + "px" : "300px";
184
  }
185
  updateWidgetSize();
186
+ if (window.ResizeObserver) {
187
+ const observer = new ResizeObserver(() => updateWidgetSize());
188
+ observer.observe(parentElem);
189
+ } else {
190
+ window.addEventListener("resize", updateWidgetSize);
191
+ }
192
 
193
  // Grab element references.
194
  const gifPreview = document.getElementById('gif-preview-container-' + instanceId);
 
203
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
204
  const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
205
 
206
+ // Set help instructions.
207
  if (isMobile) {
208
  menuContent.innerHTML = `
209
  - Pour vous déplacer, glissez deux doigts sur l'écran.<br>
 
218
  `;
219
  }
220
 
221
+ // Handle preview display.
 
 
222
  if (gifUrl) {
223
  previewImage.src = gifUrl;
224
  } else {
225
  gifPreview.style.display = 'none';
226
  viewerContainer.style.display = 'block';
227
  closeBtn.style.display = 'none';
 
228
  initializeViewer();
229
  }
230
 
 
238
  }
239
 
240
  closeBtn.addEventListener('click', function() {
241
+ if (document.fullscreenElement === widgetContainer && document.exitFullscreen) {
242
+ document.exitFullscreen();
 
 
243
  }
244
  if (widgetContainer.classList.contains('fake-fullscreen')) {
245
  widgetContainer.classList.remove('fake-fullscreen');
246
  fullscreenToggle.textContent = '⇱';
 
247
  resetCamera();
248
  }
249
  viewerContainer.style.display = 'none';
 
256
  widgetContainer.classList.add('fake-fullscreen');
257
  } else {
258
  widgetContainer.classList.remove('fake-fullscreen');
 
259
  resetCamera();
260
  }
261
  fullscreenToggle.textContent = widgetContainer.classList.contains('fake-fullscreen') ? '⇲' : '⇱';
 
270
  } else if (widgetContainer.msRequestFullscreen) {
271
  widgetContainer.msRequestFullscreen();
272
  }
273
+ } else if (document.exitFullscreen) {
274
+ document.exitFullscreen();
 
 
275
  }
276
  }
277
  });
278
 
 
279
  document.addEventListener('fullscreenchange', function() {
280
  if (document.fullscreenElement === widgetContainer) {
281
  fullscreenToggle.textContent = '⇲';
 
292
 
293
  // --- Camera Reset Function ---
294
  function resetCamera() {
295
+ console.log("Resetting camera.");
296
  if (cameraInstance && initialCameraPosition && initialCameraRotation) {
 
297
  cameraInstance.position = initialCameraPosition.clone();
298
  cameraInstance.rotation = initialCameraRotation.clone();
299
  if (typeof cameraInstance.update === 'function') {
300
  cameraInstance.update();
301
  }
 
302
  if (controlsInstance && typeof controlsInstance.dispose === 'function') {
303
  controlsInstance.dispose();
304
  }
 
305
  controlsInstance = new SPLAT.OrbitControls(
306
  cameraInstance,
307
  canvas,
308
+ 0.5, 0.5, 5, true,
 
 
 
309
  new SPLAT.Vector3(),
310
+ chosenInitAlpha, chosenInitBeta, chosenInitRadius
 
 
311
  );
312
  controlsInstance.maxZoom = maxZoom;
313
  controlsInstance.minZoom = minZoom;
 
320
  }
321
  }
322
 
323
+ resetCameraBtn.addEventListener('click', function() {
 
324
  console.log("Reset camera button clicked.");
325
  resetCamera();
326
  });
327
 
 
328
  document.addEventListener('keydown', function(e) {
329
  if (e.key === 'Escape' || e.key === 'Esc') {
330
  let wasFullscreen = false;
331
+ if (document.fullscreenElement === widgetContainer && document.exitFullscreen) {
332
  wasFullscreen = true;
333
+ document.exitFullscreen();
 
 
334
  }
335
  if (widgetContainer.classList.contains('fake-fullscreen')) {
336
  wasFullscreen = true;
 
345
 
346
  // --- Initialize the 3D PLY Viewer ---
347
  async function initializeViewer() {
 
348
  SPLAT = await import("https://bilca-gsplat-library.static.hf.space/dist/index.js");
349
  progressDialog.style.display = 'block';
350
  const renderer = new SPLAT.WebGLRenderer(canvas);
351
  const scene = new SPLAT.Scene();
 
 
352
  const camera = new SPLAT.Camera();
353
 
 
354
  controlsInstance = new SPLAT.OrbitControls(
355
  camera,
356
  canvas,
357
+ 0.5, 0.5, 5, true,
 
 
 
358
  new SPLAT.Vector3(),
359
+ chosenInitAlpha, chosenInitBeta, chosenInitRadius
 
 
360
  );
361
+
362
  cameraInstance = camera;
 
363
  initialCameraPosition = camera.position.clone();
364
  initialCameraRotation = camera.rotation.clone();
365
+
366
  canvas.style.background = "#FEFEFD";
367
  controlsInstance.maxZoom = maxZoom;
368
  controlsInstance.minZoom = minZoom;
 
371
  controlsInstance.minAzimuth = minAzimuth;
372
  controlsInstance.maxAzimuth = maxAzimuth;
373
  controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
 
374
  controlsInstance.update();
375
+
376
  try {
377
  await SPLAT.PLYLoader.LoadAsync(
378
  plyUrl,
379
  scene,
380
+ (progress) => { progressIndicator.value = progress * 100; }
 
 
381
  );
382
  progressDialog.style.display = 'none';
383
  } catch (error) {
384
  console.error("Error loading PLY file:", error);
385
  progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`;
386
  }
387
+
388
  const frame = () => {
389
  controlsInstance.update();
390
  renderer.render(scene, camera);
391
  requestAnimationFrame(frame);
392
  };
393
+
394
  const handleResize = () => {
395
  renderer.setSize(canvas.clientWidth, canvas.clientHeight);
396
  };
 
397
  handleResize();
398
  window.addEventListener("resize", handleResize);
399
  requestAnimationFrame(frame);
400
  }
 
 
 
401
  })();