MikaFil commited on
Commit
ef2a675
·
verified ·
1 Parent(s): 678cb07

Update viewer_ar.js

Browse files
Files changed (1) hide show
  1. viewer_ar.js +41 -53
viewer_ar.js CHANGED
@@ -1,6 +1,6 @@
1
  /* script_ar.js — AR PlayCanvas + GLB HuggingFace
2
  - Chargeur PlayCanvas robuste (ESM -> UMD avec fallbacks + timeout)
3
- - WebXR AR Hit Test, placement auto, drag + rotation Y via SLIDER (DOM Overlay)
4
  - Aucune dépendance externe autre que PlayCanvas et le GLB
5
  */
6
 
@@ -91,31 +91,29 @@
91
  user-select: none;
92
  pointer-events: none;
93
  }
94
- /* Racine DOM Overlay (tout ce qui doit rester visible en AR doit être descendant) */
95
- #xr-overlay-root {
96
- position: fixed;
97
- inset: 0;
98
- z-index: 10000; /* au-dessus du canvas XR */
99
- pointer-events: none; /* par défaut, mais les enfants peuvent réactiver */
100
- }
101
  /* --- UI Slider (DOM overlay) --- */
102
  .ar-ui {
103
- position: absolute;
104
  right: 12px;
105
  top: 50%;
106
  transform: translateY(-50%);
 
107
  background: rgba(0,0,0,0.55);
108
  color: #fff;
109
  padding: 12px 10px;
110
  border-radius: 12px;
111
  font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
112
- pointer-events: auto; /* capter interactions pendant AR */
113
  width: 56px;
114
  display: flex; flex-direction: column; align-items: center; gap: 8px;
115
  box-shadow: 0 6px 18px rgba(0,0,0,.25);
116
  backdrop-filter: blur(4px);
117
  }
118
- .ar-ui .label { font-size: 12px; text-align: center; opacity: 0.9; }
 
 
 
 
119
  /* Slider vertical (on le tourne) */
120
  .ar-ui input[type="range"].rotY {
121
  -webkit-appearance: none;
@@ -140,7 +138,10 @@
140
  background: rgba(255,255,255,.6);
141
  border-radius: 4px;
142
  }
143
- .ar-ui .val { font-size: 11px; opacity: 0.85; }
 
 
 
144
  `;
145
  const styleTag = document.createElement("style");
146
  styleTag.textContent = css;
@@ -161,6 +162,7 @@
161
  if (!canvas) {
162
  canvas = document.createElement("canvas");
163
  canvas.id = "application-canvas";
 
164
  canvas.style.width = "100%";
165
  canvas.style.height = "100%";
166
  document.body.appendChild(canvas);
@@ -168,19 +170,9 @@
168
  return canvas;
169
  }
170
 
171
- function ensureOverlayRoot() {
172
- let root = document.getElementById("xr-overlay-root");
173
- if (!root) {
174
- root = document.createElement("div");
175
- root.id = "xr-overlay-root";
176
- document.body.appendChild(root);
177
- }
178
- return root;
179
- }
180
-
181
- // Crée le panneau slider DANS la racine d’overlay
182
- function ensureSliderUI(overlayRoot) {
183
- let panel = overlayRoot.querySelector(".ar-ui");
184
  if (panel) return panel;
185
  panel = document.createElement("div");
186
  panel.className = "ar-ui";
@@ -189,7 +181,7 @@
189
  <input class="rotY" id="ar-rotY" type="range" min="0" max="360" step="1" value="0" />
190
  <div class="val" id="ar-rotY-val">0°</div>
191
  `;
192
- overlayRoot.appendChild(panel);
193
  return panel;
194
  }
195
 
@@ -204,6 +196,7 @@
204
  message("Impossible de charger PlayCanvas (réseau/CDN). Réessaie plus tard.");
205
  return;
206
  }
 
207
  initARApp();
208
  })();
209
 
@@ -211,10 +204,9 @@
211
  // 5) App AR PlayCanvas
212
  // =========================
213
  function initARApp() {
214
- const pc = window.pc;
215
  const canvas = ensureCanvas();
216
- const overlayRoot = ensureOverlayRoot(); // <-- racine DOM overlay
217
- const uiPanel = ensureSliderUI(overlayRoot);
218
  const rotYInput = uiPanel.querySelector("#ar-rotY");
219
  const rotYVal = uiPanel.querySelector("#ar-rotY-val");
220
 
@@ -287,6 +279,7 @@
287
  rotationYDeg = norm360(deg);
288
  const eul = modelRoot.getEulerAngles();
289
  modelRoot.setEulerAngles(eul.x, rotationYDeg, eul.z);
 
290
  rotYInput.value = String(Math.round(rotationYDeg));
291
  rotYVal.textContent = `${Math.round(rotationYDeg)}°`;
292
  };
@@ -303,6 +296,8 @@
303
  receiveShadows: false
304
  });
305
  modelRoot.addChild(instance);
 
 
306
  modelRoot.setLocalScale(0.2, 0.2, 0.2);
307
 
308
  modelLoaded = true;
@@ -315,16 +310,16 @@
315
  return;
316
  }
317
 
318
- // --- Démarrage AR (DOM Overlay assuré) ---
319
  const activateAR = () => {
320
  if (!app.xr.isAvailable(pc.XRTYPE_AR)) {
321
  message("AR immersive indisponible sur cet appareil.");
322
  return;
323
  }
324
  camera.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
325
- // IMPORTANT : tout élément DOM que tu veux voir en AR doit être descendant de 'overlayRoot'
326
- domOverlay: { root: overlayRoot },
327
- requiredFeatures: ["hit-test", "dom-overlay"], // on exige le DOM overlay
328
  optionalFeatures: ["plane-detection"],
329
  callback: (err) => {
330
  if (err) message(`Échec du démarrage AR : ${err.message}`);
@@ -366,11 +361,13 @@
366
  if (modelLoaded && !placedOnce) {
367
  modelRoot.enabled = true;
368
  modelRoot.setPosition(pos);
 
369
  const euler = new pc.Vec3();
370
  rot.getEulerAngles(euler);
371
  rotationYDeg = norm360(euler.y);
372
  applyRotationY(rotationYDeg);
373
  placedOnce = true;
 
374
  rotYInput.disabled = false;
375
  message("Objet placé. Glissez pour déplacer, tournez-le avec le slider →");
376
  }
@@ -379,24 +376,22 @@
379
  });
380
  });
381
 
382
- // --- Déplacement (drag) via input XR ---
383
  let isDragging = false;
384
- const activeTransientSources = new Set();
385
- function cancelAllTransientHitTests() {
386
- activeTransientSources.forEach(src => { try { src.remove && src.remove(); } catch(_) {} });
387
- activeTransientSources.clear();
388
- isDragging = false;
389
- }
390
 
 
391
  app.xr.input.on("add", (inputSource) => {
392
  inputSource.on("selectstart", () => {
393
  if (!placedOnce || !modelLoaded) return;
 
394
  inputSource.hitTestStart({
395
  entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
396
  callback: (err, transientSource) => {
397
  if (err) return;
398
  isDragging = true;
399
- activeTransientSources.add(transientSource);
400
 
401
  transientSource.on("result", (pos /*, rot */) => {
402
  if (!isDragging) return;
@@ -404,25 +399,20 @@
404
  });
405
 
406
  transientSource.once("remove", () => {
407
- activeTransientSources.delete(transientSource);
408
- if (activeTransientSources.size === 0) isDragging = false;
409
  });
410
  }
411
  });
412
  });
413
 
414
  inputSource.on("selectend", () => {
415
- cancelAllTransientHitTests();
416
  });
417
  });
418
 
419
  // ❌ Rotation tactile 2 doigts — SUPPRIMÉE
420
 
421
- // Souris : déplacement (clic gauche) & rotation (clic droit ou Shift+clic gauche)
422
- let rotateMode = false;
423
- let lastMouseX = 0;
424
- const ROTATE_SENSITIVITY = 0.25; // degrés / pixel approx.
425
-
426
  app.mouse.on("mousedown", (e) => {
427
  if (!app.xr.active || !placedOnce) return;
428
 
@@ -454,6 +444,7 @@
454
  rotateMode = false;
455
  });
456
 
 
457
  window.addEventListener("contextmenu", (e) => e.preventDefault());
458
 
459
  // --- Slider rotation (DOM overlay) ---
@@ -466,8 +457,6 @@
466
 
467
  // --- Événements AR globaux ---
468
  app.xr.on("start", () => {
469
- // s'assurer que l'overlay (et donc le slider) reste visible en AR
470
- overlayRoot.style.display = "block";
471
  message("Session AR démarrée. Visez le sol pour détecter un plan…");
472
  reticle.enabled = true;
473
  });
@@ -477,8 +466,7 @@
477
  reticle.enabled = false;
478
  isDragging = false;
479
  rotateMode = false;
480
- rotYInput.disabled = !placedOnce;
481
- // on garde l'overlay affiché hors AR aussi
482
  });
483
 
484
  app.xr.on(`available:${pc.XRTYPE_AR}`, (available) => {
 
1
  /* script_ar.js — AR PlayCanvas + GLB HuggingFace
2
  - Chargeur PlayCanvas robuste (ESM -> UMD avec fallbacks + timeout)
3
+ - WebXR AR Hit Test, placement auto, drag + rotation Y via SLIDER
4
  - Aucune dépendance externe autre que PlayCanvas et le GLB
5
  */
6
 
 
91
  user-select: none;
92
  pointer-events: none;
93
  }
 
 
 
 
 
 
 
94
  /* --- UI Slider (DOM overlay) --- */
95
  .ar-ui {
96
+ position: fixed;
97
  right: 12px;
98
  top: 50%;
99
  transform: translateY(-50%);
100
+ z-index: 10000;
101
  background: rgba(0,0,0,0.55);
102
  color: #fff;
103
  padding: 12px 10px;
104
  border-radius: 12px;
105
  font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
106
+ pointer-events: auto;
107
  width: 56px;
108
  display: flex; flex-direction: column; align-items: center; gap: 8px;
109
  box-shadow: 0 6px 18px rgba(0,0,0,.25);
110
  backdrop-filter: blur(4px);
111
  }
112
+ .ar-ui .label {
113
+ font-size: 12px;
114
+ text-align: center;
115
+ opacity: 0.9;
116
+ }
117
  /* Slider vertical (on le tourne) */
118
  .ar-ui input[type="range"].rotY {
119
  -webkit-appearance: none;
 
138
  background: rgba(255,255,255,.6);
139
  border-radius: 4px;
140
  }
141
+ .ar-ui .val {
142
+ font-size: 11px;
143
+ opacity: 0.85;
144
+ }
145
  `;
146
  const styleTag = document.createElement("style");
147
  styleTag.textContent = css;
 
162
  if (!canvas) {
163
  canvas = document.createElement("canvas");
164
  canvas.id = "application-canvas";
165
+ // optionnel: force plein écran si aucun style
166
  canvas.style.width = "100%";
167
  canvas.style.height = "100%";
168
  document.body.appendChild(canvas);
 
170
  return canvas;
171
  }
172
 
173
+ // Crée le panneau slider (DOM overlay)
174
+ function ensureSliderUI() {
175
+ let panel = document.querySelector(".ar-ui");
 
 
 
 
 
 
 
 
 
 
176
  if (panel) return panel;
177
  panel = document.createElement("div");
178
  panel.className = "ar-ui";
 
181
  <input class="rotY" id="ar-rotY" type="range" min="0" max="360" step="1" value="0" />
182
  <div class="val" id="ar-rotY-val">0°</div>
183
  `;
184
+ document.body.appendChild(panel);
185
  return panel;
186
  }
187
 
 
196
  message("Impossible de charger PlayCanvas (réseau/CDN). Réessaie plus tard.");
197
  return;
198
  }
199
+ // PlayCanvas dispo -> démarrer
200
  initARApp();
201
  })();
202
 
 
204
  // 5) App AR PlayCanvas
205
  // =========================
206
  function initARApp() {
207
+ const pc = window.pc; // alias
208
  const canvas = ensureCanvas();
209
+ const uiPanel = ensureSliderUI();
 
210
  const rotYInput = uiPanel.querySelector("#ar-rotY");
211
  const rotYVal = uiPanel.querySelector("#ar-rotY-val");
212
 
 
279
  rotationYDeg = norm360(deg);
280
  const eul = modelRoot.getEulerAngles();
281
  modelRoot.setEulerAngles(eul.x, rotationYDeg, eul.z);
282
+ // sync UI
283
  rotYInput.value = String(Math.round(rotationYDeg));
284
  rotYVal.textContent = `${Math.round(rotationYDeg)}°`;
285
  };
 
296
  receiveShadows: false
297
  });
298
  modelRoot.addChild(instance);
299
+
300
+ // Ajuste si besoin selon ton modèle
301
  modelRoot.setLocalScale(0.2, 0.2, 0.2);
302
 
303
  modelLoaded = true;
 
310
  return;
311
  }
312
 
313
+ // --- Démarrage AR ---
314
  const activateAR = () => {
315
  if (!app.xr.isAvailable(pc.XRTYPE_AR)) {
316
  message("AR immersive indisponible sur cet appareil.");
317
  return;
318
  }
319
  camera.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
320
+ // DOM overlay pour pouvoir utiliser le slider en AR
321
+ domOverlay: { root: document.body },
322
+ requiredFeatures: ["hit-test", "dom-overlay"],
323
  optionalFeatures: ["plane-detection"],
324
  callback: (err) => {
325
  if (err) message(`Échec du démarrage AR : ${err.message}`);
 
361
  if (modelLoaded && !placedOnce) {
362
  modelRoot.enabled = true;
363
  modelRoot.setPosition(pos);
364
+ // Conserver yaw initiale détectée
365
  const euler = new pc.Vec3();
366
  rot.getEulerAngles(euler);
367
  rotationYDeg = norm360(euler.y);
368
  applyRotationY(rotationYDeg);
369
  placedOnce = true;
370
+ // activer le slider
371
  rotYInput.disabled = false;
372
  message("Objet placé. Glissez pour déplacer, tournez-le avec le slider →");
373
  }
 
376
  });
377
  });
378
 
379
+ // --- Interactions (déplacement & rotation souris seulement) ---
380
  let isDragging = false;
381
+ let rotateMode = false;
382
+ let lastMouseX = 0;
383
+ const ROTATE_SENSITIVITY = 0.25; // degrés / pixel approx.
 
 
 
384
 
385
+ // Déplacement (input XR : maintien/drag)
386
  app.xr.input.on("add", (inputSource) => {
387
  inputSource.on("selectstart", () => {
388
  if (!placedOnce || !modelLoaded) return;
389
+
390
  inputSource.hitTestStart({
391
  entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
392
  callback: (err, transientSource) => {
393
  if (err) return;
394
  isDragging = true;
 
395
 
396
  transientSource.on("result", (pos /*, rot */) => {
397
  if (!isDragging) return;
 
399
  });
400
 
401
  transientSource.once("remove", () => {
402
+ isDragging = false;
 
403
  });
404
  }
405
  });
406
  });
407
 
408
  inputSource.on("selectend", () => {
409
+ isDragging = false;
410
  });
411
  });
412
 
413
  // ❌ Rotation tactile 2 doigts — SUPPRIMÉE
414
 
415
+ // Souris : déplacement (clic gauche) & rotation (clic droit ou Shift+clic gauche) — conservée
 
 
 
 
416
  app.mouse.on("mousedown", (e) => {
417
  if (!app.xr.active || !placedOnce) return;
418
 
 
444
  rotateMode = false;
445
  });
446
 
447
+ // Empêcher menu contextuel (utile pour la rotation au clic droit)
448
  window.addEventListener("contextmenu", (e) => e.preventDefault());
449
 
450
  // --- Slider rotation (DOM overlay) ---
 
457
 
458
  // --- Événements AR globaux ---
459
  app.xr.on("start", () => {
 
 
460
  message("Session AR démarrée. Visez le sol pour détecter un plan…");
461
  reticle.enabled = true;
462
  });
 
466
  reticle.enabled = false;
467
  isDragging = false;
468
  rotateMode = false;
469
+ rotYInput.disabled = true;
 
470
  });
471
 
472
  app.xr.on(`available:${pc.XRTYPE_AR}`, (available) => {