MikaFil commited on
Commit
768c848
·
verified ·
1 Parent(s): eaace97

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +111 -190
viewer.js CHANGED
@@ -55,43 +55,54 @@ function traverse(entity, callback) {
55
  }
56
  }
57
 
58
- // ---------- Charger orbit-camera.js une seule fois globalement ----------
59
- async function ensureOrbitScripts(app) {
60
- if (window.__orbitScriptsLoaded) return;
61
- if (window.__orbitScriptsLoadingPromise) {
62
- try {
63
- await window.__orbitScriptsLoadingPromise;
64
- } catch (e) {}
65
- return;
 
 
 
 
 
 
 
 
 
 
 
66
  }
 
67
 
68
- window.__orbitScriptsLoadingPromise = new Promise((resolve, reject) => {
69
- const asset = new pc.Asset(
70
- "orbit-script-" + Math.random().toString(36).slice(2),
71
- "script",
72
- { url: "https://mikafil-viewer-sgos.static.hf.space/orbit-camera.js" }
73
- );
74
- app.assets.add(asset);
75
-
76
- const done = () => {
77
- window.__orbitScriptsLoaded = true;
78
- resolve(true);
79
- };
80
- const fail = (e) => {
81
- console.error("[viewer.js] orbit-camera.js failed to load", e);
82
- reject(e);
83
- };
84
-
85
- asset.once("load", done);
86
- asset.once("error", fail);
87
- app.assets.load(asset);
88
- });
89
 
90
  try {
91
- await window.__orbitScriptsLoadingPromise;
 
 
 
 
 
 
 
92
  } catch (e) {
93
- // laisser l'app continuer sans scripts (mais la caméra ne marchera pas)
 
 
 
94
  }
 
 
 
 
 
 
 
95
  }
96
 
97
  let pc;
@@ -112,76 +123,58 @@ let sogsUrl, glbUrl, presentoirUrl;
112
  let color_bg_hex, color_bg, espace_expo_bool;
113
 
114
  export async function initializeViewer(config, instanceId) {
115
- // Ce module est importé avec un query param unique par interface.js,
116
- // donc 1 instance par import. On garde tout de même ce garde-fou.
117
  if (viewerInitialized) return;
118
 
119
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
120
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
121
 
122
- //prends les differents arguments de config.json
123
  sogsUrl = config.sogs_json_url;
124
- glbUrl =
125
- config.glb_url !== undefined
126
- ? config.glb_url
127
- : "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
128
- presentoirUrl =
129
- config.presentoir_url !== undefined
130
- ? config.presentoir_url
131
- : "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
132
  minZoom = parseFloat(config.minZoom || "1");
133
  maxZoom = parseFloat(config.maxZoom || "20");
134
  minAngle = parseFloat(config.minAngle || "-45");
135
  maxAngle = parseFloat(config.maxAngle || "90");
136
- minAzimuth =
137
- config.minAzimuth !== undefined ? parseFloat(config.minAzimuth) : -360;
138
- maxAzimuth =
139
- config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) : 360;
140
  minPivotY = parseFloat(config.minPivotY || "0");
141
- minY = config.minY !== undefined ? parseFloat(config.minY) : 0;
142
-
143
- modelX = config.modelX !== undefined ? parseFloat(config.modelX) : 0;
144
- modelY = config.modelY !== undefined ? parseFloat(config.modelY) : 0;
145
- modelZ = config.modelZ !== undefined ? parseFloat(config.modelZ) : 0;
146
- modelScale = config.modelScale !== undefined ? parseFloat(config.modelScale) : 1;
147
- modelRotationX =
148
- config.modelRotationX !== undefined ? parseFloat(config.modelRotationX) : 0;
149
- modelRotationY =
150
- config.modelRotationY !== undefined ? parseFloat(config.modelRotationY) : 0;
151
- modelRotationZ =
152
- config.modelRotationZ !== undefined ? parseFloat(config.modelRotationZ) : 0;
153
-
154
- presentoirScaleX =
155
- config.presentoirScaleX !== undefined ? parseFloat(config.presentoirScaleX) : 0;
156
- presentoirScaleY =
157
- config.presentoirScaleY !== undefined ? parseFloat(config.presentoirScaleY) : 0;
158
- presentoirScaleZ =
159
- config.presentoirScaleZ !== undefined ? parseFloat(config.presentoirScaleZ) : 0;
160
-
161
- const cameraX = config.cameraX !== undefined ? parseFloat(config.cameraX) : 0;
162
- const cameraY = config.cameraY !== undefined ? parseFloat(config.cameraY) : 2;
163
- const cameraZ = config.cameraZ !== undefined ? parseFloat(config.cameraZ) : 5;
164
- const cameraXPhone =
165
- config.cameraXPhone !== undefined ? parseFloat(config.cameraXPhone) : cameraX;
166
- const cameraYPhone =
167
- config.cameraYPhone !== undefined ? parseFloat(config.cameraYPhone) : cameraY;
168
- const cameraZPhone =
169
- config.cameraZPhone !== undefined ? parseFloat(config.cameraZPhone) : cameraZ * 1.5;
170
-
171
- color_bg_hex =
172
- config.canvas_background !== undefined ? config.canvas_background : "#FFFFFF";
173
- espace_expo_bool =
174
- config.espace_expo_bool !== undefined ? config.espace_expo_bool : false;
175
  color_bg = hexToRgbaArray(color_bg_hex);
176
 
177
  chosenCameraX = isMobile ? cameraXPhone : cameraX;
178
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
179
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
180
 
181
- //cree le nouveau canva (instanceId est une valeur aléatoire pour gérer plusieurs canvas sur la meme page)
182
  const canvasId = "canvas-" + instanceId;
183
  const progressDialog = document.getElementById("progress-dialog-" + instanceId);
184
- const progressIndicator = document.getElementById("progress-indicator-" + instanceId);
185
  const viewerContainer = document.getElementById("viewer-container-" + instanceId);
186
 
187
  let oldCanvas = document.getElementById(canvasId);
@@ -195,79 +188,37 @@ export async function initializeViewer(config, instanceId) {
195
  canvas.setAttribute("tabindex", "0");
196
  viewerContainer.insertBefore(canvas, progressDialog);
197
 
 
198
  canvas.style.touchAction = "none";
199
  canvas.style.webkitTouchCallout = "none";
200
  canvas.addEventListener("gesturestart", (e) => e.preventDefault());
201
  canvas.addEventListener("gesturechange", (e) => e.preventDefault());
202
  canvas.addEventListener("gestureend", (e) => e.preventDefault());
203
  canvas.addEventListener("dblclick", (e) => e.preventDefault());
204
- canvas.addEventListener(
205
- "touchstart",
206
- (e) => {
207
- if (e.touches.length > 1) e.preventDefault();
208
- },
209
- { passive: false }
210
- );
211
- canvas.addEventListener(
212
- "wheel",
213
- (e) => {
214
- e.preventDefault();
215
- },
216
- { passive: false }
217
- );
218
 
219
- // --- Clavier : bloquer le scroll seulement quand le pointeur est au-dessus du canvas ---
220
- const scrollKeys = new Set([
221
- "ArrowUp",
222
- "ArrowDown",
223
- "ArrowLeft",
224
- "ArrowRight",
225
- "PageUp",
226
- "PageDown",
227
- "Home",
228
- "End",
229
- " ",
230
- "Space",
231
- "Spacebar"
232
- ]);
233
  let isPointerOverCanvas = false;
234
  const focusCanvas = () => canvas.focus({ preventScroll: true });
235
 
236
- const onPointerEnter = () => {
237
- isPointerOverCanvas = true;
238
- focusCanvas();
239
- };
240
- const onPointerLeave = () => {
241
- isPointerOverCanvas = false;
242
- // rendre immédiatement le scroll à la page sans recliquer
243
- if (document.activeElement === canvas) canvas.blur();
244
- };
245
 
246
  canvas.addEventListener("pointerenter", onPointerEnter);
247
  canvas.addEventListener("pointerleave", onPointerLeave);
248
  canvas.addEventListener("mouseenter", onPointerEnter);
249
  canvas.addEventListener("mouseleave", onPointerLeave);
250
-
251
  canvas.addEventListener("mousedown", focusCanvas);
252
- canvas.addEventListener(
253
- "touchstart",
254
- () => {
255
- focusCanvas();
256
- },
257
- { passive: false }
258
- );
259
-
260
- const onCanvasBlur = () => {
261
- isPointerOverCanvas = false;
262
- };
263
- canvas.addEventListener("blur", onCanvasBlur);
264
 
265
  const onKeyDownCapture = (e) => {
266
  if (!isPointerOverCanvas) return;
267
- if (scrollKeys.has(e.key) || scrollKeys.has(e.code)) {
268
- e.preventDefault(); // bloque le scroll page
269
- // ne pas stopPropagation -> PlayCanvas reçoit bien les touches
270
- }
271
  };
272
  window.addEventListener("keydown", onKeyDownCapture, true);
273
 
@@ -278,7 +229,7 @@ export async function initializeViewer(config, instanceId) {
278
  window.pc = pc;
279
  }
280
 
281
- // Create app first
282
  const device = await pc.createGraphicsDevice(canvas, {
283
  deviceTypes: ["webgl2"],
284
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
@@ -291,7 +242,7 @@ export async function initializeViewer(config, instanceId) {
291
  opts.graphicsDevice = device;
292
  opts.mouse = new pc.Mouse(canvas);
293
  opts.touch = new pc.TouchDevice(canvas);
294
- opts.keyboard = new pc.Keyboard(canvas); // clavier attaché au canvas
295
  opts.componentSystems = [
296
  pc.RenderComponentSystem,
297
  pc.CameraComponentSystem,
@@ -323,49 +274,39 @@ export async function initializeViewer(config, instanceId) {
323
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight)
324
  );
325
 
326
- // Nettoyage complet à la destruction
327
  app.on("destroy", () => {
328
  resizeObserver.disconnect();
329
  if (opts.keyboard && opts.keyboard.detach) opts.keyboard.detach();
330
-
331
  window.removeEventListener("keydown", onKeyDownCapture, true);
332
-
333
  canvas.removeEventListener("pointerenter", onPointerEnter);
334
  canvas.removeEventListener("pointerleave", onPointerLeave);
335
  canvas.removeEventListener("mouseenter", onPointerEnter);
336
  canvas.removeEventListener("mouseleave", onPointerLeave);
337
  canvas.removeEventListener("mousedown", focusCanvas);
338
  canvas.removeEventListener("touchstart", focusCanvas);
339
- canvas.removeEventListener("blur", onCanvasBlur);
340
  });
341
 
342
- // Assets after app exists
343
- // ⚠️ On NE charge PAS orbit-camera.js via AssetListLoader pour éviter les
344
- // redéfinitions multiples de scripts. On l'assure via ensureOrbitScripts().
345
  const assets = {
346
  sogs: new pc.Asset("gsplat", "gsplat", { url: sogsUrl }),
347
  glb: new pc.Asset("glb", "container", { url: glbUrl }),
348
  presentoir: new pc.Asset("presentoir", "container", { url: presentoirUrl })
349
  };
350
-
351
  for (const key in assets) app.assets.add(assets[key]);
352
 
353
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
354
  loader.load(async () => {
355
- // Charger les scripts orbit (une seule fois globalement)
356
- await ensureOrbitScripts(app);
357
 
358
  app.start();
359
  progressDialog.style.display = "none";
360
 
 
361
  modelEntity = new pc.Entity("model");
362
  modelEntity.addComponent("gsplat", { asset: assets.sogs });
363
  modelEntity.setLocalPosition(modelX, modelY, modelZ);
364
- modelEntity.setLocalEulerAngles(
365
- modelRotationX,
366
- modelRotationY,
367
- modelRotationZ
368
- );
369
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
370
  app.root.addChild(modelEntity);
371
 
@@ -373,11 +314,7 @@ export async function initializeViewer(config, instanceId) {
373
  app.root.addChild(glbEntity);
374
 
375
  const presentoirEntity = assets.presentoir.resource.instantiateRenderEntity();
376
- presentoirEntity.setLocalScale(
377
- presentoirScaleX,
378
- presentoirScaleY,
379
- presentoirScaleZ
380
- );
381
  app.root.addChild(presentoirEntity);
382
 
383
  if (!espace_expo_bool) {
@@ -390,21 +327,17 @@ export async function initializeViewer(config, instanceId) {
390
 
391
  traverse(presentoirEntity, (node) => {
392
  if (node.render && node.render.meshInstances) {
393
- for (let mi of node.render.meshInstances) {
394
- mi.material = matSol;
395
- }
396
  }
397
  });
398
-
399
  traverse(glbEntity, (node) => {
400
  if (node.render && node.render.meshInstances) {
401
- for (let mi of node.render.meshInstances) {
402
- mi.material = matSol;
403
- }
404
  }
405
  });
406
  }
407
 
 
408
  cameraEntity = new pc.Entity("camera");
409
  cameraEntity.addComponent("camera", {
410
  clearColor: new pc.Color(color_bg),
@@ -431,40 +364,28 @@ export async function initializeViewer(config, instanceId) {
431
  cameraEntity.script.create("orbitCameraInputMouse");
432
  cameraEntity.script.create("orbitCameraInputTouch");
433
  cameraEntity.script.create("orbitCameraInputKeyboard", {
434
- attributes: {
435
- forwardSpeed: 1.2, // vertical (relatif à la distance)
436
- strafeSpeed: 1.2 // gauche/droite (relatif à la distance)
437
- }
438
  });
439
  app.root.addChild(cameraEntity);
440
 
441
- app.resizeCanvas(
442
- viewerContainer.clientWidth,
443
- viewerContainer.clientHeight
444
- );
445
  app.once("update", () => resetViewerCamera());
446
 
447
- // Tooltips support
448
  try {
449
  if (config.tooltips_url) {
450
- import("./tooltips.js")
451
- .then((tooltipsModule) => {
452
- tooltipsModule.initializeTooltips({
453
- app,
454
- cameraEntity,
455
- modelEntity,
456
- tooltipsUrl: config.tooltips_url,
457
- defaultVisible: !!config.showTooltipsDefault,
458
- moveDuration: config.tooltipMoveDuration || 0.6
459
- });
460
- })
461
- .catch(() => {
462
- /* optional */
463
  });
 
464
  }
465
- } catch (e) {
466
- // optional
467
- }
468
 
469
  viewerInitialized = true;
470
  });
 
55
  }
56
  }
57
 
58
+ /**
59
+ * Charge la source d'orbit-camera.js une seule fois (cache global),
60
+ * puis l'évalue POUR CHAQUE APP afin que pc.createScript enregistre
61
+ * les types dans le ScriptRegistry de l'application courante.
62
+ */
63
+ const ORBIT_URL = "https://mikafil-viewer-sgos.static.hf.space/orbit-camera.js";
64
+ async function registerOrbitScriptsForApp(app) {
65
+ // Si cette app possède déjà les scripts, on ne fait rien
66
+ try {
67
+ if (app?.scripts?.get && app.scripts.get("orbitCamera")) return;
68
+ } catch (_) {}
69
+
70
+ // Cache global de la source
71
+ if (!window.__ORBIT_SOURCE_PROMISE__) {
72
+ window.__ORBIT_SOURCE_PROMISE__ = fetch(ORBIT_URL, { cache: "no-store", credentials: "omit" })
73
+ .then(r => {
74
+ if (!r.ok) throw new Error("Failed to fetch orbit-camera.js");
75
+ return r.text();
76
+ });
77
  }
78
+ const src = await window.__ORBIT_SOURCE_PROMISE__;
79
 
80
+ // Binder l’“application courante” sur CETTE app pendant l’évaluation
81
+ const prevGetApp = pc.Application.getApplication ? pc.Application.getApplication.bind(pc.Application) : null;
82
+ const prevApp = pc.app;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  try {
85
+ if (pc.Application.getApplication) {
86
+ pc.Application.getApplication = () => app;
87
+ }
88
+ pc.app = app;
89
+
90
+ // Évaluation globale : pc.createScript enregistre dans le registry de "app"
91
+ (0, eval)(src);
92
+
93
  } catch (e) {
94
+ console.error("[viewer.js] orbit-camera.js eval error:", e);
95
+ } finally {
96
+ if (prevGetApp) pc.Application.getApplication = prevGetApp;
97
+ pc.app = prevApp;
98
  }
99
+
100
+ // Sécurité : vérifier que le type est bien dispo
101
+ try {
102
+ if (!(app?.scripts?.get && app.scripts.get("orbitCamera"))) {
103
+ console.warn("[viewer.js] orbitCamera script type not found on app after eval");
104
+ }
105
+ } catch (_) {}
106
  }
107
 
108
  let pc;
 
123
  let color_bg_hex, color_bg, espace_expo_bool;
124
 
125
  export async function initializeViewer(config, instanceId) {
 
 
126
  if (viewerInitialized) return;
127
 
128
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
129
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
130
 
131
+ // Params
132
  sogsUrl = config.sogs_json_url;
133
+ glbUrl = (config.glb_url !== undefined)
134
+ ? config.glb_url
135
+ : "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
136
+ presentoirUrl = (config.presentoir_url !== undefined)
137
+ ? config.presentoir_url
138
+ : "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
 
 
139
  minZoom = parseFloat(config.minZoom || "1");
140
  maxZoom = parseFloat(config.maxZoom || "20");
141
  minAngle = parseFloat(config.minAngle || "-45");
142
  maxAngle = parseFloat(config.maxAngle || "90");
143
+ minAzimuth = (config.minAzimuth !== undefined) ? parseFloat(config.minAzimuth) : -360;
144
+ maxAzimuth = (config.maxAzimuth !== undefined) ? parseFloat(config.maxAzimuth) : 360;
 
 
145
  minPivotY = parseFloat(config.minPivotY || "0");
146
+ minY = (config.minY !== undefined) ? parseFloat(config.minY) : 0;
147
+
148
+ modelX = (config.modelX !== undefined) ? parseFloat(config.modelX) : 0;
149
+ modelY = (config.modelY !== undefined) ? parseFloat(config.modelY) : 0;
150
+ modelZ = (config.modelZ !== undefined) ? parseFloat(config.modelZ) : 0;
151
+ modelScale = (config.modelScale !== undefined) ? parseFloat(config.modelScale) : 1;
152
+ modelRotationX = (config.modelRotationX !== undefined) ? parseFloat(config.modelRotationX) : 0;
153
+ modelRotationY = (config.modelRotationY !== undefined) ? parseFloat(config.modelRotationY) : 0;
154
+ modelRotationZ = (config.modelRotationZ !== undefined) ? parseFloat(config.modelRotationZ) : 0;
155
+
156
+ presentoirScaleX = (config.presentoirScaleX !== undefined) ? parseFloat(config.presentoirScaleX) : 0;
157
+ presentoirScaleY = (config.presentoirScaleY !== undefined) ? parseFloat(config.presentoirScaleY) : 0;
158
+ presentoirScaleZ = (config.presentoirScaleZ !== undefined) ? parseFloat(config.presentoirScaleZ) : 0;
159
+
160
+ const cameraX = (config.cameraX !== undefined) ? parseFloat(config.cameraX) : 0;
161
+ const cameraY = (config.cameraY !== undefined) ? parseFloat(config.cameraY) : 2;
162
+ const cameraZ = (config.cameraZ !== undefined) ? parseFloat(config.cameraZ) : 5;
163
+ const cameraXPhone = (config.cameraXPhone !== undefined) ? parseFloat(config.cameraXPhone) : cameraX;
164
+ const cameraYPhone = (config.cameraYPhone !== undefined) ? parseFloat(config.cameraYPhone) : cameraY;
165
+ const cameraZPhone = (config.cameraZPhone !== undefined) ? parseFloat(config.cameraZPhone) : (cameraZ * 1.5);
166
+
167
+ color_bg_hex = (config.canvas_background !== undefined) ? config.canvas_background : "#FFFFFF";
168
+ espace_expo_bool = (config.espace_expo_bool !== undefined) ? config.espace_expo_bool : false;
 
 
 
 
 
 
 
 
 
 
 
169
  color_bg = hexToRgbaArray(color_bg_hex);
170
 
171
  chosenCameraX = isMobile ? cameraXPhone : cameraX;
172
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
173
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
174
 
175
+ // Canvas
176
  const canvasId = "canvas-" + instanceId;
177
  const progressDialog = document.getElementById("progress-dialog-" + instanceId);
 
178
  const viewerContainer = document.getElementById("viewer-container-" + instanceId);
179
 
180
  let oldCanvas = document.getElementById(canvasId);
 
188
  canvas.setAttribute("tabindex", "0");
189
  viewerContainer.insertBefore(canvas, progressDialog);
190
 
191
+ // Gestes / molette
192
  canvas.style.touchAction = "none";
193
  canvas.style.webkitTouchCallout = "none";
194
  canvas.addEventListener("gesturestart", (e) => e.preventDefault());
195
  canvas.addEventListener("gesturechange", (e) => e.preventDefault());
196
  canvas.addEventListener("gestureend", (e) => e.preventDefault());
197
  canvas.addEventListener("dblclick", (e) => e.preventDefault());
198
+ canvas.addEventListener("touchstart", (e) => {
199
+ if (e.touches.length > 1) e.preventDefault();
200
+ }, { passive: false });
201
+ canvas.addEventListener("wheel", (e) => { e.preventDefault(); }, { passive: false });
 
 
 
 
 
 
 
 
 
 
202
 
203
+ // --- Clavier : bloquer le scroll uniquement quand la souris est au-dessus ---
204
+ const scrollKeys = new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","PageUp","PageDown","Home","End"," ","Space","Spacebar"]);
 
 
 
 
 
 
 
 
 
 
 
 
205
  let isPointerOverCanvas = false;
206
  const focusCanvas = () => canvas.focus({ preventScroll: true });
207
 
208
+ const onPointerEnter = () => { isPointerOverCanvas = true; focusCanvas(); };
209
+ const onPointerLeave = () => { isPointerOverCanvas = false; if (document.activeElement === canvas) canvas.blur(); };
 
 
 
 
 
 
 
210
 
211
  canvas.addEventListener("pointerenter", onPointerEnter);
212
  canvas.addEventListener("pointerleave", onPointerLeave);
213
  canvas.addEventListener("mouseenter", onPointerEnter);
214
  canvas.addEventListener("mouseleave", onPointerLeave);
 
215
  canvas.addEventListener("mousedown", focusCanvas);
216
+ canvas.addEventListener("touchstart", () => { focusCanvas(); }, { passive: false });
217
+ canvas.addEventListener("blur", () => { isPointerOverCanvas = false; });
 
 
 
 
 
 
 
 
 
 
218
 
219
  const onKeyDownCapture = (e) => {
220
  if (!isPointerOverCanvas) return;
221
+ if (scrollKeys.has(e.key) || scrollKeys.has(e.code)) e.preventDefault();
 
 
 
222
  };
223
  window.addEventListener("keydown", onKeyDownCapture, true);
224
 
 
229
  window.pc = pc;
230
  }
231
 
232
+ // App
233
  const device = await pc.createGraphicsDevice(canvas, {
234
  deviceTypes: ["webgl2"],
235
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
 
242
  opts.graphicsDevice = device;
243
  opts.mouse = new pc.Mouse(canvas);
244
  opts.touch = new pc.TouchDevice(canvas);
245
+ opts.keyboard = new pc.Keyboard(canvas);
246
  opts.componentSystems = [
247
  pc.RenderComponentSystem,
248
  pc.CameraComponentSystem,
 
274
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight)
275
  );
276
 
 
277
  app.on("destroy", () => {
278
  resizeObserver.disconnect();
279
  if (opts.keyboard && opts.keyboard.detach) opts.keyboard.detach();
 
280
  window.removeEventListener("keydown", onKeyDownCapture, true);
 
281
  canvas.removeEventListener("pointerenter", onPointerEnter);
282
  canvas.removeEventListener("pointerleave", onPointerLeave);
283
  canvas.removeEventListener("mouseenter", onPointerEnter);
284
  canvas.removeEventListener("mouseleave", onPointerLeave);
285
  canvas.removeEventListener("mousedown", focusCanvas);
286
  canvas.removeEventListener("touchstart", focusCanvas);
 
287
  });
288
 
289
+ // Assets (on ne met PAS orbit-camera ici)
 
 
290
  const assets = {
291
  sogs: new pc.Asset("gsplat", "gsplat", { url: sogsUrl }),
292
  glb: new pc.Asset("glb", "container", { url: glbUrl }),
293
  presentoir: new pc.Asset("presentoir", "container", { url: presentoirUrl })
294
  };
 
295
  for (const key in assets) app.assets.add(assets[key]);
296
 
297
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
298
  loader.load(async () => {
299
+ // Enregistrer les scripts orbit pour CETTE app
300
+ await registerOrbitScriptsForApp(app);
301
 
302
  app.start();
303
  progressDialog.style.display = "none";
304
 
305
+ // Modèle
306
  modelEntity = new pc.Entity("model");
307
  modelEntity.addComponent("gsplat", { asset: assets.sogs });
308
  modelEntity.setLocalPosition(modelX, modelY, modelZ);
309
+ modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
 
 
 
 
310
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
311
  app.root.addChild(modelEntity);
312
 
 
314
  app.root.addChild(glbEntity);
315
 
316
  const presentoirEntity = assets.presentoir.resource.instantiateRenderEntity();
317
+ presentoirEntity.setLocalScale(presentoirScaleX, presentoirScaleY, presentoirScaleZ);
 
 
 
 
318
  app.root.addChild(presentoirEntity);
319
 
320
  if (!espace_expo_bool) {
 
327
 
328
  traverse(presentoirEntity, (node) => {
329
  if (node.render && node.render.meshInstances) {
330
+ for (let mi of node.render.meshInstances) mi.material = matSol;
 
 
331
  }
332
  });
 
333
  traverse(glbEntity, (node) => {
334
  if (node.render && node.render.meshInstances) {
335
+ for (let mi of node.render.meshInstances) mi.material = matSol;
 
 
336
  }
337
  });
338
  }
339
 
340
+ // Caméra + scripts d’input
341
  cameraEntity = new pc.Entity("camera");
342
  cameraEntity.addComponent("camera", {
343
  clearColor: new pc.Color(color_bg),
 
364
  cameraEntity.script.create("orbitCameraInputMouse");
365
  cameraEntity.script.create("orbitCameraInputTouch");
366
  cameraEntity.script.create("orbitCameraInputKeyboard", {
367
+ attributes: { forwardSpeed: 1.2, strafeSpeed: 1.2 }
 
 
 
368
  });
369
  app.root.addChild(cameraEntity);
370
 
371
+ app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
 
 
 
372
  app.once("update", () => resetViewerCamera());
373
 
374
+ // Tooltips (optionnel)
375
  try {
376
  if (config.tooltips_url) {
377
+ import("./tooltips.js").then((tooltipsModule) => {
378
+ tooltipsModule.initializeTooltips({
379
+ app,
380
+ cameraEntity,
381
+ modelEntity,
382
+ tooltipsUrl: config.tooltips_url,
383
+ defaultVisible: !!config.showTooltipsDefault,
384
+ moveDuration: config.tooltipMoveDuration || 0.6
 
 
 
 
 
385
  });
386
+ }).catch(() => {});
387
  }
388
+ } catch (_) {}
 
 
389
 
390
  viewerInitialized = true;
391
  });