Update viewer_ar.js
Browse files- viewer_ar.js +49 -48
viewer_ar.js
CHANGED
|
@@ -61,13 +61,8 @@
|
|
| 61 |
throw new Error("UMD failed");
|
| 62 |
};
|
| 63 |
|
| 64 |
-
if (esmFirst) {
|
| 65 |
-
|
| 66 |
-
return await tryUMD();
|
| 67 |
-
} else {
|
| 68 |
-
try { return await tryUMD(); } catch (_) {}
|
| 69 |
-
return await tryESM();
|
| 70 |
-
}
|
| 71 |
}
|
| 72 |
|
| 73 |
// =========================
|
|
@@ -92,11 +87,12 @@
|
|
| 92 |
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
| 93 |
pointer-events: auto; width: 56px; display: flex; flex-direction: column; align-items: center; gap: 8px;
|
| 94 |
box-shadow: 0 6px 18px rgba(0,0,0,.25); backdrop-filter: blur(4px);
|
|
|
|
| 95 |
}
|
| 96 |
.ar-ui .label { font-size: 12px; text-align: center; opacity: 0.9; }
|
| 97 |
.ar-ui input[type="range"].rotY {
|
| 98 |
-webkit-appearance: none; width: 220px; height: 28px; transform: rotate(-90deg);
|
| 99 |
-
outline: none; background: transparent; touch-action: none;
|
| 100 |
}
|
| 101 |
.ar-ui input[type="range"].rotY::-webkit-slider-thumb {
|
| 102 |
-webkit-appearance: none; appearance: none; width: 20px; height: 20px; border-radius: 50%;
|
|
@@ -122,6 +118,7 @@
|
|
| 122 |
}
|
| 123 |
const overlayRoot = ensureOverlayRoot();
|
| 124 |
|
|
|
|
| 125 |
function message(msg) {
|
| 126 |
let el = overlayRoot.querySelector(".pc-ar-msg");
|
| 127 |
if (!el) {
|
|
@@ -253,58 +250,57 @@
|
|
| 253 |
|
| 254 |
// --- Chargement GLB ---
|
| 255 |
app.assets.loadFromUrl(GLB_URL, "container", (err, asset) => {
|
| 256 |
-
if (err) {
|
| 257 |
-
|
| 258 |
-
message("Échec du chargement du modèle GLB.");
|
| 259 |
-
return;
|
| 260 |
-
}
|
| 261 |
-
const instance = asset.resource.instantiateRenderEntity({
|
| 262 |
-
castShadows: false,
|
| 263 |
-
receiveShadows: false
|
| 264 |
-
});
|
| 265 |
modelRoot.addChild(instance);
|
| 266 |
modelRoot.setLocalScale(0.2, 0.2, 0.2);
|
| 267 |
-
|
| 268 |
modelLoaded = true;
|
| 269 |
message("Modèle chargé. Touchez l’écran pour démarrer l’AR.");
|
| 270 |
});
|
| 271 |
|
| 272 |
// --- Vérifs WebXR ---
|
| 273 |
-
if (!app.xr.supported) {
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
// --- Démarrage AR (CameraComponent.startXr + DOM Overlay) ---
|
| 279 |
const activateAR = () => {
|
| 280 |
-
if (!app.xr.isAvailable(pc.XRTYPE_AR)) {
|
| 281 |
-
message("AR immersive indisponible sur cet appareil.");
|
| 282 |
-
return;
|
| 283 |
-
}
|
| 284 |
|
| 285 |
// DOM overlay root (notre conteneur avec slider + messages)
|
| 286 |
app.xr.domOverlay.root = document.getElementById("xr-overlay-root");
|
| 287 |
|
| 288 |
-
// Démarre via la Camera Component (plus compatible selon versions moteur)
|
| 289 |
camera.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
|
| 290 |
requiredFeatures: ["hit-test", "dom-overlay"],
|
| 291 |
domOverlay: { root: app.xr.domOverlay.root },
|
| 292 |
callback: (err) => {
|
| 293 |
-
if (err) {
|
| 294 |
-
console.error("Échec du démarrage AR :", err);
|
| 295 |
-
message(`Échec du démarrage AR : ${err.message || err}`);
|
| 296 |
-
}
|
| 297 |
}
|
| 298 |
});
|
| 299 |
};
|
| 300 |
|
| 301 |
-
// Premier tap / clic → démarrer AR
|
| 302 |
-
app.mouse.on("mousedown", () => { if (!app.xr.active) activateAR(); });
|
| 303 |
if (app.touch) {
|
| 304 |
app.touch.on("touchend", (evt) => {
|
| 305 |
-
if (!app.xr.active) activateAR();
|
| 306 |
-
evt.event.preventDefault();
|
| 307 |
-
evt.event.stopPropagation();
|
| 308 |
});
|
| 309 |
}
|
| 310 |
|
|
@@ -325,12 +321,9 @@
|
|
| 325 |
if (modelLoaded && !placedOnce) {
|
| 326 |
modelRoot.enabled = true;
|
| 327 |
modelRoot.setPosition(pos);
|
| 328 |
-
const euler = new pc.Vec3();
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
applyRotationY(rotationYDeg);
|
| 332 |
-
placedOnce = true;
|
| 333 |
-
rotYInput.disabled = false;
|
| 334 |
message("Objet placé. Glissez pour déplacer, tournez-le avec le slider →");
|
| 335 |
}
|
| 336 |
});
|
|
@@ -344,32 +337,40 @@
|
|
| 344 |
let lastMouseX = 0;
|
| 345 |
const ROTATE_SENSITIVITY = 0.25;
|
| 346 |
|
| 347 |
-
// Déplacement (input XR : maintien/drag)
|
| 348 |
app.xr.input.on("add", (inputSource) => {
|
| 349 |
inputSource.on("selectstart", () => {
|
|
|
|
| 350 |
if (!placedOnce || !modelLoaded) return;
|
|
|
|
| 351 |
inputSource.hitTestStart({
|
| 352 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
| 353 |
callback: (err, transientSource) => {
|
| 354 |
if (err) return;
|
| 355 |
isDragging = true;
|
| 356 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
transientSource.once("remove", () => { isDragging = false; });
|
| 358 |
}
|
| 359 |
});
|
| 360 |
});
|
|
|
|
| 361 |
inputSource.on("selectend", () => { isDragging = false; });
|
| 362 |
});
|
| 363 |
|
| 364 |
-
// Souris : déplacement (gauche) & rotation (droit ou Shift+gauche)
|
| 365 |
app.mouse.on("mousedown", (e) => {
|
| 366 |
-
if (!app.xr.active || !placedOnce) return;
|
| 367 |
if (e.button === 0 && !e.shiftKey) isDragging = true;
|
| 368 |
else if (e.button === 2 || (e.button === 0 && e.shiftKey)) { rotateMode = true; lastMouseX = e.x; }
|
| 369 |
});
|
| 370 |
|
| 371 |
app.mouse.on("mousemove", (e) => {
|
| 372 |
-
if (!app.xr.active || !placedOnce) return;
|
| 373 |
if (isDragging) { if (reticle.enabled) modelRoot.setPosition(reticle.getPosition()); }
|
| 374 |
else if (rotateMode && modelRoot.enabled) {
|
| 375 |
const dx = e.x - lastMouseX; lastMouseX = e.x;
|
|
@@ -381,7 +382,7 @@
|
|
| 381 |
window.addEventListener("contextmenu", (e) => e.preventDefault());
|
| 382 |
|
| 383 |
// --- Slider rotation (DOM overlay) ---
|
| 384 |
-
rotYInput.disabled = true;
|
| 385 |
rotYInput.addEventListener("input", (e) => {
|
| 386 |
if (!modelRoot.enabled) return;
|
| 387 |
const deg = parseFloat(e.target.value || "0");
|
|
|
|
| 61 |
throw new Error("UMD failed");
|
| 62 |
};
|
| 63 |
|
| 64 |
+
if (esmFirst) { try { return await tryESM(); } catch (_) {} return await tryUMD(); }
|
| 65 |
+
else { try { return await tryUMD(); } catch (_) {} return await tryESM(); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
|
| 68 |
// =========================
|
|
|
|
| 87 |
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
| 88 |
pointer-events: auto; width: 56px; display: flex; flex-direction: column; align-items: center; gap: 8px;
|
| 89 |
box-shadow: 0 6px 18px rgba(0,0,0,.25); backdrop-filter: blur(4px);
|
| 90 |
+
touch-action: none; /* important pour éviter le scroll/zoom natif */
|
| 91 |
}
|
| 92 |
.ar-ui .label { font-size: 12px; text-align: center; opacity: 0.9; }
|
| 93 |
.ar-ui input[type="range"].rotY {
|
| 94 |
-webkit-appearance: none; width: 220px; height: 28px; transform: rotate(-90deg);
|
| 95 |
+
outline: none; background: transparent; touch-action: none; /* bloque gestures tactiles */
|
| 96 |
}
|
| 97 |
.ar-ui input[type="range"].rotY::-webkit-slider-thumb {
|
| 98 |
-webkit-appearance: none; appearance: none; width: 20px; height: 20px; border-radius: 50%;
|
|
|
|
| 118 |
}
|
| 119 |
const overlayRoot = ensureOverlayRoot();
|
| 120 |
|
| 121 |
+
// Messages dans l'overlay
|
| 122 |
function message(msg) {
|
| 123 |
let el = overlayRoot.querySelector(".pc-ar-msg");
|
| 124 |
if (!el) {
|
|
|
|
| 250 |
|
| 251 |
// --- Chargement GLB ---
|
| 252 |
app.assets.loadFromUrl(GLB_URL, "container", (err, asset) => {
|
| 253 |
+
if (err) { console.error(err); message("Échec du chargement du modèle GLB."); return; }
|
| 254 |
+
const instance = asset.resource.instantiateRenderEntity({ castShadows: false, receiveShadows: false });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
modelRoot.addChild(instance);
|
| 256 |
modelRoot.setLocalScale(0.2, 0.2, 0.2);
|
|
|
|
| 257 |
modelLoaded = true;
|
| 258 |
message("Modèle chargé. Touchez l’écran pour démarrer l’AR.");
|
| 259 |
});
|
| 260 |
|
| 261 |
// --- Vérifs WebXR ---
|
| 262 |
+
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
| 263 |
+
|
| 264 |
+
// ====== NOUVEAU : gestion anti-fuite d'événements UI ======
|
| 265 |
+
let uiInteracting = false;
|
| 266 |
+
const startUIHold = (e) => { uiInteracting = true; e.stopPropagation(); e.preventDefault(); };
|
| 267 |
+
const endUIHold = (e) => { uiInteracting = false; e.stopPropagation(); e.preventDefault(); };
|
| 268 |
+
const swallow = (e) => { e.stopPropagation(); e.preventDefault(); };
|
| 269 |
+
|
| 270 |
+
// Piégeage complet sur le panneau ET le slider
|
| 271 |
+
["pointerdown","pointermove","pointerup","pointercancel",
|
| 272 |
+
"touchstart","touchmove","touchend","touchcancel",
|
| 273 |
+
"mousedown","mousemove","mouseup","wheel","click"].forEach(evt => {
|
| 274 |
+
uiPanel.addEventListener(evt, evt === "pointerdown" || evt === "touchstart" || evt === "mousedown" ? startUIHold :
|
| 275 |
+
(evt === "pointerup" || evt === "pointercancel" || evt === "touchend" || evt === "touchcancel" || evt === "mouseup" ? endUIHold : swallow),
|
| 276 |
+
{ passive: false });
|
| 277 |
+
rotYInput.addEventListener(evt, evt === "pointerdown" || evt === "touchstart" || evt === "mousedown" ? startUIHold :
|
| 278 |
+
(evt === "pointerup" || evt === "pointercancel" || evt === "touchend" || evt === "touchcancel" || evt === "mouseup" ? endUIHold : swallow),
|
| 279 |
+
{ passive: false });
|
| 280 |
+
});
|
| 281 |
|
| 282 |
// --- Démarrage AR (CameraComponent.startXr + DOM Overlay) ---
|
| 283 |
const activateAR = () => {
|
| 284 |
+
if (!app.xr.isAvailable(pc.XRTYPE_AR)) { message("AR immersive indisponible sur cet appareil."); return; }
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
// DOM overlay root (notre conteneur avec slider + messages)
|
| 287 |
app.xr.domOverlay.root = document.getElementById("xr-overlay-root");
|
| 288 |
|
|
|
|
| 289 |
camera.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
|
| 290 |
requiredFeatures: ["hit-test", "dom-overlay"],
|
| 291 |
domOverlay: { root: app.xr.domOverlay.root },
|
| 292 |
callback: (err) => {
|
| 293 |
+
if (err) { console.error("Échec du démarrage AR :", err); message(`Échec du démarrage AR : ${err.message || err}`); }
|
|
|
|
|
|
|
|
|
|
| 294 |
}
|
| 295 |
});
|
| 296 |
};
|
| 297 |
|
| 298 |
+
// Premier tap / clic → démarrer AR (ignorer si on tape sur l'UI)
|
| 299 |
+
app.mouse.on("mousedown", (e) => { if (!app.xr.active && !uiInteracting) activateAR(); });
|
| 300 |
if (app.touch) {
|
| 301 |
app.touch.on("touchend", (evt) => {
|
| 302 |
+
if (!app.xr.active && !uiInteracting) activateAR();
|
| 303 |
+
evt.event.preventDefault(); evt.event.stopPropagation();
|
|
|
|
| 304 |
});
|
| 305 |
}
|
| 306 |
|
|
|
|
| 321 |
if (modelLoaded && !placedOnce) {
|
| 322 |
modelRoot.enabled = true;
|
| 323 |
modelRoot.setPosition(pos);
|
| 324 |
+
const euler = new pc.Vec3(); rot.getEulerAngles(euler);
|
| 325 |
+
rotationYDeg = norm360(euler.y); applyRotationY(rotationYDeg);
|
| 326 |
+
placedOnce = true; rotYInput.disabled = false;
|
|
|
|
|
|
|
|
|
|
| 327 |
message("Objet placé. Glissez pour déplacer, tournez-le avec le slider →");
|
| 328 |
}
|
| 329 |
});
|
|
|
|
| 337 |
let lastMouseX = 0;
|
| 338 |
const ROTATE_SENSITIVITY = 0.25;
|
| 339 |
|
| 340 |
+
// Déplacement (input XR : maintien/drag) — IGNORE si UI en cours
|
| 341 |
app.xr.input.on("add", (inputSource) => {
|
| 342 |
inputSource.on("selectstart", () => {
|
| 343 |
+
if (uiInteracting) return; // <<< bloque le drag si doigt sur le slider
|
| 344 |
if (!placedOnce || !modelLoaded) return;
|
| 345 |
+
|
| 346 |
inputSource.hitTestStart({
|
| 347 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
| 348 |
callback: (err, transientSource) => {
|
| 349 |
if (err) return;
|
| 350 |
isDragging = true;
|
| 351 |
+
|
| 352 |
+
transientSource.on("result", (pos) => {
|
| 353 |
+
if (!isDragging) return;
|
| 354 |
+
modelRoot.setPosition(pos);
|
| 355 |
+
});
|
| 356 |
+
|
| 357 |
transientSource.once("remove", () => { isDragging = false; });
|
| 358 |
}
|
| 359 |
});
|
| 360 |
});
|
| 361 |
+
|
| 362 |
inputSource.on("selectend", () => { isDragging = false; });
|
| 363 |
});
|
| 364 |
|
| 365 |
+
// Souris : déplacement (gauche) & rotation (droit ou Shift+gauche) — IGNORE si UI en cours
|
| 366 |
app.mouse.on("mousedown", (e) => {
|
| 367 |
+
if (!app.xr.active || !placedOnce || uiInteracting) return;
|
| 368 |
if (e.button === 0 && !e.shiftKey) isDragging = true;
|
| 369 |
else if (e.button === 2 || (e.button === 0 && e.shiftKey)) { rotateMode = true; lastMouseX = e.x; }
|
| 370 |
});
|
| 371 |
|
| 372 |
app.mouse.on("mousemove", (e) => {
|
| 373 |
+
if (!app.xr.active || !placedOnce || uiInteracting) return;
|
| 374 |
if (isDragging) { if (reticle.enabled) modelRoot.setPosition(reticle.getPosition()); }
|
| 375 |
else if (rotateMode && modelRoot.enabled) {
|
| 376 |
const dx = e.x - lastMouseX; lastMouseX = e.x;
|
|
|
|
| 382 |
window.addEventListener("contextmenu", (e) => e.preventDefault());
|
| 383 |
|
| 384 |
// --- Slider rotation (DOM overlay) ---
|
| 385 |
+
rotYInput.disabled = true; // activé au 1er placement
|
| 386 |
rotYInput.addEventListener("input", (e) => {
|
| 387 |
if (!modelRoot.enabled) return;
|
| 388 |
const deg = parseFloat(e.target.value || "0");
|