MikaFil commited on
Commit
461037d
·
verified ·
1 Parent(s): b506108

Update viewer_ar_ios.js

Browse files
Files changed (1) hide show
  1. viewer_ar_ios.js +52 -127
viewer_ar_ios.js CHANGED
@@ -1,9 +1,10 @@
1
- /* viewer_ar_ios.js — AR PlayCanvas + GLB/USDZ via config.json (+ "sol" + mode mur)
2
- - config.json : { "glb_url": "...", "usdz_url": "...", "sol": true|false }
3
- - sol = true → plans horizontaux (sol) + rotation autour de l’axe local Y (haut)
4
- - sol = false → plans verticaux (mur) + rotation autour de l’axe local Y aligné à la normale du mur
5
- - iOS : AR Quick Look (USDZ) — l’utilisateur place l’objet sur sol/mur
6
- - Android/Desktop : WebXR AR + bouton "Lancer l’AR" + slider de rotation
 
7
  */
8
 
9
  (function () {
@@ -115,7 +116,7 @@
115
  "#ar-overlay-root{position:fixed;inset:0;z-index:10001;pointer-events:none}",
116
  ".pc-ar-msg{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;z-index:10002;padding:10px 14px;background:rgba(0,0,0,.65);color:#fff;border-radius:12px;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;font-size:14px;line-height:1.3;text-align:center;max-width:min(90vw,640px);box-shadow:0 6px 20px rgba(0,0,0,.25);backdrop-filter:blur(4px);pointer-events:none}",
117
  ".pc-ar-msg.pc-ar-msg--centerBig{top:50%;bottom:auto;transform:translate(-50%,-50%);font-size:20px;padding:16px 22px;max-width:min(90vw,760px)}",
118
- "#ar-start-button{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:10003;pointer-events:auto;border:none;border-radius:16px;padding:16px 24px;font-size:18px;line-height:1;background:#000;color:#fff;box-shadow:0 10px 28px rgba(0,0,0,.28);cursor:pointer;min-width:200px;text-align:center}",
119
  "#ar-start-button[disabled]{opacity:.5;cursor:not-allowed}",
120
  "#ar-ios-quicklook-button{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:10003;pointer-events:auto;display:inline-block;text-decoration:none}",
121
  "#ar-ios-quicklook-button img{display:block;height:60px;width:auto;box-shadow:0 10px 28px rgba(0,0,0,.28);border-radius:14px}",
@@ -168,7 +169,7 @@
168
  }
169
  }
170
 
171
- // iOS : bouton Quick Look
172
  function ensureQuickLookButton(USDZ_URL) {
173
  var btn = document.getElementById("ar-ios-quicklook-button");
174
  if (btn) return btn;
@@ -283,18 +284,16 @@
283
  cfg.usdz_url :
284
  null;
285
 
286
- // lecture de "sol" (par défaut true)
287
  var PLACE_ON_FLOOR = true;
288
  if (cfg && typeof cfg.sol === "boolean") PLACE_ON_FLOOR = !!cfg.sol;
289
 
290
- // iOS → Quick Look
291
  if (isIOS()) {
292
  if (USDZ_URL) {
293
  ensureQuickLookButton(USDZ_URL);
294
- message(
295
- "Modèle chargé. Appuyez sur « Lancer l’AR » puis placez-le " +
296
- (PLACE_ON_FLOOR ? "sur le sol." : "sur un mur (faites-le glisser sur la surface).")
297
- );
298
  } else {
299
  message("iOS détecté, mais aucun 'usdz_url' dans config.json.");
300
  }
@@ -377,11 +376,6 @@
377
  app.root.addChild(modelRoot);
378
  var modelLoaded = false, placedOnce = false;
379
 
380
- // Mode mur / sol
381
- var wallMode = !PLACE_ON_FLOOR;
382
- var wallBaseRot = new pc.Quat(); // orientation de base alignée à la normale du mur
383
- var wallAngleDeg = 0; // angle autour de l’axe local Y
384
-
385
  // Ombre blob (uniquement pour le sol)
386
  var blob = null;
387
  var BLOB_SIZE = 0.4;
@@ -439,45 +433,20 @@
439
 
440
  // Rotation via slider
441
  var baseEulerX = 0, baseEulerZ = 0;
442
- var rotationYDeg = 0; // pour le mode sol
443
-
444
  function clamp360(d) { return Math.max(0, Math.min(360, d)); }
445
 
446
- function updateSliderUI(angleDeg) {
447
- var t = 1 - (angleDeg / 360);
448
  rotKnob.style.top = String(t * 100) + "%";
449
- rotInput.value = String(Math.round(angleDeg));
450
- rotVal.textContent = String(Math.round(angleDeg)) + "°";
451
- }
452
-
453
- // Rotation murale : angle autour de l’axe local Y (pointe ↔ base)
454
- function setWallRotationFromSlider(angleDeg) {
455
- wallAngleDeg = angleDeg;
456
- var qLocal = new pc.Quat();
457
- qLocal.setFromAxisAngle(new pc.Vec3(0, 1, 0), angleDeg);
458
- var q = new pc.Quat();
459
- // finale : base * rotation_locale_Y
460
- q.mul2(wallBaseRot, qLocal);
461
- modelRoot.setRotation(q);
462
- }
463
-
464
- function applySliderRotation(deg) {
465
- var a = clamp360(deg);
466
- if (!modelRoot.enabled) {
467
- updateSliderUI(a);
468
- return;
469
- }
470
- if (!wallMode) {
471
- rotationYDeg = a;
472
- modelRoot.setEulerAngles(baseEulerX, a, baseEulerZ);
473
- } else {
474
- setWallRotationFromSlider(a);
475
- }
476
- updateSliderUI(a);
477
  }
478
-
479
- function getCurrentAngle() {
480
- return wallMode ? wallAngleDeg : rotationYDeg;
 
 
481
  }
482
 
483
  function updateBlobPositionUnder(pos, rotLikePlane) {
@@ -517,25 +486,22 @@
517
 
518
  if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
519
 
520
- // ----- Détection orientation des plans -----
521
  var TMP_IN = new pc.Vec3(0, 1, 0), TMP_OUT = new pc.Vec3();
522
-
523
  // Sol : normal ≈ +Y
524
  function isHorizontalUpFacing(rot, minDot) {
525
  minDot = (typeof minDot === "number") ? minDot : 0.75;
526
  rot.transformVector(TMP_IN, TMP_OUT);
527
  return TMP_OUT.y >= minDot;
528
  }
529
-
530
  // Mur : normal ≈ horizontale (|y| petit)
531
  function isVerticalFacing(rot, maxAbsY) {
532
- maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35;
533
  rot.transformVector(TMP_IN, TMP_OUT);
534
  return Math.abs(TMP_OUT.y) <= maxAbsY;
535
  }
536
-
537
  function planeMatchesMode(rot) {
538
- return wallMode ? isVerticalFacing(rot) : isHorizontalUpFacing(rot);
539
  }
540
 
541
  // Slider (pointer capture)
@@ -558,12 +524,12 @@
558
  draggingTrack = true;
559
  activePointerId = (e.pointerId != null) ? e.pointerId : 1;
560
  if (rotTrack.setPointerCapture) { try { rotTrack.setPointerCapture(activePointerId); } catch (er) {} }
561
- applySliderRotation(degFromPointer(e));
562
  e.preventDefault(); e.stopPropagation();
563
  }
564
  function onPointerMoveCapture(e) {
565
  if (!draggingTrack || ((e.pointerId != null ? e.pointerId : 1) !== activePointerId)) return;
566
- applySliderRotation(degFromPointer(e));
567
  e.preventDefault(); e.stopPropagation();
568
  }
569
  function endDrag(e) {
@@ -596,11 +562,12 @@
596
  });
597
  }
598
 
 
599
  var startBtn = ensureARStartButton(activateAR);
600
 
601
  app.keyboard.on("keydown", function (evt) { if (evt.key === pc.KEY_ESCAPE && app.xr.active) app.xr.end(); });
602
 
603
- // Hit-test (sol vs mur)
604
  app.xr.hitTest.on("available", function () {
605
  app.xr.hitTest.start({
606
  entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
@@ -617,72 +584,31 @@
617
  modelRoot.enabled = true;
618
  modelRoot.setPosition(pos);
619
 
620
- if (!wallMode) {
621
- // --- MODE SOL ---
622
  blob = createBlobShadowAt(pos, rot);
623
- var e = new pc.Vec3();
624
- rot.getEulerAngles(e);
625
- var y0 = ((e.y % 360) + 360) % 360;
626
- applySliderRotation(y0);
627
- } else {
628
- // --- MODE MUR ---
629
- // 1) normale du plan en monde
630
- var planeNormal = new pc.Vec3();
631
- rot.transformVector(new pc.Vec3(0, 1, 0), planeNormal);
632
- planeNormal.normalize();
633
-
634
- // 2) on force la normale à pointer vers la caméra
635
- var camPos = camera.getPosition();
636
- var toCam = new pc.Vec3();
637
- toCam.sub2(camPos, pos);
638
- toCam.normalize();
639
- if (planeNormal.dot(toCam) < 0) {
640
- planeNormal.scale(-1); // on flip la normale pour qu’elle sorte vers la caméra
641
- }
642
-
643
- // 3) construire un repère orthonormé : Y = normale, X/Z quelconques mais ⟂
644
- var ref = new pc.Vec3(0, 1, 0);
645
- if (Math.abs(planeNormal.y) > 0.9) {
646
- ref.set(1, 0, 0); // si la normale est trop verticale, on change le ref
647
- }
648
-
649
- var xAxis = new pc.Vec3();
650
- xAxis.cross(ref, planeNormal).normalize(); // X ⟂ normale
651
- var zAxis = new pc.Vec3();
652
- zAxis.cross(xAxis, planeNormal).normalize(); // Z = X × Y
653
-
654
- // 4) construire la matrice (colonnes = X, Y, Z) puis le quaternion
655
- var m = new pc.Mat4();
656
- m.set(
657
- xAxis.x, planeNormal.x, zAxis.x, 0,
658
- xAxis.y, planeNormal.y, zAxis.y, 0,
659
- xAxis.z, planeNormal.z, zAxis.z, 0,
660
- 0, 0, 0, 1
661
- );
662
- var baseQ = new pc.Quat();
663
- baseQ.setFromMat4(m);
664
-
665
- modelRoot.setRotation(baseQ);
666
- wallBaseRot.copy(baseQ);
667
-
668
- // angle initial = 0 → pointe toujours "vers dehors"
669
- applySliderRotation(0);
670
  }
671
 
 
 
 
 
 
 
 
 
672
  placedOnce = true;
673
  rotInput.disabled = false;
674
- message(
675
- wallMode
676
- ? "Objet placé contre le mur. Glissez pour déplacer sur le mur, tournez-le avec le slider →"
677
- : "Objet placé. Glissez pour déplacer, tournez-le avec le slider →"
678
- );
679
  }
680
  });
681
  }
682
  });
683
  });
684
 
685
- // Drag XR continu
686
  var isDragging = false;
687
  app.xr.input.on("add", function (inputSource) {
688
  inputSource.on("selectstart", function () {
@@ -699,7 +625,7 @@
699
  if (!isDragging) return;
700
  if (!planeMatchesMode(rot)) return;
701
  modelRoot.setPosition(pos);
702
- if (!wallMode) updateBlobPositionUnder(pos, rot);
703
  });
704
 
705
  transientSource.once("remove", function () { isDragging = false; });
@@ -727,12 +653,12 @@
727
  if (reticle.enabled) {
728
  var p = reticle.getPosition();
729
  modelRoot.setPosition(p);
730
- if (!wallMode) updateBlobPositionUnder(p, null);
731
  }
732
  } else if (rotateMode && modelRoot.enabled) {
733
  var dx = e.x - lastMouseX;
734
  lastMouseX = e.x;
735
- applySliderRotation(getCurrentAngle() + dx * ROTATE_SENSITIVITY);
736
  }
737
  });
738
  app.mouse.on("mouseup", function () { isDragging = false; rotateMode = false; });
@@ -741,18 +667,17 @@
741
  // Slider (accessibilité clavier)
742
  rotInput.disabled = true;
743
  rotInput.addEventListener("input", function (e) {
 
744
  var v = parseFloat(e.target.value || "0");
745
- applySliderRotation(v);
746
  }, { passive: true });
747
 
748
  // Événements AR (feedback + visibilité bouton)
749
  app.xr.on("start", function () {
750
  if (startBtn) startBtn.style.display = "none";
751
- message(
752
- wallMode
753
- ? "Session AR démarrée. Visez un mur pour détecter un plan vertical…"
754
- : "Session AR démarrée. Visez le sol pour détecter un plan…"
755
- );
756
  reticle.enabled = true;
757
  });
758
  app.xr.on("end", function () {
 
1
+ /* viewer_ar_ios.js — AR PlayCanvas + GLB/USDZ via config.json (+ support murs via "sol": false)
2
+ - Lit config.json (data-config) => { "glb_url": "...", "usdz_url": "...", "sol": true|false }
3
+ - iOS : AR Quick Look (USDZ) on ne peut PAS forcer mur/sol, mais l’utilisateur peut glisser l’objet sur un mur
4
+ - Android/Desktop : WebXR AR
5
+ * sol=true => plans horizontaux (sol)
6
+ * sol=false => plans verticaux (murs)
7
+ + bouton "Lancer l’AR" (centré) + curseur rotation (yaw)
8
  */
9
 
10
  (function () {
 
116
  "#ar-overlay-root{position:fixed;inset:0;z-index:10001;pointer-events:none}",
117
  ".pc-ar-msg{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;z-index:10002;padding:10px 14px;background:rgba(0,0,0,.65);color:#fff;border-radius:12px;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;font-size:14px;line-height:1.3;text-align:center;max-width:min(90vw,640px);box-shadow:0 6px 20px rgba(0,0,0,.25);backdrop-filter:blur(4px);pointer-events:none}",
118
  ".pc-ar-msg.pc-ar-msg--centerBig{top:50%;bottom:auto;transform:translate(-50%,-50%);font-size:20px;padding:16px 22px;max-width:min(90vw,760px)}",
119
+ "#ar-start-button{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:10003;pointer-events:auto;border:none;border-radius:16px;padding:16px 24px;font-size:18px;background:#000;color:#fff;box-shadow:0 10px 28px rgba(0,0,0,.28);cursor:pointer;min-width:200px;text-align:center}",
120
  "#ar-start-button[disabled]{opacity:.5;cursor:not-allowed}",
121
  "#ar-ios-quicklook-button{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:10003;pointer-events:auto;display:inline-block;text-decoration:none}",
122
  "#ar-ios-quicklook-button img{display:block;height:60px;width:auto;box-shadow:0 10px 28px rgba(0,0,0,.28);border-radius:14px}",
 
169
  }
170
  }
171
 
172
+ // iOS : bouton Quick Look (l’utilisateur peut glisser l’objet sur un mur si souhaité)
173
  function ensureQuickLookButton(USDZ_URL) {
174
  var btn = document.getElementById("ar-ios-quicklook-button");
175
  if (btn) return btn;
 
284
  cfg.usdz_url :
285
  null;
286
 
287
+ // NEW: lecture de "sol" (par défaut true)
288
  var PLACE_ON_FLOOR = true;
289
  if (cfg && typeof cfg.sol === "boolean") PLACE_ON_FLOOR = !!cfg.sol;
290
 
291
+ // iOS → Quick Look (on ne peut pas imposer mur/sol)
292
  if (isIOS()) {
293
  if (USDZ_URL) {
294
  ensureQuickLookButton(USDZ_URL);
295
+ message("Modèle chargé. Appuyez sur « Lancer l’AR » puis placez-le " +
296
+ (PLACE_ON_FLOOR ? "sur le sol." : "sur un mur (faites-le glisser)."));
 
 
297
  } else {
298
  message("iOS détecté, mais aucun 'usdz_url' dans config.json.");
299
  }
 
376
  app.root.addChild(modelRoot);
377
  var modelLoaded = false, placedOnce = false;
378
 
 
 
 
 
 
379
  // Ombre blob (uniquement pour le sol)
380
  var blob = null;
381
  var BLOB_SIZE = 0.4;
 
433
 
434
  // Rotation via slider
435
  var baseEulerX = 0, baseEulerZ = 0;
436
+ var rotationYDeg = 0;
 
437
  function clamp360(d) { return Math.max(0, Math.min(360, d)); }
438
 
439
+ function updateKnobFromY(yDeg) {
440
+ var t = 1 - (yDeg / 360);
441
  rotKnob.style.top = String(t * 100) + "%";
442
+ rotInput.value = String(Math.round(yDeg));
443
+ rotVal.textContent = String(Math.round(yDeg)) + "°";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  }
445
+ function applyRotationY(deg) {
446
+ var y = clamp360(deg);
447
+ rotationYDeg = y;
448
+ modelRoot.setEulerAngles(baseEulerX, y, baseEulerZ);
449
+ updateKnobFromY(y);
450
  }
451
 
452
  function updateBlobPositionUnder(pos, rotLikePlane) {
 
486
 
487
  if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
488
 
489
+ // ----- Détection d’orientation de plan -----
490
  var TMP_IN = new pc.Vec3(0, 1, 0), TMP_OUT = new pc.Vec3();
 
491
  // Sol : normal ≈ +Y
492
  function isHorizontalUpFacing(rot, minDot) {
493
  minDot = (typeof minDot === "number") ? minDot : 0.75;
494
  rot.transformVector(TMP_IN, TMP_OUT);
495
  return TMP_OUT.y >= minDot;
496
  }
 
497
  // Mur : normal ≈ horizontale (|y| petit)
498
  function isVerticalFacing(rot, maxAbsY) {
499
+ maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35; // tolérance
500
  rot.transformVector(TMP_IN, TMP_OUT);
501
  return Math.abs(TMP_OUT.y) <= maxAbsY;
502
  }
 
503
  function planeMatchesMode(rot) {
504
+ return PLACE_ON_FLOOR ? isHorizontalUpFacing(rot) : isVerticalFacing(rot);
505
  }
506
 
507
  // Slider (pointer capture)
 
524
  draggingTrack = true;
525
  activePointerId = (e.pointerId != null) ? e.pointerId : 1;
526
  if (rotTrack.setPointerCapture) { try { rotTrack.setPointerCapture(activePointerId); } catch (er) {} }
527
+ applyRotationY(degFromPointer(e));
528
  e.preventDefault(); e.stopPropagation();
529
  }
530
  function onPointerMoveCapture(e) {
531
  if (!draggingTrack || ((e.pointerId != null ? e.pointerId : 1) !== activePointerId)) return;
532
+ applyRotationY(degFromPointer(e));
533
  e.preventDefault(); e.stopPropagation();
534
  }
535
  function endDrag(e) {
 
562
  });
563
  }
564
 
565
+ // Bouton start Android/Desktop
566
  var startBtn = ensureARStartButton(activateAR);
567
 
568
  app.keyboard.on("keydown", function (evt) { if (evt.key === pc.KEY_ESCAPE && app.xr.active) app.xr.end(); });
569
 
570
+ // Hit-test (sol vs mur)
571
  app.xr.hitTest.on("available", function () {
572
  app.xr.hitTest.start({
573
  entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
 
584
  modelRoot.enabled = true;
585
  modelRoot.setPosition(pos);
586
 
587
+ // Ombre : seulement pour le sol
588
+ if (PLACE_ON_FLOOR) {
589
  blob = createBlobShadowAt(pos, rot);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  }
591
 
592
+ // Aligner l’orientation “yaw” avec le plan détecté (sol ou mur)
593
+ var e = new pc.Vec3();
594
+ rot.getEulerAngles(e);
595
+ var y0 = ((e.y % 360) + 360) % 360;
596
+ // sur mur : garder l’objet droit (pas d’inclinaison X/Z)
597
+ if (!PLACE_ON_FLOOR) { baseEulerX = 0; baseEulerZ = 0; }
598
+ applyRotationY(y0);
599
+
600
  placedOnce = true;
601
  rotInput.disabled = false;
602
+ message(PLACE_ON_FLOOR ?
603
+ "Objet placé. Glissez pour déplacer, tournez-le avec le slider →" :
604
+ "Objet placé contre le mur. Glissez pour déplacer sur le mur, tournez-le avec le slider →");
 
 
605
  }
606
  });
607
  }
608
  });
609
  });
610
 
611
+ // Drag XR continu (suivant le mode)
612
  var isDragging = false;
613
  app.xr.input.on("add", function (inputSource) {
614
  inputSource.on("selectstart", function () {
 
625
  if (!isDragging) return;
626
  if (!planeMatchesMode(rot)) return;
627
  modelRoot.setPosition(pos);
628
+ if (PLACE_ON_FLOOR) updateBlobPositionUnder(pos, rot);
629
  });
630
 
631
  transientSource.once("remove", function () { isDragging = false; });
 
653
  if (reticle.enabled) {
654
  var p = reticle.getPosition();
655
  modelRoot.setPosition(p);
656
+ if (PLACE_ON_FLOOR) updateBlobPositionUnder(p, null);
657
  }
658
  } else if (rotateMode && modelRoot.enabled) {
659
  var dx = e.x - lastMouseX;
660
  lastMouseX = e.x;
661
+ applyRotationY(rotationYDeg + dx * ROTATE_SENSITIVITY);
662
  }
663
  });
664
  app.mouse.on("mouseup", function () { isDragging = false; rotateMode = false; });
 
667
  // Slider (accessibilité clavier)
668
  rotInput.disabled = true;
669
  rotInput.addEventListener("input", function (e) {
670
+ if (!modelRoot.enabled) return;
671
  var v = parseFloat(e.target.value || "0");
672
+ applyRotationY(v);
673
  }, { passive: true });
674
 
675
  // Événements AR (feedback + visibilité bouton)
676
  app.xr.on("start", function () {
677
  if (startBtn) startBtn.style.display = "none";
678
+ message(PLACE_ON_FLOOR ?
679
+ "Session AR démarrée. Visez le sol pour détecter un plan…" :
680
+ "Session AR démarrée. Visez un mur pour détecter un plan vertical…");
 
 
681
  reticle.enabled = true;
682
  });
683
  app.xr.on("end", function () {