MikaFil commited on
Commit
b5d808f
·
verified ·
1 Parent(s): 86c5b74

Update viewer_ar.js

Browse files
Files changed (1) hide show
  1. viewer_ar.js +111 -52
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
4
  - Aucune dépendance externe autre que PlayCanvas et le GLB
5
  */
6
 
@@ -91,6 +91,57 @@
91
  user-select: none;
92
  pointer-events: none;
93
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  `;
95
  const styleTag = document.createElement("style");
96
  styleTag.textContent = css;
@@ -119,6 +170,21 @@
119
  return canvas;
120
  }
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  // =========================
123
  // 4) Lancement
124
  // =========================
@@ -140,6 +206,10 @@
140
  function initARApp() {
141
  const pc = window.pc; // alias
142
  const canvas = ensureCanvas();
 
 
 
 
143
  window.focus();
144
 
145
  const app = new pc.Application(canvas, {
@@ -198,6 +268,22 @@
198
  let modelLoaded = false;
199
  let placedOnce = false;
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  // --- Chargement GLB ---
202
  app.assets.loadFromUrl(GLB_URL, "container", (err, asset) => {
203
  if (err) {
@@ -231,6 +317,10 @@
231
  return;
232
  }
233
  camera.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
 
 
 
 
234
  callback: (err) => {
235
  if (err) message(`Échec du démarrage AR : ${err.message}`);
236
  }
@@ -271,23 +361,25 @@
271
  if (modelLoaded && !placedOnce) {
272
  modelRoot.enabled = true;
273
  modelRoot.setPosition(pos);
274
- // Conserver uniquement la yaw (rotation Y)
275
  const euler = new pc.Vec3();
276
  rot.getEulerAngles(euler);
277
- modelRoot.setEulerAngles(0, euler.y, 0);
 
278
  placedOnce = true;
279
- message("Objet placé. Glissez pour déplacer, 2 doigts / clic droit pour tourner.");
 
 
280
  }
281
  });
282
  }
283
  });
284
  });
285
 
286
- // --- Interactions ---
287
  let isDragging = false;
288
  let rotateMode = false;
289
- let lastAvgX = 0; // rotation tactile (2 doigts)
290
- let lastMouseX = 0; // rotation souris
291
  const ROTATE_SENSITIVITY = 0.25; // degrés / pixel approx.
292
 
293
  // Déplacement (input XR : maintien/drag)
@@ -318,49 +410,9 @@
318
  });
319
  });
320
 
321
- // Rotation tactile (2 doigts glisser horizontal)
322
- if (app.touch) {
323
- let touches = new Map();
324
- const getAvgX = () => {
325
- if (touches.size === 0) return 0;
326
- let s = 0;
327
- touches.forEach((t) => (s += t.x));
328
- return s / touches.size;
329
- };
330
-
331
- app.touch.on("touchstart", (e) => {
332
- for (const t of e.touches) touches.set(t.id, { x: t.x, y: t.y });
333
- if (touches.size >= 2) {
334
- rotateMode = true;
335
- lastAvgX = getAvgX();
336
- }
337
- });
338
-
339
- app.touch.on("touchmove", (e) => {
340
- for (const t of e.touches) {
341
- if (touches.has(t.id)) touches.set(t.id, { x: t.x, y: t.y });
342
- }
343
- if (rotateMode && modelRoot.enabled) {
344
- const avgX = getAvgX();
345
- const dx = avgX - lastAvgX;
346
- lastAvgX = avgX;
347
- const euler = modelRoot.getEulerAngles();
348
- modelRoot.setEulerAngles(euler.x, euler.y + dx * ROTATE_SENSITIVITY, euler.z);
349
- }
350
- });
351
 
352
- app.touch.on("touchend", (e) => {
353
- for (const t of e.changedTouches) touches.delete(t.id);
354
- if (touches.size < 2) rotateMode = false;
355
- });
356
-
357
- app.touch.on("touchcancel", () => {
358
- touches.clear();
359
- rotateMode = false;
360
- });
361
- }
362
-
363
- // Souris : déplacement (clic gauche maintenu) & rotation (clic droit ou Shift+clic gauche)
364
  app.mouse.on("mousedown", (e) => {
365
  if (!app.xr.active || !placedOnce) return;
366
 
@@ -376,7 +428,6 @@
376
  if (!app.xr.active || !placedOnce) return;
377
 
378
  if (isDragging) {
379
- // On s’aligne sur le réticule (alimenté en continu par le hit test)
380
  if (reticle.enabled) {
381
  const pos = reticle.getPosition();
382
  modelRoot.setPosition(pos);
@@ -384,8 +435,7 @@
384
  } else if (rotateMode && modelRoot.enabled) {
385
  const dx = e.x - lastMouseX;
386
  lastMouseX = e.x;
387
- const euler = modelRoot.getEulerAngles();
388
- modelRoot.setEulerAngles(euler.x, euler.y + dx * ROTATE_SENSITIVITY, euler.z);
389
  }
390
  });
391
 
@@ -397,6 +447,14 @@
397
  // Empêcher menu contextuel (utile pour la rotation au clic droit)
398
  window.addEventListener("contextmenu", (e) => e.preventDefault());
399
 
 
 
 
 
 
 
 
 
400
  // --- Événements AR globaux ---
401
  app.xr.on("start", () => {
402
  message("Session AR démarrée. Visez le sol pour détecter un plan…");
@@ -408,6 +466,7 @@
408
  reticle.enabled = false;
409
  isDragging = false;
410
  rotateMode = false;
 
411
  });
412
 
413
  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;
120
+ width: 220px; /* longueur avant rotation */
121
+ height: 28px;
122
+ transform: rotate(-90deg);
123
+ outline: none;
124
+ background: transparent;
125
+ touch-action: none;
126
+ }
127
+ .ar-ui input[type="range"].rotY::-webkit-slider-thumb {
128
+ -webkit-appearance: none;
129
+ appearance: none;
130
+ width: 20px; height: 20px;
131
+ border-radius: 50%;
132
+ background: #fff;
133
+ border: none;
134
+ box-shadow: 0 2px 8px rgba(0,0,0,.35);
135
+ }
136
+ .ar-ui input[type="range"].rotY::-webkit-slider-runnable-track {
137
+ height: 6px;
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;
 
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";
179
+ panel.innerHTML = `
180
+ <div class="label">Rotation</div>
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
+
188
  // =========================
189
  // 4) Lancement
190
  // =========================
 
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
+
213
  window.focus();
214
 
215
  const app = new pc.Application(canvas, {
 
268
  let modelLoaded = false;
269
  let placedOnce = false;
270
 
271
+ // État rotation Y (en degrés, 0..360)
272
+ let rotationYDeg = 0;
273
+ const norm360 = (deg) => {
274
+ let d = deg % 360;
275
+ if (d < 0) d += 360;
276
+ return d;
277
+ };
278
+ const applyRotationY = (deg) => {
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
+ };
286
+
287
  // --- Chargement GLB ---
288
  app.assets.loadFromUrl(GLB_URL, "container", (err, asset) => {
289
  if (err) {
 
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}`);
326
  }
 
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
  }
374
  });
375
  }
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)
 
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
 
 
428
  if (!app.xr.active || !placedOnce) return;
429
 
430
  if (isDragging) {
 
431
  if (reticle.enabled) {
432
  const pos = reticle.getPosition();
433
  modelRoot.setPosition(pos);
 
435
  } else if (rotateMode && modelRoot.enabled) {
436
  const dx = e.x - lastMouseX;
437
  lastMouseX = e.x;
438
+ applyRotationY(rotationYDeg + dx * ROTATE_SENSITIVITY);
 
439
  }
440
  });
441
 
 
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) ---
451
+ rotYInput.disabled = true; // activé au 1er placement
452
+ rotYInput.addEventListener("input", (e) => {
453
+ if (!modelRoot.enabled) return;
454
+ const deg = parseFloat(e.target.value || "0");
455
+ applyRotationY(deg);
456
+ }, { passive: true });
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…");
 
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) => {