Spaces:
Running
Running
Update index_sans_gif.js
Browse files- index_sans_gif.js +51 -22
index_sans_gif.js
CHANGED
@@ -7,7 +7,7 @@
|
|
7 |
|
8 |
// Generate a unique identifier for this widget instance.
|
9 |
const instanceId = Math.random().toString(36).substr(2, 8);
|
10 |
-
|
11 |
// Helper: Get query parameters from THIS script’s src URL.
|
12 |
function getScriptQueryParam(param) {
|
13 |
var params = new URLSearchParams("");
|
@@ -19,21 +19,49 @@
|
|
19 |
}
|
20 |
return params.get(param);
|
21 |
}
|
22 |
-
|
23 |
// Read required URLs.
|
24 |
// The gifUrl is no longer used.
|
25 |
var plyUrl = getScriptQueryParam("ply_url");
|
26 |
-
|
27 |
// Optional parameters for zoom and rotation limits.
|
28 |
// Defaults: zoom from 0 to 20; rotation from 0 to 360.
|
29 |
var minZoom = parseFloat(getScriptQueryParam("minZoom") || "0");
|
30 |
var maxZoom = parseFloat(getScriptQueryParam("maxZoom") || "20");
|
31 |
var minAngle = parseFloat(getScriptQueryParam("minAngle") || "0");
|
32 |
var maxAngle = parseFloat(getScriptQueryParam("maxAngle") || "360");
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
// Detect if the device is iOS.
|
35 |
var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
36 |
-
|
37 |
// Inject CSS styles into the document head, scoped with the unique id.
|
38 |
var styleEl = document.createElement('style');
|
39 |
styleEl.textContent = `
|
@@ -42,7 +70,7 @@
|
|
42 |
position: relative;
|
43 |
width: 100%;
|
44 |
height: 0;
|
45 |
-
padding-bottom:
|
46 |
}
|
47 |
/* When in fake fullscreen mode (iOS fallback) */
|
48 |
#ply-widget-container-${instanceId}.fake-fullscreen {
|
@@ -135,10 +163,11 @@
|
|
135 |
/* Adjust the ⟲ icon position within the reset camera button */
|
136 |
.reset-icon {
|
137 |
display: inline-block;
|
|
|
138 |
}
|
139 |
`;
|
140 |
document.head.appendChild(styleEl);
|
141 |
-
|
142 |
// Create the widget container and set its inner HTML.
|
143 |
var widgetContainer = document.createElement('div');
|
144 |
widgetContainer.id = 'ply-widget-container-' + instanceId;
|
@@ -162,7 +191,7 @@
|
|
162 |
</div>
|
163 |
`;
|
164 |
document.currentScript.parentNode.appendChild(widgetContainer);
|
165 |
-
|
166 |
// Grab element references using the unique IDs.
|
167 |
var viewerContainer = document.getElementById('viewer-container-' + instanceId);
|
168 |
var fullscreenToggle = document.getElementById('fullscreen-toggle-' + instanceId);
|
@@ -172,9 +201,9 @@
|
|
172 |
var canvas = document.getElementById('canvas-' + instanceId);
|
173 |
var progressDialog = document.getElementById('progress-dialog-' + instanceId);
|
174 |
var progressIndicator = document.getElementById('progress-indicator-' + instanceId);
|
175 |
-
|
176 |
// --- Button Event Handlers ---
|
177 |
-
|
178 |
fullscreenToggle.addEventListener('click', function() {
|
179 |
if (isIOS) {
|
180 |
if (!widgetContainer.classList.contains('fake-fullscreen')) {
|
@@ -201,7 +230,7 @@
|
|
201 |
}
|
202 |
}
|
203 |
});
|
204 |
-
|
205 |
document.addEventListener('fullscreenchange', function() {
|
206 |
if (document.fullscreenElement === widgetContainer) {
|
207 |
fullscreenToggle.textContent = '⇲';
|
@@ -209,12 +238,12 @@
|
|
209 |
fullscreenToggle.textContent = '⇱';
|
210 |
}
|
211 |
});
|
212 |
-
|
213 |
helpToggle.addEventListener('click', function(e) {
|
214 |
e.stopPropagation();
|
215 |
menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
|
216 |
});
|
217 |
-
|
218 |
resetCameraBtn.addEventListener('click', function() {
|
219 |
console.log("Reset camera button clicked.");
|
220 |
if (cameraInstance && initialCameraPosition && initialCameraRotation) {
|
@@ -228,7 +257,7 @@
|
|
228 |
}
|
229 |
}
|
230 |
});
|
231 |
-
|
232 |
// --- Initialize the 3D PLY Viewer ---
|
233 |
async function initializeViewer() {
|
234 |
const SPLAT = await import("https://cdn.jsdelivr.net/npm/gsplat@latest");
|
@@ -237,20 +266,20 @@
|
|
237 |
const scene = new SPLAT.Scene();
|
238 |
const camera = new SPLAT.Camera();
|
239 |
const controls = new SPLAT.OrbitControls(camera, canvas);
|
240 |
-
|
241 |
cameraInstance = camera;
|
242 |
controlsInstance = controls;
|
243 |
initialCameraPosition = camera.position.clone();
|
244 |
initialCameraRotation = camera.rotation.clone();
|
245 |
-
|
246 |
canvas.style.background = "#FEFEFD";
|
247 |
controls.maxZoom = maxZoom;
|
248 |
controls.minZoom = minZoom;
|
249 |
controls.minAngle = minAngle;
|
250 |
controls.maxAngle = maxAngle;
|
251 |
-
|
252 |
controls.update();
|
253 |
-
|
254 |
try {
|
255 |
await SPLAT.PLYLoader.LoadAsync(
|
256 |
plyUrl,
|
@@ -264,22 +293,22 @@
|
|
264 |
console.error("Error loading PLY file:", error);
|
265 |
progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`;
|
266 |
}
|
267 |
-
|
268 |
const frame = () => {
|
269 |
controls.update();
|
270 |
renderer.render(scene, camera);
|
271 |
requestAnimationFrame(frame);
|
272 |
};
|
273 |
-
|
274 |
const handleResize = () => {
|
275 |
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
|
276 |
};
|
277 |
-
|
278 |
handleResize();
|
279 |
window.addEventListener("resize", handleResize);
|
280 |
requestAnimationFrame(frame);
|
281 |
}
|
282 |
-
|
283 |
// Initialize the viewer immediately.
|
284 |
initializeViewer();
|
285 |
})();
|
|
|
7 |
|
8 |
// Generate a unique identifier for this widget instance.
|
9 |
const instanceId = Math.random().toString(36).substr(2, 8);
|
10 |
+
|
11 |
// Helper: Get query parameters from THIS script’s src URL.
|
12 |
function getScriptQueryParam(param) {
|
13 |
var params = new URLSearchParams("");
|
|
|
19 |
}
|
20 |
return params.get(param);
|
21 |
}
|
22 |
+
|
23 |
// Read required URLs.
|
24 |
// The gifUrl is no longer used.
|
25 |
var plyUrl = getScriptQueryParam("ply_url");
|
26 |
+
|
27 |
// Optional parameters for zoom and rotation limits.
|
28 |
// Defaults: zoom from 0 to 20; rotation from 0 to 360.
|
29 |
var minZoom = parseFloat(getScriptQueryParam("minZoom") || "0");
|
30 |
var maxZoom = parseFloat(getScriptQueryParam("maxZoom") || "20");
|
31 |
var minAngle = parseFloat(getScriptQueryParam("minAngle") || "0");
|
32 |
var maxAngle = parseFloat(getScriptQueryParam("maxAngle") || "360");
|
33 |
+
|
34 |
+
// Determine the aspect ratio.
|
35 |
+
// Default aspect: 1:1 (i.e. 100% padding-bottom)
|
36 |
+
var aspectPercent = "100%";
|
37 |
+
var aspectParam = getScriptQueryParam("aspect");
|
38 |
+
if (aspectParam) {
|
39 |
+
if (aspectParam.indexOf(":") !== -1) {
|
40 |
+
var parts = aspectParam.split(":");
|
41 |
+
var w = parseFloat(parts[0]);
|
42 |
+
var h = parseFloat(parts[1]);
|
43 |
+
if (!isNaN(w) && !isNaN(h) && w > 0) {
|
44 |
+
aspectPercent = (h / w * 100) + "%";
|
45 |
+
}
|
46 |
+
} else {
|
47 |
+
var aspectValue = parseFloat(aspectParam);
|
48 |
+
if (!isNaN(aspectValue) && aspectValue > 0) {
|
49 |
+
aspectPercent = (100 / aspectValue) + "%";
|
50 |
+
}
|
51 |
+
}
|
52 |
+
} else {
|
53 |
+
// If no aspect parameter is provided, compute the aspect ratio from the parent element.
|
54 |
+
var parentContainer = document.currentScript.parentNode;
|
55 |
+
var containerWidth = parentContainer.offsetWidth;
|
56 |
+
var containerHeight = parentContainer.offsetHeight;
|
57 |
+
if (containerWidth > 0 && containerHeight > 0) {
|
58 |
+
aspectPercent = (containerHeight / containerWidth * 100) + "%";
|
59 |
+
}
|
60 |
+
}
|
61 |
+
|
62 |
// Detect if the device is iOS.
|
63 |
var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
64 |
+
|
65 |
// Inject CSS styles into the document head, scoped with the unique id.
|
66 |
var styleEl = document.createElement('style');
|
67 |
styleEl.textContent = `
|
|
|
70 |
position: relative;
|
71 |
width: 100%;
|
72 |
height: 0;
|
73 |
+
padding-bottom: ${aspectPercent};
|
74 |
}
|
75 |
/* When in fake fullscreen mode (iOS fallback) */
|
76 |
#ply-widget-container-${instanceId}.fake-fullscreen {
|
|
|
163 |
/* Adjust the ⟲ icon position within the reset camera button */
|
164 |
.reset-icon {
|
165 |
display: inline-block;
|
166 |
+
transform: translateY(-3px);
|
167 |
}
|
168 |
`;
|
169 |
document.head.appendChild(styleEl);
|
170 |
+
|
171 |
// Create the widget container and set its inner HTML.
|
172 |
var widgetContainer = document.createElement('div');
|
173 |
widgetContainer.id = 'ply-widget-container-' + instanceId;
|
|
|
191 |
</div>
|
192 |
`;
|
193 |
document.currentScript.parentNode.appendChild(widgetContainer);
|
194 |
+
|
195 |
// Grab element references using the unique IDs.
|
196 |
var viewerContainer = document.getElementById('viewer-container-' + instanceId);
|
197 |
var fullscreenToggle = document.getElementById('fullscreen-toggle-' + instanceId);
|
|
|
201 |
var canvas = document.getElementById('canvas-' + instanceId);
|
202 |
var progressDialog = document.getElementById('progress-dialog-' + instanceId);
|
203 |
var progressIndicator = document.getElementById('progress-indicator-' + instanceId);
|
204 |
+
|
205 |
// --- Button Event Handlers ---
|
206 |
+
|
207 |
fullscreenToggle.addEventListener('click', function() {
|
208 |
if (isIOS) {
|
209 |
if (!widgetContainer.classList.contains('fake-fullscreen')) {
|
|
|
230 |
}
|
231 |
}
|
232 |
});
|
233 |
+
|
234 |
document.addEventListener('fullscreenchange', function() {
|
235 |
if (document.fullscreenElement === widgetContainer) {
|
236 |
fullscreenToggle.textContent = '⇲';
|
|
|
238 |
fullscreenToggle.textContent = '⇱';
|
239 |
}
|
240 |
});
|
241 |
+
|
242 |
helpToggle.addEventListener('click', function(e) {
|
243 |
e.stopPropagation();
|
244 |
menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
|
245 |
});
|
246 |
+
|
247 |
resetCameraBtn.addEventListener('click', function() {
|
248 |
console.log("Reset camera button clicked.");
|
249 |
if (cameraInstance && initialCameraPosition && initialCameraRotation) {
|
|
|
257 |
}
|
258 |
}
|
259 |
});
|
260 |
+
|
261 |
// --- Initialize the 3D PLY Viewer ---
|
262 |
async function initializeViewer() {
|
263 |
const SPLAT = await import("https://cdn.jsdelivr.net/npm/gsplat@latest");
|
|
|
266 |
const scene = new SPLAT.Scene();
|
267 |
const camera = new SPLAT.Camera();
|
268 |
const controls = new SPLAT.OrbitControls(camera, canvas);
|
269 |
+
|
270 |
cameraInstance = camera;
|
271 |
controlsInstance = controls;
|
272 |
initialCameraPosition = camera.position.clone();
|
273 |
initialCameraRotation = camera.rotation.clone();
|
274 |
+
|
275 |
canvas.style.background = "#FEFEFD";
|
276 |
controls.maxZoom = maxZoom;
|
277 |
controls.minZoom = minZoom;
|
278 |
controls.minAngle = minAngle;
|
279 |
controls.maxAngle = maxAngle;
|
280 |
+
|
281 |
controls.update();
|
282 |
+
|
283 |
try {
|
284 |
await SPLAT.PLYLoader.LoadAsync(
|
285 |
plyUrl,
|
|
|
293 |
console.error("Error loading PLY file:", error);
|
294 |
progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`;
|
295 |
}
|
296 |
+
|
297 |
const frame = () => {
|
298 |
controls.update();
|
299 |
renderer.render(scene, camera);
|
300 |
requestAnimationFrame(frame);
|
301 |
};
|
302 |
+
|
303 |
const handleResize = () => {
|
304 |
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
|
305 |
};
|
306 |
+
|
307 |
handleResize();
|
308 |
window.addEventListener("resize", handleResize);
|
309 |
requestAnimationFrame(frame);
|
310 |
}
|
311 |
+
|
312 |
// Initialize the viewer immediately.
|
313 |
initializeViewer();
|
314 |
})();
|