Spaces:
Running
Running
Update index.js
Browse files
index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
(function() {
|
| 2 |
-
// --- Outer scope variables for camera state
|
| 3 |
let cameraInstance = null;
|
| 4 |
let controlsInstance = null;
|
| 5 |
let initialCameraPosition = null;
|
| 6 |
let initialCameraRotation = null;
|
| 7 |
-
let prevCameraPos = null;
|
| 8 |
|
| 9 |
// Helper: Get query parameters from THIS script’s src URL.
|
| 10 |
function getScriptQueryParam(param) {
|
|
@@ -30,7 +29,8 @@
|
|
| 30 |
var maxAngle = parseFloat(getScriptQueryParam("maxAngle") || "360");
|
| 31 |
|
| 32 |
// Optional parameters for translation limits.
|
| 33 |
-
//
|
|
|
|
| 34 |
var minX = parseFloat(getScriptQueryParam("minX") || "-10");
|
| 35 |
var maxX = parseFloat(getScriptQueryParam("maxX") || "10");
|
| 36 |
var minY = parseFloat(getScriptQueryParam("minY") || "-10");
|
|
@@ -160,7 +160,6 @@
|
|
| 160 |
document.head.appendChild(styleEl);
|
| 161 |
|
| 162 |
// Create the widget container and set its inner HTML.
|
| 163 |
-
// Added new reset button with id "reset-camera-btn" displaying the 🗘 emoji.
|
| 164 |
var widgetContainer = document.createElement('div');
|
| 165 |
widgetContainer.id = 'ply-widget-container';
|
| 166 |
widgetContainer.innerHTML = `
|
|
@@ -185,7 +184,6 @@
|
|
| 185 |
</div>
|
| 186 |
</div>
|
| 187 |
`;
|
| 188 |
-
// Append widgetContainer to the current script's parent so it appears in place.
|
| 189 |
document.currentScript.parentNode.appendChild(widgetContainer);
|
| 190 |
|
| 191 |
// Grab element references.
|
|
@@ -208,40 +206,32 @@
|
|
| 208 |
|
| 209 |
// --- Button Event Handlers ---
|
| 210 |
|
| 211 |
-
// When the preview image is clicked, hide it, show the viewer, and initialize the 3D viewer.
|
| 212 |
gifPreview.addEventListener('click', function() {
|
| 213 |
gifPreview.style.display = 'none';
|
| 214 |
viewerContainer.style.display = 'block';
|
| 215 |
initializeViewer();
|
| 216 |
});
|
| 217 |
|
| 218 |
-
// Close button: hide the viewer and show the preview.
|
| 219 |
closeBtn.addEventListener('click', function() {
|
| 220 |
-
// Exit fullscreen if active.
|
| 221 |
if (document.fullscreenElement === widgetContainer) {
|
| 222 |
if (document.exitFullscreen) {
|
| 223 |
document.exitFullscreen();
|
| 224 |
}
|
| 225 |
}
|
| 226 |
-
// Remove fake-fullscreen class (for iOS) if present.
|
| 227 |
widgetContainer.classList.remove('fake-fullscreen');
|
| 228 |
viewerContainer.style.display = 'none';
|
| 229 |
gifPreview.style.display = 'block';
|
| 230 |
});
|
| 231 |
|
| 232 |
-
// Fullscreen toggle: use native Fullscreen API if available; otherwise, for iOS, toggle a CSS-based fullscreen.
|
| 233 |
fullscreenToggle.addEventListener('click', function() {
|
| 234 |
if (isIOS) {
|
| 235 |
-
// Toggle fake fullscreen via CSS on iOS.
|
| 236 |
if (!widgetContainer.classList.contains('fake-fullscreen')) {
|
| 237 |
widgetContainer.classList.add('fake-fullscreen');
|
| 238 |
} else {
|
| 239 |
widgetContainer.classList.remove('fake-fullscreen');
|
| 240 |
}
|
| 241 |
-
// Update icon based on state.
|
| 242 |
fullscreenToggle.textContent = widgetContainer.classList.contains('fake-fullscreen') ? '⇲' : '⇱';
|
| 243 |
} else {
|
| 244 |
-
// Non-iOS: use standard Fullscreen API.
|
| 245 |
if (!document.fullscreenElement) {
|
| 246 |
if (widgetContainer.requestFullscreen) {
|
| 247 |
widgetContainer.requestFullscreen();
|
|
@@ -260,7 +250,6 @@
|
|
| 260 |
}
|
| 261 |
});
|
| 262 |
|
| 263 |
-
// Update the fullscreen button icon on fullscreen change (for non-iOS browsers).
|
| 264 |
document.addEventListener('fullscreenchange', function() {
|
| 265 |
if (document.fullscreenElement === widgetContainer) {
|
| 266 |
fullscreenToggle.textContent = '⇲';
|
|
@@ -269,17 +258,14 @@
|
|
| 269 |
}
|
| 270 |
});
|
| 271 |
|
| 272 |
-
// Help (instructions) toggle: show/hide the instructions.
|
| 273 |
helpToggle.addEventListener('click', function(e) {
|
| 274 |
e.stopPropagation();
|
| 275 |
menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
|
| 276 |
});
|
| 277 |
|
| 278 |
-
// Reset Camera button: restore the camera to its initial state.
|
| 279 |
resetCameraBtn.addEventListener('click', function() {
|
| 280 |
console.log("Reset camera button clicked.");
|
| 281 |
if (cameraInstance && initialCameraPosition && initialCameraRotation) {
|
| 282 |
-
// Reset camera's position and rotation using clone().
|
| 283 |
cameraInstance.position = initialCameraPosition.clone();
|
| 284 |
cameraInstance.rotation = initialCameraRotation.clone();
|
| 285 |
if (typeof cameraInstance.update === 'function') {
|
|
@@ -293,25 +279,17 @@
|
|
| 293 |
|
| 294 |
// --- Initialize the 3D PLY Viewer ---
|
| 295 |
async function initializeViewer() {
|
| 296 |
-
// Dynamically import the gsplat library.
|
| 297 |
const SPLAT = await import("https://cdn.jsdelivr.net/npm/gsplat@latest");
|
| 298 |
-
|
| 299 |
-
// Display the progress dialog.
|
| 300 |
progressDialog.style.display = 'block';
|
| 301 |
-
|
| 302 |
-
// Create renderer, scene, camera, and controls.
|
| 303 |
const renderer = new SPLAT.WebGLRenderer(canvas);
|
| 304 |
const scene = new SPLAT.Scene();
|
| 305 |
const camera = new SPLAT.Camera();
|
| 306 |
const controls = new SPLAT.OrbitControls(camera, canvas);
|
| 307 |
|
| 308 |
-
// Store the camera and controls and capture their initial state using clone().
|
| 309 |
cameraInstance = camera;
|
| 310 |
controlsInstance = controls;
|
| 311 |
initialCameraPosition = camera.position.clone();
|
| 312 |
initialCameraRotation = camera.rotation.clone();
|
| 313 |
-
// Initialize previous camera position as the initial state.
|
| 314 |
-
prevCameraPos = camera.position.clone();
|
| 315 |
|
| 316 |
canvas.style.background = "#FEFEFD";
|
| 317 |
controls.maxZoom = maxZoom;
|
|
@@ -321,7 +299,6 @@
|
|
| 321 |
|
| 322 |
controls.update();
|
| 323 |
|
| 324 |
-
// Load the PLY model from the provided URL.
|
| 325 |
try {
|
| 326 |
await SPLAT.PLYLoader.LoadAsync(
|
| 327 |
plyUrl,
|
|
@@ -330,39 +307,31 @@
|
|
| 330 |
progressIndicator.value = progress * 100;
|
| 331 |
}
|
| 332 |
);
|
| 333 |
-
// Hide the progress dialog once the model is loaded.
|
| 334 |
progressDialog.style.display = 'none';
|
| 335 |
} catch (error) {
|
| 336 |
console.error("Error loading PLY file:", error);
|
| 337 |
progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`;
|
| 338 |
}
|
| 339 |
|
| 340 |
-
//
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
}
|
| 344 |
|
| 345 |
const frame = () => {
|
| 346 |
-
// Save the current camera position
|
| 347 |
-
let currentPos = camera.position.clone();
|
| 348 |
controls.update();
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
currentPos.z < minZ || currentPos.z > maxZ) {
|
| 354 |
-
// Restore previous allowed position.
|
| 355 |
-
camera.position = prevCameraPos.clone();
|
| 356 |
-
// Optionally, you could also update controlsInstance target here if accessible.
|
| 357 |
-
} else {
|
| 358 |
-
// Otherwise, update prevCameraPos.
|
| 359 |
-
prevCameraPos = camera.position.clone();
|
| 360 |
-
}
|
| 361 |
-
|
| 362 |
renderer.render(scene, camera);
|
| 363 |
requestAnimationFrame(frame);
|
| 364 |
};
|
| 365 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
handleResize();
|
| 367 |
window.addEventListener("resize", handleResize);
|
| 368 |
requestAnimationFrame(frame);
|
|
|
|
| 1 |
(function() {
|
| 2 |
+
// --- Outer scope variables for camera state ---
|
| 3 |
let cameraInstance = null;
|
| 4 |
let controlsInstance = null;
|
| 5 |
let initialCameraPosition = null;
|
| 6 |
let initialCameraRotation = null;
|
|
|
|
| 7 |
|
| 8 |
// Helper: Get query parameters from THIS script’s src URL.
|
| 9 |
function getScriptQueryParam(param) {
|
|
|
|
| 29 |
var maxAngle = parseFloat(getScriptQueryParam("maxAngle") || "360");
|
| 30 |
|
| 31 |
// Optional parameters for translation limits.
|
| 32 |
+
// For this example, you can pass minX, maxX, minY, maxY, minZ, maxZ.
|
| 33 |
+
// If not provided, default to some values (e.g., -10 to 10).
|
| 34 |
var minX = parseFloat(getScriptQueryParam("minX") || "-10");
|
| 35 |
var maxX = parseFloat(getScriptQueryParam("maxX") || "10");
|
| 36 |
var minY = parseFloat(getScriptQueryParam("minY") || "-10");
|
|
|
|
| 160 |
document.head.appendChild(styleEl);
|
| 161 |
|
| 162 |
// Create the widget container and set its inner HTML.
|
|
|
|
| 163 |
var widgetContainer = document.createElement('div');
|
| 164 |
widgetContainer.id = 'ply-widget-container';
|
| 165 |
widgetContainer.innerHTML = `
|
|
|
|
| 184 |
</div>
|
| 185 |
</div>
|
| 186 |
`;
|
|
|
|
| 187 |
document.currentScript.parentNode.appendChild(widgetContainer);
|
| 188 |
|
| 189 |
// Grab element references.
|
|
|
|
| 206 |
|
| 207 |
// --- Button Event Handlers ---
|
| 208 |
|
|
|
|
| 209 |
gifPreview.addEventListener('click', function() {
|
| 210 |
gifPreview.style.display = 'none';
|
| 211 |
viewerContainer.style.display = 'block';
|
| 212 |
initializeViewer();
|
| 213 |
});
|
| 214 |
|
|
|
|
| 215 |
closeBtn.addEventListener('click', function() {
|
|
|
|
| 216 |
if (document.fullscreenElement === widgetContainer) {
|
| 217 |
if (document.exitFullscreen) {
|
| 218 |
document.exitFullscreen();
|
| 219 |
}
|
| 220 |
}
|
|
|
|
| 221 |
widgetContainer.classList.remove('fake-fullscreen');
|
| 222 |
viewerContainer.style.display = 'none';
|
| 223 |
gifPreview.style.display = 'block';
|
| 224 |
});
|
| 225 |
|
|
|
|
| 226 |
fullscreenToggle.addEventListener('click', function() {
|
| 227 |
if (isIOS) {
|
|
|
|
| 228 |
if (!widgetContainer.classList.contains('fake-fullscreen')) {
|
| 229 |
widgetContainer.classList.add('fake-fullscreen');
|
| 230 |
} else {
|
| 231 |
widgetContainer.classList.remove('fake-fullscreen');
|
| 232 |
}
|
|
|
|
| 233 |
fullscreenToggle.textContent = widgetContainer.classList.contains('fake-fullscreen') ? '⇲' : '⇱';
|
| 234 |
} else {
|
|
|
|
| 235 |
if (!document.fullscreenElement) {
|
| 236 |
if (widgetContainer.requestFullscreen) {
|
| 237 |
widgetContainer.requestFullscreen();
|
|
|
|
| 250 |
}
|
| 251 |
});
|
| 252 |
|
|
|
|
| 253 |
document.addEventListener('fullscreenchange', function() {
|
| 254 |
if (document.fullscreenElement === widgetContainer) {
|
| 255 |
fullscreenToggle.textContent = '⇲';
|
|
|
|
| 258 |
}
|
| 259 |
});
|
| 260 |
|
|
|
|
| 261 |
helpToggle.addEventListener('click', function(e) {
|
| 262 |
e.stopPropagation();
|
| 263 |
menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
|
| 264 |
});
|
| 265 |
|
|
|
|
| 266 |
resetCameraBtn.addEventListener('click', function() {
|
| 267 |
console.log("Reset camera button clicked.");
|
| 268 |
if (cameraInstance && initialCameraPosition && initialCameraRotation) {
|
|
|
|
| 269 |
cameraInstance.position = initialCameraPosition.clone();
|
| 270 |
cameraInstance.rotation = initialCameraRotation.clone();
|
| 271 |
if (typeof cameraInstance.update === 'function') {
|
|
|
|
| 279 |
|
| 280 |
// --- Initialize the 3D PLY Viewer ---
|
| 281 |
async function initializeViewer() {
|
|
|
|
| 282 |
const SPLAT = await import("https://cdn.jsdelivr.net/npm/gsplat@latest");
|
|
|
|
|
|
|
| 283 |
progressDialog.style.display = 'block';
|
|
|
|
|
|
|
| 284 |
const renderer = new SPLAT.WebGLRenderer(canvas);
|
| 285 |
const scene = new SPLAT.Scene();
|
| 286 |
const camera = new SPLAT.Camera();
|
| 287 |
const controls = new SPLAT.OrbitControls(camera, canvas);
|
| 288 |
|
|
|
|
| 289 |
cameraInstance = camera;
|
| 290 |
controlsInstance = controls;
|
| 291 |
initialCameraPosition = camera.position.clone();
|
| 292 |
initialCameraRotation = camera.rotation.clone();
|
|
|
|
|
|
|
| 293 |
|
| 294 |
canvas.style.background = "#FEFEFD";
|
| 295 |
controls.maxZoom = maxZoom;
|
|
|
|
| 299 |
|
| 300 |
controls.update();
|
| 301 |
|
|
|
|
| 302 |
try {
|
| 303 |
await SPLAT.PLYLoader.LoadAsync(
|
| 304 |
plyUrl,
|
|
|
|
| 307 |
progressIndicator.value = progress * 100;
|
| 308 |
}
|
| 309 |
);
|
|
|
|
| 310 |
progressDialog.style.display = 'none';
|
| 311 |
} catch (error) {
|
| 312 |
console.error("Error loading PLY file:", error);
|
| 313 |
progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`;
|
| 314 |
}
|
| 315 |
|
| 316 |
+
// A simple clamp function.
|
| 317 |
+
function clamp(val, min, max) {
|
| 318 |
+
return Math.min(Math.max(val, min), max);
|
| 319 |
+
}
|
| 320 |
|
| 321 |
const frame = () => {
|
|
|
|
|
|
|
| 322 |
controls.update();
|
| 323 |
+
// Clamp each axis individually.
|
| 324 |
+
camera.position.x = clamp(camera.position.x, minX, maxX);
|
| 325 |
+
camera.position.y = clamp(camera.position.y, minY, maxY);
|
| 326 |
+
camera.position.z = clamp(camera.position.z, minZ, maxZ);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
renderer.render(scene, camera);
|
| 328 |
requestAnimationFrame(frame);
|
| 329 |
};
|
| 330 |
|
| 331 |
+
const handleResize = () => {
|
| 332 |
+
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
|
| 333 |
+
};
|
| 334 |
+
|
| 335 |
handleResize();
|
| 336 |
window.addEventListener("resize", handleResize);
|
| 337 |
requestAnimationFrame(frame);
|