MikaFil commited on
Commit
d8a7f36
·
verified ·
1 Parent(s): d6869e7

Update deplacement_dans_env/viewer_pr_env.js

Browse files
Files changed (1) hide show
  1. deplacement_dans_env/viewer_pr_env.js +60 -87
deplacement_dans_env/viewer_pr_env.js CHANGED
@@ -72,7 +72,7 @@ async function ensureOrbitScriptsLoaded() {
72
 
73
  window.__PLY_ORBIT_LOADING__ = new Promise((resolve, reject) => {
74
  const s = document.createElement("script");
75
- // même URL que précédemment ; le fichier a été mis à jour (bbox + no maxZoom)
76
  s.src = "https://mikafil-viewer-sgos.static.hf.space/deplacement_dans_env/ctrl_camera_pr_env.js";
77
  s.async = true;
78
  s.onload = () => {
@@ -96,8 +96,8 @@ async function ensureOrbitScriptsLoaded() {
96
  let pc;
97
  export let app = null;
98
  let cameraEntity = null;
99
- let modelEntity = null; // gsplat principal
100
- let envEntity = null; // scène GLB optionnelle (présentoir / environnement)
101
  let viewerInitialized = false;
102
  let resizeObserver = null;
103
 
@@ -114,7 +114,7 @@ let color_bg_hex, color_bg, espace_expo_bool;
114
  -------------------------------------------- */
115
 
116
  export async function initializeViewer(config, instanceId) {
117
- // ce module ES est importé avec un param unique ?inst=..., donc 1 instance par import
118
  if (viewerInitialized) return;
119
 
120
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
@@ -124,12 +124,10 @@ export async function initializeViewer(config, instanceId) {
124
  sogsUrl = config.sogs_json_url || null;
125
  glbUrl = config.glb_url || null;
126
 
127
- // plus de minZoom/maxZoom dans le JSON : on expose uniquement distanceMin (défaut 1)
128
  distanceMin = config.minZoom !== undefined
129
- ? parseFloat(config.minZoom) // rétro-compat éventuelle
130
- : (config.distanceMin !== undefined
131
- ? parseFloat(config.distanceMin) // si quelqu'un fournit distanceMin
132
- : 1);
133
 
134
  minAngle = parseFloat(config.minAngle ?? "-45");
135
  maxAngle = parseFloat(config.maxAngle ?? "90");
@@ -145,7 +143,7 @@ export async function initializeViewer(config, instanceId) {
145
  modelRotationY = config.modelRotationY !== undefined ? parseFloat(config.modelRotationY) : 0;
146
  modelRotationZ = config.modelRotationZ !== undefined ? parseFloat(config.modelRotationZ) : 0;
147
 
148
- // défauts à 1 (et non 0) pour éviter l'invisibilité si le JSON n'en fournit pas
149
  presentoirScaleX = config.presentoirScaleX !== undefined ? parseFloat(config.presentoirScaleX) : 1;
150
  presentoirScaleY = config.presentoirScaleY !== undefined ? parseFloat(config.presentoirScaleY) : 1;
151
  presentoirScaleZ = config.presentoirScaleZ !== undefined ? parseFloat(config.presentoirScaleZ) : 1;
@@ -182,7 +180,7 @@ export async function initializeViewer(config, instanceId) {
182
  canvas.setAttribute("tabindex", "0");
183
  viewerContainer.insertBefore(canvas, progressDialog);
184
 
185
- // interactions de base
186
  canvas.style.touchAction = "none";
187
  canvas.style.webkitTouchCallout = "none";
188
  canvas.addEventListener("gesturestart", (e) => e.preventDefault());
@@ -198,60 +196,33 @@ export async function initializeViewer(config, instanceId) {
198
  );
199
  canvas.addEventListener(
200
  "wheel",
201
- (e) => {
202
- e.preventDefault();
203
- },
204
  { passive: false }
205
  );
206
 
207
  // Bloque le scroll page uniquement quand le pointeur est sur le canvas
208
- const scrollKeys = new Set([
209
- "ArrowUp",
210
- "ArrowDown",
211
- "ArrowLeft",
212
- "ArrowRight",
213
- "PageUp",
214
- "PageDown",
215
- "Home",
216
- "End",
217
- " ",
218
- "Space",
219
- "Spacebar"
220
- ]);
221
  let isPointerOverCanvas = false;
222
  const focusCanvas = () => canvas.focus({ preventScroll: true });
223
 
224
- const onPointerEnter = () => {
225
- isPointerOverCanvas = true;
226
- focusCanvas();
227
- };
228
  const onPointerLeave = () => {
229
  isPointerOverCanvas = false;
230
  if (document.activeElement === canvas) canvas.blur();
231
  };
232
- const onCanvasBlur = () => {
233
- isPointerOverCanvas = false;
234
- };
235
 
236
  canvas.addEventListener("pointerenter", onPointerEnter);
237
  canvas.addEventListener("pointerleave", onPointerLeave);
238
  canvas.addEventListener("mouseenter", onPointerEnter);
239
  canvas.addEventListener("mouseleave", onPointerLeave);
240
  canvas.addEventListener("mousedown", focusCanvas);
241
- canvas.addEventListener(
242
- "touchstart",
243
- () => {
244
- focusCanvas();
245
- },
246
- { passive: false }
247
- );
248
  canvas.addEventListener("blur", onCanvasBlur);
249
 
250
  const onKeyDownCapture = (e) => {
251
  if (!isPointerOverCanvas) return;
252
- if (scrollKeys.has(e.key) || scrollKeys.has(e.code)) {
253
- e.preventDefault();
254
- }
255
  };
256
  window.addEventListener("keydown", onKeyDownCapture, true);
257
 
@@ -260,7 +231,7 @@ export async function initializeViewer(config, instanceId) {
260
  // --- Charge PlayCanvas lib ESM (une par module/instance) ---
261
  if (!pc) {
262
  pc = await import("https://esm.run/playcanvas");
263
- window.pc = pc; // utile pour tooltips.js
264
  }
265
 
266
  // --- Crée l'Application ---
@@ -276,7 +247,7 @@ export async function initializeViewer(config, instanceId) {
276
  opts.graphicsDevice = device;
277
  opts.mouse = new pc.Mouse(canvas);
278
  opts.touch = new pc.TouchDevice(canvas);
279
- opts.keyboard = new pc.Keyboard(canvas); // clavier scoping canvas
280
  opts.componentSystems = [
281
  pc.RenderComponentSystem,
282
  pc.CameraComponentSystem,
@@ -305,11 +276,8 @@ export async function initializeViewer(config, instanceId) {
305
 
306
  // Nettoyage complet
307
  app.on("destroy", () => {
308
- try {
309
- resizeObserver.disconnect();
310
- } catch {}
311
  if (opts.keyboard && opts.keyboard.detach) opts.keyboard.detach();
312
-
313
  window.removeEventListener("keydown", onKeyDownCapture, true);
314
 
315
  canvas.removeEventListener("pointerenter", onPointerEnter);
@@ -323,21 +291,14 @@ export async function initializeViewer(config, instanceId) {
323
 
324
  // --- Enregistre les assets (SAUF orbit script : chargé globalement) ---
325
  const assets = {};
326
-
327
- if (sogsUrl) {
328
- assets.sogs = new pc.Asset("gsplat", "gsplat", { url: sogsUrl });
329
- }
330
-
331
- // Ajoute le GLB d'environnement / présentoir si fourni
332
- if (glbUrl) {
333
- assets.env = new pc.Asset("env", "container", { url: glbUrl });
334
- }
335
 
336
  for (const k in assets) app.assets.add(assets[k]);
337
 
338
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
339
 
340
- // Assure le chargement unique des scripts caméra
341
  await ensureOrbitScriptsLoaded();
342
 
343
  loader.load(() => {
@@ -362,7 +323,7 @@ export async function initializeViewer(config, instanceId) {
362
  receiveShadows: true
363
  });
364
 
365
- // Position/rotation/échelle par défaut (adaptable selon besoin)
366
  envEntity.setLocalPosition(0, 0, 0);
367
  envEntity.setLocalEulerAngles(0, 0, 0);
368
  envEntity.setLocalScale(presentoirScaleX, presentoirScaleY, presentoirScaleZ);
@@ -370,10 +331,13 @@ export async function initializeViewer(config, instanceId) {
370
  app.root.addChild(envEntity);
371
  }
372
 
373
- // Fallback: si pas de gsplat, mais un env GLB existe, utiliser l'env comme focus du viewer
374
- const focusEntity = modelEntity || envEntity;
 
 
 
375
 
376
- // --- Caméra + scripts d’input ---
377
  cameraEntity = new pc.Entity("camera");
378
  cameraEntity.addComponent("camera", {
379
  clearColor: new pc.Color(color_bg[0], color_bg[1], color_bg[2], color_bg[3]),
@@ -381,20 +345,27 @@ export async function initializeViewer(config, instanceId) {
381
  farClip: 100
382
  });
383
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
384
- if (focusEntity) cameraEntity.lookAt(focusEntity.getPosition());
385
  cameraEntity.addComponent("script");
386
 
387
- // Attributs orbitCamera sans distanceMax, avec bbox optionnelle
388
  const orbitAttrs = {
389
- focusEntity: focusEntity || undefined,
 
 
 
390
  inertiaFactor: 0.2,
391
- distanceMin: distanceMin,
392
  pitchAngleMax: maxAngle,
393
  pitchAngleMin: minAngle,
 
 
 
394
  yawAngleMax: maxAzimuth,
395
  yawAngleMin: minAzimuth,
396
- minY: minY,
397
- frameOnStart: false
 
 
398
  };
399
 
400
  // Injecte la BBox uniquement si présente dans le config (évite Infinity explicite)
@@ -413,15 +384,17 @@ export async function initializeViewer(config, instanceId) {
413
  if (Zmin !== undefined) orbitAttrs.Zmin = Zmin;
414
  if (Zmax !== undefined) orbitAttrs.Zmax = Zmax;
415
 
 
 
 
 
 
 
 
416
  cameraEntity.script.create("orbitCamera", { attributes: orbitAttrs });
417
  cameraEntity.script.create("orbitCameraInputMouse");
418
  cameraEntity.script.create("orbitCameraInputTouch");
419
- cameraEntity.script.create("orbitCameraInputKeyboard", {
420
- attributes: {
421
- forwardSpeed: 1.2,
422
- strafeSpeed: 1.2
423
- }
424
- });
425
  app.root.addChild(cameraEntity);
426
 
427
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
@@ -437,19 +410,15 @@ export async function initializeViewer(config, instanceId) {
437
  tooltipsModule.initializeTooltips({
438
  app,
439
  cameraEntity,
440
- modelEntity: focusEntity, // focusEntity pour coller à ce qui est visible
441
  tooltipsUrl: config.tooltips_url,
442
  defaultVisible: !!config.showTooltipsDefault,
443
  moveDuration: config.tooltipMoveDuration || 0.6
444
  });
445
  })
446
- .catch(() => {
447
- /* optional */
448
- });
449
  }
450
- } catch (e) {
451
- /* optional */
452
- }
453
 
454
  viewerInitialized = true;
455
  });
@@ -463,7 +432,7 @@ export function resetViewerCamera() {
463
  try {
464
  if (!cameraEntity || !app) return;
465
 
466
- // Choisir la cible prioritaire : gsplat puis glb, sinon (0,0,0)
467
  const targetEntity = modelEntity || envEntity;
468
  const targetPos = targetEntity ? targetEntity.getPosition().clone() : new pc.Vec3(0, 0, 0);
469
 
@@ -481,10 +450,14 @@ export function resetViewerCamera() {
481
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
482
  cameraEntity.lookAt(targetPos);
483
 
484
- orbitCam.pivotPoint = targetPos.clone();
485
- orbitCam._targetDistance = Math.max(distanceMin, dist);
486
- orbitCam._distance = Math.max(distanceMin, dist);
 
 
 
487
 
 
488
  const rot = tempEnt.getRotation();
489
  const fwd = new pc.Vec3();
490
  rot.transformVector(pc.Vec3.FORWARD, fwd);
 
72
 
73
  window.__PLY_ORBIT_LOADING__ = new Promise((resolve, reject) => {
74
  const s = document.createElement("script");
75
+ // Script caméra libre + collisions (garde le nom public "orbitCamera")
76
  s.src = "https://mikafil-viewer-sgos.static.hf.space/deplacement_dans_env/ctrl_camera_pr_env.js";
77
  s.async = true;
78
  s.onload = () => {
 
96
  let pc;
97
  export let app = null;
98
  let cameraEntity = null;
99
+ let modelEntity = null; // gsplat principal (oeuvre)
100
+ let envEntity = null; // GLB d'environnement / présentoir
101
  let viewerInitialized = false;
102
  let resizeObserver = null;
103
 
 
114
  -------------------------------------------- */
115
 
116
  export async function initializeViewer(config, instanceId) {
117
+ // une seule initialisation par import
118
  if (viewerInitialized) return;
119
 
120
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
 
124
  sogsUrl = config.sogs_json_url || null;
125
  glbUrl = config.glb_url || null;
126
 
127
+ // rétro-compat minZoom => distanceMin (conservé pour compat mais non utilisé par la free cam)
128
  distanceMin = config.minZoom !== undefined
129
+ ? parseFloat(config.minZoom)
130
+ : (config.distanceMin !== undefined ? parseFloat(config.distanceMin) : 1);
 
 
131
 
132
  minAngle = parseFloat(config.minAngle ?? "-45");
133
  maxAngle = parseFloat(config.maxAngle ?? "90");
 
143
  modelRotationY = config.modelRotationY !== undefined ? parseFloat(config.modelRotationY) : 0;
144
  modelRotationZ = config.modelRotationZ !== undefined ? parseFloat(config.modelRotationZ) : 0;
145
 
146
+ // défauts à 1 (et non 0) pour éviter l'invisibilité si manquants
147
  presentoirScaleX = config.presentoirScaleX !== undefined ? parseFloat(config.presentoirScaleX) : 1;
148
  presentoirScaleY = config.presentoirScaleY !== undefined ? parseFloat(config.presentoirScaleY) : 1;
149
  presentoirScaleZ = config.presentoirScaleZ !== undefined ? parseFloat(config.presentoirScaleZ) : 1;
 
180
  canvas.setAttribute("tabindex", "0");
181
  viewerContainer.insertBefore(canvas, progressDialog);
182
 
183
+ // interactions de base (éviter scroll/gestes par défaut sur le canvas)
184
  canvas.style.touchAction = "none";
185
  canvas.style.webkitTouchCallout = "none";
186
  canvas.addEventListener("gesturestart", (e) => e.preventDefault());
 
196
  );
197
  canvas.addEventListener(
198
  "wheel",
199
+ (e) => { e.preventDefault(); },
 
 
200
  { passive: false }
201
  );
202
 
203
  // Bloque le scroll page uniquement quand le pointeur est sur le canvas
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 = () => {
210
  isPointerOverCanvas = false;
211
  if (document.activeElement === canvas) canvas.blur();
212
  };
213
+ const onCanvasBlur = () => { isPointerOverCanvas = false; };
 
 
214
 
215
  canvas.addEventListener("pointerenter", onPointerEnter);
216
  canvas.addEventListener("pointerleave", onPointerLeave);
217
  canvas.addEventListener("mouseenter", onPointerEnter);
218
  canvas.addEventListener("mouseleave", onPointerLeave);
219
  canvas.addEventListener("mousedown", focusCanvas);
220
+ canvas.addEventListener("touchstart", () => { focusCanvas(); }, { passive: false });
 
 
 
 
 
 
221
  canvas.addEventListener("blur", onCanvasBlur);
222
 
223
  const onKeyDownCapture = (e) => {
224
  if (!isPointerOverCanvas) return;
225
+ if (scrollKeys.has(e.key) || scrollKeys.has(e.code)) e.preventDefault();
 
 
226
  };
227
  window.addEventListener("keydown", onKeyDownCapture, true);
228
 
 
231
  // --- Charge PlayCanvas lib ESM (une par module/instance) ---
232
  if (!pc) {
233
  pc = await import("https://esm.run/playcanvas");
234
+ window.pc = pc; // utile pour tooltips.js et debug
235
  }
236
 
237
  // --- Crée l'Application ---
 
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); // scoping clavier au canvas
251
  opts.componentSystems = [
252
  pc.RenderComponentSystem,
253
  pc.CameraComponentSystem,
 
276
 
277
  // Nettoyage complet
278
  app.on("destroy", () => {
279
+ try { resizeObserver.disconnect(); } catch {}
 
 
280
  if (opts.keyboard && opts.keyboard.detach) opts.keyboard.detach();
 
281
  window.removeEventListener("keydown", onKeyDownCapture, true);
282
 
283
  canvas.removeEventListener("pointerenter", onPointerEnter);
 
291
 
292
  // --- Enregistre les assets (SAUF orbit script : chargé globalement) ---
293
  const assets = {};
294
+ if (sogsUrl) assets.sogs = new pc.Asset("gsplat", "gsplat", { url: sogsUrl });
295
+ if (glbUrl) assets.env = new pc.Asset("env", "container", { url: glbUrl });
 
 
 
 
 
 
 
296
 
297
  for (const k in assets) app.assets.add(assets[k]);
298
 
299
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
300
 
301
+ // Assure le chargement unique des scripts de caméra
302
  await ensureOrbitScriptsLoaded();
303
 
304
  loader.load(() => {
 
323
  receiveShadows: true
324
  });
325
 
326
+ // Position/rotation/échelle (adaptables)
327
  envEntity.setLocalPosition(0, 0, 0);
328
  envEntity.setLocalEulerAngles(0, 0, 0);
329
  envEntity.setLocalScale(presentoirScaleX, presentoirScaleY, presentoirScaleZ);
 
331
  app.root.addChild(envEntity);
332
  }
333
 
334
+ // Entité qui sert d'ancrage visuel (lookAt) : l'oeuvre si dispo, sinon l'env
335
+ const focusVisual = modelEntity || envEntity;
336
+
337
+ // Racine de collision pour la free cam : l'environnement GLB de préférence
338
+ const collisionRoot = envEntity || modelEntity || null;
339
 
340
+ // --- Caméra + scripts d’input (free cam + collisions) ---
341
  cameraEntity = new pc.Entity("camera");
342
  cameraEntity.addComponent("camera", {
343
  clearColor: new pc.Color(color_bg[0], color_bg[1], color_bg[2], color_bg[3]),
 
345
  farClip: 100
346
  });
347
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
348
+ if (focusVisual) cameraEntity.lookAt(focusVisual.getPosition());
349
  cameraEntity.addComponent("script");
350
 
351
+ // Prépare les attributs : les champs inconnus sont ignorés par le script
352
  const orbitAttrs = {
353
+ // Collision root : active le blocage contre le GLB
354
+ focusEntity: collisionRoot || undefined,
355
+
356
+ // Inertie/angles (pitch clampé), yaw libre
357
  inertiaFactor: 0.2,
 
358
  pitchAngleMax: maxAngle,
359
  pitchAngleMin: minAngle,
360
+
361
+ // Compat (ignorés par le free cam, mais gardés pour ne pas casser la config)
362
+ distanceMin: distanceMin,
363
  yawAngleMax: maxAzimuth,
364
  yawAngleMin: minAzimuth,
365
+ frameOnStart: false,
366
+
367
+ // Contraintes position
368
+ minY: minY
369
  };
370
 
371
  // Injecte la BBox uniquement si présente dans le config (évite Infinity explicite)
 
384
  if (Zmin !== undefined) orbitAttrs.Zmin = Zmin;
385
  if (Zmax !== undefined) orbitAttrs.Zmax = Zmax;
386
 
387
+ // Paramètres collision optionnels depuis la config (sinon valeurs par défaut du script)
388
+ if (config.collisionRadius !== undefined) orbitAttrs.collisionRadius = parseFloat(config.collisionRadius);
389
+ if (config.collisionEpsilon !== undefined) orbitAttrs.collisionEpsilon = parseFloat(config.collisionEpsilon);
390
+ if (config.moveSpeed !== undefined) orbitAttrs.moveSpeed = parseFloat(config.moveSpeed);
391
+ if (config.strafeSpeed !== undefined) orbitAttrs.strafeSpeed = parseFloat(config.strafeSpeed);
392
+ if (config.dollySpeed !== undefined) orbitAttrs.dollySpeed = parseFloat(config.dollySpeed);
393
+
394
  cameraEntity.script.create("orbitCamera", { attributes: orbitAttrs });
395
  cameraEntity.script.create("orbitCameraInputMouse");
396
  cameraEntity.script.create("orbitCameraInputTouch");
397
+ cameraEntity.script.create("orbitCameraInputKeyboard", { attributes: { acceleration: 1.0 } });
 
 
 
 
 
398
  app.root.addChild(cameraEntity);
399
 
400
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
 
410
  tooltipsModule.initializeTooltips({
411
  app,
412
  cameraEntity,
413
+ modelEntity: focusVisual,
414
  tooltipsUrl: config.tooltips_url,
415
  defaultVisible: !!config.showTooltipsDefault,
416
  moveDuration: config.tooltipMoveDuration || 0.6
417
  });
418
  })
419
+ .catch(() => { /* optional */ });
 
 
420
  }
421
+ } catch (e) { /* optional */ }
 
 
422
 
423
  viewerInitialized = true;
424
  });
 
432
  try {
433
  if (!cameraEntity || !app) return;
434
 
435
+ // cible visuelle : gsplat prioritaire, sinon glb, sinon (0,0,0)
436
  const targetEntity = modelEntity || envEntity;
437
  const targetPos = targetEntity ? targetEntity.getPosition().clone() : new pc.Vec3(0, 0, 0);
438
 
 
450
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
451
  cameraEntity.lookAt(targetPos);
452
 
453
+ // Ces champs existent dans la version "orbit" historique ; notre free cam les ignore,
454
+ // mais on conserve l'initialisation pour compat.
455
+ if (orbitCam) {
456
+ orbitCam._targetDistance = Math.max(distanceMin, dist);
457
+ orbitCam._distance = Math.max(distanceMin, dist);
458
+ }
459
 
460
+ // Recalcule yaw/pitch cibles pour aligner l'orientation
461
  const rot = tempEnt.getRotation();
462
  const fwd = new pc.Vec3();
463
  rot.transformVector(pc.Vec3.FORWARD, fwd);