Update viewer_ar_ios.js
Browse files- 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"
|
| 2 |
-
- config.json
|
| 3 |
-
-
|
| 4 |
-
-
|
| 5 |
-
|
| 6 |
-
|
|
|
|
| 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;
|
| 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 |
-
|
| 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;
|
| 443 |
-
|
| 444 |
function clamp360(d) { return Math.max(0, Math.min(360, d)); }
|
| 445 |
|
| 446 |
-
function
|
| 447 |
-
var t = 1 - (
|
| 448 |
rotKnob.style.top = String(t * 100) + "%";
|
| 449 |
-
rotInput.value = String(Math.round(
|
| 450 |
-
rotVal.textContent = String(Math.round(
|
| 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 |
-
|
| 480 |
-
|
|
|
|
|
|
|
| 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
|
| 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
|
| 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 |
-
|
| 562 |
e.preventDefault(); e.stopPropagation();
|
| 563 |
}
|
| 564 |
function onPointerMoveCapture(e) {
|
| 565 |
if (!draggingTrack || ((e.pointerId != null ? e.pointerId : 1) !== activePointerId)) return;
|
| 566 |
-
|
| 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 |
-
|
| 621 |
-
|
| 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 |
-
|
| 676 |
-
|
| 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 (
|
| 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 (
|
| 731 |
}
|
| 732 |
} else if (rotateMode && modelRoot.enabled) {
|
| 733 |
var dx = e.x - lastMouseX;
|
| 734 |
lastMouseX = e.x;
|
| 735 |
-
|
| 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 |
-
|
| 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 |
-
|
| 753 |
-
|
| 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 () {
|