MikaFil commited on
Commit
0ab9780
·
verified ·
1 Parent(s): 32d74ef

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +289 -3
viewer.js CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  /* -------------------------------------------
2
  Utils
3
  (les helpers image ne sont plus nécessaires pour .sog,
@@ -7,18 +10,102 @@
7
  async function loadImageAsTexture(url, app) {
8
  return new Promise((resolve, reject) => {
9
  const img = new window.Image();
 
 
 
 
 
 
 
 
 
 
 
 
10
  });
11
  }
 
12
  // Patch global Image -> force CORS (sans incidence pour .sog)
13
  (function () {
14
  const OriginalImage = window.Image;
15
  window.Image = function (...args) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  let modelEntity = null;
17
  let viewerInitialized = false;
18
  let resizeObserver = null;
19
 
20
  // paramètres courants de l'instance
21
  let chosenCameraX, chosenCameraY, chosenCameraZ;
 
 
 
22
  let sogUrl, glbUrl, presentoirUrl;
23
  let color_bg_hex, color_bg, espace_expo_bool;
24
 
@@ -39,13 +126,152 @@ export async function initializeViewer(config, instanceId) {
39
  sogUrl = config.sog_url || config.sogs_json_url;
40
 
41
  glbUrl =
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
43
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
44
 
45
-
46
  // --- Prépare le canvas unique à cette instance ---
47
  const canvasId = "canvas-" + instanceId;
48
  const progressDialog = document.getElementById("progress-dialog-" + instanceId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
50
  antialias: false
51
  });
@@ -59,6 +285,9 @@ export async function initializeViewer(config, instanceId) {
59
  opts.componentSystems = [
60
  pc.RenderComponentSystem,
61
  pc.CameraComponentSystem,
 
 
 
62
  pc.CollisionComponentSystem,
63
  pc.RigidbodyComponentSystem
64
  ];
@@ -69,7 +298,6 @@ export async function initializeViewer(config, instanceId) {
69
  app.setCanvasFillMode(pc.FILLMODE_NONE);
70
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
71
 
72
-
73
  resizeObserver = new ResizeObserver((entries) => {
74
  entries.forEach((entry) => {
75
  app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
@@ -80,8 +308,10 @@ export async function initializeViewer(config, instanceId) {
80
  window.addEventListener("resize", () =>
81
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight)
82
  );
 
83
  // Nettoyage complet
84
  app.on("destroy", () => {
 
85
  resizeObserver.disconnect();
86
  } catch {}
87
  if (opts.keyboard && opts.keyboard.detach) opts.keyboard.detach();
@@ -89,8 +319,14 @@ export async function initializeViewer(config, instanceId) {
89
  window.removeEventListener("keydown", onKeyDownCapture, true);
90
 
91
  canvas.removeEventListener("pointerenter", onPointerEnter);
 
 
 
 
 
92
  canvas.removeEventListener("blur", onCanvasBlur);
93
  });
 
94
  // --- Enregistre les assets ---
95
  // IMPORTANT : pour .sog on déclare un asset de type "gsplat" avec l'URL .sog
96
  const assets = {
@@ -101,6 +337,7 @@ export async function initializeViewer(config, instanceId) {
101
  for (const k in assets) app.assets.add(assets[k]);
102
 
103
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
 
104
  // Assure orbit-camera.js une seule fois
105
  await ensureOrbitScriptsLoaded();
106
 
@@ -147,6 +384,7 @@ export async function initializeViewer(config, instanceId) {
147
  ////// MODIFIE A LA MANO FAIRE GAFFE //////
148
  glbEntity.setLocalScale(10, 10, 10);
149
  }
 
150
  // --- Caméra + scripts d’input (disponibles car orbit chargé globalement) ---
151
  cameraEntity = new pc.Entity("camera");
152
  cameraEntity.addComponent("camera", {
@@ -206,8 +444,56 @@ export async function initializeViewer(config, instanceId) {
206
  } catch (e) {
207
  /* optional */
208
  }
 
209
  viewerInitialized = true;
210
  });
211
  }
212
 
213
- /* -------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // viewer.js
2
+ // ==============================
3
+
4
  /* -------------------------------------------
5
  Utils
6
  (les helpers image ne sont plus nécessaires pour .sog,
 
10
  async function loadImageAsTexture(url, app) {
11
  return new Promise((resolve, reject) => {
12
  const img = new window.Image();
13
+ img.crossOrigin = "anonymous";
14
+ img.onload = function () {
15
+ const tex = new pc.Texture(app.graphicsDevice, {
16
+ width: img.width,
17
+ height: img.height,
18
+ format: pc.PIXELFORMAT_R8_G8_B8_A8
19
+ });
20
+ tex.setSource(img);
21
+ resolve(tex);
22
+ };
23
+ img.onerror = reject;
24
+ img.src = url;
25
  });
26
  }
27
+
28
  // Patch global Image -> force CORS (sans incidence pour .sog)
29
  (function () {
30
  const OriginalImage = window.Image;
31
  window.Image = function (...args) {
32
+ const img = new OriginalImage(...args);
33
+ img.crossOrigin = "anonymous";
34
+ return img;
35
+ };
36
+ })();
37
+
38
+ function hexToRgbaArray(hex) {
39
+ try {
40
+ hex = String(hex || "").replace("#", "");
41
+ if (hex.length === 6) hex += "FF";
42
+ if (hex.length !== 8) return [1, 1, 1, 1];
43
+ const num = parseInt(hex, 16);
44
+ return [
45
+ ((num >> 24) & 0xff) / 255,
46
+ ((num >> 16) & 0xff) / 255,
47
+ ((num >> 8) & 0xff) / 255,
48
+ (num & 0xff) / 255
49
+ ];
50
+ } catch (e) {
51
+ console.warn("hexToRgbaArray error:", e);
52
+ return [1, 1, 1, 1];
53
+ }
54
+ }
55
+
56
+ // Parcours récursif d'une hiérarchie d'entités
57
+ function traverse(entity, callback) {
58
+ callback(entity);
59
+ if (entity.children) {
60
+ entity.children.forEach((child) => traverse(child, callback));
61
+ }
62
+ }
63
+
64
+ /* -------------------------------------------
65
+ Chargement unique de orbit-camera.js
66
+ -------------------------------------------- */
67
+
68
+ async function ensureOrbitScriptsLoaded() {
69
+ if (window.__PLY_ORBIT_LOADED__) return;
70
+ if (window.__PLY_ORBIT_LOADING__) {
71
+ await window.__PLY_ORBIT_LOADING__;
72
+ return;
73
+ }
74
+
75
+ window.__PLY_ORBIT_LOADING__ = new Promise((resolve, reject) => {
76
+ const s = document.createElement("script");
77
+ s.src = "https://mikafil-viewer-sgos.static.hf.space/orbit-camera.js";
78
+ s.async = true;
79
+ s.onload = () => {
80
+ window.__PLY_ORBIT_LOADED__ = true;
81
+ resolve();
82
+ };
83
+ s.onerror = (e) => {
84
+ console.error("[viewer.js] Failed to load orbit-camera.js", e);
85
+ reject(e);
86
+ };
87
+ document.head.appendChild(s);
88
+ });
89
+
90
+ await window.__PLY_ORBIT_LOADING__;
91
+ }
92
+
93
+ /* -------------------------------------------
94
+ State (par module = par instance importée)
95
+ -------------------------------------------- */
96
+
97
+ let pc;
98
+ export let app = null;
99
+ let cameraEntity = null;
100
  let modelEntity = null;
101
  let viewerInitialized = false;
102
  let resizeObserver = null;
103
 
104
  // paramètres courants de l'instance
105
  let chosenCameraX, chosenCameraY, chosenCameraZ;
106
+ let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minY;
107
+ let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
108
+ let presentoirScaleX, presentoirScaleY, presentoirScaleZ;
109
  let sogUrl, glbUrl, presentoirUrl;
110
  let color_bg_hex, color_bg, espace_expo_bool;
111
 
 
126
  sogUrl = config.sog_url || config.sogs_json_url;
127
 
128
  glbUrl =
129
+ config.glb_url !== undefined
130
+ ? config.glb_url
131
+ : "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
132
+
133
+ presentoirUrl =
134
+ config.presentoir_url !== undefined
135
+ ? config.presentoir_url
136
+ : "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/ressources/espace_expo/sol_blanc_2.glb";
137
+
138
+ minZoom = parseFloat(config.minZoom || "1");
139
+ maxZoom = parseFloat(config.maxZoom || "20");
140
+ minAngle = parseFloat(config.minAngle || "-2000");
141
+ maxAngle = parseFloat(config.maxAngle || "2000");
142
+ minAzimuth = config.minAzimuth !== undefined ? parseFloat(config.minAzimuth) : -360;
143
+ maxAzimuth = config.maxAzimuth !== undefined ? parseFloat(config.maxAzimuth) : 360;
144
+ minY = config.minY !== undefined ? parseFloat(config.minY) : 0;
145
+
146
+ modelX = config.modelX !== undefined ? parseFloat(config.modelX) : 0;
147
+ modelY = config.modelY !== undefined ? parseFloat(config.modelY) : 0;
148
+ modelZ = config.modelZ !== undefined ? parseFloat(config.modelZ) : 0;
149
+ modelScale = config.modelScale !== undefined ? parseFloat(config.modelScale) : 1;
150
+ modelRotationX = config.modelRotationX !== undefined ? parseFloat(config.modelRotationX) : 0;
151
+ modelRotationY = config.modelRotationY !== undefined ? parseFloat(config.modelRotationY) : 0;
152
+ modelRotationZ = config.modelRotationZ !== undefined ? parseFloat(config.modelRotationZ) : 0;
153
+
154
+ presentoirScaleX = config.presentoirScaleX !== undefined ? parseFloat(config.presentoirScaleX) : 0;
155
+ presentoirScaleY = config.presentoirScaleY !== undefined ? parseFloat(config.presentoirScaleY) : 0;
156
+ presentoirScaleZ = config.presentoirScaleZ !== undefined ? parseFloat(config.presentoirScaleZ) : 0;
157
+
158
+ const cameraX = config.cameraX !== undefined ? parseFloat(config.cameraX) : 0;
159
+ const cameraY = config.cameraY !== undefined ? parseFloat(config.cameraY) : 2;
160
+ const cameraZ = config.cameraZ !== undefined ? parseFloat(config.cameraZ) : 5;
161
+
162
+ const cameraXPhone = config.cameraXPhone !== undefined ? parseFloat(config.cameraXPhone) : cameraX;
163
+ const cameraYPhone = config.cameraYPhone !== undefined ? parseFloat(config.cameraYPhone) : cameraY;
164
+ const cameraZPhone = config.cameraZPhone !== undefined ? parseFloat(config.cameraZPhone) : cameraZ * 1.5;
165
+
166
+ color_bg_hex = config.canvas_background !== undefined ? config.canvas_background : "#FFFFFF";
167
+ espace_expo_bool = config.espace_expo_bool !== undefined ? config.espace_expo_bool : false;
168
+ color_bg = hexToRgbaArray(color_bg_hex);
169
+
170
+ chosenCameraX = isMobile ? cameraXPhone : cameraX;
171
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
172
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
173
 
 
174
  // --- Prépare le canvas unique à cette instance ---
175
  const canvasId = "canvas-" + instanceId;
176
  const progressDialog = document.getElementById("progress-dialog-" + instanceId);
177
+ const viewerContainer = document.getElementById("viewer-container-" + instanceId);
178
+
179
+ const old = document.getElementById(canvasId);
180
+ if (old) old.remove();
181
+
182
+ const canvas = document.createElement("canvas");
183
+ canvas.id = canvasId;
184
+ canvas.className = "ply-canvas";
185
+ canvas.style.width = "100%";
186
+ canvas.style.height = "100%";
187
+ canvas.setAttribute("tabindex", "0");
188
+ viewerContainer.insertBefore(canvas, progressDialog);
189
+
190
+ // interactions de base
191
+ canvas.style.touchAction = "none";
192
+ canvas.style.webkitTouchCallout = "none";
193
+ canvas.addEventListener("gesturestart", (e) => e.preventDefault());
194
+ canvas.addEventListener("gesturechange", (e) => e.preventDefault());
195
+ canvas.addEventListener("gestureend", (e) => e.preventDefault());
196
+ canvas.addEventListener("dblclick", (e) => e.preventDefault());
197
+ canvas.addEventListener(
198
+ "touchstart",
199
+ (e) => {
200
+ if (e.touches.length > 1) e.preventDefault();
201
+ },
202
+ { passive: false }
203
+ );
204
+ canvas.addEventListener(
205
+ "wheel",
206
+ (e) => {
207
+ e.preventDefault();
208
+ },
209
+ { passive: false }
210
+ );
211
+
212
+ // Bloque le scroll page uniquement quand le pointeur est sur le canvas
213
+ const scrollKeys = new Set([
214
+ "ArrowUp",
215
+ "ArrowDown",
216
+ "ArrowLeft",
217
+ "ArrowRight",
218
+ "PageUp",
219
+ "PageDown",
220
+ "Home",
221
+ "End",
222
+ " ",
223
+ "Space",
224
+ "Spacebar"
225
+ ]);
226
+ let isPointerOverCanvas = false;
227
+ const focusCanvas = () => canvas.focus({ preventScroll: true });
228
+
229
+ const onPointerEnter = () => {
230
+ isPointerOverCanvas = true;
231
+ focusCanvas();
232
+ };
233
+ const onPointerLeave = () => {
234
+ isPointerOverCanvas = false;
235
+ if (document.activeElement === canvas) canvas.blur();
236
+ };
237
+ const onCanvasBlur = () => {
238
+ isPointerOverCanvas = false;
239
+ };
240
+
241
+ canvas.addEventListener("pointerenter", onPointerEnter);
242
+ canvas.addEventListener("pointerleave", onPointerLeave);
243
+ canvas.addEventListener("mouseenter", onPointerEnter);
244
+ canvas.addEventListener("mouseleave", onPointerLeave);
245
+ canvas.addEventListener("mousedown", focusCanvas);
246
+ canvas.addEventListener(
247
+ "touchstart",
248
+ () => {
249
+ focusCanvas();
250
+ },
251
+ { passive: false }
252
+ );
253
+ canvas.addEventListener("blur", onCanvasBlur);
254
+
255
+ const onKeyDownCapture = (e) => {
256
+ if (!isPointerOverCanvas) return;
257
+ if (scrollKeys.has(e.key) || scrollKeys.has(e.code)) {
258
+ e.preventDefault();
259
+ }
260
+ };
261
+ window.addEventListener("keydown", onKeyDownCapture, true);
262
+
263
+ progressDialog.style.display = "block";
264
+
265
+ // --- Charge PlayCanvas lib ESM (une par module/instance) ---
266
+ if (!pc) {
267
+ pc = await import("https://esm.run/playcanvas");
268
+ window.pc = pc; // utiles pour tooltips.js
269
+ }
270
+
271
+ // --- Crée l'Application ---
272
+ const device = await pc.createGraphicsDevice(canvas, {
273
+ deviceTypes: ["webgl2"],
274
+ glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
275
  twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
276
  antialias: false
277
  });
 
285
  opts.componentSystems = [
286
  pc.RenderComponentSystem,
287
  pc.CameraComponentSystem,
288
+ pc.LightComponentSystem,
289
+ pc.ScriptComponentSystem,
290
+ pc.GSplatComponentSystem,
291
  pc.CollisionComponentSystem,
292
  pc.RigidbodyComponentSystem
293
  ];
 
298
  app.setCanvasFillMode(pc.FILLMODE_NONE);
299
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
300
 
 
301
  resizeObserver = new ResizeObserver((entries) => {
302
  entries.forEach((entry) => {
303
  app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
 
308
  window.addEventListener("resize", () =>
309
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight)
310
  );
311
+
312
  // Nettoyage complet
313
  app.on("destroy", () => {
314
+ try {
315
  resizeObserver.disconnect();
316
  } catch {}
317
  if (opts.keyboard && opts.keyboard.detach) opts.keyboard.detach();
 
319
  window.removeEventListener("keydown", onKeyDownCapture, true);
320
 
321
  canvas.removeEventListener("pointerenter", onPointerEnter);
322
+ canvas.removeEventListener("pointerleave", onPointerLeave);
323
+ canvas.removeEventListener("mouseenter", onPointerEnter);
324
+ canvas.removeEventListener("mouseleave", onPointerLeave);
325
+ canvas.removeEventListener("mousedown", focusCanvas);
326
+ canvas.removeEventListener("touchstart", focusCanvas);
327
  canvas.removeEventListener("blur", onCanvasBlur);
328
  });
329
+
330
  // --- Enregistre les assets ---
331
  // IMPORTANT : pour .sog on déclare un asset de type "gsplat" avec l'URL .sog
332
  const assets = {
 
337
  for (const k in assets) app.assets.add(assets[k]);
338
 
339
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
340
+
341
  // Assure orbit-camera.js une seule fois
342
  await ensureOrbitScriptsLoaded();
343
 
 
384
  ////// MODIFIE A LA MANO FAIRE GAFFE //////
385
  glbEntity.setLocalScale(10, 10, 10);
386
  }
387
+
388
  // --- Caméra + scripts d’input (disponibles car orbit chargé globalement) ---
389
  cameraEntity = new pc.Entity("camera");
390
  cameraEntity.addComponent("camera", {
 
444
  } catch (e) {
445
  /* optional */
446
  }
447
+
448
  viewerInitialized = true;
449
  });
450
  }
451
 
452
+ /* -------------------------------------------
453
+ Reset caméra (API)
454
+ -------------------------------------------- */
455
+
456
+ export function resetViewerCamera() {
457
+ try {
458
+ if (!cameraEntity || !modelEntity || !app) return;
459
+ const orbitCam = cameraEntity.script.orbitCamera;
460
+ if (!orbitCam) return;
461
+
462
+ const modelPos = modelEntity.getPosition();
463
+ const tempEnt = new pc.Entity();
464
+ tempEnt.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
465
+ tempEnt.lookAt(modelPos);
466
+
467
+ const dist = new pc.Vec3()
468
+ .sub2(new pc.Vec3(chosenCameraX, chosenCameraY, chosenCameraZ), modelPos)
469
+ .length();
470
+
471
+ cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
472
+ cameraEntity.lookAt(modelPos);
473
+
474
+ orbitCam.pivotPoint = modelPos.clone();
475
+ orbitCam._targetDistance = dist;
476
+ orbitCam._distance = dist;
477
+
478
+ const rot = tempEnt.getRotation();
479
+ const fwd = new pc.Vec3();
480
+ rot.transformVector(pc.Vec3.FORWARD, fwd);
481
+
482
+ const yaw = Math.atan2(-fwd.x, -fwd.z) * pc.math.RAD_TO_DEG;
483
+ const yawQuat = new pc.Quat().setFromEulerAngles(0, -yaw, 0);
484
+ const rotNoYaw = new pc.Quat().mul2(yawQuat, rot);
485
+ const fNoYaw = new pc.Vec3();
486
+ rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
487
+ const pitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
488
+
489
+ orbitCam._targetYaw = yaw;
490
+ orbitCam._yaw = yaw;
491
+ orbitCam._targetPitch = pitch;
492
+ orbitCam._pitch = pitch;
493
+ if (orbitCam._updatePosition) orbitCam._updatePosition();
494
+
495
+ tempEnt.destroy();
496
+ } catch (e) {
497
+ console.error("[viewer.js] resetViewerCamera error:", e);
498
+ }
499
+ }