bilca commited on
Commit
f8672d0
·
verified ·
1 Parent(s): 15ca35a

Update js_scripts/index.js

Browse files
Files changed (1) hide show
  1. js_scripts/index.js +310 -114
js_scripts/index.js CHANGED
@@ -1,6 +1,36 @@
 
 
 
1
  (async function() {
2
- // Retrieve the current script tag and load the JSON configuration file from the data-config attribute.
3
- const scriptTag = document.currentScript;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  const configUrl = scriptTag.getAttribute("data-config");
5
  let config = {};
6
  if (configUrl) {
@@ -24,12 +54,10 @@
24
  document.head.appendChild(linkEl);
25
  }
26
 
27
- // --- Outer scope variables for camera state ---
28
- let cameraInstance = null;
29
- let controlsInstance = null;
30
- let initialCameraPosition = null;
31
- let initialCameraRotation = null;
32
- let SPLAT = null; // We'll save the imported SPLAT module here.
33
 
34
  // Generate a unique identifier for this widget instance.
35
  const instanceId = Math.random().toString(36).substr(2, 8);
@@ -37,31 +65,45 @@
37
  // Read configuration values from the JSON file.
38
  const gifUrl = config.gif_url;
39
  const plyUrl = config.ply_url;
40
- const minZoom = parseFloat(config.minZoom || "0");
 
 
41
  const maxZoom = parseFloat(config.maxZoom || "20");
42
- const minAngle = parseFloat(config.minAngle || "0");
43
- const maxAngle = parseFloat(config.maxAngle || "360");
44
- const minAzimuth = config.minAzimuth !== undefined ? parseFloat(config.minAzimuth) : -Infinity;
45
- const maxAzimuth = config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) : Infinity;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
- // Read initial orbit parameters for desktop.
48
- const initAlphaDesktop = config.initAlpha !== undefined ? parseFloat(config.initAlpha) : 0.5;
49
- const initBetaDesktop = config.initBeta !== undefined ? parseFloat(config.initBeta) : 0.5;
50
- const initRadiusDesktop = config.initRadius !== undefined ? parseFloat(config.initRadius) : 5;
51
- // Read initial orbit parameters for phone.
52
- const initAlphaPhone = config.initAlphaPhone !== undefined ? parseFloat(config.initAlphaPhone) : initAlphaDesktop;
53
- const initBetaPhone = config.initBetaPhone !== undefined ? parseFloat(config.initBetaPhone) : initBetaDesktop;
54
- const initRadiusPhone = config.initRadiusPhone !== undefined ? parseFloat(config.initRadiusPhone) : initRadiusDesktop;
55
 
56
  // Detect if the device is iOS.
57
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
58
  // Also detect Android devices.
59
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
60
 
61
- // Choose the appropriate initial orbit values based on device type.
62
- const chosenInitAlpha = isMobile ? initAlphaPhone : initAlphaDesktop;
63
- const chosenInitBeta = isMobile ? initBetaPhone : initBetaDesktop;
64
- const chosenInitRadius = isMobile ? initRadiusPhone : initRadiusDesktop;
65
 
66
  // Determine the aspect ratio.
67
  let aspectPercent = "100%";
@@ -234,42 +276,76 @@
234
  menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
235
  });
236
 
237
- // --- Camera Reset Function ---
238
  function resetCamera() {
239
- console.log("Resetting camera to initial position.");
240
- if (cameraInstance && initialCameraPosition && initialCameraRotation) {
241
- cameraInstance.position = initialCameraPosition.clone();
242
- cameraInstance.rotation = initialCameraRotation.clone();
243
- if (typeof cameraInstance.update === 'function') {
244
- cameraInstance.update();
245
- }
246
- if (controlsInstance && typeof controlsInstance.dispose === 'function') {
247
- controlsInstance.dispose();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  }
249
- controlsInstance = new SPLAT.OrbitControls(
250
- cameraInstance,
251
- canvas,
252
- 0.5,
253
- 0.5,
254
- 5,
255
- true,
256
- new SPLAT.Vector3(),
257
- chosenInitAlpha,
258
- chosenInitBeta,
259
- chosenInitRadius
260
- );
261
- controlsInstance.maxZoom = maxZoom;
262
- controlsInstance.minZoom = minZoom;
263
- controlsInstance.minAngle = minAngle;
264
- controlsInstance.maxAngle = maxAngle;
265
- controlsInstance.minAzimuth = minAzimuth;
266
- controlsInstance.maxAzimuth = maxAzimuth;
267
- controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
268
- controlsInstance.update();
269
  }
270
  }
271
 
272
- resetCameraBtn.addEventListener('click', async function() {
273
  console.log("Reset camera button clicked.");
274
  resetCamera();
275
  });
@@ -294,70 +370,190 @@
294
  }
295
  });
296
 
297
- // --- Initialize the 3D PLY Viewer ---
298
  async function initializeViewer() {
299
- SPLAT = await import("https://bilca-gsplat-library.static.hf.space/dist/index.js");
300
  progressDialog.style.display = 'block';
301
- const renderer = new SPLAT.WebGLRenderer(canvas);
302
- const scene = new SPLAT.Scene();
303
 
304
- const camera = new SPLAT.Camera();
305
-
306
- controlsInstance = new SPLAT.OrbitControls(
307
- camera,
308
- canvas,
309
- 0.5,
310
- 0.5,
311
- 5,
312
- true,
313
- new SPLAT.Vector3(),
314
- chosenInitAlpha,
315
- chosenInitBeta,
316
- chosenInitRadius
317
- );
318
-
319
- cameraInstance = camera;
320
- initialCameraPosition = camera.position.clone();
321
- initialCameraRotation = camera.rotation.clone();
322
-
323
- canvas.style.background = config.canvas_background || "#FEFEFD";
324
 
325
- controlsInstance.maxZoom = maxZoom;
326
- controlsInstance.minZoom = minZoom;
327
- controlsInstance.minAngle = minAngle;
328
- controlsInstance.maxAngle = maxAngle;
329
- controlsInstance.minAzimuth = minAzimuth;
330
- controlsInstance.maxAzimuth = maxAzimuth;
331
- controlsInstance.panSpeed = isMobile ? 0.5 : 1.2;
332
-
333
- controlsInstance.update();
334
-
335
  try {
336
- await SPLAT.PLYLoader.LoadAsync(
337
- plyUrl,
338
- scene,
339
- (progress) => {
340
- progressIndicator.value = progress * 100;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  }
342
- );
343
- progressDialog.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  } catch (error) {
345
- console.error("Error loading PLY file:", error);
346
- progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`;
347
  }
348
-
349
- const frame = () => {
350
- controlsInstance.update();
351
- renderer.render(scene, camera);
352
- requestAnimationFrame(frame);
353
- };
354
-
355
- const handleResize = () => {
356
- renderer.setSize(canvas.clientWidth, canvas.clientHeight);
357
- };
358
-
359
- handleResize();
360
- window.addEventListener("resize", handleResize);
361
- requestAnimationFrame(frame);
362
  }
363
- })();
 
1
+ // Store the current script reference before the async function runs
2
+ const currentScriptTag = document.currentScript;
3
+
4
  (async function() {
5
+ // Import PlayCanvas
6
+ const pc = await import("https://cdn.skypack.dev/[email protected]");
7
+ window.pc = pc;
8
+
9
+ // Find the script tag using a more reliable method
10
+ let scriptTag = currentScriptTag;
11
+
12
+ // Fallback method if currentScriptTag is null
13
+ if (!scriptTag) {
14
+ const scripts = document.getElementsByTagName('script');
15
+ for (let i = 0; i < scripts.length; i++) {
16
+ if (scripts[i].src.includes('index.js') && scripts[i].hasAttribute('data-config')) {
17
+ scriptTag = scripts[i];
18
+ break;
19
+ }
20
+ }
21
+
22
+ // If still not found, try the last script on the page
23
+ if (!scriptTag && scripts.length > 0) {
24
+ scriptTag = scripts[scripts.length - 1];
25
+ }
26
+ }
27
+
28
+ // Check if we found a script tag
29
+ if (!scriptTag) {
30
+ console.error("Could not find the script tag with data-config attribute.");
31
+ return;
32
+ }
33
+
34
  const configUrl = scriptTag.getAttribute("data-config");
35
  let config = {};
36
  if (configUrl) {
 
54
  document.head.appendChild(linkEl);
55
  }
56
 
57
+ // --- Outer scope variables ---
58
+ let cameraEntity = null;
59
+ let app = null;
60
+ let modelEntity = null;
 
 
61
 
62
  // Generate a unique identifier for this widget instance.
63
  const instanceId = Math.random().toString(36).substr(2, 8);
 
65
  // Read configuration values from the JSON file.
66
  const gifUrl = config.gif_url;
67
  const plyUrl = config.ply_url;
68
+
69
+ // Camera constraint parameters
70
+ const minZoom = parseFloat(config.minZoom || "1");
71
  const maxZoom = parseFloat(config.maxZoom || "20");
72
+ const minAngle = parseFloat(config.minAngle || "-45");
73
+ const maxAngle = parseFloat(config.maxAngle || "90");
74
+ const minAzimuth = config.minAzimuth !== undefined ? parseFloat(config.minAzimuth) : -360;
75
+ const maxAzimuth = config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) : 360;
76
+
77
+ // Model position, scale, and rotation parameters
78
+ const modelX = config.modelX !== undefined ? parseFloat(config.modelX) : 0;
79
+ const modelY = config.modelY !== undefined ? parseFloat(config.modelY) : 0;
80
+ const modelZ = config.modelZ !== undefined ? parseFloat(config.modelZ) : 0;
81
+
82
+ const modelScale = config.modelScale !== undefined ? parseFloat(config.modelScale) : 1;
83
+
84
+ const modelRotationX = config.modelRotationX !== undefined ? parseFloat(config.modelRotationX) : 0;
85
+ const modelRotationY = config.modelRotationY !== undefined ? parseFloat(config.modelRotationY) : 0;
86
+ const modelRotationZ = config.modelRotationZ !== undefined ? parseFloat(config.modelRotationZ) : 0;
87
+
88
+ // Direct camera coordinates
89
+ const cameraX = config.cameraX !== undefined ? parseFloat(config.cameraX) : 0;
90
+ const cameraY = config.cameraY !== undefined ? parseFloat(config.cameraY) : 2;
91
+ const cameraZ = config.cameraZ !== undefined ? parseFloat(config.cameraZ) : 5;
92
 
93
+ // Camera coordinates for mobile devices
94
+ const cameraXPhone = config.cameraXPhone !== undefined ? parseFloat(config.cameraXPhone) : cameraX;
95
+ const cameraYPhone = config.cameraYPhone !== undefined ? parseFloat(config.cameraYPhone) : cameraY;
96
+ const cameraZPhone = config.cameraZPhone !== undefined ? parseFloat(config.cameraZPhone) : cameraZ * 1.5;
 
 
 
 
97
 
98
  // Detect if the device is iOS.
99
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
100
  // Also detect Android devices.
101
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
102
 
103
+ // Choose the appropriate coordinates based on device type
104
+ const chosenCameraX = isMobile ? cameraXPhone : cameraX;
105
+ const chosenCameraY = isMobile ? cameraYPhone : cameraY;
106
+ const chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
107
 
108
  // Determine the aspect ratio.
109
  let aspectPercent = "100%";
 
276
  menuContent.style.display = (menuContent.style.display === 'block') ? 'none' : 'block';
277
  });
278
 
279
+ // --- Camera Reset Function - FIXED TO PRESERVE CAMERA MOVEMENT ---
280
  function resetCamera() {
281
+ if (!cameraEntity || !modelEntity || !app) return;
282
+
283
+ try {
284
+ // Get the orbit camera script
285
+ const orbitCam = cameraEntity.script.orbitCamera;
286
+ if (!orbitCam) return;
287
+
288
+ // Store model position
289
+ const modelPos = modelEntity.getPosition();
290
+
291
+ // 1. We'll create a temporary entity to help us calculate new values
292
+ const tempEntity = new pc.Entity();
293
+ tempEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
294
+ tempEntity.lookAt(modelPos);
295
+
296
+ // 2. Calculate the distance between camera and model
297
+ const distance = new pc.Vec3().sub2(
298
+ new pc.Vec3(chosenCameraX, chosenCameraY, chosenCameraZ),
299
+ modelPos
300
+ ).length();
301
+
302
+ // 3. Set camera position
303
+ cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
304
+ cameraEntity.lookAt(modelPos);
305
+
306
+ // 4. Create and apply a new transform for the orbit camera
307
+ orbitCam.pivotPoint = new pc.Vec3(modelPos.x, modelPos.y, modelPos.z);
308
+
309
+ // 5. Set the distance and remove inertia
310
+ orbitCam._targetDistance = distance;
311
+ orbitCam._distance = distance;
312
+
313
+ // 6. Reset internal yaw and pitch values (this is critical)
314
+ // Get the current camera rotation
315
+ const rotation = tempEntity.getRotation();
316
+ const tempForward = new pc.Vec3();
317
+ rotation.transformVector(pc.Vec3.FORWARD, tempForward);
318
+
319
+ // Calculate yaw from the forward vector
320
+ const yaw = Math.atan2(-tempForward.x, -tempForward.z) * pc.math.RAD_TO_DEG;
321
+
322
+ // Calculate pitch
323
+ const yawQuat = new pc.Quat().setFromEulerAngles(0, -yaw, 0);
324
+ const rotWithoutYaw = new pc.Quat().mul2(yawQuat, rotation);
325
+ const forwardWithoutYaw = new pc.Vec3();
326
+ rotWithoutYaw.transformVector(pc.Vec3.FORWARD, forwardWithoutYaw);
327
+ const pitch = Math.atan2(forwardWithoutYaw.y, -forwardWithoutYaw.z) * pc.math.RAD_TO_DEG;
328
+
329
+ // Set yaw and pitch directly on the internal variables
330
+ orbitCam._targetYaw = yaw;
331
+ orbitCam._yaw = yaw;
332
+ orbitCam._targetPitch = pitch;
333
+ orbitCam._pitch = pitch;
334
+
335
+ // 7. Force an update of the camera position
336
+ if (typeof orbitCam._updatePosition === 'function') {
337
+ orbitCam._updatePosition();
338
  }
339
+
340
+ // Clean up
341
+ tempEntity.destroy();
342
+
343
+ } catch (error) {
344
+ console.error("Error resetting camera:", error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  }
346
  }
347
 
348
+ resetCameraBtn.addEventListener('click', function() {
349
  console.log("Reset camera button clicked.");
350
  resetCamera();
351
  });
 
370
  }
371
  });
372
 
373
+ // --- Initialize the 3D PLY Viewer using PlayCanvas ---
374
  async function initializeViewer() {
 
375
  progressDialog.style.display = 'block';
 
 
376
 
377
+ // Initialize PlayCanvas
378
+ const deviceType = "webgl2";
379
+ const gfxOptions = {
380
+ deviceTypes: [deviceType],
381
+ glslangUrl: `https://playcanvas.vercel.app/static/lib/glslang/glslang.js`,
382
+ twgslUrl: `https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js`,
383
+ antialias: false
384
+ };
 
 
 
 
 
 
 
 
 
 
 
 
385
 
 
 
 
 
 
 
 
 
 
 
386
  try {
387
+ // Create graphics device
388
+ const device = await pc.createGraphicsDevice(canvas, gfxOptions);
389
+ device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
390
+
391
+ // Create app
392
+ const createOptions = new pc.AppOptions();
393
+ createOptions.graphicsDevice = device;
394
+ createOptions.mouse = new pc.Mouse(document.body);
395
+ createOptions.touch = new pc.TouchDevice(document.body);
396
+ createOptions.componentSystems = [
397
+ pc.RenderComponentSystem,
398
+ pc.CameraComponentSystem,
399
+ pc.LightComponentSystem,
400
+ pc.ScriptComponentSystem,
401
+ pc.GSplatComponentSystem
402
+ ];
403
+ createOptions.resourceHandlers = [
404
+ pc.TextureHandler,
405
+ pc.ContainerHandler,
406
+ pc.ScriptHandler,
407
+ pc.GSplatHandler
408
+ ];
409
+
410
+ app = new pc.AppBase(canvas);
411
+ app.init(createOptions);
412
+
413
+ // Set canvas fill mode to match the container
414
+ app.setCanvasFillMode(pc.FILLMODE_NONE);
415
+ app.setCanvasResolution(pc.RESOLUTION_AUTO);
416
+
417
+ // Set scene options
418
+ app.scene.exposure = 0.8;
419
+ app.scene.toneMapping = pc.TONEMAP_ACES;
420
+
421
+ // Handle window resizing
422
+ const resize = () => {
423
+ if (app) {
424
+ app.resizeCanvas(canvas.clientWidth, canvas.clientHeight);
425
  }
426
+ };
427
+ window.addEventListener('resize', resize);
428
+ app.on('destroy', () => window.removeEventListener('resize', resize));
429
+
430
+ // Load required assets
431
+ const assets = {
432
+ model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
433
+ orbit: new pc.Asset('script', 'script', { url: `https://bilca-visionneur-play-canva-2.static.hf.space/orbit-camera.js` })
434
+ };
435
+
436
+ // Create asset loader with progress tracking
437
+ const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
438
+
439
+ // Handle asset loading progress
440
+ let lastProgress = 0;
441
+ assets.model.on('load', (asset) => {
442
+ progressDialog.style.display = 'none';
443
+ });
444
+
445
+ assets.model.on('error', (err) => {
446
+ console.error("Error loading PLY file:", err);
447
+ progressDialog.innerHTML = `<p style="color: red">Error loading model: ${err}</p>`;
448
+ });
449
+
450
+ // Set up progress monitoring
451
+ const checkProgress = () => {
452
+ if (app && assets.model.resource) {
453
+ progressIndicator.value = 100;
454
+ clearInterval(progressChecker);
455
+ progressDialog.style.display = 'none';
456
+ } else if (assets.model.loading) {
457
+ // Increment progress for visual feedback
458
+ lastProgress += 2;
459
+ if (lastProgress > 90) lastProgress = 90; // Cap at 90% until fully loaded
460
+ progressIndicator.value = lastProgress;
461
+ }
462
+ };
463
+
464
+ const progressChecker = setInterval(checkProgress, 100);
465
+
466
+ // Load assets and set up scene
467
+ assetListLoader.load(() => {
468
+ app.start();
469
+
470
+ // Create model entity
471
+ modelEntity = new pc.Entity('model');
472
+ modelEntity.addComponent('gsplat', {
473
+ asset: assets.model
474
+ });
475
+
476
+ // Position the model using JSON parameters
477
+ modelEntity.setLocalPosition(modelX, modelY, modelZ);
478
+ modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
479
+ modelEntity.setLocalScale(modelScale, modelScale, modelScale);
480
+
481
+ app.root.addChild(modelEntity);
482
+
483
+ // Create camera entity
484
+ cameraEntity = new pc.Entity('camera');
485
+ cameraEntity.addComponent('camera', {
486
+ clearColor: new pc.Color(
487
+ config.canvas_background ? parseInt(config.canvas_background.substr(1, 2), 16) / 255 : 0,
488
+ config.canvas_background ? parseInt(config.canvas_background.substr(3, 2), 16) / 255 : 0,
489
+ config.canvas_background ? parseInt(config.canvas_background.substr(5, 2), 16) / 255 : 0
490
+ ),
491
+ toneMapping: pc.TONEMAP_ACES
492
+ });
493
+
494
+ // Set camera position directly using X, Y, Z coordinates from config
495
+ cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
496
+ cameraEntity.lookAt(modelEntity.getPosition());
497
+
498
+ // Add orbit camera script for interactive navigation
499
+ cameraEntity.addComponent('script');
500
+ cameraEntity.script.create('orbitCamera', {
501
+ attributes: {
502
+ inertiaFactor: 0.2,
503
+ focusEntity: modelEntity,
504
+ distanceMax: maxZoom,
505
+ distanceMin: minZoom,
506
+ pitchAngleMax: maxAngle,
507
+ pitchAngleMin: minAngle,
508
+ yawAngleMax: maxAzimuth,
509
+ yawAngleMin: minAzimuth,
510
+ frameOnStart: false // Don't auto-frame since we're setting position directly
511
+ }
512
+ });
513
+
514
+ // Initialize the orbit controller to match our direct coordinates
515
+ setTimeout(() => {
516
+ if (cameraEntity.script.orbitCamera) {
517
+ // Calculate distance from camera to model
518
+ const modelPos = modelEntity.getPosition();
519
+ const camPos = cameraEntity.getPosition();
520
+ const distanceVec = new pc.Vec3();
521
+ distanceVec.sub2(camPos, modelPos);
522
+ const distance = distanceVec.length();
523
+
524
+ // Set up the orbit controller
525
+ cameraEntity.script.orbitCamera.pivotPoint.copy(modelPos);
526
+ cameraEntity.script.orbitCamera.distance = distance;
527
+ cameraEntity.script.orbitCamera._removeInertia();
528
+ }
529
+ }, 100);
530
+
531
+ // Add input controllers
532
+ cameraEntity.script.create('orbitCameraInputMouse', {
533
+ attributes: {
534
+ orbitSensitivity: isMobile ? 0.6 : 0.3,
535
+ distanceSensitivity: isMobile ? 0.5 : 0.4
536
+ }
537
+ });
538
+ cameraEntity.script.create('orbitCameraInputTouch', {
539
+ attributes: {
540
+ orbitSensitivity: 0.6,
541
+ distanceSensitivity: 0.5
542
+ }
543
+ });
544
+
545
+ app.root.addChild(cameraEntity);
546
+
547
+ // Initial resize to match container
548
+ resize();
549
+
550
+ // Hide progress dialog when everything is set up
551
+ progressDialog.style.display = 'none';
552
+ });
553
+
554
  } catch (error) {
555
+ console.error("Error initializing PlayCanvas viewer:", error);
556
+ progressDialog.innerHTML = `<p style="color: red">Error loading viewer: ${error.message}</p>`;
557
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  }
559
+ })();