Update viewer_ar_ios.js
Browse files- viewer_ar_ios.js +51 -55
viewer_ar_ios.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 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) :
|
| 5 |
-
*
|
| 6 |
-
*
|
| 7 |
-
* rotation utilisateur = autour de
|
| 8 |
* PENDANT une translation : orientation verrouillée (pas de twist parasite)
|
| 9 |
-
* À la fin d’une translation :
|
| 10 |
- iOS : AR Quick Look (USDZ)
|
| 11 |
- Android/Desktop : WebXR AR + bouton "Lancer l’AR" + slider de rotation
|
| 12 |
*/
|
|
@@ -305,8 +305,7 @@
|
|
| 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
|
| 309 |
-
"contre le mur (Z vers le plafond)."
|
| 310 |
);
|
| 311 |
}
|
| 312 |
} else {
|
|
@@ -397,18 +396,17 @@
|
|
| 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
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
Z
|
| 410 |
-
|
| 411 |
-
Z.cross(Y, X).normalize(); // Re-orthonormalise
|
| 412 |
return { X: X, Y: Y, Z: Z };
|
| 413 |
}
|
| 414 |
function quatFromBasis(X, Y, Z) {
|
|
@@ -447,19 +445,19 @@
|
|
| 447 |
q.normalize();
|
| 448 |
return q;
|
| 449 |
}
|
| 450 |
-
function
|
| 451 |
-
//
|
| 452 |
-
var
|
| 453 |
-
rot.transformVector(new pc.Vec3(0,
|
| 454 |
-
|
| 455 |
-
var basis =
|
| 456 |
-
return quatFromBasis(basis.X, basis.Y, basis.Z);
|
| 457 |
}
|
| 458 |
|
| 459 |
// Mode mur / sol
|
| 460 |
var wallMode = !PLACE_ON_FLOOR;
|
| 461 |
-
var wallBaseRot = new pc.Quat(); // base (mur) :
|
| 462 |
-
var wallAngleDeg = 0; //
|
| 463 |
|
| 464 |
// Ombre blob (sol uniquement)
|
| 465 |
var blob = null;
|
|
@@ -516,7 +514,7 @@
|
|
| 516 |
return e;
|
| 517 |
}
|
| 518 |
|
| 519 |
-
// Rotation via slider (sol: Euler Y, mur: twist autour de
|
| 520 |
var baseEulerX = 0, baseEulerZ = 0;
|
| 521 |
var rotationYDeg = 0;
|
| 522 |
function clamp360(d) { return Math.max(0, Math.min(360, d)); }
|
|
@@ -536,11 +534,12 @@
|
|
| 536 |
rotationYDeg = y;
|
| 537 |
modelRoot.setEulerAngles(baseEulerX, y, baseEulerZ);
|
| 538 |
} else {
|
|
|
|
| 539 |
wallAngleDeg = y;
|
| 540 |
var qLocal = new pc.Quat();
|
| 541 |
-
qLocal.setFromAxisAngle(pc.Vec3
|
| 542 |
var qFinal = new pc.Quat();
|
| 543 |
-
qFinal.mul2(wallBaseRot, qLocal);
|
| 544 |
modelRoot.setRotation(qFinal);
|
| 545 |
}
|
| 546 |
updateKnobFromY(y);
|
|
@@ -583,17 +582,19 @@
|
|
| 583 |
|
| 584 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
| 585 |
|
| 586 |
-
// ----- Détection orientation des plans -----
|
| 587 |
-
var
|
| 588 |
|
| 589 |
function isHorizontalUpFacing(rot, minDot) {
|
|
|
|
| 590 |
minDot = (typeof minDot === "number") ? minDot : 0.75;
|
| 591 |
-
rot.transformVector(
|
| 592 |
return TMP_OUT.y >= minDot;
|
| 593 |
}
|
| 594 |
function isVerticalFacing(rot, maxAbsY) {
|
|
|
|
| 595 |
maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35;
|
| 596 |
-
rot.transformVector(
|
| 597 |
return Math.abs(TMP_OUT.y) <= maxAbsY;
|
| 598 |
}
|
| 599 |
function planeMatchesMode(rot) {
|
|
@@ -692,16 +693,16 @@
|
|
| 692 |
applyRotationY(y0);
|
| 693 |
} else {
|
| 694 |
// --- MODE MUR (première pose) ---
|
| 695 |
-
wallBaseRot.copy(
|
| 696 |
modelRoot.setRotation(wallBaseRot);
|
| 697 |
-
applyRotationY(0); //
|
| 698 |
}
|
| 699 |
|
| 700 |
placedOnce = true;
|
| 701 |
rotInput.disabled = false;
|
| 702 |
message(
|
| 703 |
wallMode
|
| 704 |
-
? "Objet placé contre le mur (
|
| 705 |
: "Objet placé. Glissez pour déplacer, tournez-le avec le slider →"
|
| 706 |
);
|
| 707 |
}
|
|
@@ -711,6 +712,10 @@
|
|
| 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;
|
|
@@ -729,29 +734,23 @@
|
|
| 729 |
if (!isDragging) return;
|
| 730 |
if (!planeMatchesMode(rot)) return;
|
| 731 |
|
| 732 |
-
//
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
// Déplace uniquement
|
| 736 |
-
modelRoot.setPosition(pos);
|
| 737 |
|
| 738 |
if (!wallMode) {
|
| 739 |
updateBlobPositionUnder(pos, rot);
|
| 740 |
} else {
|
| 741 |
-
//
|
| 742 |
-
modelRoot.setRotation(dragLockedRot);
|
| 743 |
}
|
| 744 |
});
|
| 745 |
|
| 746 |
-
// Fin du drag : snap
|
| 747 |
transientSource.once("remove", function () {
|
| 748 |
isDragging = false;
|
| 749 |
if (wallMode) {
|
| 750 |
-
|
| 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
|
| 755 |
var qFinal = new pc.Quat();
|
| 756 |
qFinal.mul2(wallBaseRot, qLocal);
|
| 757 |
modelRoot.setRotation(qFinal);
|
|
@@ -795,10 +794,7 @@
|
|
| 795 |
}
|
| 796 |
});
|
| 797 |
app.mouse.on("mouseup", function () {
|
| 798 |
-
|
| 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 |
});
|
|
@@ -815,7 +811,7 @@
|
|
| 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 (
|
| 819 |
: "Session AR démarrée. Visez le sol pour détecter un plan…");
|
| 820 |
reticle.enabled = true;
|
| 821 |
});
|
|
|
|
| 1 |
+
/* viewer_ar_ios.js — AR PlayCanvas + GLB/USDZ via config.json (+ "sol" + mode mur : Z = normale, Y = haut)
|
| 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 |
+
* Z local = normale du mur (perpendiculaire au plan)
|
| 6 |
+
* Y local = direction plafond (Up monde) projetée dans le plan
|
| 7 |
+
* rotation utilisateur = autour de Z local (normale)
|
| 8 |
* PENDANT une translation : orientation verrouillée (pas de twist parasite)
|
| 9 |
+
* À la fin d’une translation : réalignement base sur le NOUVEAU mur (Z=normale, Y→plafond)
|
| 10 |
- iOS : AR Quick Look (USDZ)
|
| 11 |
- Android/Desktop : WebXR AR + bouton "Lancer l’AR" + slider de rotation
|
| 12 |
*/
|
|
|
|
| 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 {
|
|
|
|
| 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 |
+
function buildWallBasisFromNormalZ(Nz) {
|
| 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) {
|
|
|
|
| 445 |
q.normalize();
|
| 446 |
return q;
|
| 447 |
}
|
| 448 |
+
function computeWallBaseFromRot_ZNormal(rot) {
|
| 449 |
+
// WebXR/PlayCanvas : la normale du plan est l’axe Z de la pose
|
| 450 |
+
var Nz = new pc.Vec3();
|
| 451 |
+
rot.transformVector(new pc.Vec3(0, 0, 1), Nz); // Z monde de la pose
|
| 452 |
+
Nz.normalize();
|
| 453 |
+
var basis = buildWallBasisFromNormalZ(Nz); // Z=normale, Y=Up projeté
|
| 454 |
+
return quatFromBasis(basis.X, basis.Y, basis.Z);
|
| 455 |
}
|
| 456 |
|
| 457 |
// Mode mur / sol
|
| 458 |
var wallMode = !PLACE_ON_FLOOR;
|
| 459 |
+
var wallBaseRot = new pc.Quat(); // base (mur) : Z=normale, Y=up-projeté
|
| 460 |
+
var wallAngleDeg = 0; // rotation utilisateur autour de Z local (normale)
|
| 461 |
|
| 462 |
// Ombre blob (sol uniquement)
|
| 463 |
var blob = null;
|
|
|
|
| 514 |
return e;
|
| 515 |
}
|
| 516 |
|
| 517 |
+
// Rotation via slider (sol: Euler Y, mur: twist autour de Z local)
|
| 518 |
var baseEulerX = 0, baseEulerZ = 0;
|
| 519 |
var rotationYDeg = 0;
|
| 520 |
function clamp360(d) { return Math.max(0, Math.min(360, d)); }
|
|
|
|
| 534 |
rotationYDeg = y;
|
| 535 |
modelRoot.setEulerAngles(baseEulerX, y, baseEulerZ);
|
| 536 |
} else {
|
| 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);
|
|
|
|
| 582 |
|
| 583 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
| 584 |
|
| 585 |
+
// ----- Détection orientation des plans (utilise Z comme normale) -----
|
| 586 |
+
var TMP_Z = new pc.Vec3(0, 0, 1), TMP_OUT = new pc.Vec3();
|
| 587 |
|
| 588 |
function isHorizontalUpFacing(rot, minDot) {
|
| 589 |
+
// sol : normale (Z) ≈ +Y
|
| 590 |
minDot = (typeof minDot === "number") ? minDot : 0.75;
|
| 591 |
+
rot.transformVector(TMP_Z, TMP_OUT);
|
| 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(TMP_Z, TMP_OUT);
|
| 598 |
return Math.abs(TMP_OUT.y) <= maxAbsY;
|
| 599 |
}
|
| 600 |
function planeMatchesMode(rot) {
|
|
|
|
| 693 |
applyRotationY(y0);
|
| 694 |
} else {
|
| 695 |
// --- MODE MUR (première pose) ---
|
| 696 |
+
wallBaseRot.copy(computeWallBaseFromRot_ZNormal(rot)); // Z=normale, Y=up-projeté
|
| 697 |
modelRoot.setRotation(wallBaseRot);
|
| 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 (Z=normale, Y→plafond). Glissez pour déplacer, tournez avec le slider →"
|
| 706 |
: "Objet placé. Glissez pour déplacer, tournez-le avec le slider →"
|
| 707 |
);
|
| 708 |
}
|
|
|
|
| 712 |
});
|
| 713 |
|
| 714 |
// Drag XR continu (déplacements suivants, sans rotation parasite)
|
| 715 |
+
var isDragging = false;
|
| 716 |
+
var dragLockedRot = new pc.Quat();
|
| 717 |
+
var lastHitRot = new pc.Quat();
|
| 718 |
+
|
| 719 |
app.xr.input.on("add", function (inputSource) {
|
| 720 |
inputSource.on("selectstart", function () {
|
| 721 |
if (uiInteracting) return;
|
|
|
|
| 734 |
if (!isDragging) return;
|
| 735 |
if (!planeMatchesMode(rot)) return;
|
| 736 |
|
| 737 |
+
lastHitRot.copy(rot); // garde trace du dernier mur rencontré
|
| 738 |
+
modelRoot.setPosition(pos); // translate uniquement
|
|
|
|
|
|
|
|
|
|
| 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 le NOUVEAU mur, en conservant l'angle utilisateur
|
| 748 |
transientSource.once("remove", function () {
|
| 749 |
isDragging = false;
|
| 750 |
if (wallMode) {
|
| 751 |
+
wallBaseRot.copy(computeWallBaseFromRot_ZNormal(lastHitRot));
|
|
|
|
|
|
|
| 752 |
var qLocal = new pc.Quat();
|
| 753 |
+
qLocal.setFromAxisAngle(new pc.Vec3(0, 0, 1), wallAngleDeg); // rotation autour de Z local
|
| 754 |
var qFinal = new pc.Quat();
|
| 755 |
qFinal.mul2(wallBaseRot, qLocal);
|
| 756 |
modelRoot.setRotation(qFinal);
|
|
|
|
| 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 |
});
|
|
|
|
| 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 (Z=normale, Y du modèle vers le plafond)…"
|
| 815 |
: "Session AR démarrée. Visez le sol pour détecter un plan…");
|
| 816 |
reticle.enabled = true;
|
| 817 |
});
|