MikaFil commited on
Commit
459cefb
·
verified ·
1 Parent(s): f3cc75e

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +767 -146
viewer.js CHANGED
@@ -55,26 +55,703 @@ function traverse(entity, callback) {
55
  }
56
  }
57
 
58
- // Charger orbit-camera.js **par application**
59
- const ORBIT_URL = "https://mikafil-viewer-sgos.static.hf.space/orbit-camera.js";
60
- function loadOrbitScriptsForApp(app, instanceId) {
61
- return new Promise((resolve, reject) => {
62
- // Si déjà enregistré dans CETTE app, ne rien faire
63
- try {
64
- if (app?.scripts?.get && app.scripts.get("orbitCamera")) return resolve();
65
- } catch (_) {}
66
-
67
- const asset = new pc.Asset("orbit-" + instanceId, "script", { url: ORBIT_URL });
68
- app.assets.add(asset);
69
- asset.once("load", () => resolve());
70
- asset.once("error", (e) => {
71
- console.error("[viewer.js] orbit-camera.js load error:", e);
72
- reject(e);
73
- });
74
- app.assets.load(asset);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
77
 
 
 
78
  let pc;
79
  export let app = null;
80
  let cameraEntity = null;
@@ -98,59 +775,40 @@ export async function initializeViewer(config, instanceId) {
98
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
99
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
100
 
101
- // Config
102
  sogsUrl = config.sogs_json_url;
103
- glbUrl =
104
- config.glb_url !== undefined
105
- ? config.glb_url
106
- : "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
107
- presentoirUrl =
108
- config.presentoir_url !== undefined
109
- ? config.presentoir_url
110
- : "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
111
  minZoom = parseFloat(config.minZoom || "1");
112
  maxZoom = parseFloat(config.maxZoom || "20");
113
  minAngle = parseFloat(config.minAngle || "-45");
114
  maxAngle = parseFloat(config.maxAngle || "90");
115
- minAzimuth =
116
- config.minAzimuth !== undefined ? parseFloat(config.minAzimuth) : -360;
117
- maxAzimuth =
118
- config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) : 360;
119
  minPivotY = parseFloat(config.minPivotY || "0");
120
- minY = config.minY !== undefined ? parseFloat(config.minY) : 0;
121
-
122
- modelX = config.modelX !== undefined ? parseFloat(config.modelX) : 0;
123
- modelY = config.modelY !== undefined ? parseFloat(config.modelY) : 0;
124
- modelZ = config.modelZ !== undefined ? parseFloat(config.modelZ) : 0;
125
- modelScale = config.modelScale !== undefined ? parseFloat(config.modelScale) : 1;
126
- modelRotationX =
127
- config.modelRotationX !== undefined ? parseFloat(config.modelRotationX) : 0;
128
- modelRotationY =
129
- config.modelRotationY !== undefined ? parseFloat(config.modelRotationY) : 0;
130
- modelRotationZ =
131
- config.modelRotationZ !== undefined ? parseFloat(config.modelRotationZ) : 0;
132
-
133
- presentoirScaleX =
134
- config.presentoirScaleX !== undefined ? parseFloat(config.presentoirScaleX) : 0;
135
- presentoirScaleY =
136
- config.presentoirScaleY !== undefined ? parseFloat(config.presentoirScaleY) : 0;
137
- presentoirScaleZ =
138
- config.presentoirScaleZ !== undefined ? parseFloat(config.presentoirScaleZ) : 0;
139
-
140
- const cameraX = config.cameraX !== undefined ? parseFloat(config.cameraX) : 0;
141
- const cameraY = config.cameraY !== undefined ? parseFloat(config.cameraY) : 2;
142
- const cameraZ = config.cameraZ !== undefined ? parseFloat(config.cameraZ) : 5;
143
- const cameraXPhone =
144
- config.cameraXPhone !== undefined ? parseFloat(config.cameraXPhone) : cameraX;
145
- const cameraYPhone =
146
- config.cameraYPhone !== undefined ? parseFloat(config.cameraYPhone) : cameraY;
147
- const cameraZPhone =
148
- config.cameraZPhone !== undefined ? parseFloat(config.cameraZPhone) : cameraZ * 1.5;
149
-
150
- color_bg_hex =
151
- config.canvas_background !== undefined ? config.canvas_background : "#FFFFFF";
152
- espace_expo_bool =
153
- config.espace_expo_bool !== undefined ? config.espace_expo_bool : false;
154
  color_bg = hexToRgbaArray(color_bg_hex);
155
 
156
  chosenCameraX = isMobile ? cameraXPhone : cameraX;
@@ -160,6 +818,7 @@ export async function initializeViewer(config, instanceId) {
160
  // Canvas
161
  const canvasId = "canvas-" + instanceId;
162
  const progressDialog = document.getElementById("progress-dialog-" + instanceId);
 
163
  const viewerContainer = document.getElementById("viewer-container-" + instanceId);
164
 
165
  let oldCanvas = document.getElementById(canvasId);
@@ -173,59 +832,21 @@ export async function initializeViewer(config, instanceId) {
173
  canvas.setAttribute("tabindex", "0");
174
  viewerContainer.insertBefore(canvas, progressDialog);
175
 
176
- // Gestes / molette
177
  canvas.style.touchAction = "none";
178
  canvas.style.webkitTouchCallout = "none";
179
  canvas.addEventListener("gesturestart", (e) => e.preventDefault());
180
  canvas.addEventListener("gesturechange", (e) => e.preventDefault());
181
  canvas.addEventListener("gestureend", (e) => e.preventDefault());
182
  canvas.addEventListener("dblclick", (e) => e.preventDefault());
183
- canvas.addEventListener(
184
- "touchstart",
185
- (e) => {
186
- if (e.touches.length > 1) e.preventDefault();
187
- },
188
- { passive: false }
189
- );
190
- canvas.addEventListener(
191
- "wheel",
192
- (e) => {
193
- e.preventDefault();
194
- },
195
- { passive: false }
196
- );
197
 
198
- // --- Clavier : bloquer le scroll seulement quand le pointeur est au-dessus du canvas ---
199
- const scrollKeys = new Set([
200
- "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight",
201
- "PageUp", "PageDown", "Home", "End", " ", "Space", "Spacebar"
202
- ]);
203
- let isPointerOverCanvas = false;
204
- const focusCanvas = () => canvas.focus({ preventScroll: true });
205
-
206
- const onPointerEnter = () => { isPointerOverCanvas = true; focusCanvas(); };
207
- const onPointerLeave = () => {
208
- isPointerOverCanvas = false;
209
- if (document.activeElement === canvas) canvas.blur();
210
- };
211
-
212
- canvas.addEventListener("pointerenter", onPointerEnter);
213
- canvas.addEventListener("pointerleave", onPointerLeave);
214
- canvas.addEventListener("mouseenter", onPointerEnter);
215
- canvas.addEventListener("mouseleave", onPointerLeave);
216
-
217
- canvas.addEventListener("mousedown", focusCanvas);
218
- canvas.addEventListener("touchstart", () => { focusCanvas(); }, { passive: false });
219
-
220
- canvas.addEventListener("blur", () => { isPointerOverCanvas = false; });
221
-
222
- const onKeyDownCapture = (e) => {
223
- if (!isPointerOverCanvas) return;
224
- if (scrollKeys.has(e.key) || scrollKeys.has(e.code)) {
225
- e.preventDefault();
226
- }
227
- };
228
- window.addEventListener("keydown", onKeyDownCapture, true);
229
 
230
  progressDialog.style.display = "block";
231
 
@@ -234,7 +855,7 @@ export async function initializeViewer(config, instanceId) {
234
  window.pc = pc;
235
  }
236
 
237
- // App
238
  const device = await pc.createGraphicsDevice(canvas, {
239
  deviceTypes: ["webgl2"],
240
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
@@ -247,7 +868,7 @@ export async function initializeViewer(config, instanceId) {
247
  opts.graphicsDevice = device;
248
  opts.mouse = new pc.Mouse(canvas);
249
  opts.touch = new pc.TouchDevice(canvas);
250
- //opts.keyboard = new pc.Keyboard(canvas); // clavier par canvas
251
  opts.componentSystems = [
252
  pc.RenderComponentSystem,
253
  pc.CameraComponentSystem,
@@ -281,17 +902,11 @@ export async function initializeViewer(config, instanceId) {
281
 
282
  app.on("destroy", () => {
283
  resizeObserver.disconnect();
284
- if (opts.keyboard && opts.keyboard.detach) opts.keyboard.detach();
285
- window.removeEventListener("keydown", onKeyDownCapture, true);
286
- canvas.removeEventListener("pointerenter", onPointerEnter);
287
- canvas.removeEventListener("pointerleave", onPointerLeave);
288
- canvas.removeEventListener("mouseenter", onPointerEnter);
289
- canvas.removeEventListener("mouseleave", onPointerLeave);
290
- canvas.removeEventListener("mousedown", focusCanvas);
291
- canvas.removeEventListener("touchstart", focusCanvas);
292
  });
293
 
294
- // Assets (sans orbit-camera ici)
295
  const assets = {
296
  sogs: new pc.Asset("gsplat", "gsplat", { url: sogsUrl }),
297
  glb: new pc.Asset("glb", "container", { url: glbUrl }),
@@ -301,13 +916,12 @@ export async function initializeViewer(config, instanceId) {
301
 
302
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
303
  loader.load(async () => {
304
- // Charger et enregistrer orbit-camera.js pour CETTE app
305
- await loadOrbitScriptsForApp(app, instanceId);
306
 
307
  app.start();
308
  progressDialog.style.display = "none";
309
 
310
- // Entités
311
  modelEntity = new pc.Entity("model");
312
  modelEntity.addComponent("gsplat", { asset: assets.sogs });
313
  modelEntity.setLocalPosition(modelX, modelY, modelZ);
@@ -342,7 +956,6 @@ export async function initializeViewer(config, instanceId) {
342
  });
343
  }
344
 
345
- // Caméra + scripts orbit
346
  cameraEntity = new pc.Entity("camera");
347
  cameraEntity.addComponent("camera", {
348
  clearColor: new pc.Color(color_bg),
@@ -352,7 +965,7 @@ export async function initializeViewer(config, instanceId) {
352
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
353
  cameraEntity.lookAt(modelEntity.getPosition());
354
  cameraEntity.addComponent("script");
355
- cameraEntity.script.create("orbitCamera", {
356
  attributes: {
357
  focusEntity: modelEntity,
358
  inertiaFactor: 0.2,
@@ -366,31 +979,36 @@ export async function initializeViewer(config, instanceId) {
366
  frameOnStart: false
367
  }
368
  });
369
- cameraEntity.script.create("orbitCameraInputMouse");
370
- cameraEntity.script.create("orbitCameraInputTouch");
371
- cameraEntity.script.create("orbitCameraInputKeyboard", {
372
- attributes: { forwardSpeed: 1.2, strafeSpeed: 1.2 }
 
 
 
373
  });
374
  app.root.addChild(cameraEntity);
375
 
376
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
377
  app.once("update", () => resetViewerCamera());
378
 
379
- // Tooltips (optionnel)
380
  try {
381
  if (config.tooltips_url) {
382
- import("./tooltips.js").then((tooltipsModule) => {
383
- tooltipsModule.initializeTooltips({
384
- app,
385
- cameraEntity,
386
- modelEntity,
387
- tooltipsUrl: config.tooltips_url,
388
- defaultVisible: !!config.showTooltipsDefault,
389
- moveDuration: config.tooltipMoveDuration || 0.6
390
- });
391
- }).catch(() => {});
 
 
392
  }
393
- } catch (_) {}
394
 
395
  viewerInitialized = true;
396
  });
@@ -399,19 +1017,22 @@ export async function initializeViewer(config, instanceId) {
399
  export function resetViewerCamera() {
400
  try {
401
  if (!cameraEntity || !modelEntity || !app) return;
402
- const orbitCam = cameraEntity.script.orbitCamera;
 
 
 
403
  if (!orbitCam) return;
404
 
405
  const modelPos = modelEntity.getPosition();
406
  const tempEnt = new pc.Entity();
407
- tempEnt.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
408
  tempEnt.lookAt(modelPos);
409
 
410
  const dist = new pc.Vec3()
411
- .sub2(new pc.Vec3(chosenCameraX, chosenCameraY, chosenCameraZ), modelPos)
412
  .length();
413
 
414
- cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
415
  cameraEntity.lookAt(modelPos);
416
 
417
  orbitCam.pivotPoint = modelPos.clone();
 
55
  }
56
  }
57
 
58
+ // ---------- Enregistre les scripts d’orbite sous des NOMS UNIQUES ----------
59
+ function registerOrbitScriptsForInstance(suffix) {
60
+ const ORBIT = "orbitCamera_" + suffix;
61
+ const MOUSE = "orbitCameraInputMouse_" + suffix;
62
+ const TOUCH = "orbitCameraInputTouch_" + suffix;
63
+ const KEYBD = "orbitCameraInputKeyboard_" + suffix;
64
+
65
+ // ---------------- OrbitCamera ----------------
66
+ var OrbitCamera = pc.createScript(ORBIT);
67
+
68
+ OrbitCamera.attributes.add('distanceMax', { type: 'number', default: 20, title: 'Distance Max' });
69
+ OrbitCamera.attributes.add('distanceMin', { type: 'number', default: 1, title: 'Distance Min' });
70
+ OrbitCamera.attributes.add('pitchAngleMax', { type: 'number', default: 90, title: 'Pitch Angle Max (degrees)' });
71
+ OrbitCamera.attributes.add('pitchAngleMin', { type: 'number', default: 0, title: 'Pitch Angle Min (degrees)' });
72
+ OrbitCamera.attributes.add('yawAngleMax', { type: 'number', default: 360, title: 'Yaw Angle Max (degrees)' });
73
+ OrbitCamera.attributes.add('yawAngleMin', { type: 'number', default: -360, title: 'Yaw Angle Min (degrees)' });
74
+ OrbitCamera.attributes.add('minY', { type: 'number', default: 0, title: 'Minimum Y' });
75
+
76
+ OrbitCamera.attributes.add('inertiaFactor', { type: 'number', default: 0.2, title: 'Inertia Factor' });
77
+ OrbitCamera.attributes.add('focusEntity', { type: 'entity', title: 'Focus Entity' });
78
+ OrbitCamera.attributes.add('frameOnStart', { type: 'boolean', default: true, title: 'Frame on Start' });
79
+
80
+ Object.defineProperty(OrbitCamera.prototype, 'distance', {
81
+ get: function () { return this._targetDistance; },
82
+ set: function (value) { this._targetDistance = this._clampDistance(value); }
83
+ });
84
+
85
+ Object.defineProperty(OrbitCamera.prototype, 'orthoHeight', {
86
+ get: function () { return this.entity.camera.orthoHeight; },
87
+ set: function (value) { this.entity.camera.orthoHeight = Math.max(0, value); }
88
+ });
89
+
90
+ Object.defineProperty(OrbitCamera.prototype, 'pitch', {
91
+ get: function () { return this._targetPitch; },
92
+ set: function (value) { this._targetPitch = this._clampPitchAngle(value); }
93
+ });
94
+
95
+ Object.defineProperty(OrbitCamera.prototype, 'yaw', {
96
+ get: function () { return this._targetYaw; },
97
+ set: function (value) { this._targetYaw = this._clampYawAngle(value); }
98
  });
99
+
100
+ Object.defineProperty(OrbitCamera.prototype, 'pivotPoint', {
101
+ get: function () { return this._pivotPoint; },
102
+ set: function (value) { this._pivotPoint.copy(value); }
103
+ });
104
+
105
+ OrbitCamera.prototype.focus = function (focusEntity) {
106
+ this._buildAabb(focusEntity);
107
+ var halfExtents = this._modelsAabb.halfExtents;
108
+ var radius = Math.max(halfExtents.x, Math.max(halfExtents.y, halfExtents.z));
109
+ this.distance = (radius * 1.5) / Math.sin(0.5 * this.entity.camera.fov * pc.math.DEG_TO_RAD);
110
+ this._removeInertia();
111
+ this._pivotPoint.copy(this._modelsAabb.center);
112
+ };
113
+
114
+ OrbitCamera.distanceBetween = new pc.Vec3();
115
+
116
+ OrbitCamera.prototype.resetAndLookAtPoint = function (resetPoint, lookAtPoint) {
117
+ this.pivotPoint.copy(lookAtPoint);
118
+ this.entity.setPosition(resetPoint);
119
+ this.entity.lookAt(lookAtPoint);
120
+ var distance = OrbitCamera.distanceBetween;
121
+ distance.sub2(lookAtPoint, resetPoint);
122
+ this.distance = distance.length();
123
+ this.pivotPoint.copy(lookAtPoint);
124
+ var cameraQuat = this.entity.getRotation();
125
+ this.yaw = this._calcYaw(cameraQuat);
126
+ this.pitch = this._calcPitch(cameraQuat, this.yaw);
127
+ this._removeInertia();
128
+ this._updatePosition();
129
+ };
130
+
131
+ OrbitCamera.prototype.resetAndLookAtEntity = function (resetPoint, entity) {
132
+ this._buildAabb(entity);
133
+ this.resetAndLookAtPoint(resetPoint, this._modelsAabb.center);
134
+ };
135
+
136
+ OrbitCamera.prototype.reset = function (yaw, pitch, distance) {
137
+ this.pitch = pitch;
138
+ this.yaw = yaw;
139
+ this.distance = distance;
140
+ this._removeInertia();
141
+ };
142
+
143
+ OrbitCamera.prototype.resetToPosition = function (position, lookAtPoint) {
144
+ this.entity.setPosition(position);
145
+ this.entity.lookAt(lookAtPoint);
146
+ this._pivotPoint.copy(lookAtPoint);
147
+ var distanceVec = new pc.Vec3();
148
+ distanceVec.sub2(position, lookAtPoint);
149
+ this._targetDistance = this._distance = distanceVec.length();
150
+ var cameraQuat = this.entity.getRotation();
151
+ this._targetYaw = this._yaw = this._calcYaw(cameraQuat);
152
+ this._targetPitch = this._pitch = this._calcPitch(cameraQuat, this._yaw);
153
+ this._removeInertia();
154
+ this._updatePosition();
155
+ };
156
+
157
+ // Helper: calc cam Y if pivot became 'pivot'
158
+ OrbitCamera.prototype.worldCameraYForPivot = function(pivot) {
159
+ var quat = new pc.Quat();
160
+ quat.setFromEulerAngles(this._pitch, this._yaw, 0);
161
+ var forward = new pc.Vec3();
162
+ quat.transformVector(pc.Vec3.FORWARD, forward);
163
+ var camPos = pivot.clone();
164
+ camPos.add(forward.clone().scale(-this._distance));
165
+ return camPos.y;
166
+ };
167
+
168
+ OrbitCamera.prototype.initialize = function () {
169
+ var self = this;
170
+ var onWindowResize = function () { self._checkAspectRatio(); };
171
+ window.addEventListener('resize', onWindowResize, false);
172
+ this._checkAspectRatio();
173
+
174
+ this._modelsAabb = new pc.BoundingBox();
175
+ this._buildAabb(this.focusEntity || this.app.root);
176
+
177
+ this.entity.lookAt(this._modelsAabb.center);
178
+ this._pivotPoint = new pc.Vec3();
179
+ this._pivotPoint.copy(this._modelsAabb.center);
180
+
181
+ var cameraQuat = this.entity.getRotation();
182
+ this._yaw = this._calcYaw(cameraQuat);
183
+ this._pitch = this._clampPitchAngle(this._calcPitch(cameraQuat, this._yaw));
184
+ this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
185
+
186
+ this._distance = 0;
187
+ this._targetYaw = this._yaw;
188
+ this._targetPitch = this._pitch;
189
+
190
+ if (this.frameOnStart) {
191
+ this.focus(this.focusEntity || this.app.root);
192
+ } else {
193
+ var distanceBetween = new pc.Vec3();
194
+ distanceBetween.sub2(this.entity.getPosition(), this._pivotPoint);
195
+ this._distance = this._clampDistance(distanceBetween.length());
196
+ }
197
+ this._targetDistance = this._distance;
198
+
199
+ this.on('attr:distanceMin', function () { this._distance = this._clampDistance(this._distance); });
200
+ this.on('attr:distanceMax', function () { this._distance = this._clampDistance(this._distance); });
201
+ this.on('attr:pitchAngleMin', function () { this._pitch = this._clampPitchAngle(this._pitch); });
202
+ this.on('attr:pitchAngleMax', function () { this._pitch = this._clampPitchAngle(this._pitch); });
203
+
204
+ this.on('attr:focusEntity', function (value) {
205
+ if (this.frameOnStart) { this.focus(value || this.app.root); }
206
+ else { this.resetAndLookAtEntity(this.entity.getPosition(), value || this.app.root); }
207
+ });
208
+
209
+ this.on('attr:frameOnStart', function (value) {
210
+ if (value) { this.focus(this.focusEntity || this.app.root); }
211
+ });
212
+
213
+ this.on('destroy', function () { window.removeEventListener('resize', onWindowResize, false); });
214
+ };
215
+
216
+ OrbitCamera.prototype.update = function (dt) {
217
+ var t = this.inertiaFactor === 0 ? 1 : Math.min(dt / this.inertiaFactor, 1);
218
+ this._distance = pc.math.lerp(this._distance, this._targetDistance, t);
219
+ this._yaw = pc.math.lerp(this._yaw, this._targetYaw, t);
220
+ this._pitch = pc.math.lerp(this._pitch, this._targetPitch, t);
221
+ this._updatePosition();
222
+ };
223
+
224
+ OrbitCamera.prototype._updatePosition = function () {
225
+ this.entity.setLocalPosition(0, 0, 0);
226
+ this.entity.setLocalEulerAngles(this._pitch, this._yaw, 0);
227
+
228
+ var position = this.entity.getPosition();
229
+ position.copy(this.entity.forward);
230
+ position.mulScalar(-this._distance);
231
+ position.add(this.pivotPoint);
232
+
233
+ position.y = Math.max(position.y, this.minY);
234
+ this.entity.setPosition(position);
235
+ };
236
+
237
+ OrbitCamera.prototype._removeInertia = function () {
238
+ this._yaw = this._targetYaw;
239
+ this._pitch = this._targetPitch;
240
+ this._distance = this._targetDistance;
241
+ };
242
+
243
+ OrbitCamera.prototype._checkAspectRatio = function () {
244
+ var height = this.app.graphicsDevice.height;
245
+ var width = this.app.graphicsDevice.width;
246
+ this.entity.camera.horizontalFov = (height > width);
247
+ };
248
+
249
+ OrbitCamera.prototype._buildAabb = function (entity) {
250
+ var i, m, meshInstances = [];
251
+
252
+ var renders = entity.findComponents('render');
253
+ for (i = 0; i < renders.length; i++) {
254
+ var render = renders[i];
255
+ for (m = 0; m < render.meshInstances.length; m++) {
256
+ meshInstances.push(render.meshInstances[m]);
257
+ }
258
+ }
259
+
260
+ var models = entity.findComponents('model');
261
+ for (i = 0; i < models.length; i++) {
262
+ var model = models[i];
263
+ for (m = 0; m < model.meshInstances.length; m++) {
264
+ meshInstances.push(model.meshInstances[m]);
265
+ }
266
+ }
267
+
268
+ var gsplats = entity.findComponents('gsplat');
269
+ for (i = 0; i < gsplats.length; i++) {
270
+ var gsplat = gsplats[i];
271
+ var instance = gsplat.instance;
272
+ if (instance?.meshInstance) {
273
+ meshInstances.push(instance.meshInstance);
274
+ }
275
+ }
276
+
277
+ for (i = 0; i < meshInstances.length; i++) {
278
+ if (i === 0) {
279
+ this._modelsAabb.copy(meshInstances[i].aabb);
280
+ } else {
281
+ this._modelsAabb.add(meshInstances[i].aabb);
282
+ }
283
+ }
284
+ };
285
+
286
+ OrbitCamera.prototype._calcYaw = function (quat) {
287
+ var transformedForward = new pc.Vec3();
288
+ quat.transformVector(pc.Vec3.FORWARD, transformedForward);
289
+ return Math.atan2(-transformedForward.x, -transformedForward.z) * pc.math.RAD_TO_DEG;
290
+ };
291
+
292
+ OrbitCamera.prototype._clampDistance = function (distance) {
293
+ if (this.distanceMax > 0) {
294
+ return pc.math.clamp(distance, this.distanceMin, this.distanceMax);
295
+ }
296
+ return Math.max(distance, this.distanceMin);
297
+ };
298
+
299
+ OrbitCamera.prototype._clampPitchAngle = function (pitch) {
300
+ return pc.math.clamp(pitch, this.pitchAngleMin, this.pitchAngleMax);
301
+ };
302
+
303
+ OrbitCamera.prototype._clampYawAngle = function (yaw) {
304
+ return pc.math.clamp(yaw, -this.yawAngleMax, -this.yawAngleMin);
305
+ };
306
+
307
+ OrbitCamera.quatWithoutYaw = new pc.Quat();
308
+ OrbitCamera.yawOffset = new pc.Quat();
309
+
310
+ OrbitCamera.prototype._calcPitch = function (quat, yaw) {
311
+ var quatWithoutYaw = OrbitCamera.quatWithoutYaw;
312
+ var yawOffset = OrbitCamera.yawOffset;
313
+ yawOffset.setFromEulerAngles(0, -yaw, 0);
314
+ quatWithoutYaw.mul2(yawOffset, quat);
315
+ var transformedForward = new pc.Vec3();
316
+ quatWithoutYaw.transformVector(pc.Vec3.FORWARD, transformedForward);
317
+ return Math.atan2(-transformedForward.y, -transformedForward.z) * pc.math.RAD_TO_DEG;
318
+ };
319
+
320
+ // ---------------- Mouse ----------------
321
+ var OrbitCameraInputMouse = pc.createScript(MOUSE);
322
+ OrbitCameraInputMouse.attributes.add('orbitSensitivity', { type: 'number', default: 0.3 });
323
+ OrbitCameraInputMouse.attributes.add('distanceSensitivity', { type: 'number', default: 0.4 });
324
+
325
+ OrbitCameraInputMouse.prototype.initialize = function () {
326
+ this.orbitCamera = this.entity.script[ORBIT];
327
+
328
+ if (this.orbitCamera) {
329
+ var self = this;
330
+ var onMouseOut = function () { self.onMouseOut(); };
331
+
332
+ this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
333
+ this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
334
+ this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
335
+ this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
336
+ window.addEventListener('mouseout', onMouseOut, false);
337
+
338
+ this.on('destroy', function () {
339
+ this.app.mouse.off(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
340
+ this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);
341
+ this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
342
+ this.app.mouse.off(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
343
+ window.removeEventListener('mouseout', onMouseOut, false);
344
+ });
345
+ }
346
+
347
+ this.app.mouse.disableContextMenu();
348
+
349
+ this.lookButtonDown = false;
350
+ this.panButtonDown = false;
351
+ this.lastPoint = new pc.Vec2();
352
+ };
353
+
354
+ OrbitCameraInputMouse.fromWorldPoint = new pc.Vec3();
355
+ OrbitCameraInputMouse.toWorldPoint = new pc.Vec3();
356
+ OrbitCameraInputMouse.worldDiff = new pc.Vec3();
357
+
358
+ OrbitCameraInputMouse.prototype.pan = function (screenPoint) {
359
+ var fromWorldPoint = OrbitCameraInputMouse.fromWorldPoint;
360
+ var toWorldPoint = OrbitCameraInputMouse.toWorldPoint;
361
+ var worldDiff = OrbitCameraInputMouse.worldDiff;
362
+
363
+ var camera = this.entity.camera;
364
+ var distance = this.orbitCamera.distance;
365
+
366
+ camera.screenToWorld(screenPoint.x, screenPoint.y, distance, fromWorldPoint);
367
+ camera.screenToWorld(this.lastPoint.x, this.lastPoint.y, distance, toWorldPoint);
368
+
369
+ worldDiff.sub2(toWorldPoint, fromWorldPoint);
370
+
371
+ var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
372
+ var minY = this.orbitCamera.minY;
373
+ var resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
374
+
375
+ if (resultingY >= minY - 1e-4) {
376
+ this.orbitCamera.pivotPoint.add(worldDiff);
377
+ } else {
378
+ worldDiff.y = 0;
379
+ proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
380
+ resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
381
+ if (resultingY >= minY - 1e-4) {
382
+ this.orbitCamera.pivotPoint.add(worldDiff);
383
+ }
384
+ }
385
+ };
386
+
387
+ OrbitCameraInputMouse.prototype.onMouseDown = function (event) {
388
+ switch (event.button) {
389
+ case pc.MOUSEBUTTON_LEFT: this.panButtonDown = true; break;
390
+ case pc.MOUSEBUTTON_MIDDLE:
391
+ case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = true; break;
392
+ }
393
+ };
394
+
395
+ OrbitCameraInputMouse.prototype.onMouseUp = function (event) {
396
+ switch (event.button) {
397
+ case pc.MOUSEBUTTON_LEFT: this.panButtonDown = false; break;
398
+ case pc.MOUSEBUTTON_MIDDLE:
399
+ case pc.MOUSEBUTTON_RIGHT: this.lookButtonDown = false; break;
400
+ }
401
+ };
402
+
403
+ OrbitCameraInputMouse.prototype.onMouseMove = function (event) {
404
+ if (this.lookButtonDown) {
405
+ var sens = this.orbitSensitivity;
406
+
407
+ var deltaPitch = event.dy * sens;
408
+ var deltaYaw = event.dx * sens;
409
+
410
+ var currPitch = this.orbitCamera.pitch;
411
+ var currYaw = this.orbitCamera.yaw;
412
+ var currDist = this.orbitCamera.distance;
413
+ var currPivot = this.orbitCamera.pivotPoint.clone();
414
+
415
+ var camQuat = new pc.Quat();
416
+ camQuat.setFromEulerAngles(currPitch, currYaw, 0);
417
+ var forward = new pc.Vec3();
418
+ camQuat.transformVector(pc.Vec3.FORWARD, forward);
419
+ var preY = currPivot.y + (-forward.y) * currDist;
420
+
421
+ var proposedPitch = currPitch - deltaPitch;
422
+ var testQuat = new pc.Quat();
423
+ testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
424
+ var testForward = new pc.Vec3();
425
+ testQuat.transformVector(pc.Vec3.FORWARD, testForward);
426
+ var proposedY = currPivot.y + (-testForward.y) * currDist;
427
+
428
+ var minY = this.orbitCamera.minY;
429
+ var wouldGoBelow = proposedY < minY - 1e-4;
430
+
431
+ if (wouldGoBelow && (proposedY < preY)) {
432
+ this.orbitCamera.yaw = currYaw - deltaYaw;
433
+ } else {
434
+ this.orbitCamera.pitch = proposedPitch;
435
+ this.orbitCamera.yaw = currYaw - deltaYaw;
436
+ }
437
+ } else if (this.panButtonDown) {
438
+ this.pan(new pc.Vec2(event.x, event.y));
439
+ }
440
+
441
+ this.lastPoint.set(event.x, event.y);
442
+ };
443
+
444
+ OrbitCameraInputMouse.prototype.onMouseWheel = function (event) {
445
+ if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
446
+ this.orbitCamera.distance -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.distance * 0.1);
447
+ } else {
448
+ this.orbitCamera.orthoHeight -= event.wheelDelta * this.distanceSensitivity * (this.orbitCamera.orthoHeight * 0.1);
449
+ }
450
+ event.event.preventDefault();
451
+ };
452
+
453
+ OrbitCameraInputMouse.prototype.onMouseOut = function () {
454
+ this.lookButtonDown = false;
455
+ this.panButtonDown = false;
456
+ };
457
+
458
+ // ---------------- Touch ----------------
459
+ var OrbitCameraInputTouch = pc.createScript(TOUCH);
460
+ OrbitCameraInputTouch.attributes.add('orbitSensitivity', { type: 'number', default: 0.6 });
461
+ OrbitCameraInputTouch.attributes.add('distanceSensitivity', { type: 'number', default: 0.5 });
462
+
463
+ OrbitCameraInputTouch.prototype.initialize = function () {
464
+ this.orbitCamera = this.entity.script[ORBIT];
465
+ this.lastTouchPoint = new pc.Vec2();
466
+ this.lastPinchMidPoint = new pc.Vec2();
467
+ this.lastPinchDistance = 0;
468
+
469
+ if (this.orbitCamera && this.app.touch) {
470
+ this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
471
+ this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
472
+ this.app.touch.on(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
473
+ this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
474
+
475
+ this.on('destroy', function () {
476
+ this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStartEndCancel, this);
477
+ this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchStartEndCancel, this);
478
+ this.app.touch.off(pc.EVENT_TOUCHCANCEL, this.onTouchStartEndCancel, this);
479
+ this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
480
+ });
481
+ }
482
+ };
483
+
484
+ OrbitCameraInputTouch.prototype.getPinchDistance = function (pointA, pointB) {
485
+ var dx = pointA.x - pointB.x;
486
+ var dy = pointA.y - pointB.y;
487
+ return Math.sqrt((dx * dx) + (dy * dy));
488
+ };
489
+
490
+ OrbitCameraInputTouch.prototype.calcMidPoint = function (pointA, pointB, result) {
491
+ result.set(pointB.x - pointA.x, pointB.y - pointA.y);
492
+ result.mulScalar(0.5);
493
+ result.x += pointA.x;
494
+ result.y += pointA.y;
495
+ };
496
+
497
+ OrbitCameraInputTouch.prototype.onTouchStartEndCancel = function (event) {
498
+ var touches = event.touches;
499
+ if (touches.length === 1) {
500
+ this.lastTouchPoint.set(touches[0].x, touches[0].y);
501
+ } else if (touches.length === 2) {
502
+ this.lastPinchDistance = this.getPinchDistance(touches[0], touches[1]);
503
+ this.calcMidPoint(touches[0], touches[1], this.lastPinchMidPoint);
504
+ }
505
+ };
506
+
507
+ OrbitCameraInputTouch.fromWorldPoint = new pc.Vec3();
508
+ OrbitCameraInputTouch.toWorldPoint = new pc.Vec3();
509
+ OrbitCameraInputTouch.worldDiff = new pc.Vec3();
510
+
511
+ OrbitCameraInputTouch.prototype.pan = function (midPoint) {
512
+ var fromWorldPoint = OrbitCameraInputTouch.fromWorldPoint;
513
+ var toWorldPoint = OrbitCameraInputTouch.toWorldPoint;
514
+ var worldDiff = OrbitCameraInputTouch.worldDiff;
515
+
516
+ var camera = this.entity.camera;
517
+ var distance = this.orbitCamera.distance;
518
+
519
+ camera.screenToWorld(midPoint.x, midPoint.y, distance, fromWorldPoint);
520
+ camera.screenToWorld(this.lastPinchMidPoint.x, this.lastPinchMidPoint.y, distance, toWorldPoint);
521
+
522
+ worldDiff.sub2(toWorldPoint, fromWorldPoint);
523
+
524
+ var proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
525
+ var minY = this.orbitCamera.minY;
526
+ var resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
527
+
528
+ if (resultingY >= minY - 1e-4) {
529
+ this.orbitCamera.pivotPoint.add(worldDiff);
530
+ } else {
531
+ worldDiff.y = 0;
532
+ proposedPivot = this.orbitCamera.pivotPoint.clone().add(worldDiff);
533
+ resultingY = this.orbitCamera.worldCameraYForPivot(proposedPivot);
534
+ if (resultingY >= minY - 1e-4) {
535
+ this.orbitCamera.pivotPoint.add(worldDiff);
536
+ }
537
+ }
538
+ };
539
+
540
+ OrbitCameraInputTouch.pinchMidPoint = new pc.Vec2();
541
+
542
+ OrbitCameraInputTouch.prototype.onTouchMove = function (event) {
543
+ var pinchMidPoint = OrbitCameraInputTouch.pinchMidPoint;
544
+ var touches = event.touches;
545
+
546
+ if (touches.length === 1) {
547
+ var touch = touches[0];
548
+ var sens = this.orbitSensitivity;
549
+
550
+ var deltaPitch = (touch.y - this.lastTouchPoint.y) * sens;
551
+ var deltaYaw = (touch.x - this.lastTouchPoint.x) * sens;
552
+
553
+ var currPitch = this.orbitCamera.pitch;
554
+ var currYaw = this.orbitCamera.yaw;
555
+ var currDist = this.orbitCamera.distance;
556
+ var currPivot = this.orbitCamera.pivotPoint.clone();
557
+
558
+ var camQuat = new pc.Quat();
559
+ camQuat.setFromEulerAngles(currPitch, currYaw, 0);
560
+ var forward = new pc.Vec3();
561
+ camQuat.transformVector(pc.Vec3.FORWARD, forward);
562
+ var preY = currPivot.y + (-forward.y) * currDist;
563
+
564
+ var proposedPitch = currPitch - deltaPitch;
565
+ var testQuat = new pc.Quat();
566
+ testQuat.setFromEulerAngles(proposedPitch, currYaw, 0);
567
+ var testForward = new pc.Vec3();
568
+ testQuat.transformVector(pc.Vec3.FORWARD, testForward);
569
+ var proposedY = currPivot.y + (-testForward.y) * currDist;
570
+
571
+ var minY = this.orbitCamera.minY;
572
+ var wouldGoBelow = proposedY < minY - 1e-4;
573
+
574
+ if (wouldGoBelow && (proposedY < preY)) {
575
+ this.orbitCamera.yaw = currYaw - deltaYaw;
576
+ } else {
577
+ this.orbitCamera.pitch = proposedPitch;
578
+ this.orbitCamera.yaw = currYaw - deltaYaw;
579
+ }
580
+
581
+ this.lastTouchPoint.set(touch.x, touch.y);
582
+ } else if (touches.length === 2) {
583
+ var currentPinchDistance = this.getPinchDistance(touches[0], touches[1]);
584
+ var diffInPinchDistance = currentPinchDistance - this.lastPinchDistance;
585
+ this.lastPinchDistance = currentPinchDistance;
586
+
587
+ this.orbitCamera.distance -= (diffInPinchDistance * this.distanceSensitivity * 0.1) * (this.orbitCamera.distance * 0.1);
588
+
589
+ this.calcMidPoint(touches[0], touches[1], pinchMidPoint);
590
+ this.pan(pinchMidPoint);
591
+ this.lastPinchMidPoint.copy(pinchMidPoint);
592
+ }
593
+ };
594
+
595
+ // ---------------- Keyboard (par canvas) ----------------
596
+ var OrbitCameraInputKeyboard = pc.createScript(KEYBD);
597
+
598
+ OrbitCameraInputKeyboard.attributes.add('forwardSpeed', { type: 'number', default: 1.2 });
599
+ OrbitCameraInputKeyboard.attributes.add('strafeSpeed', { type: 'number', default: 1.2 });
600
+ OrbitCameraInputKeyboard.attributes.add('orbitPitchSpeedDeg', { type: 'number', default: 90 });
601
+ OrbitCameraInputKeyboard.attributes.add('orbitYawSpeedDeg', { type: 'number', default: 120 });
602
+ OrbitCameraInputKeyboard.attributes.add('zoomKeySensitivity', { type: 'number', default: 3.0 });
603
+
604
+ OrbitCameraInputKeyboard.prototype.initialize = function () {
605
+ this.orbitCamera = this.entity.script[ORBIT];
606
+
607
+ this._keys = Object.create(null);
608
+ this._active = false;
609
+
610
+ var el = this.app.graphicsDevice.canvas;
611
+ if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0');
612
+
613
+ var shouldBlock = function(ev) {
614
+ if (!ev) return false;
615
+ var k = ev.key;
616
+ return k === 'ArrowUp' || k === 'ArrowDown' || k === 'ArrowLeft' || k === 'ArrowRight' ||
617
+ k === 'PageUp' || k === 'PageDown' || k === 'Home' || k === 'End' ||
618
+ k === ' ' || k === 'Spacebar' || (ev.ctrlKey && (k === 'ArrowUp' || k === 'ArrowDown'));
619
+ };
620
+
621
+ this._onKeyDown = (ev) => {
622
+ if (!this._active) return;
623
+ this._keys[ev.key] = true;
624
+ if (shouldBlock(ev)) ev.preventDefault();
625
+ };
626
+
627
+ this._onKeyUp = (ev) => { this._keys[ev.key] = false; };
628
+ this._onBlur = () => { this._keys = Object.create(null); };
629
+
630
+ this._onEnter = () => {
631
+ this._active = true;
632
+ try { el.focus({ preventScroll: true }); } catch(e) { el.focus(); }
633
+ };
634
+
635
+ this._onLeave = () => {
636
+ this._active = false;
637
+ el.blur();
638
+ this._keys = Object.create(null);
639
+ };
640
+
641
+ el.addEventListener('keydown', this._onKeyDown);
642
+ el.addEventListener('keyup', this._onKeyUp);
643
+ el.addEventListener('blur', this._onBlur);
644
+ el.addEventListener('pointerenter', this._onEnter);
645
+ el.addEventListener('pointerleave', this._onLeave);
646
+
647
+ this.on('destroy', function () {
648
+ el.removeEventListener('keydown', this._onKeyDown);
649
+ el.removeEventListener('keyup', this._onKeyUp);
650
+ el.removeEventListener('blur', this._onBlur);
651
+ el.removeEventListener('pointerenter', this._onEnter);
652
+ el.removeEventListener('pointerleave', this._onLeave);
653
+ }, this);
654
+ };
655
+
656
+ OrbitCameraInputKeyboard.prototype._pressed = function(name) {
657
+ return !!(this._keys[name] || this._keys[name + 'Left'] || this._keys[name + 'Right']);
658
+ };
659
+
660
+ OrbitCameraInputKeyboard.prototype.update = function (dt) {
661
+ if (!this.orbitCamera) return;
662
+
663
+ var up = !!this._keys['ArrowUp'];
664
+ var dn = !!this._keys['ArrowDown'];
665
+ var lt = !!this._keys['ArrowLeft'];
666
+ var rt = !!this._keys['ArrowRight'];
667
+
668
+ var shift = this._pressed('Shift');
669
+ var ctrl = this._pressed('Control');
670
+
671
+ if (shift && (up || dn || lt || rt)) {
672
+ var yawDir = (rt ? 1 : 0) - (lt ? 1 : 0);
673
+ if (yawDir !== 0) {
674
+ var dYaw = yawDir * this.orbitYawSpeedDeg * dt;
675
+ this.orbitCamera.yaw = this.orbitCamera.yaw + dYaw;
676
+ }
677
+
678
+ var pitchDir = (up ? 1 : 0) - (dn ? 1 : 0);
679
+ if (pitchDir !== 0) {
680
+ var dPitch = pitchDir * this.orbitPitchSpeedDeg * dt;
681
+
682
+ var currPitch = this.orbitCamera.pitch;
683
+ var currYaw = this.orbitCamera.yaw;
684
+ var currDist = this.orbitCamera.distance;
685
+ var currPivot = this.orbitCamera.pivotPoint.clone();
686
+
687
+ var camQuat = new pc.Quat().setFromEulerAngles(currPitch, currYaw, 0);
688
+ var forward = new pc.Vec3(); camQuat.transformVector(pc.Vec3.FORWARD, forward);
689
+ var preY = currPivot.y + (-forward.y) * currDist;
690
+
691
+ var testPitch = currPitch + dPitch;
692
+ var testQuat = new pc.Quat().setFromEulerAngles(testPitch, currYaw, 0);
693
+ var testForward = new pc.Vec3(); testQuat.transformVector(pc.Vec3.FORWARD, testForward);
694
+ var proposedY = currPivot.y + (-testForward.y) * currDist;
695
+
696
+ var minY = this.orbitCamera.minY;
697
+ var wouldGoBelow = proposedY < minY - 1e-4;
698
+
699
+ if (!(wouldGoBelow && (proposedY < preY))) {
700
+ this.orbitCamera.pitch = testPitch;
701
+ }
702
+ }
703
+ return;
704
+ }
705
+
706
+ if (ctrl && (up || dn)) {
707
+ var zoomSign = (up ? 1 : 0) - (dn ? 1 : 0);
708
+ if (zoomSign !== 0) {
709
+ if (this.entity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
710
+ var dz = zoomSign * this.zoomKeySensitivity * (this.orbitCamera.distance * 0.5) * dt;
711
+ this.orbitCamera.distance -= dz;
712
+ } else {
713
+ var doh = zoomSign * this.zoomKeySensitivity * (this.orbitCamera.orthoHeight * 0.5) * dt;
714
+ this.orbitCamera.orthoHeight -= doh;
715
+ }
716
+ }
717
+ return;
718
+ }
719
+
720
+ var moveVert = (up ? 1 : 0) - (dn ? 1 : 0);
721
+ var moveRight = (rt ? 1 : 0) - (lt ? 1 : 0);
722
+
723
+ if (moveVert === 0 && moveRight === 0) return;
724
+
725
+ var dist = Math.max(0.1, this.orbitCamera.distance);
726
+ var speedV = this.forwardSpeed * dist;
727
+ var speedR = this.strafeSpeed * dist;
728
+
729
+ var dy = moveVert * speedV * dt;
730
+ if (dy !== 0) {
731
+ var currentCamY = this.orbitCamera.worldCameraYForPivot(this.orbitCamera.pivotPoint);
732
+ var minY = this.orbitCamera.minY;
733
+ var proposedCamY = currentCamY + dy;
734
+ if (proposedCamY < minY) {
735
+ dy = Math.max(dy, minY - currentCamY);
736
+ }
737
+ if (dy !== 0) {
738
+ this.orbitCamera._pivotPoint.y += dy;
739
+ }
740
+ }
741
+
742
+ var right = this.entity.right.clone(); right.y = 0; if (right.lengthSq() > 1e-8) right.normalize();
743
+ var dx = moveRight * speedR * dt;
744
+ if (dx !== 0) {
745
+ this.orbitCamera._pivotPoint.add(right.mulScalar(dx));
746
+ }
747
+ };
748
+
749
+ // renvoie les noms pour créer les scripts
750
+ return { ORBIT, MOUSE, TOUCH, KEYBD };
751
  }
752
 
753
+ // ---------------------------------------------------------------------------
754
+
755
  let pc;
756
  export let app = null;
757
  let cameraEntity = null;
 
775
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
776
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
777
 
778
+ // Params
779
  sogsUrl = config.sogs_json_url;
780
+ glbUrl = (config.glb_url !== undefined) ? config.glb_url : "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
781
+ presentoirUrl = (config.presentoir_url !== undefined) ? config.presentoir_url : "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
 
 
 
 
 
 
782
  minZoom = parseFloat(config.minZoom || "1");
783
  maxZoom = parseFloat(config.maxZoom || "20");
784
  minAngle = parseFloat(config.minAngle || "-45");
785
  maxAngle = parseFloat(config.maxAngle || "90");
786
+ minAzimuth = (config.minAzimuth !== undefined) ? parseFloat(config.minAzimuth) : -360;
787
+ maxAzimuth = (config.maxAzimuth !== undefined) ? parseFloat(config.maxAzimuth) : 360;
 
 
788
  minPivotY = parseFloat(config.minPivotY || "0");
789
+ minY = (config.minY !== undefined) ? parseFloat(config.minY) : 0;
790
+
791
+ modelX = (config.modelX !== undefined) ? parseFloat(config.modelX) : 0;
792
+ modelY = (config.modelY !== undefined) ? parseFloat(config.modelY) : 0;
793
+ modelZ = (config.modelZ !== undefined) ? parseFloat(config.modelZ) : 0;
794
+ modelScale = (config.modelScale !== undefined) ? parseFloat(config.modelScale) : 1;
795
+ modelRotationX = (config.modelRotationX !== undefined) ? parseFloat(config.modelRotationX) : 0;
796
+ modelRotationY = (config.modelRotationY !== undefined) ? parseFloat(config.modelRotationY) : 0;
797
+ modelRotationZ = (config.modelRotationZ !== undefined) ? parseFloat(config.modelRotationZ) : 0;
798
+
799
+ presentoirScaleX = (config.presentoirScaleX !== undefined) ? parseFloat(config.presentoirScaleX) : 0;
800
+ presentoirScaleY = (config.presentoirScaleY !== undefined) ? parseFloat(config.presentoirScaleY) : 0;
801
+ presentoirScaleZ = (config.presentoirScaleZ !== undefined) ? parseFloat(config.presentoirScaleZ) : 0;
802
+
803
+ const cameraX = (config.cameraX !== undefined) ? parseFloat(config.cameraX) : 0;
804
+ const cameraY = (config.cameraY !== undefined) ? parseFloat(config.cameraY) : 2;
805
+ const cameraZ = (config.cameraZ !== undefined) ? parseFloat(config.cameraZ) : 5;
806
+ const cameraXPhone = (config.cameraXPhone !== undefined) ? parseFloat(config.cameraXPhone) : cameraX;
807
+ const cameraYPhone = (config.cameraYPhone !== undefined) ? parseFloat(config.cameraYPhone) : cameraY;
808
+ const cameraZPhone = (config.cameraZPhone !== undefined) ? parseFloat(config.cameraZPhone) : (cameraZ * 1.5);
809
+
810
+ color_bg_hex = (config.canvas_background !== undefined) ? config.canvas_background : "#FFFFFF";
811
+ espace_expo_bool = (config.espace_expo_bool !== undefined) ? config.espace_expo_bool : false;
 
 
 
 
 
 
 
 
 
 
 
812
  color_bg = hexToRgbaArray(color_bg_hex);
813
 
814
  chosenCameraX = isMobile ? cameraXPhone : cameraX;
 
818
  // Canvas
819
  const canvasId = "canvas-" + instanceId;
820
  const progressDialog = document.getElementById("progress-dialog-" + instanceId);
821
+ const progressIndicator = document.getElementById("progress-indicator-" + instanceId);
822
  const viewerContainer = document.getElementById("viewer-container-" + instanceId);
823
 
824
  let oldCanvas = document.getElementById(canvasId);
 
832
  canvas.setAttribute("tabindex", "0");
833
  viewerContainer.insertBefore(canvas, progressDialog);
834
 
 
835
  canvas.style.touchAction = "none";
836
  canvas.style.webkitTouchCallout = "none";
837
  canvas.addEventListener("gesturestart", (e) => e.preventDefault());
838
  canvas.addEventListener("gesturechange", (e) => e.preventDefault());
839
  canvas.addEventListener("gestureend", (e) => e.preventDefault());
840
  canvas.addEventListener("dblclick", (e) => e.preventDefault());
841
+ canvas.addEventListener("touchstart", (e) => {
842
+ if (e.touches.length > 1) e.preventDefault();
843
+ }, { passive: false });
844
+ canvas.addEventListener("wheel", (e) => { e.preventDefault(); }, { passive: false });
 
 
 
 
 
 
 
 
 
 
845
 
846
+ // focus / blur au survol (pour clavier local)
847
+ const focusCanvas = () => { try { canvas.focus({ preventScroll: true }); } catch (e) { canvas.focus(); } };
848
+ canvas.addEventListener('pointerenter', focusCanvas);
849
+ canvas.addEventListener('pointerleave', () => canvas.blur());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
850
 
851
  progressDialog.style.display = "block";
852
 
 
855
  window.pc = pc;
856
  }
857
 
858
+ // Create app
859
  const device = await pc.createGraphicsDevice(canvas, {
860
  deviceTypes: ["webgl2"],
861
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
 
868
  opts.graphicsDevice = device;
869
  opts.mouse = new pc.Mouse(canvas);
870
  opts.touch = new pc.TouchDevice(canvas);
871
+ // ⚠️ on ne crée PAS pc.Keyboard ici ; notre clavier est par-canvas dans le script
872
  opts.componentSystems = [
873
  pc.RenderComponentSystem,
874
  pc.CameraComponentSystem,
 
902
 
903
  app.on("destroy", () => {
904
  resizeObserver.disconnect();
905
+ canvas.removeEventListener('pointerenter', focusCanvas);
906
+ canvas.removeEventListener('pointerleave', () => canvas.blur());
 
 
 
 
 
 
907
  });
908
 
909
+ // Assets (sans orbit-camera.js)
910
  const assets = {
911
  sogs: new pc.Asset("gsplat", "gsplat", { url: sogsUrl }),
912
  glb: new pc.Asset("glb", "container", { url: glbUrl }),
 
916
 
917
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
918
  loader.load(async () => {
919
+ // Enregistre les scripts d’orbite pour CETTE instance
920
+ const ScriptNames = registerOrbitScriptsForInstance(instanceId);
921
 
922
  app.start();
923
  progressDialog.style.display = "none";
924
 
 
925
  modelEntity = new pc.Entity("model");
926
  modelEntity.addComponent("gsplat", { asset: assets.sogs });
927
  modelEntity.setLocalPosition(modelX, modelY, modelZ);
 
956
  });
957
  }
958
 
 
959
  cameraEntity = new pc.Entity("camera");
960
  cameraEntity.addComponent("camera", {
961
  clearColor: new pc.Color(color_bg),
 
965
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
966
  cameraEntity.lookAt(modelEntity.getPosition());
967
  cameraEntity.addComponent("script");
968
+ cameraEntity.script.create(ScriptNames.ORBIT, {
969
  attributes: {
970
  focusEntity: modelEntity,
971
  inertiaFactor: 0.2,
 
979
  frameOnStart: false
980
  }
981
  });
982
+ cameraEntity.script.create(ScriptNames.MOUSE);
983
+ cameraEntity.script.create(ScriptNames.TOUCH);
984
+ cameraEntity.script.create(ScriptNames.KEYBD, {
985
+ attributes: {
986
+ forwardSpeed: 1.2,
987
+ strafeSpeed: 1.2
988
+ }
989
  });
990
  app.root.addChild(cameraEntity);
991
 
992
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
993
  app.once("update", () => resetViewerCamera());
994
 
995
+ // Tooltips
996
  try {
997
  if (config.tooltips_url) {
998
+ import("./tooltips.js")
999
+ .then((tooltipsModule) => {
1000
+ tooltipsModule.initializeTooltips({
1001
+ app,
1002
+ cameraEntity,
1003
+ modelEntity,
1004
+ tooltipsUrl: config.tooltips_url,
1005
+ defaultVisible: !!config.showTooltipsDefault,
1006
+ moveDuration: config.tooltipMoveDuration || 0.6
1007
+ });
1008
+ })
1009
+ .catch(() => {});
1010
  }
1011
+ } catch (e) {}
1012
 
1013
  viewerInitialized = true;
1014
  });
 
1017
  export function resetViewerCamera() {
1018
  try {
1019
  if (!cameraEntity || !modelEntity || !app) return;
1020
+ // le nom du script d’orbite dépend de l’instance ; on le retrouve automatiquement
1021
+ const orbitKey = Object.keys(cameraEntity.script._scriptsIndex).find(k => k.startsWith('orbitCamera_'));
1022
+ if (!orbitKey) return;
1023
+ const orbitCam = cameraEntity.script[orbitKey];
1024
  if (!orbitCam) return;
1025
 
1026
  const modelPos = modelEntity.getPosition();
1027
  const tempEnt = new pc.Entity();
1028
+ tempEnt.setPosition(cameraEntity.getPosition());
1029
  tempEnt.lookAt(modelPos);
1030
 
1031
  const dist = new pc.Vec3()
1032
+ .sub2(new pc.Vec3(cameraEntity.getPosition().x, cameraEntity.getPosition().y, cameraEntity.getPosition().z), modelPos)
1033
  .length();
1034
 
1035
+ cameraEntity.setPosition(cameraEntity.getPosition());
1036
  cameraEntity.lookAt(modelPos);
1037
 
1038
  orbitCam.pivotPoint = modelPos.clone();