Spaces:
Running
Running
Update index_3.js
Browse files- 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 |
-
//
|
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
|
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
|
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
|
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 -
|
64 |
#ply-widget-container-${instanceId} {
|
65 |
position: relative;
|
66 |
width: 100%;
|
67 |
height: 100%;
|
68 |
}
|
69 |
-
/*
|
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
|
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
|
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 |
-
/*
|
159 |
-
#close-btn-${instanceId} {
|
160 |
-
|
161 |
-
|
162 |
-
|
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
|
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 |
-
//
|
|
|
214 |
function updateWidgetSize() {
|
215 |
-
const
|
216 |
-
//
|
217 |
-
widgetContainer.style.height =
|
218 |
}
|
219 |
updateWidgetSize();
|
220 |
-
window.
|
|
|
|
|
|
|
|
|
|
|
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
|
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 |
-
//
|
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 |
-
|
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 |
-
|
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
|
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,
|
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 |
-
|
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 |
-
|
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,
|
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 |
})();
|