MikaFil commited on
Commit
e3a65de
·
verified ·
1 Parent(s): 59f10a1

Update viewer_ar_ios.js

Browse files
Files changed (1) hide show
  1. viewer_ar_ios.js +99 -52
viewer_ar_ios.js CHANGED
@@ -1,10 +1,9 @@
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,7 +115,7 @@
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,7 +168,7 @@
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;
@@ -279,21 +278,23 @@
279
  var cfg = await loadConfigJson(cfgUrl);
280
  var GLB_URL = (cfg && typeof cfg.glb_url === "string" && cfg.glb_url) ?
281
  cfg.glb_url :
282
- null;
283
  var USDZ_URL = (cfg && typeof cfg.usdz_url === "string" && cfg.usdz_url) ?
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,6 +377,12 @@
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;
@@ -434,19 +441,42 @@
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,22 +516,25 @@
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,12 +557,12 @@
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,12 +595,11 @@
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,31 +616,45 @@
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,7 +671,7 @@
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,12 +699,12 @@
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,17 +713,18 @@
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 () {
 
1
+ /* viewer_ar_ios.js — AR PlayCanvas + GLB/USDZ via config.json (+ "sol" + rotation mur)
2
+ - config.json : { "glb_url": "...", "usdz_url": "...", "sol": true|false }
3
+ - sol = true → plans horizontaux (sol) + rotation autour de l’axe vertical (Y)
4
+ - sol = false → plans verticaux (mur) + rotation autour d’un axe horizontal perpendiculaire au mur
5
+ - iOS : 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
  "#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
  }
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;
 
278
  var cfg = await loadConfigJson(cfgUrl);
279
  var GLB_URL = (cfg && typeof cfg.glb_url === "string" && cfg.glb_url) ?
280
  cfg.glb_url :
281
+ "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/AR/tests/danae_no_metallic.glb";
282
  var USDZ_URL = (cfg && typeof cfg.usdz_url === "string" && cfg.usdz_url) ?
283
  cfg.usdz_url :
284
  null;
285
 
286
+ // NEW : 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
  app.root.addChild(modelRoot);
378
  var modelLoaded = false, placedOnce = false;
379
 
380
+ // Mode mur / sol
381
+ var wallMode = !PLACE_ON_FLOOR;
382
+ var wallAxis = new pc.Vec3(1, 0, 0); // axe horizontal perpendiculaire au mur
383
+ var wallBaseRot = new pc.Quat();
384
+ var wallAngleDeg = 0;
385
+
386
  // Ombre blob (uniquement pour le sol)
387
  var blob = null;
388
  var BLOB_SIZE = 0.4;
 
441
  // Rotation via slider
442
  var baseEulerX = 0, baseEulerZ = 0;
443
  var rotationYDeg = 0;
444
+
445
  function clamp360(d) { return Math.max(0, Math.min(360, d)); }
446
 
447
+ function updateSliderUI(angleDeg) {
448
+ var t = 1 - (angleDeg / 360);
449
  rotKnob.style.top = String(t * 100) + "%";
450
+ rotInput.value = String(Math.round(angleDeg));
451
+ rotVal.textContent = String(Math.round(angleDeg)) + "°";
452
+ }
453
+
454
+ function setWallRotationFromSlider(angleDeg) {
455
+ wallAngleDeg = angleDeg;
456
+ var qExtra = new pc.Quat();
457
+ qExtra.setFromAxisAngle(wallAxis, angleDeg);
458
+ var q = new pc.Quat();
459
+ q.mul2(qExtra, wallBaseRot); // rotation autour de l’axe horizontal normal au mur
460
+ modelRoot.setRotation(q);
461
+ }
462
+
463
+ function applySliderRotation(deg) {
464
+ var a = clamp360(deg);
465
+ if (!modelRoot.enabled) {
466
+ updateSliderUI(a);
467
+ return;
468
+ }
469
+ if (!wallMode) {
470
+ rotationYDeg = a;
471
+ modelRoot.setEulerAngles(baseEulerX, a, baseEulerZ);
472
+ } else {
473
+ setWallRotationFromSlider(a);
474
+ }
475
+ updateSliderUI(a);
476
  }
477
+
478
+ function getCurrentAngle() {
479
+ return wallMode ? wallAngleDeg : rotationYDeg;
 
 
480
  }
481
 
482
  function updateBlobPositionUnder(pos, rotLikePlane) {
 
516
 
517
  if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
518
 
519
+ // ----- Détection orientation des plans -----
520
  var TMP_IN = new pc.Vec3(0, 1, 0), TMP_OUT = new pc.Vec3();
521
+
522
  // Sol : normal ≈ +Y
523
  function isHorizontalUpFacing(rot, minDot) {
524
  minDot = (typeof minDot === "number") ? minDot : 0.75;
525
  rot.transformVector(TMP_IN, TMP_OUT);
526
  return TMP_OUT.y >= minDot;
527
  }
528
+
529
  // Mur : normal ≈ horizontale (|y| petit)
530
  function isVerticalFacing(rot, maxAbsY) {
531
+ maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35;
532
  rot.transformVector(TMP_IN, TMP_OUT);
533
  return Math.abs(TMP_OUT.y) <= maxAbsY;
534
  }
535
+
536
  function planeMatchesMode(rot) {
537
+ return wallMode ? isVerticalFacing(rot) : isHorizontalUpFacing(rot);
538
  }
539
 
540
  // Slider (pointer capture)
 
557
  draggingTrack = true;
558
  activePointerId = (e.pointerId != null) ? e.pointerId : 1;
559
  if (rotTrack.setPointerCapture) { try { rotTrack.setPointerCapture(activePointerId); } catch (er) {} }
560
+ applySliderRotation(degFromPointer(e));
561
  e.preventDefault(); e.stopPropagation();
562
  }
563
  function onPointerMoveCapture(e) {
564
  if (!draggingTrack || ((e.pointerId != null ? e.pointerId : 1) !== activePointerId)) return;
565
+ applySliderRotation(degFromPointer(e));
566
  e.preventDefault(); e.stopPropagation();
567
  }
568
  function endDrag(e) {
 
595
  });
596
  }
597
 
 
598
  var startBtn = ensureARStartButton(activateAR);
599
 
600
  app.keyboard.on("keydown", function (evt) { if (evt.key === pc.KEY_ESCAPE && app.xr.active) app.xr.end(); });
601
 
602
+ // Hit-test (sol vs mur)
603
  app.xr.hitTest.on("available", function () {
604
  app.xr.hitTest.start({
605
  entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
 
616
  modelRoot.enabled = true;
617
  modelRoot.setPosition(pos);
618
 
619
+ if (!wallMode) {
 
620
  blob = createBlobShadowAt(pos, rot);
621
+ var e = new pc.Vec3();
622
+ rot.getEulerAngles(e);
623
+ var y0 = ((e.y % 360) + 360) % 360;
624
+ applySliderRotation(y0);
625
+ } else {
626
+ // Mode mur : aligner l’objet avec le mur et préparer l’axe horizontal perpendiculaire
627
+ modelRoot.setRotation(rot);
628
+ wallBaseRot.copy(modelRoot.getRotation());
629
+
630
+ var upLocal = new pc.Vec3(0, 1, 0);
631
+ var normalWorld = new pc.Vec3();
632
+ rot.transformVector(upLocal, normalWorld);
633
+ // on projette sur le plan XZ pour avoir un axe horizontal
634
+ normalWorld.y = 0;
635
+ if (normalWorld.lengthSq() < 1e-4) {
636
+ normalWorld.set(0, 0, 1);
637
+ }
638
+ normalWorld.normalize();
639
+ wallAxis.copy(normalWorld);
640
+
641
+ applySliderRotation(0);
642
  }
643
 
 
 
 
 
 
 
 
 
644
  placedOnce = true;
645
  rotInput.disabled = false;
646
+ message(
647
+ wallMode
648
+ ? "Objet placé contre le mur. Glissez pour déplacer sur le mur, tournez-le avec le slider →"
649
+ : "Objet placé. Glissez pour déplacer, tournez-le avec le slider →"
650
+ );
651
  }
652
  });
653
  }
654
  });
655
  });
656
 
657
+ // Drag XR continu
658
  var isDragging = false;
659
  app.xr.input.on("add", function (inputSource) {
660
  inputSource.on("selectstart", function () {
 
671
  if (!isDragging) return;
672
  if (!planeMatchesMode(rot)) return;
673
  modelRoot.setPosition(pos);
674
+ if (!wallMode) updateBlobPositionUnder(pos, rot);
675
  });
676
 
677
  transientSource.once("remove", function () { isDragging = false; });
 
699
  if (reticle.enabled) {
700
  var p = reticle.getPosition();
701
  modelRoot.setPosition(p);
702
+ if (!wallMode) updateBlobPositionUnder(p, null);
703
  }
704
  } else if (rotateMode && modelRoot.enabled) {
705
  var dx = e.x - lastMouseX;
706
  lastMouseX = e.x;
707
+ applySliderRotation(getCurrentAngle() + dx * ROTATE_SENSITIVITY);
708
  }
709
  });
710
  app.mouse.on("mouseup", function () { isDragging = false; rotateMode = false; });
 
713
  // Slider (accessibilité clavier)
714
  rotInput.disabled = true;
715
  rotInput.addEventListener("input", function (e) {
 
716
  var v = parseFloat(e.target.value || "0");
717
+ applySliderRotation(v);
718
  }, { passive: true });
719
 
720
  // Événements AR (feedback + visibilité bouton)
721
  app.xr.on("start", function () {
722
  if (startBtn) startBtn.style.display = "none";
723
+ message(
724
+ wallMode
725
+ ? "Session AR démarrée. Visez un mur pour détecter un plan vertical…"
726
+ : "Session AR démarrée. Visez le sol pour détecter un plan…"
727
+ );
728
  reticle.enabled = true;
729
  });
730
  app.xr.on("end", function () {