Update viewer_ar_ios.js
Browse files- viewer_ar_ios.js +94 -104
viewer_ar_ios.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
| 1 |
-
/* viewer_ar_ios.js — AR PlayCanvas + GLB/USDZ via config.json (+ "sol"
|
| 2 |
- config.json : { "glb_url": "...", "usdz_url": "...", "sol": true|false }
|
| 3 |
-
- sol = true → plans horizontaux (sol)
|
| 4 |
-
|
| 5 |
-
*
|
| 6 |
-
|
| 7 |
-
*
|
| 8 |
-
*
|
| 9 |
-
*
|
| 10 |
-
-
|
| 11 |
-
-
|
|
|
|
| 12 |
*/
|
| 13 |
|
| 14 |
(function () {
|
|
@@ -298,15 +299,9 @@
|
|
| 298 |
ensureQuickLookButton(USDZ_URL);
|
| 299 |
|
| 300 |
if (PLACE_ON_FLOOR) {
|
| 301 |
-
message(
|
| 302 |
-
"Modèle chargé. Appuyez sur « Lancer l’AR ». Dans la vue AR (Quick Look), " +
|
| 303 |
-
"placez l’objet sur le sol et ajustez sa position/orientation avec les gestes iOS."
|
| 304 |
-
);
|
| 305 |
} else {
|
| 306 |
-
message(
|
| 307 |
-
"Modèle chargé. Appuyez sur « Lancer l’AR ». Sur iOS (Quick Look), l’objet ne peut pas être " +
|
| 308 |
-
"aligné automatiquement au mur : placez-le et orientez-le manuellement (Y vers le plafond)."
|
| 309 |
-
);
|
| 310 |
}
|
| 311 |
} else {
|
| 312 |
message("iOS détecté, mais aucun 'usdz_url' dans config.json.");
|
|
@@ -390,25 +385,14 @@
|
|
| 390 |
app.root.addChild(modelRoot);
|
| 391 |
var modelLoaded = false, placedOnce = false;
|
| 392 |
|
| 393 |
-
// --- Helpers vecteurs/quaternions
|
| 394 |
var UP = new pc.Vec3(0, 1, 0);
|
|
|
|
| 395 |
function projOnPlane(v, n) {
|
| 396 |
var d = v.dot(n);
|
| 397 |
return new pc.Vec3(v.x - d*n.x, v.y - d*n.y, v.z - d*n.z);
|
| 398 |
}
|
| 399 |
-
|
| 400 |
-
// Z local = normale du mur
|
| 401 |
-
var Z = Nz.clone().normalize();
|
| 402 |
-
// Y local = Up projété dans le plan (toujours "vers le plafond" le long du mur)
|
| 403 |
-
var Y = projOnPlane(UP, Z);
|
| 404 |
-
if (Y.lengthSq() < 1e-8) Y = projOnPlane(new pc.Vec3(0,1,0), Z);
|
| 405 |
-
Y.normalize();
|
| 406 |
-
// X = Y × Z (garantit base orthonormée et droite, avec X×Y = Z)
|
| 407 |
-
var X = new pc.Vec3(); X.cross(Y, Z).normalize();
|
| 408 |
-
// Re-orthonormalise Y = Z × X pour éliminer la dérive
|
| 409 |
-
Y.cross(Z, X).normalize();
|
| 410 |
-
return { X: X, Y: Y, Z: Z };
|
| 411 |
-
}
|
| 412 |
function quatFromBasis(X, Y, Z) {
|
| 413 |
// colonnes = X,Y,Z
|
| 414 |
var m00 = X.x, m01 = Y.x, m02 = Z.x;
|
|
@@ -445,19 +429,43 @@
|
|
| 445 |
q.normalize();
|
| 446 |
return q;
|
| 447 |
}
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
var
|
| 454 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
}
|
| 456 |
|
| 457 |
// Mode mur / sol
|
| 458 |
var wallMode = !PLACE_ON_FLOOR;
|
| 459 |
-
var
|
| 460 |
-
var
|
| 461 |
|
| 462 |
// Ombre blob (sol uniquement)
|
| 463 |
var blob = null;
|
|
@@ -514,9 +522,7 @@
|
|
| 514 |
return e;
|
| 515 |
}
|
| 516 |
|
| 517 |
-
// Rotation via slider (
|
| 518 |
-
var baseEulerX = 0, baseEulerZ = 0;
|
| 519 |
-
var rotationYDeg = 0;
|
| 520 |
function clamp360(d) { return Math.max(0, Math.min(360, d)); }
|
| 521 |
function updateKnobFromY(yDeg) {
|
| 522 |
var t = 1 - (yDeg / 360);
|
|
@@ -524,24 +530,16 @@
|
|
| 524 |
rotInput.value = String(Math.round(yDeg));
|
| 525 |
rotVal.textContent = String(Math.round(yDeg)) + "°";
|
| 526 |
}
|
| 527 |
-
function getCurrentAngle() { return wallMode ? wallAngleDeg : rotationYDeg; }
|
| 528 |
|
| 529 |
-
function
|
| 530 |
var y = clamp360(deg);
|
|
|
|
| 531 |
if (!modelRoot.enabled) { updateKnobFromY(y); return; }
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
// mur : rotation autour de Z local
|
| 538 |
-
wallAngleDeg = y;
|
| 539 |
-
var qLocal = new pc.Quat();
|
| 540 |
-
qLocal.setFromAxisAngle(new pc.Vec3(0, 0, 1), y); // rotation locale autour de Z
|
| 541 |
-
var qFinal = new pc.Quat();
|
| 542 |
-
qFinal.mul2(wallBaseRot, qLocal); // base (Z=normale, Y=up projeté) puis twist utilisateur
|
| 543 |
-
modelRoot.setRotation(qFinal);
|
| 544 |
-
}
|
| 545 |
updateKnobFromY(y);
|
| 546 |
}
|
| 547 |
|
|
@@ -558,6 +556,7 @@
|
|
| 558 |
modelRoot.addChild(instance);
|
| 559 |
modelRoot.setLocalScale(1, 1, 1);
|
| 560 |
|
|
|
|
| 561 |
var renders = instance.findComponents('render');
|
| 562 |
for (var ri = 0; ri < renders.length; ri++) {
|
| 563 |
var r = renders[ri];
|
|
@@ -573,30 +572,29 @@
|
|
| 573 |
}
|
| 574 |
}
|
| 575 |
|
| 576 |
-
var initE = modelRoot.getEulerAngles();
|
| 577 |
-
baseEulerX = initE.x; baseEulerZ = initE.z;
|
| 578 |
-
|
| 579 |
modelLoaded = true;
|
| 580 |
message("Modèle chargé. Appuyez sur « Lancer l’AR ».");
|
| 581 |
});
|
| 582 |
|
| 583 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
| 584 |
|
| 585 |
-
// ----- Détection orientation des plans (
|
| 586 |
-
var
|
| 587 |
|
|
|
|
| 588 |
function isHorizontalUpFacing(rot, minDot) {
|
| 589 |
-
// sol : normale (Z) ≈ +Y
|
| 590 |
minDot = (typeof minDot === "number") ? minDot : 0.75;
|
| 591 |
-
rot.transformVector(
|
| 592 |
return TMP_OUT.y >= minDot;
|
| 593 |
}
|
|
|
|
|
|
|
| 594 |
function isVerticalFacing(rot, maxAbsY) {
|
| 595 |
-
// mur : normale (Z) quasi horizontale → |Z.y| petit
|
| 596 |
maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35;
|
| 597 |
-
rot.transformVector(
|
| 598 |
return Math.abs(TMP_OUT.y) <= maxAbsY;
|
| 599 |
}
|
|
|
|
| 600 |
function planeMatchesMode(rot) {
|
| 601 |
return wallMode ? isVerticalFacing(rot) : isHorizontalUpFacing(rot);
|
| 602 |
}
|
|
@@ -626,12 +624,12 @@
|
|
| 626 |
draggingTrack = true;
|
| 627 |
activePointerId = (e.pointerId != null) ? e.pointerId : 1;
|
| 628 |
if (rotTrack.setPointerCapture) { try { rotTrack.setPointerCapture(activePointerId); } catch (er) {} }
|
| 629 |
-
|
| 630 |
e.preventDefault(); e.stopPropagation();
|
| 631 |
}
|
| 632 |
function onPointerMoveCapture(e) {
|
| 633 |
if (!draggingTrack || ((e.pointerId != null ? e.pointerId : 1) !== activePointerId)) return;
|
| 634 |
-
|
| 635 |
e.preventDefault(); e.stopPropagation();
|
| 636 |
}
|
| 637 |
function endDrag(e) {
|
|
@@ -686,23 +684,23 @@
|
|
| 686 |
|
| 687 |
if (!wallMode) {
|
| 688 |
// --- MODE SOL ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
blob = createBlobShadowAt(pos, rot);
|
| 690 |
-
var e = new pc.Vec3();
|
| 691 |
-
rot.getEulerAngles(e);
|
| 692 |
-
var y0 = ((e.y % 360) + 360) % 360;
|
| 693 |
-
applyRotationY(y0);
|
| 694 |
} else {
|
| 695 |
// --- MODE MUR (première pose) ---
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
applyRotationY(0); // angle initial = 0 (autour de Z)
|
| 699 |
}
|
| 700 |
|
| 701 |
placedOnce = true;
|
| 702 |
rotInput.disabled = false;
|
| 703 |
message(
|
| 704 |
wallMode
|
| 705 |
-
? "Objet placé contre le mur (
|
| 706 |
: "Objet placé. Glissez pour déplacer, tournez-le avec le slider →"
|
| 707 |
);
|
| 708 |
}
|
|
@@ -712,16 +710,15 @@
|
|
| 712 |
});
|
| 713 |
|
| 714 |
// Drag XR continu (déplacements suivants, sans rotation parasite)
|
| 715 |
-
var
|
| 716 |
-
var
|
| 717 |
-
var lastHitRot = new pc.Quat();
|
| 718 |
|
| 719 |
app.xr.input.on("add", function (inputSource) {
|
| 720 |
inputSource.on("selectstart", function () {
|
| 721 |
if (uiInteracting) return;
|
| 722 |
if (!placedOnce || !modelLoaded) return;
|
| 723 |
|
| 724 |
-
// Verrouille l'orientation
|
| 725 |
dragLockedRot.copy(modelRoot.getRotation());
|
| 726 |
|
| 727 |
inputSource.hitTestStart({
|
|
@@ -734,26 +731,22 @@
|
|
| 734 |
if (!isDragging) return;
|
| 735 |
if (!planeMatchesMode(rot)) return;
|
| 736 |
|
| 737 |
-
lastHitRot.copy(rot);
|
| 738 |
-
modelRoot.setPosition(pos);
|
|
|
|
| 739 |
|
| 740 |
-
if (!wallMode)
|
| 741 |
-
updateBlobPositionUnder(pos, rot);
|
| 742 |
-
} else {
|
| 743 |
-
modelRoot.setRotation(dragLockedRot); // aucune rotation pendant le drag
|
| 744 |
-
}
|
| 745 |
});
|
| 746 |
|
| 747 |
-
// Fin du drag : snap orientation sur
|
| 748 |
transientSource.once("remove", function () {
|
| 749 |
isDragging = false;
|
| 750 |
if (wallMode) {
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
modelRoot.setRotation(qFinal);
|
| 757 |
}
|
| 758 |
});
|
| 759 |
}
|
|
@@ -765,9 +758,7 @@
|
|
| 765 |
});
|
| 766 |
});
|
| 767 |
|
| 768 |
-
// Desktop : rotation à la souris (
|
| 769 |
-
var rotateMode = false, lastMouseX = 0;
|
| 770 |
-
var ROTATE_SENSITIVITY = 0.25;
|
| 771 |
app.mouse.on("mousedown", function (e) {
|
| 772 |
if (!app.xr.active || !placedOnce || uiInteracting) return;
|
| 773 |
if (e.button === 0 && !e.shiftKey) {
|
|
@@ -784,17 +775,16 @@
|
|
| 784 |
if (reticle.enabled) {
|
| 785 |
var p = reticle.getPosition();
|
| 786 |
modelRoot.setPosition(p);
|
|
|
|
| 787 |
if (!wallMode) updateBlobPositionUnder(p, null);
|
| 788 |
-
else modelRoot.setRotation(dragLockedRot);
|
| 789 |
}
|
| 790 |
} else if (rotateMode && modelRoot.enabled) {
|
| 791 |
var dx = e.x - lastMouseX;
|
| 792 |
lastMouseX = e.x;
|
| 793 |
-
|
| 794 |
}
|
| 795 |
});
|
| 796 |
app.mouse.on("mouseup", function () {
|
| 797 |
-
// (optionnel) faire un snap sur desktop si tu déclenches un hit-test ici.
|
| 798 |
isDragging = false;
|
| 799 |
rotateMode = false;
|
| 800 |
});
|
|
@@ -804,14 +794,14 @@
|
|
| 804 |
rotInput.disabled = true;
|
| 805 |
rotInput.addEventListener("input", function (e) {
|
| 806 |
var v = parseFloat(e.target.value || "0");
|
| 807 |
-
|
| 808 |
}, { passive: true });
|
| 809 |
|
| 810 |
// Événements AR
|
| 811 |
app.xr.on("start", function () {
|
| 812 |
if (startBtn) startBtn.style.display = "none";
|
| 813 |
message(wallMode
|
| 814 |
-
? "Session AR démarrée. Visez un mur (
|
| 815 |
: "Session AR démarrée. Visez le sol pour détecter un plan…");
|
| 816 |
reticle.enabled = true;
|
| 817 |
});
|
|
|
|
| 1 |
+
/* viewer_ar_ios.js — AR PlayCanvas + GLB/USDZ via config.json (+ "sol")
|
| 2 |
- config.json : { "glb_url": "...", "usdz_url": "...", "sol": true|false }
|
| 3 |
+
- sol = true → plans horizontaux (sol)
|
| 4 |
+
* Y local = Up monde
|
| 5 |
+
* rotation utilisateur = autour de Y local
|
| 6 |
+
- sol = false → plans verticaux (mur)
|
| 7 |
+
* Y local = normale du mur
|
| 8 |
+
* Z local = Up monde projeté dans le plan du mur
|
| 9 |
+
* rotation utilisateur = autour de Y local (normale)
|
| 10 |
+
- Translation : aucune rotation pendant le drag ; snap sur nouveau plan en fin de drag
|
| 11 |
+
- iOS : AR Quick Look (manuel)
|
| 12 |
+
- Android/Desktop : WebXR + PlayCanvas
|
| 13 |
*/
|
| 14 |
|
| 15 |
(function () {
|
|
|
|
| 299 |
ensureQuickLookButton(USDZ_URL);
|
| 300 |
|
| 301 |
if (PLACE_ON_FLOOR) {
|
| 302 |
+
message("Modèle chargé. Appuyez sur « Lancer l’AR ». Dans Quick Look, placez l’objet sur le sol et ajustez-le avec les gestes iOS.");
|
|
|
|
|
|
|
|
|
|
| 303 |
} else {
|
| 304 |
+
message("Modèle chargé. Appuyez sur « Lancer l’AR ». Sur iOS (Quick Look), l’alignement automatique au mur n’est pas disponible ; placez et orientez l’objet manuellement (Y vers le plafond).");
|
|
|
|
|
|
|
|
|
|
| 305 |
}
|
| 306 |
} else {
|
| 307 |
message("iOS détecté, mais aucun 'usdz_url' dans config.json.");
|
|
|
|
| 385 |
app.root.addChild(modelRoot);
|
| 386 |
var modelLoaded = false, placedOnce = false;
|
| 387 |
|
| 388 |
+
// --- Helpers vecteurs/quaternions ---
|
| 389 |
var UP = new pc.Vec3(0, 1, 0);
|
| 390 |
+
|
| 391 |
function projOnPlane(v, n) {
|
| 392 |
var d = v.dot(n);
|
| 393 |
return new pc.Vec3(v.x - d*n.x, v.y - d*n.y, v.z - d*n.z);
|
| 394 |
}
|
| 395 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
function quatFromBasis(X, Y, Z) {
|
| 397 |
// colonnes = X,Y,Z
|
| 398 |
var m00 = X.x, m01 = Y.x, m02 = Z.x;
|
|
|
|
| 429 |
q.normalize();
|
| 430 |
return q;
|
| 431 |
}
|
| 432 |
+
|
| 433 |
+
// --- Bases pour chaque mode ---
|
| 434 |
+
function computeFloorBase() {
|
| 435 |
+
// Y local = UP (0,1,0). On fabrique une base quelconque stable.
|
| 436 |
+
var Y = UP.clone(); // vertical
|
| 437 |
+
var Z = new pc.Vec3(0, 0, 1); // avant “monde”
|
| 438 |
+
// Orthonormalise Z dans le plan horizontal
|
| 439 |
+
Z = projOnPlane(Z, Y); if (Z.lengthSq() < 1e-8) Z = new pc.Vec3(1,0,0);
|
| 440 |
+
Z.normalize();
|
| 441 |
+
var X = new pc.Vec3(); X.cross(Y, Z).normalize();
|
| 442 |
+
Z.cross(X, Y).normalize();
|
| 443 |
+
return quatFromBasis(X, Y, Z);
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
function computeWallBaseFromHit(rot) {
|
| 447 |
+
// Hypothèse PlayCanvas/WebXR : la normale du plan est l’axe Y de la pose.
|
| 448 |
+
// N = rot * (0,1,0)
|
| 449 |
+
var N = new pc.Vec3();
|
| 450 |
+
rot.transformVector(new pc.Vec3(0,1,0), N);
|
| 451 |
+
N.normalize(); // Y local = N (normale du mur)
|
| 452 |
+
|
| 453 |
+
// Z local = Up projeté dans le plan (toujours “vers le plafond” dans le plan vertical)
|
| 454 |
+
var Z = projOnPlane(UP, N);
|
| 455 |
+
if (Z.lengthSq() < 1e-8) Z = new pc.Vec3(0,0,1);
|
| 456 |
+
Z.normalize();
|
| 457 |
+
|
| 458 |
+
// X = Y × Z ; re-orthonormalise
|
| 459 |
+
var X = new pc.Vec3(); X.cross(N, Z).normalize();
|
| 460 |
+
Z.cross(X, N).normalize();
|
| 461 |
+
|
| 462 |
+
return quatFromBasis(X, N /*Y*/, Z);
|
| 463 |
}
|
| 464 |
|
| 465 |
// Mode mur / sol
|
| 466 |
var wallMode = !PLACE_ON_FLOOR;
|
| 467 |
+
var baseRot = new pc.Quat(); // base : définit les axes locaux comme voulu (voir ci-dessus)
|
| 468 |
+
var userYawDeg = 0; // rotation utilisateur autour de Y local (toujours)
|
| 469 |
|
| 470 |
// Ombre blob (sol uniquement)
|
| 471 |
var blob = null;
|
|
|
|
| 522 |
return e;
|
| 523 |
}
|
| 524 |
|
| 525 |
+
// Rotation via slider (toujours autour de Y local)
|
|
|
|
|
|
|
| 526 |
function clamp360(d) { return Math.max(0, Math.min(360, d)); }
|
| 527 |
function updateKnobFromY(yDeg) {
|
| 528 |
var t = 1 - (yDeg / 360);
|
|
|
|
| 530 |
rotInput.value = String(Math.round(yDeg));
|
| 531 |
rotVal.textContent = String(Math.round(yDeg)) + "°";
|
| 532 |
}
|
|
|
|
| 533 |
|
| 534 |
+
function applyYawAroundLocalY(deg) {
|
| 535 |
var y = clamp360(deg);
|
| 536 |
+
userYawDeg = y;
|
| 537 |
if (!modelRoot.enabled) { updateKnobFromY(y); return; }
|
| 538 |
+
var qLocal = new pc.Quat();
|
| 539 |
+
qLocal.setFromAxisAngle(pc.Vec3.UP, y); // UP ici = axe Y LOCAL quand on post-multiplie
|
| 540 |
+
var qFinal = new pc.Quat();
|
| 541 |
+
qFinal.mul2(baseRot, qLocal);
|
| 542 |
+
modelRoot.setRotation(qFinal);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 543 |
updateKnobFromY(y);
|
| 544 |
}
|
| 545 |
|
|
|
|
| 556 |
modelRoot.addChild(instance);
|
| 557 |
modelRoot.setLocalScale(1, 1, 1);
|
| 558 |
|
| 559 |
+
// matériaux safe
|
| 560 |
var renders = instance.findComponents('render');
|
| 561 |
for (var ri = 0; ri < renders.length; ri++) {
|
| 562 |
var r = renders[ri];
|
|
|
|
| 572 |
}
|
| 573 |
}
|
| 574 |
|
|
|
|
|
|
|
|
|
|
| 575 |
modelLoaded = true;
|
| 576 |
message("Modèle chargé. Appuyez sur « Lancer l’AR ».");
|
| 577 |
});
|
| 578 |
|
| 579 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
| 580 |
|
| 581 |
+
// ----- Détection orientation des plans (pose Y = normale du plan) -----
|
| 582 |
+
var TMP_Y = new pc.Vec3(0, 1, 0), TMP_OUT = new pc.Vec3();
|
| 583 |
|
| 584 |
+
// Sol : normale (Y) ≈ +Up
|
| 585 |
function isHorizontalUpFacing(rot, minDot) {
|
|
|
|
| 586 |
minDot = (typeof minDot === "number") ? minDot : 0.75;
|
| 587 |
+
rot.transformVector(TMP_Y, TMP_OUT);
|
| 588 |
return TMP_OUT.y >= minDot;
|
| 589 |
}
|
| 590 |
+
|
| 591 |
+
// Mur : normale (Y) horizontale → |Y.y| petit
|
| 592 |
function isVerticalFacing(rot, maxAbsY) {
|
|
|
|
| 593 |
maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35;
|
| 594 |
+
rot.transformVector(TMP_Y, TMP_OUT);
|
| 595 |
return Math.abs(TMP_OUT.y) <= maxAbsY;
|
| 596 |
}
|
| 597 |
+
|
| 598 |
function planeMatchesMode(rot) {
|
| 599 |
return wallMode ? isVerticalFacing(rot) : isHorizontalUpFacing(rot);
|
| 600 |
}
|
|
|
|
| 624 |
draggingTrack = true;
|
| 625 |
activePointerId = (e.pointerId != null) ? e.pointerId : 1;
|
| 626 |
if (rotTrack.setPointerCapture) { try { rotTrack.setPointerCapture(activePointerId); } catch (er) {} }
|
| 627 |
+
applyYawAroundLocalY(degFromPointer(e));
|
| 628 |
e.preventDefault(); e.stopPropagation();
|
| 629 |
}
|
| 630 |
function onPointerMoveCapture(e) {
|
| 631 |
if (!draggingTrack || ((e.pointerId != null ? e.pointerId : 1) !== activePointerId)) return;
|
| 632 |
+
applyYawAroundLocalY(degFromPointer(e));
|
| 633 |
e.preventDefault(); e.stopPropagation();
|
| 634 |
}
|
| 635 |
function endDrag(e) {
|
|
|
|
| 684 |
|
| 685 |
if (!wallMode) {
|
| 686 |
// --- MODE SOL ---
|
| 687 |
+
baseRot.copy(computeFloorBase());
|
| 688 |
+
// angle initial : récupérer un yaw “monde” pour démarrer aligné (optionnel)
|
| 689 |
+
var e = new pc.Vec3(); rot.getEulerAngles(e);
|
| 690 |
+
applyYawAroundLocalY(((e.y % 360) + 360) % 360);
|
| 691 |
+
// Ombre au sol
|
| 692 |
blob = createBlobShadowAt(pos, rot);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 693 |
} else {
|
| 694 |
// --- MODE MUR (première pose) ---
|
| 695 |
+
baseRot.copy(computeWallBaseFromHit(rot)); // Y=normale, Z=Up projeté
|
| 696 |
+
applyYawAroundLocalY(0); // yaw utilisateur = 0 au départ
|
|
|
|
| 697 |
}
|
| 698 |
|
| 699 |
placedOnce = true;
|
| 700 |
rotInput.disabled = false;
|
| 701 |
message(
|
| 702 |
wallMode
|
| 703 |
+
? "Objet placé contre le mur (Y=normale, Z→plafond). Glissez pour déplacer, tournez avec le slider →"
|
| 704 |
: "Objet placé. Glissez pour déplacer, tournez-le avec le slider →"
|
| 705 |
);
|
| 706 |
}
|
|
|
|
| 710 |
});
|
| 711 |
|
| 712 |
// Drag XR continu (déplacements suivants, sans rotation parasite)
|
| 713 |
+
var rotateMode = false, lastMouseX = 0;
|
| 714 |
+
var ROTATE_SENSITIVITY = 0.25;
|
|
|
|
| 715 |
|
| 716 |
app.xr.input.on("add", function (inputSource) {
|
| 717 |
inputSource.on("selectstart", function () {
|
| 718 |
if (uiInteracting) return;
|
| 719 |
if (!placedOnce || !modelLoaded) return;
|
| 720 |
|
| 721 |
+
// Verrouille l'orientation pendant la translation
|
| 722 |
dragLockedRot.copy(modelRoot.getRotation());
|
| 723 |
|
| 724 |
inputSource.hitTestStart({
|
|
|
|
| 731 |
if (!isDragging) return;
|
| 732 |
if (!planeMatchesMode(rot)) return;
|
| 733 |
|
| 734 |
+
lastHitRot.copy(rot); // garde trace du dernier plan
|
| 735 |
+
modelRoot.setPosition(pos); // translate uniquement
|
| 736 |
+
modelRoot.setRotation(dragLockedRot); // aucune rotation pendant le drag
|
| 737 |
|
| 738 |
+
if (!wallMode) updateBlobPositionUnder(pos, rot);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 739 |
});
|
| 740 |
|
| 741 |
+
// Fin du drag : snap orientation sur NOUVEAU plan + ré-applique yaw local Y
|
| 742 |
transientSource.once("remove", function () {
|
| 743 |
isDragging = false;
|
| 744 |
if (wallMode) {
|
| 745 |
+
baseRot.copy(computeWallBaseFromHit(lastHitRot)); // recalcule Y/Z
|
| 746 |
+
applyYawAroundLocalY(userYawDeg); // conserve l'angle utilisateur autour de Y local
|
| 747 |
+
} else {
|
| 748 |
+
// sol : garder baseRot (Y=Up monde), ne rien changer d'orientation
|
| 749 |
+
modelRoot.setRotation(dragLockedRot);
|
|
|
|
| 750 |
}
|
| 751 |
});
|
| 752 |
}
|
|
|
|
| 758 |
});
|
| 759 |
});
|
| 760 |
|
| 761 |
+
// Desktop : rotation à la souris (Shift+drag gauche ou clic droit)
|
|
|
|
|
|
|
| 762 |
app.mouse.on("mousedown", function (e) {
|
| 763 |
if (!app.xr.active || !placedOnce || uiInteracting) return;
|
| 764 |
if (e.button === 0 && !e.shiftKey) {
|
|
|
|
| 775 |
if (reticle.enabled) {
|
| 776 |
var p = reticle.getPosition();
|
| 777 |
modelRoot.setPosition(p);
|
| 778 |
+
modelRoot.setRotation(dragLockedRot);
|
| 779 |
if (!wallMode) updateBlobPositionUnder(p, null);
|
|
|
|
| 780 |
}
|
| 781 |
} else if (rotateMode && modelRoot.enabled) {
|
| 782 |
var dx = e.x - lastMouseX;
|
| 783 |
lastMouseX = e.x;
|
| 784 |
+
applyYawAroundLocalY(userYawDeg + dx * ROTATE_SENSITIVITY);
|
| 785 |
}
|
| 786 |
});
|
| 787 |
app.mouse.on("mouseup", function () {
|
|
|
|
| 788 |
isDragging = false;
|
| 789 |
rotateMode = false;
|
| 790 |
});
|
|
|
|
| 794 |
rotInput.disabled = true;
|
| 795 |
rotInput.addEventListener("input", function (e) {
|
| 796 |
var v = parseFloat(e.target.value || "0");
|
| 797 |
+
applyYawAroundLocalY(v);
|
| 798 |
}, { passive: true });
|
| 799 |
|
| 800 |
// Événements AR
|
| 801 |
app.xr.on("start", function () {
|
| 802 |
if (startBtn) startBtn.style.display = "none";
|
| 803 |
message(wallMode
|
| 804 |
+
? "Session AR démarrée. Visez un mur (Y=normale du mur, Z du modèle vers le plafond)…"
|
| 805 |
: "Session AR démarrée. Visez le sol pour détecter un plan…");
|
| 806 |
reticle.enabled = true;
|
| 807 |
});
|