Update viewer_ar_ios.js
Browse files- viewer_ar_ios.js +99 -52
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;
|
|
@@ -279,21 +278,23 @@
|
|
| 279 |
var cfg = await loadConfigJson(cfgUrl);
|
| 280 |
var GLB_URL = (cfg && typeof cfg.glb_url === "string" && cfg.glb_url) ?
|
| 281 |
cfg.glb_url :
|
| 282 |
-
|
| 283 |
var USDZ_URL = (cfg && typeof cfg.usdz_url === "string" && cfg.usdz_url) ?
|
| 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
|
| 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,6 +377,12 @@
|
|
| 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;
|
|
@@ -434,19 +441,42 @@
|
|
| 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
|
| 440 |
-
var t = 1 - (
|
| 441 |
rotKnob.style.top = String(t * 100) + "%";
|
| 442 |
-
rotInput.value = String(Math.round(
|
| 443 |
-
rotVal.textContent = String(Math.round(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
}
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
modelRoot.setEulerAngles(baseEulerX, y, baseEulerZ);
|
| 449 |
-
updateKnobFromY(y);
|
| 450 |
}
|
| 451 |
|
| 452 |
function updateBlobPositionUnder(pos, rotLikePlane) {
|
|
@@ -486,22 +516,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 |
// Mur : normal ≈ horizontale (|y| petit)
|
| 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)
|
|
@@ -524,12 +557,12 @@
|
|
| 524 |
draggingTrack = true;
|
| 525 |
activePointerId = (e.pointerId != null) ? e.pointerId : 1;
|
| 526 |
if (rotTrack.setPointerCapture) { try { rotTrack.setPointerCapture(activePointerId); } catch (er) {} }
|
| 527 |
-
|
| 528 |
e.preventDefault(); e.stopPropagation();
|
| 529 |
}
|
| 530 |
function onPointerMoveCapture(e) {
|
| 531 |
if (!draggingTrack || ((e.pointerId != null ? e.pointerId : 1) !== activePointerId)) return;
|
| 532 |
-
|
| 533 |
e.preventDefault(); e.stopPropagation();
|
| 534 |
}
|
| 535 |
function endDrag(e) {
|
|
@@ -562,12 +595,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 +616,45 @@
|
|
| 584 |
modelRoot.enabled = true;
|
| 585 |
modelRoot.setPosition(pos);
|
| 586 |
|
| 587 |
-
|
| 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(
|
| 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 +671,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 +699,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 |
-
|
| 662 |
}
|
| 663 |
});
|
| 664 |
app.mouse.on("mouseup", function () { isDragging = false; rotateMode = false; });
|
|
@@ -667,17 +713,18 @@
|
|
| 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 |
-
|
| 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(
|
| 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" + rotation mur)
|
| 2 |
+
- config.json : { "glb_url": "...", "usdz_url": "...", "sol": true|false }
|
| 3 |
+
- sol = true → plans horizontaux (sol) + rotation autour de l’axe vertical (Y)
|
| 4 |
+
- sol = false → plans verticaux (mur) + rotation autour d’un axe horizontal perpendiculaire au mur
|
| 5 |
+
- iOS : Quick Look (USDZ) — l’utilisateur place l’objet sur sol/mur
|
| 6 |
+
- Android/Desktop : WebXR AR + bouton "Lancer l’AR" + slider de rotation
|
|
|
|
| 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;
|
|
|
|
| 278 |
var cfg = await loadConfigJson(cfgUrl);
|
| 279 |
var GLB_URL = (cfg && typeof cfg.glb_url === "string" && cfg.glb_url) ?
|
| 280 |
cfg.glb_url :
|
| 281 |
+
"https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/AR/tests/danae_no_metallic.glb";
|
| 282 |
var USDZ_URL = (cfg && typeof cfg.usdz_url === "string" && cfg.usdz_url) ?
|
| 283 |
cfg.usdz_url :
|
| 284 |
null;
|
| 285 |
|
| 286 |
+
// NEW : 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." : "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 |
app.root.addChild(modelRoot);
|
| 378 |
var modelLoaded = false, placedOnce = false;
|
| 379 |
|
| 380 |
+
// Mode mur / sol
|
| 381 |
+
var wallMode = !PLACE_ON_FLOOR;
|
| 382 |
+
var wallAxis = new pc.Vec3(1, 0, 0); // axe horizontal perpendiculaire au mur
|
| 383 |
+
var wallBaseRot = new pc.Quat();
|
| 384 |
+
var wallAngleDeg = 0;
|
| 385 |
+
|
| 386 |
// Ombre blob (uniquement pour le sol)
|
| 387 |
var blob = null;
|
| 388 |
var BLOB_SIZE = 0.4;
|
|
|
|
| 441 |
// Rotation via slider
|
| 442 |
var baseEulerX = 0, baseEulerZ = 0;
|
| 443 |
var rotationYDeg = 0;
|
| 444 |
+
|
| 445 |
function clamp360(d) { return Math.max(0, Math.min(360, d)); }
|
| 446 |
|
| 447 |
+
function updateSliderUI(angleDeg) {
|
| 448 |
+
var t = 1 - (angleDeg / 360);
|
| 449 |
rotKnob.style.top = String(t * 100) + "%";
|
| 450 |
+
rotInput.value = String(Math.round(angleDeg));
|
| 451 |
+
rotVal.textContent = String(Math.round(angleDeg)) + "°";
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
function setWallRotationFromSlider(angleDeg) {
|
| 455 |
+
wallAngleDeg = angleDeg;
|
| 456 |
+
var qExtra = new pc.Quat();
|
| 457 |
+
qExtra.setFromAxisAngle(wallAxis, angleDeg);
|
| 458 |
+
var q = new pc.Quat();
|
| 459 |
+
q.mul2(qExtra, wallBaseRot); // rotation autour de l’axe horizontal normal au mur
|
| 460 |
+
modelRoot.setRotation(q);
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
function applySliderRotation(deg) {
|
| 464 |
+
var a = clamp360(deg);
|
| 465 |
+
if (!modelRoot.enabled) {
|
| 466 |
+
updateSliderUI(a);
|
| 467 |
+
return;
|
| 468 |
+
}
|
| 469 |
+
if (!wallMode) {
|
| 470 |
+
rotationYDeg = a;
|
| 471 |
+
modelRoot.setEulerAngles(baseEulerX, a, baseEulerZ);
|
| 472 |
+
} else {
|
| 473 |
+
setWallRotationFromSlider(a);
|
| 474 |
+
}
|
| 475 |
+
updateSliderUI(a);
|
| 476 |
}
|
| 477 |
+
|
| 478 |
+
function getCurrentAngle() {
|
| 479 |
+
return wallMode ? wallAngleDeg : rotationYDeg;
|
|
|
|
|
|
|
| 480 |
}
|
| 481 |
|
| 482 |
function updateBlobPositionUnder(pos, rotLikePlane) {
|
|
|
|
| 516 |
|
| 517 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
| 518 |
|
| 519 |
+
// ----- Détection orientation des plans -----
|
| 520 |
var TMP_IN = new pc.Vec3(0, 1, 0), TMP_OUT = new pc.Vec3();
|
| 521 |
+
|
| 522 |
// Sol : normal ≈ +Y
|
| 523 |
function isHorizontalUpFacing(rot, minDot) {
|
| 524 |
minDot = (typeof minDot === "number") ? minDot : 0.75;
|
| 525 |
rot.transformVector(TMP_IN, TMP_OUT);
|
| 526 |
return TMP_OUT.y >= minDot;
|
| 527 |
}
|
| 528 |
+
|
| 529 |
// Mur : normal ≈ horizontale (|y| petit)
|
| 530 |
function isVerticalFacing(rot, maxAbsY) {
|
| 531 |
+
maxAbsY = (typeof maxAbsY === "number") ? maxAbsY : 0.35;
|
| 532 |
rot.transformVector(TMP_IN, TMP_OUT);
|
| 533 |
return Math.abs(TMP_OUT.y) <= maxAbsY;
|
| 534 |
}
|
| 535 |
+
|
| 536 |
function planeMatchesMode(rot) {
|
| 537 |
+
return wallMode ? isVerticalFacing(rot) : isHorizontalUpFacing(rot);
|
| 538 |
}
|
| 539 |
|
| 540 |
// Slider (pointer capture)
|
|
|
|
| 557 |
draggingTrack = true;
|
| 558 |
activePointerId = (e.pointerId != null) ? e.pointerId : 1;
|
| 559 |
if (rotTrack.setPointerCapture) { try { rotTrack.setPointerCapture(activePointerId); } catch (er) {} }
|
| 560 |
+
applySliderRotation(degFromPointer(e));
|
| 561 |
e.preventDefault(); e.stopPropagation();
|
| 562 |
}
|
| 563 |
function onPointerMoveCapture(e) {
|
| 564 |
if (!draggingTrack || ((e.pointerId != null ? e.pointerId : 1) !== activePointerId)) return;
|
| 565 |
+
applySliderRotation(degFromPointer(e));
|
| 566 |
e.preventDefault(); e.stopPropagation();
|
| 567 |
}
|
| 568 |
function endDrag(e) {
|
|
|
|
| 595 |
});
|
| 596 |
}
|
| 597 |
|
|
|
|
| 598 |
var startBtn = ensureARStartButton(activateAR);
|
| 599 |
|
| 600 |
app.keyboard.on("keydown", function (evt) { if (evt.key === pc.KEY_ESCAPE && app.xr.active) app.xr.end(); });
|
| 601 |
|
| 602 |
+
// Hit-test (sol vs mur)
|
| 603 |
app.xr.hitTest.on("available", function () {
|
| 604 |
app.xr.hitTest.start({
|
| 605 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
|
|
|
| 616 |
modelRoot.enabled = true;
|
| 617 |
modelRoot.setPosition(pos);
|
| 618 |
|
| 619 |
+
if (!wallMode) {
|
|
|
|
| 620 |
blob = createBlobShadowAt(pos, rot);
|
| 621 |
+
var e = new pc.Vec3();
|
| 622 |
+
rot.getEulerAngles(e);
|
| 623 |
+
var y0 = ((e.y % 360) + 360) % 360;
|
| 624 |
+
applySliderRotation(y0);
|
| 625 |
+
} else {
|
| 626 |
+
// Mode mur : aligner l’objet avec le mur et préparer l’axe horizontal perpendiculaire
|
| 627 |
+
modelRoot.setRotation(rot);
|
| 628 |
+
wallBaseRot.copy(modelRoot.getRotation());
|
| 629 |
+
|
| 630 |
+
var upLocal = new pc.Vec3(0, 1, 0);
|
| 631 |
+
var normalWorld = new pc.Vec3();
|
| 632 |
+
rot.transformVector(upLocal, normalWorld);
|
| 633 |
+
// on projette sur le plan XZ pour avoir un axe horizontal
|
| 634 |
+
normalWorld.y = 0;
|
| 635 |
+
if (normalWorld.lengthSq() < 1e-4) {
|
| 636 |
+
normalWorld.set(0, 0, 1);
|
| 637 |
+
}
|
| 638 |
+
normalWorld.normalize();
|
| 639 |
+
wallAxis.copy(normalWorld);
|
| 640 |
+
|
| 641 |
+
applySliderRotation(0);
|
| 642 |
}
|
| 643 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
placedOnce = true;
|
| 645 |
rotInput.disabled = false;
|
| 646 |
+
message(
|
| 647 |
+
wallMode
|
| 648 |
+
? "Objet placé contre le mur. Glissez pour déplacer sur le mur, tournez-le avec le slider →"
|
| 649 |
+
: "Objet placé. Glissez pour déplacer, tournez-le avec le slider →"
|
| 650 |
+
);
|
| 651 |
}
|
| 652 |
});
|
| 653 |
}
|
| 654 |
});
|
| 655 |
});
|
| 656 |
|
| 657 |
+
// Drag XR continu
|
| 658 |
var isDragging = false;
|
| 659 |
app.xr.input.on("add", function (inputSource) {
|
| 660 |
inputSource.on("selectstart", function () {
|
|
|
|
| 671 |
if (!isDragging) return;
|
| 672 |
if (!planeMatchesMode(rot)) return;
|
| 673 |
modelRoot.setPosition(pos);
|
| 674 |
+
if (!wallMode) updateBlobPositionUnder(pos, rot);
|
| 675 |
});
|
| 676 |
|
| 677 |
transientSource.once("remove", function () { isDragging = false; });
|
|
|
|
| 699 |
if (reticle.enabled) {
|
| 700 |
var p = reticle.getPosition();
|
| 701 |
modelRoot.setPosition(p);
|
| 702 |
+
if (!wallMode) updateBlobPositionUnder(p, null);
|
| 703 |
}
|
| 704 |
} else if (rotateMode && modelRoot.enabled) {
|
| 705 |
var dx = e.x - lastMouseX;
|
| 706 |
lastMouseX = e.x;
|
| 707 |
+
applySliderRotation(getCurrentAngle() + dx * ROTATE_SENSITIVITY);
|
| 708 |
}
|
| 709 |
});
|
| 710 |
app.mouse.on("mouseup", function () { isDragging = false; rotateMode = false; });
|
|
|
|
| 713 |
// Slider (accessibilité clavier)
|
| 714 |
rotInput.disabled = true;
|
| 715 |
rotInput.addEventListener("input", function (e) {
|
|
|
|
| 716 |
var v = parseFloat(e.target.value || "0");
|
| 717 |
+
applySliderRotation(v);
|
| 718 |
}, { passive: true });
|
| 719 |
|
| 720 |
// Événements AR (feedback + visibilité bouton)
|
| 721 |
app.xr.on("start", function () {
|
| 722 |
if (startBtn) startBtn.style.display = "none";
|
| 723 |
+
message(
|
| 724 |
+
wallMode
|
| 725 |
+
? "Session AR démarrée. Visez un mur pour détecter un plan vertical…"
|
| 726 |
+
: "Session AR démarrée. Visez le sol pour détecter un plan…"
|
| 727 |
+
);
|
| 728 |
reticle.enabled = true;
|
| 729 |
});
|
| 730 |
app.xr.on("end", function () {
|