Update viewer_ar_ios.js
Browse files- viewer_ar_ios.js +79 -43
viewer_ar_ios.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
-
/* viewer_ar_ios.js — AR PlayCanvas + GLB/USDZ via config.json (+
|
| 2 |
-
-
|
| 3 |
-
-
|
| 4 |
-
-
|
| 5 |
-
|
| 6 |
-
|
| 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
|
| 173 |
function ensureQuickLookButton(USDZ_URL) {
|
| 174 |
var btn = document.getElementById("ar-ios-quicklook-button");
|
| 175 |
if (btn) return btn;
|
|
@@ -284,16 +283,18 @@
|
|
| 284 |
cfg.usdz_url :
|
| 285 |
null;
|
| 286 |
|
| 287 |
-
//
|
| 288 |
var PLACE_ON_FLOOR = true;
|
| 289 |
if (cfg && typeof cfg.sol === "boolean") PLACE_ON_FLOOR = !!cfg.sol;
|
| 290 |
|
| 291 |
-
// iOS → Quick Look
|
| 292 |
if (isIOS()) {
|
| 293 |
if (USDZ_URL) {
|
| 294 |
ensureQuickLookButton(USDZ_URL);
|
| 295 |
-
message(
|
| 296 |
-
|
|
|
|
|
|
|
| 297 |
} else {
|
| 298 |
message("iOS détecté, mais aucun 'usdz_url' dans config.json.");
|
| 299 |
}
|
|
@@ -376,7 +377,12 @@
|
|
| 376 |
app.root.addChild(modelRoot);
|
| 377 |
var modelLoaded = false, placedOnce = false;
|
| 378 |
|
| 379 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
var blob = null;
|
| 381 |
var BLOB_SIZE = 0.4;
|
| 382 |
var BLOB_OFFSET_Y = 0.005;
|
|
@@ -434,6 +440,7 @@
|
|
| 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) {
|
|
@@ -442,10 +449,30 @@
|
|
| 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 |
-
|
| 448 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
updateKnobFromY(y);
|
| 450 |
}
|
| 451 |
|
|
@@ -486,22 +513,25 @@
|
|
| 486 |
|
| 487 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
| 488 |
|
| 489 |
-
// ----- Détection
|
| 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 |
-
|
|
|
|
| 498 |
function isVerticalFacing(rot, maxAbsY) {
|
| 499 |
-
maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35;
|
| 500 |
rot.transformVector(TMP_IN, TMP_OUT);
|
| 501 |
return Math.abs(TMP_OUT.y) <= maxAbsY;
|
| 502 |
}
|
|
|
|
| 503 |
function planeMatchesMode(rot) {
|
| 504 |
-
return
|
| 505 |
}
|
| 506 |
|
| 507 |
// Slider (pointer capture)
|
|
@@ -562,12 +592,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
|
| 571 |
app.xr.hitTest.on("available", function () {
|
| 572 |
app.xr.hitTest.start({
|
| 573 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
|
@@ -584,31 +613,37 @@
|
|
| 584 |
modelRoot.enabled = true;
|
| 585 |
modelRoot.setPosition(pos);
|
| 586 |
|
| 587 |
-
|
| 588 |
-
|
| 589 |
blob = createBlobShadowAt(pos, rot);
|
| 590 |
-
}
|
| 591 |
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 599 |
|
| 600 |
placedOnce = true;
|
| 601 |
rotInput.disabled = false;
|
| 602 |
-
message(
|
| 603 |
-
|
| 604 |
-
|
|
|
|
|
|
|
| 605 |
}
|
| 606 |
});
|
| 607 |
}
|
| 608 |
});
|
| 609 |
});
|
| 610 |
|
| 611 |
-
// Drag XR continu
|
| 612 |
var isDragging = false;
|
| 613 |
app.xr.input.on("add", function (inputSource) {
|
| 614 |
inputSource.on("selectstart", function () {
|
|
@@ -625,7 +660,7 @@
|
|
| 625 |
if (!isDragging) return;
|
| 626 |
if (!planeMatchesMode(rot)) return;
|
| 627 |
modelRoot.setPosition(pos);
|
| 628 |
-
if (
|
| 629 |
});
|
| 630 |
|
| 631 |
transientSource.once("remove", function () { isDragging = false; });
|
|
@@ -653,12 +688,12 @@
|
|
| 653 |
if (reticle.enabled) {
|
| 654 |
var p = reticle.getPosition();
|
| 655 |
modelRoot.setPosition(p);
|
| 656 |
-
if (
|
| 657 |
}
|
| 658 |
} else if (rotateMode && modelRoot.enabled) {
|
| 659 |
var dx = e.x - lastMouseX;
|
| 660 |
lastMouseX = e.x;
|
| 661 |
-
applyRotationY(
|
| 662 |
}
|
| 663 |
});
|
| 664 |
app.mouse.on("mouseup", function () { isDragging = false; rotateMode = false; });
|
|
@@ -667,7 +702,6 @@
|
|
| 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 });
|
|
@@ -675,9 +709,11 @@
|
|
| 675 |
// Événements AR (feedback + visibilité bouton)
|
| 676 |
app.xr.on("start", function () {
|
| 677 |
if (startBtn) startBtn.style.display = "none";
|
| 678 |
-
message(
|
| 679 |
-
|
| 680 |
-
|
|
|
|
|
|
|
| 681 |
reticle.enabled = true;
|
| 682 |
});
|
| 683 |
app.xr.on("end", function () {
|
|
|
|
| 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) + rotation autour de l’axe local Y aligné à la normale du mur
|
| 5 |
+
- iOS : AR Quick Look (USDZ) — l’utilisateur place l’objet sur sol/mur
|
| 6 |
+
- Android/Desktop : WebXR AR + bouton "Lancer l’AR" + slider de rotation + ombre blob (sol uniquement)
|
|
|
|
| 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;
|
|
|
|
| 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 |
+
"Modèle chargé. Appuyez sur « Lancer l’AR » puis placez-le " +
|
| 296 |
+
(PLACE_ON_FLOOR ? "sur le sol." : "contre un mur.")
|
| 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 wallBaseRot = new pc.Quat(); // orientation de base alignée au mur
|
| 383 |
+
var wallAngleDeg = 0;
|
| 384 |
+
|
| 385 |
+
// Ombre blob (sol uniquement)
|
| 386 |
var blob = null;
|
| 387 |
var BLOB_SIZE = 0.4;
|
| 388 |
var BLOB_OFFSET_Y = 0.005;
|
|
|
|
| 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 updateKnobFromY(yDeg) {
|
|
|
|
| 449 |
rotInput.value = String(Math.round(yDeg));
|
| 450 |
rotVal.textContent = String(Math.round(yDeg)) + "°";
|
| 451 |
}
|
| 452 |
+
|
| 453 |
+
function getCurrentAngle() {
|
| 454 |
+
return wallMode ? wallAngleDeg : rotationYDeg;
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
function applyRotationY(deg) {
|
| 458 |
var y = clamp360(deg);
|
| 459 |
+
if (!modelRoot.enabled) {
|
| 460 |
+
updateKnobFromY(y);
|
| 461 |
+
return;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
if (!wallMode) {
|
| 465 |
+
rotationYDeg = y;
|
| 466 |
+
modelRoot.setEulerAngles(baseEulerX, y, baseEulerZ);
|
| 467 |
+
} else {
|
| 468 |
+
wallAngleDeg = y;
|
| 469 |
+
var qLocal = new pc.Quat();
|
| 470 |
+
qLocal.setFromAxisAngle(new pc.Vec3(0, 1, 0), y); // rotation locale autour de Y
|
| 471 |
+
var qFinal = new pc.Quat();
|
| 472 |
+
qFinal.mul2(wallBaseRot, qLocal); // base (mur) puis rotation locale Y
|
| 473 |
+
modelRoot.setRotation(qFinal);
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
updateKnobFromY(y);
|
| 477 |
}
|
| 478 |
|
|
|
|
| 513 |
|
| 514 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
| 515 |
|
| 516 |
+
// ----- Détection orientation des plans -----
|
| 517 |
var TMP_IN = new pc.Vec3(0, 1, 0), TMP_OUT = new pc.Vec3();
|
| 518 |
+
|
| 519 |
// Sol : normal ≈ +Y
|
| 520 |
function isHorizontalUpFacing(rot, minDot) {
|
| 521 |
minDot = (typeof minDot === "number") ? minDot : 0.75;
|
| 522 |
rot.transformVector(TMP_IN, TMP_OUT);
|
| 523 |
return TMP_OUT.y >= minDot;
|
| 524 |
}
|
| 525 |
+
|
| 526 |
+
// Mur : normal horizontale → axe Y du hit-test presque horizontal
|
| 527 |
function isVerticalFacing(rot, maxAbsY) {
|
| 528 |
+
maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35;
|
| 529 |
rot.transformVector(TMP_IN, TMP_OUT);
|
| 530 |
return Math.abs(TMP_OUT.y) <= maxAbsY;
|
| 531 |
}
|
| 532 |
+
|
| 533 |
function planeMatchesMode(rot) {
|
| 534 |
+
return wallMode ? isVerticalFacing(rot) : isHorizontalUpFacing(rot);
|
| 535 |
}
|
| 536 |
|
| 537 |
// Slider (pointer capture)
|
|
|
|
| 592 |
});
|
| 593 |
}
|
| 594 |
|
|
|
|
| 595 |
var startBtn = ensureARStartButton(activateAR);
|
| 596 |
|
| 597 |
app.keyboard.on("keydown", function (evt) { if (evt.key === pc.KEY_ESCAPE && app.xr.active) app.xr.end(); });
|
| 598 |
|
| 599 |
+
// Hit-test (sol vs mur)
|
| 600 |
app.xr.hitTest.on("available", function () {
|
| 601 |
app.xr.hitTest.start({
|
| 602 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
|
|
|
| 613 |
modelRoot.enabled = true;
|
| 614 |
modelRoot.setPosition(pos);
|
| 615 |
|
| 616 |
+
if (!wallMode) {
|
| 617 |
+
// --- MODE SOL ---
|
| 618 |
blob = createBlobShadowAt(pos, rot);
|
|
|
|
| 619 |
|
| 620 |
+
var e = new pc.Vec3();
|
| 621 |
+
rot.getEulerAngles(e);
|
| 622 |
+
var y0 = ((e.y % 360) + 360) % 360;
|
| 623 |
+
applyRotationY(y0);
|
| 624 |
+
} else {
|
| 625 |
+
// --- MODE MUR ---
|
| 626 |
+
// on aligne directement l’objet sur la rotation du hit-test
|
| 627 |
+
// (axe local Y du modèle = normale du mur)
|
| 628 |
+
modelRoot.setRotation(rot);
|
| 629 |
+
wallBaseRot.copy(rot);
|
| 630 |
+
applyRotationY(0); // angle initial = 0
|
| 631 |
+
}
|
| 632 |
|
| 633 |
placedOnce = true;
|
| 634 |
rotInput.disabled = false;
|
| 635 |
+
message(
|
| 636 |
+
wallMode
|
| 637 |
+
? "Objet placé contre le mur. Glissez pour déplacer sur le mur, tournez-le avec le slider →"
|
| 638 |
+
: "Objet placé. Glissez pour déplacer, tournez-le avec le slider →"
|
| 639 |
+
);
|
| 640 |
}
|
| 641 |
});
|
| 642 |
}
|
| 643 |
});
|
| 644 |
});
|
| 645 |
|
| 646 |
+
// Drag XR continu
|
| 647 |
var isDragging = false;
|
| 648 |
app.xr.input.on("add", function (inputSource) {
|
| 649 |
inputSource.on("selectstart", function () {
|
|
|
|
| 660 |
if (!isDragging) return;
|
| 661 |
if (!planeMatchesMode(rot)) return;
|
| 662 |
modelRoot.setPosition(pos);
|
| 663 |
+
if (!wallMode) updateBlobPositionUnder(pos, rot);
|
| 664 |
});
|
| 665 |
|
| 666 |
transientSource.once("remove", function () { isDragging = false; });
|
|
|
|
| 688 |
if (reticle.enabled) {
|
| 689 |
var p = reticle.getPosition();
|
| 690 |
modelRoot.setPosition(p);
|
| 691 |
+
if (!wallMode) updateBlobPositionUnder(p, null);
|
| 692 |
}
|
| 693 |
} else if (rotateMode && modelRoot.enabled) {
|
| 694 |
var dx = e.x - lastMouseX;
|
| 695 |
lastMouseX = e.x;
|
| 696 |
+
applyRotationY(getCurrentAngle() + dx * ROTATE_SENSITIVITY);
|
| 697 |
}
|
| 698 |
});
|
| 699 |
app.mouse.on("mouseup", function () { isDragging = false; rotateMode = false; });
|
|
|
|
| 702 |
// Slider (accessibilité clavier)
|
| 703 |
rotInput.disabled = true;
|
| 704 |
rotInput.addEventListener("input", function (e) {
|
|
|
|
| 705 |
var v = parseFloat(e.target.value || "0");
|
| 706 |
applyRotationY(v);
|
| 707 |
}, { passive: true });
|
|
|
|
| 709 |
// Événements AR (feedback + visibilité bouton)
|
| 710 |
app.xr.on("start", function () {
|
| 711 |
if (startBtn) startBtn.style.display = "none";
|
| 712 |
+
message(
|
| 713 |
+
wallMode
|
| 714 |
+
? "Session AR démarrée. Visez un mur pour détecter un plan vertical…"
|
| 715 |
+
: "Session AR démarrée. Visez le sol pour détecter un plan…"
|
| 716 |
+
);
|
| 717 |
reticle.enabled = true;
|
| 718 |
});
|
| 719 |
app.xr.on("end", function () {
|