Spaces:
Running
Running
Update index_2.js
Browse files- index_2.js +101 -86
index_2.js
CHANGED
@@ -21,52 +21,68 @@
|
|
21 |
let controlsInstance = null;
|
22 |
let initialCameraPosition = null;
|
23 |
let initialCameraRotation = null;
|
|
|
|
|
24 |
|
25 |
// Generate a unique identifier for this widget instance.
|
26 |
const instanceId = Math.random().toString(36).substr(2, 8);
|
27 |
|
28 |
// Read configuration values from the JSON file.
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
// Determine the aspect ratio.
|
39 |
-
|
40 |
if (config.aspect) {
|
41 |
if (config.aspect.indexOf(":") !== -1) {
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
if (!isNaN(w) && !isNaN(h) && w > 0) {
|
46 |
aspectPercent = (h / w * 100) + "%";
|
47 |
}
|
48 |
} else {
|
49 |
-
|
50 |
if (!isNaN(aspectValue) && aspectValue > 0) {
|
51 |
aspectPercent = (100 / aspectValue) + "%";
|
52 |
}
|
53 |
}
|
54 |
} else {
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
if (containerWidth > 0 && containerHeight > 0) {
|
59 |
aspectPercent = (containerHeight / containerWidth * 100) + "%";
|
60 |
}
|
61 |
}
|
62 |
|
63 |
-
// Detect if the device is iOS.
|
64 |
-
var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
65 |
-
// Also detect Android devices.
|
66 |
-
const isMobile = isIOS || /Android/i.test(navigator.userAgent);
|
67 |
-
|
68 |
// Inject CSS styles into the document head, scoped with the unique id.
|
69 |
-
|
70 |
styleEl.textContent = `
|
71 |
/* Widget container styling */
|
72 |
#ply-widget-container-${instanceId} {
|
@@ -192,7 +208,7 @@
|
|
192 |
document.head.appendChild(styleEl);
|
193 |
|
194 |
// Create the widget container and set its inner HTML.
|
195 |
-
|
196 |
widgetContainer.id = 'ply-widget-container-' + instanceId;
|
197 |
widgetContainer.innerHTML = `
|
198 |
<!-- GIF Preview Container -->
|
@@ -217,17 +233,17 @@
|
|
217 |
scriptTag.parentNode.appendChild(widgetContainer);
|
218 |
|
219 |
// Grab element references.
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
|
232 |
// Set help instructions based on device type.
|
233 |
if (isMobile) {
|
@@ -317,85 +333,84 @@
|
|
317 |
menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
|
318 |
});
|
319 |
|
320 |
-
|
|
|
321 |
console.log("Reset camera button clicked.");
|
322 |
if (cameraInstance && initialCameraPosition && initialCameraRotation) {
|
|
|
323 |
cameraInstance.position = initialCameraPosition.clone();
|
324 |
cameraInstance.rotation = initialCameraRotation.clone();
|
325 |
if (typeof cameraInstance.update === 'function') {
|
326 |
cameraInstance.update();
|
327 |
}
|
328 |
-
|
329 |
-
|
|
|
330 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
331 |
}
|
332 |
});
|
333 |
|
334 |
// --- Initialize the 3D PLY Viewer ---
|
335 |
async function initializeViewer() {
|
336 |
-
|
|
|
337 |
progressDialog.style.display = 'block';
|
338 |
const renderer = new SPLAT.WebGLRenderer(canvas);
|
339 |
const scene = new SPLAT.Scene();
|
340 |
|
341 |
-
//
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
camPosArray = config.cameraPositionComputer;
|
347 |
-
} else {
|
348 |
-
// Fallback to a default position if none provided.
|
349 |
-
camPosArray = [1, 1, 5];
|
350 |
-
}
|
351 |
-
const camPos = new SPLAT.Vector3(camPosArray[0], camPosArray[1], camPosArray[2]);
|
352 |
-
|
353 |
-
// --- Compute orbit parameters (alpha, beta, radius) using target = (0,0,0) ---
|
354 |
-
const target = new SPLAT.Vector3(0, 0, 0);
|
355 |
-
const dx = camPos.x - target.x;
|
356 |
-
const dy = camPos.y - target.y;
|
357 |
-
const dz = camPos.z - target.z;
|
358 |
-
const computedRadius = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
359 |
-
// Note: OrbitControls expects: camera.y = target.y - radius*sin(beta)
|
360 |
-
// Thus, beta is computed as:
|
361 |
-
const computedBeta = -Math.asin(dy / computedRadius);
|
362 |
-
// And for alpha, using:
|
363 |
-
// camera.x = target.x + radius*sin(alpha)*cos(beta)
|
364 |
-
// camera.z = target.z - radius*cos(alpha)*cos(beta)
|
365 |
-
const computedAlpha = Math.atan2(dx, -dz);
|
366 |
-
|
367 |
-
// Construct the camera using the computed position.
|
368 |
-
const camera = new SPLAT.Camera(undefined, camPos);
|
369 |
-
|
370 |
-
// Construct OrbitControls using the computed alpha, beta, and radius.
|
371 |
-
const controls = new SPLAT.OrbitControls(
|
372 |
camera,
|
373 |
canvas,
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
true,
|
378 |
-
|
|
|
|
|
|
|
379 |
);
|
380 |
|
381 |
cameraInstance = camera;
|
382 |
-
|
383 |
-
// Save the initial state for the reset button.
|
384 |
initialCameraPosition = camera.position.clone();
|
385 |
initialCameraRotation = camera.rotation.clone();
|
386 |
|
387 |
canvas.style.background = "#FEFEFD";
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
// Set panSpeed: 0.5 on mobile, 1.2 on computer.
|
396 |
-
controls.panSpeed = isMobile ? 0.5 : 1.2;
|
397 |
|
398 |
-
|
399 |
|
400 |
try {
|
401 |
await SPLAT.PLYLoader.LoadAsync(
|
@@ -412,7 +427,7 @@
|
|
412 |
}
|
413 |
|
414 |
const frame = () => {
|
415 |
-
|
416 |
renderer.render(scene, camera);
|
417 |
requestAnimationFrame(frame);
|
418 |
};
|
|
|
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);
|
29 |
|
30 |
// Read configuration values from the JSON file.
|
31 |
+
const gifUrl = config.gif_url;
|
32 |
+
const plyUrl = config.ply_url;
|
33 |
+
const minZoom = parseFloat(config.minZoom || "0");
|
34 |
+
const maxZoom = parseFloat(config.maxZoom || "20");
|
35 |
+
const minAngle = parseFloat(config.minAngle || "0");
|
36 |
+
const maxAngle = parseFloat(config.maxAngle || "360");
|
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 |
// Determine the aspect ratio.
|
60 |
+
let aspectPercent = "100%";
|
61 |
if (config.aspect) {
|
62 |
if (config.aspect.indexOf(":") !== -1) {
|
63 |
+
const parts = config.aspect.split(":");
|
64 |
+
const w = parseFloat(parts[0]);
|
65 |
+
const h = parseFloat(parts[1]);
|
66 |
if (!isNaN(w) && !isNaN(h) && w > 0) {
|
67 |
aspectPercent = (h / w * 100) + "%";
|
68 |
}
|
69 |
} else {
|
70 |
+
const aspectValue = parseFloat(config.aspect);
|
71 |
if (!isNaN(aspectValue) && aspectValue > 0) {
|
72 |
aspectPercent = (100 / aspectValue) + "%";
|
73 |
}
|
74 |
}
|
75 |
} else {
|
76 |
+
const parentContainer = scriptTag.parentNode;
|
77 |
+
const containerWidth = parentContainer.offsetWidth;
|
78 |
+
const containerHeight = parentContainer.offsetHeight;
|
79 |
if (containerWidth > 0 && containerHeight > 0) {
|
80 |
aspectPercent = (containerHeight / containerWidth * 100) + "%";
|
81 |
}
|
82 |
}
|
83 |
|
|
|
|
|
|
|
|
|
|
|
84 |
// Inject CSS styles into the document head, scoped with the unique id.
|
85 |
+
const styleEl = document.createElement('style');
|
86 |
styleEl.textContent = `
|
87 |
/* Widget container styling */
|
88 |
#ply-widget-container-${instanceId} {
|
|
|
208 |
document.head.appendChild(styleEl);
|
209 |
|
210 |
// Create the widget container and set its inner HTML.
|
211 |
+
const widgetContainer = document.createElement('div');
|
212 |
widgetContainer.id = 'ply-widget-container-' + instanceId;
|
213 |
widgetContainer.innerHTML = `
|
214 |
<!-- GIF Preview Container -->
|
|
|
233 |
scriptTag.parentNode.appendChild(widgetContainer);
|
234 |
|
235 |
// Grab element references.
|
236 |
+
const gifPreview = document.getElementById('gif-preview-container-' + instanceId);
|
237 |
+
const viewerContainer = document.getElementById('viewer-container-' + instanceId);
|
238 |
+
const previewImage = document.getElementById('preview-image-' + instanceId);
|
239 |
+
const closeBtn = document.getElementById('close-btn-' + instanceId);
|
240 |
+
const fullscreenToggle = document.getElementById('fullscreen-toggle-' + instanceId);
|
241 |
+
const helpToggle = document.getElementById('help-toggle-' + instanceId);
|
242 |
+
const resetCameraBtn = document.getElementById('reset-camera-btn-' + instanceId);
|
243 |
+
const menuContent = document.getElementById('menu-content-' + instanceId);
|
244 |
+
const canvas = document.getElementById('canvas-' + instanceId);
|
245 |
+
const progressDialog = document.getElementById('progress-dialog-' + instanceId);
|
246 |
+
const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
|
247 |
|
248 |
// Set help instructions based on device type.
|
249 |
if (isMobile) {
|
|
|
333 |
menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
|
334 |
});
|
335 |
|
336 |
+
// Modified reset button: re-creates the OrbitControls with the original parameters.
|
337 |
+
resetCameraBtn.addEventListener('click', async function() {
|
338 |
console.log("Reset camera button clicked.");
|
339 |
if (cameraInstance && initialCameraPosition && initialCameraRotation) {
|
340 |
+
// Reset camera position and rotation.
|
341 |
cameraInstance.position = initialCameraPosition.clone();
|
342 |
cameraInstance.rotation = initialCameraRotation.clone();
|
343 |
if (typeof cameraInstance.update === 'function') {
|
344 |
cameraInstance.update();
|
345 |
}
|
346 |
+
// Dispose of the current controls.
|
347 |
+
if (controlsInstance && typeof controlsInstance.dispose === 'function') {
|
348 |
+
controlsInstance.dispose();
|
349 |
}
|
350 |
+
// Re-create OrbitControls with the original chosen parameters.
|
351 |
+
controlsInstance = new SPLAT.OrbitControls(
|
352 |
+
cameraInstance,
|
353 |
+
canvas,
|
354 |
+
0.5, // default alpha fallback
|
355 |
+
0.5, // default beta fallback
|
356 |
+
5, // default radius fallback
|
357 |
+
true,
|
358 |
+
new SPLAT.Vector3(),
|
359 |
+
chosenInitAlpha,
|
360 |
+
chosenInitBeta,
|
361 |
+
chosenInitRadius
|
362 |
+
);
|
363 |
+
controlsInstance.maxZoom = maxZoom;
|
364 |
+
controlsInstance.minZoom = minZoom;
|
365 |
+
controlsInstance.minAngle = minAngle;
|
366 |
+
controlsInstance.maxAngle = maxAngle;
|
367 |
+
controlsInstance.minAzimuth = minAzimuth;
|
368 |
+
controlsInstance.maxAzimuth = maxAzimuth;
|
369 |
+
controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
|
370 |
+
controlsInstance.update();
|
371 |
}
|
372 |
});
|
373 |
|
374 |
// --- Initialize the 3D PLY Viewer ---
|
375 |
async function initializeViewer() {
|
376 |
+
// Import SPLAT and store it globally.
|
377 |
+
SPLAT = await import("https://bilca-gsplat-library.static.hf.space/dist/index.js");
|
378 |
progressDialog.style.display = 'block';
|
379 |
const renderer = new SPLAT.WebGLRenderer(canvas);
|
380 |
const scene = new SPLAT.Scene();
|
381 |
|
382 |
+
// Construct the camera (no custom position since it's no longer in the JSON).
|
383 |
+
const camera = new SPLAT.Camera();
|
384 |
+
|
385 |
+
// Construct OrbitControls with the chosen initial orbit parameters.
|
386 |
+
controlsInstance = new SPLAT.OrbitControls(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
387 |
camera,
|
388 |
canvas,
|
389 |
+
0.5, // default alpha fallback
|
390 |
+
0.5, // default beta fallback
|
391 |
+
5, // default radius fallback
|
392 |
true,
|
393 |
+
new SPLAT.Vector3(),
|
394 |
+
chosenInitAlpha,
|
395 |
+
chosenInitBeta,
|
396 |
+
chosenInitRadius
|
397 |
);
|
398 |
|
399 |
cameraInstance = camera;
|
400 |
+
// Save the initial camera state (after controls are created)
|
|
|
401 |
initialCameraPosition = camera.position.clone();
|
402 |
initialCameraRotation = camera.rotation.clone();
|
403 |
|
404 |
canvas.style.background = "#FEFEFD";
|
405 |
+
controlsInstance.maxZoom = maxZoom;
|
406 |
+
controlsInstance.minZoom = minZoom;
|
407 |
+
controlsInstance.minAngle = minAngle;
|
408 |
+
controlsInstance.maxAngle = maxAngle;
|
409 |
+
controlsInstance.minAzimuth = minAzimuth;
|
410 |
+
controlsInstance.maxAzimuth = maxAzimuth;
|
411 |
+
controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
|
|
|
|
|
412 |
|
413 |
+
controlsInstance.update();
|
414 |
|
415 |
try {
|
416 |
await SPLAT.PLYLoader.LoadAsync(
|
|
|
427 |
}
|
428 |
|
429 |
const frame = () => {
|
430 |
+
controlsInstance.update();
|
431 |
renderer.render(scene, camera);
|
432 |
requestAnimationFrame(frame);
|
433 |
};
|