Update viewer_ar_ios.js
Browse files- viewer_ar_ios.js +132 -50
viewer_ar_ios.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
| 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) + rotation autour de l’axe local Y (haut)
|
| 4 |
-
- sol = false → plans verticaux (mur)
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
- Android/Desktop : WebXR AR + bouton "Lancer l’AR" + slider de rotation
|
| 7 |
*/
|
| 8 |
|
|
@@ -300,8 +305,8 @@
|
|
| 300 |
} else {
|
| 301 |
message(
|
| 302 |
"Modèle chargé. Appuyez sur « Lancer l’AR ». Sur iOS (Quick Look), l’objet ne peut pas être " +
|
| 303 |
-
"automatiquement aligné au mur
|
| 304 |
-
"contre le mur (
|
| 305 |
);
|
| 306 |
}
|
| 307 |
} else {
|
|
@@ -386,10 +391,75 @@
|
|
| 386 |
app.root.addChild(modelRoot);
|
| 387 |
var modelLoaded = false, placedOnce = false;
|
| 388 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
// Mode mur / sol
|
| 390 |
var wallMode = !PLACE_ON_FLOOR;
|
| 391 |
-
var wallBaseRot = new pc.Quat(); //
|
| 392 |
-
var wallAngleDeg = 0;
|
| 393 |
|
| 394 |
// Ombre blob (sol uniquement)
|
| 395 |
var blob = null;
|
|
@@ -446,29 +516,21 @@
|
|
| 446 |
return e;
|
| 447 |
}
|
| 448 |
|
| 449 |
-
// Rotation via slider
|
| 450 |
var baseEulerX = 0, baseEulerZ = 0;
|
| 451 |
var rotationYDeg = 0;
|
| 452 |
-
|
| 453 |
function clamp360(d) { return Math.max(0, Math.min(360, d)); }
|
| 454 |
-
|
| 455 |
function updateKnobFromY(yDeg) {
|
| 456 |
var t = 1 - (yDeg / 360);
|
| 457 |
rotKnob.style.top = String(t * 100) + "%";
|
| 458 |
rotInput.value = String(Math.round(yDeg));
|
| 459 |
rotVal.textContent = String(Math.round(yDeg)) + "°";
|
| 460 |
}
|
| 461 |
-
|
| 462 |
-
function getCurrentAngle() {
|
| 463 |
-
return wallMode ? wallAngleDeg : rotationYDeg;
|
| 464 |
-
}
|
| 465 |
|
| 466 |
function applyRotationY(deg) {
|
| 467 |
var y = clamp360(deg);
|
| 468 |
-
if (!modelRoot.enabled) {
|
| 469 |
-
updateKnobFromY(y);
|
| 470 |
-
return;
|
| 471 |
-
}
|
| 472 |
|
| 473 |
if (!wallMode) {
|
| 474 |
rotationYDeg = y;
|
|
@@ -476,12 +538,11 @@
|
|
| 476 |
} else {
|
| 477 |
wallAngleDeg = y;
|
| 478 |
var qLocal = new pc.Quat();
|
| 479 |
-
qLocal.setFromAxisAngle(pc.Vec3.UP, y); // rotation
|
| 480 |
var qFinal = new pc.Quat();
|
| 481 |
-
qFinal.mul2(wallBaseRot, qLocal); // base (
|
| 482 |
modelRoot.setRotation(qFinal);
|
| 483 |
}
|
| 484 |
-
|
| 485 |
updateKnobFromY(y);
|
| 486 |
}
|
| 487 |
|
|
@@ -517,7 +578,7 @@
|
|
| 517 |
baseEulerX = initE.x; baseEulerZ = initE.z;
|
| 518 |
|
| 519 |
modelLoaded = true;
|
| 520 |
-
message("Modèle chargé. Appuyez sur «
|
| 521 |
});
|
| 522 |
|
| 523 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
|
@@ -525,25 +586,26 @@
|
|
| 525 |
// ----- Détection orientation des plans -----
|
| 526 |
var TMP_IN = new pc.Vec3(0, 1, 0), TMP_OUT = new pc.Vec3();
|
| 527 |
|
| 528 |
-
// Sol : normal ≈ +Y
|
| 529 |
function isHorizontalUpFacing(rot, minDot) {
|
| 530 |
minDot = (typeof minDot === "number") ? minDot : 0.75;
|
| 531 |
rot.transformVector(TMP_IN, TMP_OUT);
|
| 532 |
return TMP_OUT.y >= minDot;
|
| 533 |
}
|
| 534 |
-
|
| 535 |
-
// Mur : normal horizontale → axe Y du hit-test presque horizontal
|
| 536 |
function isVerticalFacing(rot, maxAbsY) {
|
| 537 |
maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35;
|
| 538 |
rot.transformVector(TMP_IN, TMP_OUT);
|
| 539 |
return Math.abs(TMP_OUT.y) <= maxAbsY;
|
| 540 |
}
|
| 541 |
-
|
| 542 |
function planeMatchesMode(rot) {
|
| 543 |
return wallMode ? isVerticalFacing(rot) : isHorizontalUpFacing(rot);
|
| 544 |
}
|
| 545 |
|
| 546 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 547 |
var uiInteracting = false;
|
| 548 |
var draggingTrack = false;
|
| 549 |
var activePointerId = null;
|
|
@@ -602,10 +664,9 @@
|
|
| 602 |
}
|
| 603 |
|
| 604 |
var startBtn = ensureARStartButton(activateAR);
|
| 605 |
-
|
| 606 |
app.keyboard.on("keydown", function (evt) { if (evt.key === pc.KEY_ESCAPE && app.xr.active) app.xr.end(); });
|
| 607 |
|
| 608 |
-
// Hit-test principal (
|
| 609 |
app.xr.hitTest.on("available", function () {
|
| 610 |
app.xr.hitTest.start({
|
| 611 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
|
@@ -631,16 +692,16 @@
|
|
| 631 |
applyRotationY(y0);
|
| 632 |
} else {
|
| 633 |
// --- MODE MUR (première pose) ---
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
applyRotationY(0);
|
| 637 |
}
|
| 638 |
|
| 639 |
placedOnce = true;
|
| 640 |
rotInput.disabled = false;
|
| 641 |
message(
|
| 642 |
wallMode
|
| 643 |
-
? "Objet placé contre le mur. Glissez pour déplacer
|
| 644 |
: "Objet placé. Glissez pour déplacer, tournez-le avec le slider →"
|
| 645 |
);
|
| 646 |
}
|
|
@@ -649,13 +710,15 @@
|
|
| 649 |
});
|
| 650 |
});
|
| 651 |
|
| 652 |
-
// Drag XR continu (
|
| 653 |
-
var isDragging = false;
|
| 654 |
app.xr.input.on("add", function (inputSource) {
|
| 655 |
inputSource.on("selectstart", function () {
|
| 656 |
if (uiInteracting) return;
|
| 657 |
if (!placedOnce || !modelLoaded) return;
|
| 658 |
|
|
|
|
|
|
|
|
|
|
| 659 |
inputSource.hitTestStart({
|
| 660 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
| 661 |
callback: function (err, transientSource) {
|
|
@@ -666,15 +729,27 @@
|
|
| 666 |
if (!isDragging) return;
|
| 667 |
if (!planeMatchesMode(rot)) return;
|
| 668 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 669 |
modelRoot.setPosition(pos);
|
| 670 |
|
| 671 |
if (!wallMode) {
|
| 672 |
-
// sol : on ne change que la position, l'orientation reste pilotée par le slider
|
| 673 |
updateBlobPositionUnder(pos, rot);
|
| 674 |
} else {
|
| 675 |
-
// mur :
|
| 676 |
-
|
| 677 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
var qLocal = new pc.Quat();
|
| 679 |
qLocal.setFromAxisAngle(pc.Vec3.UP, wallAngleDeg);
|
| 680 |
var qFinal = new pc.Quat();
|
|
@@ -682,12 +757,13 @@
|
|
| 682 |
modelRoot.setRotation(qFinal);
|
| 683 |
}
|
| 684 |
});
|
| 685 |
-
|
| 686 |
-
transientSource.once("remove", function () { isDragging = false; });
|
| 687 |
}
|
| 688 |
});
|
| 689 |
});
|
| 690 |
-
|
|
|
|
|
|
|
|
|
|
| 691 |
});
|
| 692 |
|
| 693 |
// Desktop : rotation à la souris (clic droit ou Shift+clic gauche)
|
|
@@ -697,6 +773,7 @@
|
|
| 697 |
if (!app.xr.active || !placedOnce || uiInteracting) return;
|
| 698 |
if (e.button === 0 && !e.shiftKey) {
|
| 699 |
isDragging = true;
|
|
|
|
| 700 |
} else if (e.button === 2 || (e.button === 0 && e.shiftKey)) {
|
| 701 |
rotateMode = true;
|
| 702 |
lastMouseX = e.x;
|
|
@@ -709,6 +786,7 @@
|
|
| 709 |
var p = reticle.getPosition();
|
| 710 |
modelRoot.setPosition(p);
|
| 711 |
if (!wallMode) updateBlobPositionUnder(p, null);
|
|
|
|
| 712 |
}
|
| 713 |
} else if (rotateMode && modelRoot.enabled) {
|
| 714 |
var dx = e.x - lastMouseX;
|
|
@@ -716,7 +794,14 @@
|
|
| 716 |
applyRotationY(getCurrentAngle() + dx * ROTATE_SENSITIVITY);
|
| 717 |
}
|
| 718 |
});
|
| 719 |
-
app.mouse.on("mouseup", function () {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 720 |
window.addEventListener("contextmenu", function (e) { e.preventDefault(); });
|
| 721 |
|
| 722 |
// Slider (accessibilité clavier)
|
|
@@ -726,14 +811,12 @@
|
|
| 726 |
applyRotationY(v);
|
| 727 |
}, { passive: true });
|
| 728 |
|
| 729 |
-
// Événements AR
|
| 730 |
app.xr.on("start", function () {
|
| 731 |
if (startBtn) startBtn.style.display = "none";
|
| 732 |
-
message(
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
: "Session AR démarrée. Visez le sol pour détecter un plan…"
|
| 736 |
-
);
|
| 737 |
reticle.enabled = true;
|
| 738 |
});
|
| 739 |
app.xr.on("end", function () {
|
|
@@ -741,7 +824,6 @@
|
|
| 741 |
message("Session AR terminée.");
|
| 742 |
reticle.enabled = false;
|
| 743 |
isDragging = false;
|
| 744 |
-
rotateMode = false;
|
| 745 |
rotInput.disabled = true;
|
| 746 |
});
|
| 747 |
app.xr.on("available:" + pc.XRTYPE_AR, function (a) {
|
|
|
|
| 1 |
+
/* viewer_ar_ios.js — AR PlayCanvas + GLB/USDZ via config.json (+ "sol" + mode mur stable)
|
| 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) :
|
| 5 |
+
* Y local = normale du mur
|
| 6 |
+
* Z local = direction plafond (Up monde) projetée dans le plan du mur
|
| 7 |
+
* rotation utilisateur = autour de Y local (normale)
|
| 8 |
+
* PENDANT une translation : orientation verrouillée (pas de twist parasite)
|
| 9 |
+
* À la fin d’une translation : on recalcule la base par rapport AU NOUVEAU MUR
|
| 10 |
+
- iOS : AR Quick Look (USDZ)
|
| 11 |
- Android/Desktop : WebXR AR + bouton "Lancer l’AR" + slider de rotation
|
| 12 |
*/
|
| 13 |
|
|
|
|
| 305 |
} else {
|
| 306 |
message(
|
| 307 |
"Modèle chargé. Appuyez sur « Lancer l’AR ». Sur iOS (Quick Look), l’objet ne peut pas être " +
|
| 308 |
+
"automatiquement aligné au mur : placez-le et orientez-le manuellement " +
|
| 309 |
+
"contre le mur (Z vers le plafond)."
|
| 310 |
);
|
| 311 |
}
|
| 312 |
} else {
|
|
|
|
| 391 |
app.root.addChild(modelRoot);
|
| 392 |
var modelLoaded = false, placedOnce = false;
|
| 393 |
|
| 394 |
+
// --- Helpers vecteurs/quaternions stables ---
|
| 395 |
+
var UP = new pc.Vec3(0, 1, 0);
|
| 396 |
+
function projOnPlane(v, n) {
|
| 397 |
+
var d = v.dot(n);
|
| 398 |
+
return new pc.Vec3(v.x - d*n.x, v.y - d*n.y, v.z - d*n.z);
|
| 399 |
+
}
|
| 400 |
+
function normalizeOr(v, fallback) {
|
| 401 |
+
var len = v.length();
|
| 402 |
+
if (len > 1e-6) { v.scale(1/len); return v; }
|
| 403 |
+
return fallback.clone ? fallback.clone() : new pc.Vec3(fallback.x, fallback.y, fallback.z);
|
| 404 |
+
}
|
| 405 |
+
function buildWallBasisFromNormal(N) {
|
| 406 |
+
var Y = N.clone().normalize(); // Y = normale du mur
|
| 407 |
+
var Z = projOnPlane(UP, Y); // Z = Up projeté (vers plafond)
|
| 408 |
+
if (Z.lengthSq() < 1e-8) Z = projOnPlane(new pc.Vec3(0,0,1), Y);
|
| 409 |
+
Z.normalize();
|
| 410 |
+
var X = new pc.Vec3(); X.cross(Z, Y).normalize(); // X = Z × Y
|
| 411 |
+
Z.cross(Y, X).normalize(); // Re-orthonormalise
|
| 412 |
+
return { X: X, Y: Y, Z: Z };
|
| 413 |
+
}
|
| 414 |
+
function quatFromBasis(X, Y, Z) {
|
| 415 |
+
// colonnes = X,Y,Z
|
| 416 |
+
var m00 = X.x, m01 = Y.x, m02 = Z.x;
|
| 417 |
+
var m10 = X.y, m11 = Y.y, m12 = Z.y;
|
| 418 |
+
var m20 = X.z, m21 = Y.z, m22 = Z.z;
|
| 419 |
+
|
| 420 |
+
var t = m00 + m11 + m22;
|
| 421 |
+
var q = new pc.Quat();
|
| 422 |
+
if (t > 0) {
|
| 423 |
+
var s = Math.sqrt(t + 1.0) * 2;
|
| 424 |
+
q.w = 0.25 * s;
|
| 425 |
+
q.x = (m21 - m12) / s;
|
| 426 |
+
q.y = (m02 - m20) / s;
|
| 427 |
+
q.z = (m10 - m01) / s;
|
| 428 |
+
} else if (m00 > m11 && m00 > m22) {
|
| 429 |
+
var s = Math.sqrt(1.0 + m00 - m11 - m22) * 2;
|
| 430 |
+
q.w = (m21 - m12) / s;
|
| 431 |
+
q.x = 0.25 * s;
|
| 432 |
+
q.y = (m01 + m10) / s;
|
| 433 |
+
q.z = (m02 + m20) / s;
|
| 434 |
+
} else if (m11 > m22) {
|
| 435 |
+
var s2 = Math.sqrt(1.0 + m11 - m00 - m22) * 2;
|
| 436 |
+
q.w = (m02 - m20) / s2;
|
| 437 |
+
q.x = (m01 + m10) / s2;
|
| 438 |
+
q.y = 0.25 * s2;
|
| 439 |
+
q.z = (m12 + m21) / s2;
|
| 440 |
+
} else {
|
| 441 |
+
var s3 = Math.sqrt(1.0 + m22 - m00 - m11) * 2;
|
| 442 |
+
q.w = (m10 - m01) / s3;
|
| 443 |
+
q.x = (m02 + m20) / s3;
|
| 444 |
+
q.y = (m12 + m21) / s3;
|
| 445 |
+
q.z = 0.25 * s3;
|
| 446 |
+
}
|
| 447 |
+
q.normalize();
|
| 448 |
+
return q;
|
| 449 |
+
}
|
| 450 |
+
function computeWallBaseFromRot(rot) {
|
| 451 |
+
// Y monde = rot * (0,1,0) = normale du mur
|
| 452 |
+
var N = new pc.Vec3();
|
| 453 |
+
rot.transformVector(new pc.Vec3(0, 1, 0), N);
|
| 454 |
+
N.normalize();
|
| 455 |
+
var basis = buildWallBasisFromNormal(N);
|
| 456 |
+
return quatFromBasis(basis.X, basis.Y, basis.Z); // Y=normale, Z=plafond
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
// Mode mur / sol
|
| 460 |
var wallMode = !PLACE_ON_FLOOR;
|
| 461 |
+
var wallBaseRot = new pc.Quat(); // base (mur) : Y=normale, Z=plafond
|
| 462 |
+
var wallAngleDeg = 0; // yaw utilisateur autour de Y local
|
| 463 |
|
| 464 |
// Ombre blob (sol uniquement)
|
| 465 |
var blob = null;
|
|
|
|
| 516 |
return e;
|
| 517 |
}
|
| 518 |
|
| 519 |
+
// Rotation via slider (sol: Euler Y, mur: twist autour de Y local)
|
| 520 |
var baseEulerX = 0, baseEulerZ = 0;
|
| 521 |
var rotationYDeg = 0;
|
|
|
|
| 522 |
function clamp360(d) { return Math.max(0, Math.min(360, d)); }
|
|
|
|
| 523 |
function updateKnobFromY(yDeg) {
|
| 524 |
var t = 1 - (yDeg / 360);
|
| 525 |
rotKnob.style.top = String(t * 100) + "%";
|
| 526 |
rotInput.value = String(Math.round(yDeg));
|
| 527 |
rotVal.textContent = String(Math.round(yDeg)) + "°";
|
| 528 |
}
|
| 529 |
+
function getCurrentAngle() { return wallMode ? wallAngleDeg : rotationYDeg; }
|
|
|
|
|
|
|
|
|
|
| 530 |
|
| 531 |
function applyRotationY(deg) {
|
| 532 |
var y = clamp360(deg);
|
| 533 |
+
if (!modelRoot.enabled) { updateKnobFromY(y); return; }
|
|
|
|
|
|
|
|
|
|
| 534 |
|
| 535 |
if (!wallMode) {
|
| 536 |
rotationYDeg = y;
|
|
|
|
| 538 |
} else {
|
| 539 |
wallAngleDeg = y;
|
| 540 |
var qLocal = new pc.Quat();
|
| 541 |
+
qLocal.setFromAxisAngle(pc.Vec3.UP, y); // rotation autour de Y local (avant base)
|
| 542 |
var qFinal = new pc.Quat();
|
| 543 |
+
qFinal.mul2(wallBaseRot, qLocal); // base mur (Y=normale, Z=up projeté) puis yaw utilisateur
|
| 544 |
modelRoot.setRotation(qFinal);
|
| 545 |
}
|
|
|
|
| 546 |
updateKnobFromY(y);
|
| 547 |
}
|
| 548 |
|
|
|
|
| 578 |
baseEulerX = initE.x; baseEulerZ = initE.z;
|
| 579 |
|
| 580 |
modelLoaded = true;
|
| 581 |
+
message("Modèle chargé. Appuyez sur « Lancer l’AR ».");
|
| 582 |
});
|
| 583 |
|
| 584 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
|
|
|
| 586 |
// ----- Détection orientation des plans -----
|
| 587 |
var TMP_IN = new pc.Vec3(0, 1, 0), TMP_OUT = new pc.Vec3();
|
| 588 |
|
|
|
|
| 589 |
function isHorizontalUpFacing(rot, minDot) {
|
| 590 |
minDot = (typeof minDot === "number") ? minDot : 0.75;
|
| 591 |
rot.transformVector(TMP_IN, TMP_OUT);
|
| 592 |
return TMP_OUT.y >= minDot;
|
| 593 |
}
|
|
|
|
|
|
|
| 594 |
function isVerticalFacing(rot, maxAbsY) {
|
| 595 |
maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35;
|
| 596 |
rot.transformVector(TMP_IN, TMP_OUT);
|
| 597 |
return Math.abs(TMP_OUT.y) <= maxAbsY;
|
| 598 |
}
|
|
|
|
| 599 |
function planeMatchesMode(rot) {
|
| 600 |
return wallMode ? isVerticalFacing(rot) : isHorizontalUpFacing(rot);
|
| 601 |
}
|
| 602 |
|
| 603 |
+
// --- Translation sans rotation parasite ---
|
| 604 |
+
var isDragging = false;
|
| 605 |
+
var dragLockedRot = new pc.Quat(); // orientation figée pendant le drag
|
| 606 |
+
var lastHitRot = new pc.Quat(); // dernière rotation de hit (pour snap en fin de drag)
|
| 607 |
+
|
| 608 |
+
// Slider (pointer capture UI)
|
| 609 |
var uiInteracting = false;
|
| 610 |
var draggingTrack = false;
|
| 611 |
var activePointerId = null;
|
|
|
|
| 664 |
}
|
| 665 |
|
| 666 |
var startBtn = ensureARStartButton(activateAR);
|
|
|
|
| 667 |
app.keyboard.on("keydown", function (evt) { if (evt.key === pc.KEY_ESCAPE && app.xr.active) app.xr.end(); });
|
| 668 |
|
| 669 |
+
// Hit-test principal (reticle + PREMIER placement)
|
| 670 |
app.xr.hitTest.on("available", function () {
|
| 671 |
app.xr.hitTest.start({
|
| 672 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
|
|
|
| 692 |
applyRotationY(y0);
|
| 693 |
} else {
|
| 694 |
// --- MODE MUR (première pose) ---
|
| 695 |
+
wallBaseRot.copy(computeWallBaseFromRot(rot)); // Y=normale, Z=plafond
|
| 696 |
+
modelRoot.setRotation(wallBaseRot);
|
| 697 |
+
applyRotationY(0); // yaw initial = 0
|
| 698 |
}
|
| 699 |
|
| 700 |
placedOnce = true;
|
| 701 |
rotInput.disabled = false;
|
| 702 |
message(
|
| 703 |
wallMode
|
| 704 |
+
? "Objet placé contre le mur (Y=normale, Z→plafond). Glissez pour déplacer, tournez avec le slider →"
|
| 705 |
: "Objet placé. Glissez pour déplacer, tournez-le avec le slider →"
|
| 706 |
);
|
| 707 |
}
|
|
|
|
| 710 |
});
|
| 711 |
});
|
| 712 |
|
| 713 |
+
// Drag XR continu (déplacements suivants, sans rotation parasite)
|
|
|
|
| 714 |
app.xr.input.on("add", function (inputSource) {
|
| 715 |
inputSource.on("selectstart", function () {
|
| 716 |
if (uiInteracting) return;
|
| 717 |
if (!placedOnce || !modelLoaded) return;
|
| 718 |
|
| 719 |
+
// Verrouille l'orientation courante pendant la translation
|
| 720 |
+
dragLockedRot.copy(modelRoot.getRotation());
|
| 721 |
+
|
| 722 |
inputSource.hitTestStart({
|
| 723 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
| 724 |
callback: function (err, transientSource) {
|
|
|
|
| 729 |
if (!isDragging) return;
|
| 730 |
if (!planeMatchesMode(rot)) return;
|
| 731 |
|
| 732 |
+
// Mémorise la dernière orientation de plan rencontrée
|
| 733 |
+
lastHitRot.copy(rot);
|
| 734 |
+
|
| 735 |
+
// Déplace uniquement
|
| 736 |
modelRoot.setPosition(pos);
|
| 737 |
|
| 738 |
if (!wallMode) {
|
|
|
|
| 739 |
updateBlobPositionUnder(pos, rot);
|
| 740 |
} else {
|
| 741 |
+
// En mode mur : PAS de rotation pendant le drag
|
| 742 |
+
modelRoot.setRotation(dragLockedRot);
|
| 743 |
+
}
|
| 744 |
+
});
|
| 745 |
+
|
| 746 |
+
// Fin du drag : snap propre au NOUVEAU mur (si on est en mode mur)
|
| 747 |
+
transientSource.once("remove", function () {
|
| 748 |
+
isDragging = false;
|
| 749 |
+
if (wallMode) {
|
| 750 |
+
// Recalcule la base avec le dernier mur touché
|
| 751 |
+
wallBaseRot.copy(computeWallBaseFromRot(lastHitRot));
|
| 752 |
+
// Ré-applique le yaw utilisateur autour de Y local (normale du NOUVEAU mur)
|
| 753 |
var qLocal = new pc.Quat();
|
| 754 |
qLocal.setFromAxisAngle(pc.Vec3.UP, wallAngleDeg);
|
| 755 |
var qFinal = new pc.Quat();
|
|
|
|
| 757 |
modelRoot.setRotation(qFinal);
|
| 758 |
}
|
| 759 |
});
|
|
|
|
|
|
|
| 760 |
}
|
| 761 |
});
|
| 762 |
});
|
| 763 |
+
|
| 764 |
+
inputSource.on("selectend", function () {
|
| 765 |
+
isDragging = false;
|
| 766 |
+
});
|
| 767 |
});
|
| 768 |
|
| 769 |
// Desktop : rotation à la souris (clic droit ou Shift+clic gauche)
|
|
|
|
| 773 |
if (!app.xr.active || !placedOnce || uiInteracting) return;
|
| 774 |
if (e.button === 0 && !e.shiftKey) {
|
| 775 |
isDragging = true;
|
| 776 |
+
dragLockedRot.copy(modelRoot.getRotation());
|
| 777 |
} else if (e.button === 2 || (e.button === 0 && e.shiftKey)) {
|
| 778 |
rotateMode = true;
|
| 779 |
lastMouseX = e.x;
|
|
|
|
| 786 |
var p = reticle.getPosition();
|
| 787 |
modelRoot.setPosition(p);
|
| 788 |
if (!wallMode) updateBlobPositionUnder(p, null);
|
| 789 |
+
else modelRoot.setRotation(dragLockedRot);
|
| 790 |
}
|
| 791 |
} else if (rotateMode && modelRoot.enabled) {
|
| 792 |
var dx = e.x - lastMouseX;
|
|
|
|
| 794 |
applyRotationY(getCurrentAngle() + dx * ROTATE_SENSITIVITY);
|
| 795 |
}
|
| 796 |
});
|
| 797 |
+
app.mouse.on("mouseup", function () {
|
| 798 |
+
if (isDragging) {
|
| 799 |
+
// En desktop on n'a pas lastHitRot : on garde l'orientation verrouillée
|
| 800 |
+
// (optionnel : on pourrait lancer un hit-test unique ici pour "snap", selon l'intégration)
|
| 801 |
+
}
|
| 802 |
+
isDragging = false;
|
| 803 |
+
rotateMode = false;
|
| 804 |
+
});
|
| 805 |
window.addEventListener("contextmenu", function (e) { e.preventDefault(); });
|
| 806 |
|
| 807 |
// Slider (accessibilité clavier)
|
|
|
|
| 811 |
applyRotationY(v);
|
| 812 |
}, { passive: true });
|
| 813 |
|
| 814 |
+
// Événements AR
|
| 815 |
app.xr.on("start", function () {
|
| 816 |
if (startBtn) startBtn.style.display = "none";
|
| 817 |
+
message(wallMode
|
| 818 |
+
? "Session AR démarrée. Visez un mur (Y=normale, Z du modèle sera vers le plafond)…"
|
| 819 |
+
: "Session AR démarrée. Visez le sol pour détecter un plan…");
|
|
|
|
|
|
|
| 820 |
reticle.enabled = true;
|
| 821 |
});
|
| 822 |
app.xr.on("end", function () {
|
|
|
|
| 824 |
message("Session AR terminée.");
|
| 825 |
reticle.enabled = false;
|
| 826 |
isDragging = false;
|
|
|
|
| 827 |
rotInput.disabled = true;
|
| 828 |
});
|
| 829 |
app.xr.on("available:" + pc.XRTYPE_AR, function (a) {
|