Update viewer_ar.js
Browse files- viewer_ar.js +70 -69
viewer_ar.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
/* script_ar.js — AR PlayCanvas + GLB HuggingFace
|
| 2 |
- Chargeur PlayCanvas robuste (ESM -> UMD avec fallbacks + timeout)
|
| 3 |
- WebXR AR Hit Test, placement auto, drag + rotation Y (2 doigts)
|
|
|
|
| 4 |
- Aucune dépendance externe autre que PlayCanvas et le GLB
|
| 5 |
*/
|
| 6 |
|
|
@@ -222,13 +223,17 @@
|
|
| 222 |
return;
|
| 223 |
}
|
| 224 |
|
| 225 |
-
// --- Démarrage AR ---
|
| 226 |
const activateAR = () => {
|
| 227 |
if (!app.xr.isAvailable(pc.XRTYPE_AR)) {
|
| 228 |
message("AR immersive indisponible sur cet appareil.");
|
| 229 |
return;
|
| 230 |
}
|
| 231 |
camera.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
callback: (err) => {
|
| 233 |
if (err) message(`Échec du démarrage AR : ${err.message}`);
|
| 234 |
}
|
|
@@ -279,7 +284,7 @@
|
|
| 279 |
});
|
| 280 |
|
| 281 |
// =========================
|
| 282 |
-
// --- Interactions
|
| 283 |
// =========================
|
| 284 |
let isDragging = false; // déplacement du modèle
|
| 285 |
let rotateMode = false; // rotation 2 doigts (tactile) ou souris
|
|
@@ -296,13 +301,6 @@
|
|
| 296 |
isDragging = false;
|
| 297 |
}
|
| 298 |
|
| 299 |
-
// ===== Rotation 2 doigts (incrémentale) =====
|
| 300 |
-
let lastTwoFingerAngle = null; // radians
|
| 301 |
-
const RAD2DEG = 180 / Math.PI;
|
| 302 |
-
|
| 303 |
-
// --- DEBUG: alerte une seule fois au début d'un geste 2 doigts ---
|
| 304 |
-
let rotationAlertShown = false;
|
| 305 |
-
|
| 306 |
// Déplacement (input XR) — ne démarre que si pas de 2 doigts
|
| 307 |
app.xr.input.on("add", (inputSource) => {
|
| 308 |
inputSource.on("selectstart", () => {
|
|
@@ -332,78 +330,75 @@
|
|
| 332 |
});
|
| 333 |
});
|
| 334 |
|
| 335 |
-
// =====
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
const a = it.next().value;
|
| 343 |
-
const b = it.next().value;
|
| 344 |
-
return Math.atan2(b.y - a.y, b.x - a.x);
|
| 345 |
-
};
|
| 346 |
-
|
| 347 |
-
app.touch.on("touchstart", (e) => {
|
| 348 |
-
for (const t of e.touches) touches.set(t.id, { x: t.x, y: t.y });
|
| 349 |
-
|
| 350 |
-
if (touches.size >= 2) {
|
| 351 |
-
multiTouchActive = true;
|
| 352 |
-
rotateMode = true;
|
| 353 |
-
cancelAllTransientHitTests();
|
| 354 |
-
lastTwoFingerAngle = twoFingerAngle();
|
| 355 |
-
|
| 356 |
-
// --- DEBUG ALERT ---
|
| 357 |
-
if (!rotationAlertShown) {
|
| 358 |
-
rotationAlertShown = true;
|
| 359 |
-
console.log("[AR] Rotation 2 doigts détectée — touches:", Array.from(touches.keys()), "count:", touches.size);
|
| 360 |
-
try { alert("Rotation à 2 doigts détectée"); } catch(_) {}
|
| 361 |
-
}
|
| 362 |
-
}
|
| 363 |
-
});
|
| 364 |
-
|
| 365 |
-
app.touch.on("touchmove", (e) => {
|
| 366 |
-
for (const t of e.touches) {
|
| 367 |
-
if (touches.has(t.id)) touches.set(t.id, { x: t.x, y: t.y });
|
| 368 |
-
}
|
| 369 |
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
if (delta < -Math.PI) delta += 2 * Math.PI;
|
| 376 |
|
| 377 |
-
|
| 378 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
|
| 380 |
-
|
| 381 |
-
|
|
|
|
|
|
|
| 382 |
}
|
| 383 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
|
| 385 |
-
|
| 386 |
-
|
| 387 |
|
| 388 |
-
|
| 389 |
-
rotateMode = false;
|
| 390 |
-
multiTouchActive = false;
|
| 391 |
-
lastTwoFingerAngle = null;
|
| 392 |
-
rotationAlertShown = false; // reset pour le prochain geste
|
| 393 |
}
|
| 394 |
-
|
|
|
|
|
|
|
|
|
|
| 395 |
|
| 396 |
-
|
| 397 |
-
|
|
|
|
| 398 |
rotateMode = false;
|
| 399 |
multiTouchActive = false;
|
| 400 |
lastTwoFingerAngle = null;
|
| 401 |
-
rotationAlertShown = false;
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
}
|
| 405 |
|
| 406 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
app.mouse.on("mousedown", (e) => {
|
| 408 |
if (!app.xr.active || !placedOnce) return;
|
| 409 |
|
|
@@ -453,6 +448,12 @@
|
|
| 453 |
lastTwoFingerAngle = null;
|
| 454 |
rotationAlertShown = false;
|
| 455 |
cancelAllTransientHitTests();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
});
|
| 457 |
|
| 458 |
app.xr.on(`available:${pc.XRTYPE_AR}`, (available) => {
|
|
|
|
| 1 |
/* script_ar.js — AR PlayCanvas + GLB HuggingFace
|
| 2 |
- Chargeur PlayCanvas robuste (ESM -> UMD avec fallbacks + timeout)
|
| 3 |
- WebXR AR Hit Test, placement auto, drag + rotation Y (2 doigts)
|
| 4 |
+
- DOM Overlay activé pour capter les touches en AR
|
| 5 |
- Aucune dépendance externe autre que PlayCanvas et le GLB
|
| 6 |
*/
|
| 7 |
|
|
|
|
| 223 |
return;
|
| 224 |
}
|
| 225 |
|
| 226 |
+
// --- Démarrage AR (MODIF: activer DOM Overlay & features) ---
|
| 227 |
const activateAR = () => {
|
| 228 |
if (!app.xr.isAvailable(pc.XRTYPE_AR)) {
|
| 229 |
message("AR immersive indisponible sur cet appareil.");
|
| 230 |
return;
|
| 231 |
}
|
| 232 |
camera.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
|
| 233 |
+
// IMPORTANT: activer l'overlay DOM pour recevoir les events tactiles en AR
|
| 234 |
+
domOverlay: { root: document.body }, // <-- MODIF
|
| 235 |
+
requiredFeatures: ["hit-test", "dom-overlay"], // <-- MODIF (hit-test déjà géré mais ok)
|
| 236 |
+
optionalFeatures: ["plane-detection"], // <-- MODIF (facultatif)
|
| 237 |
callback: (err) => {
|
| 238 |
if (err) message(`Échec du démarrage AR : ${err.message}`);
|
| 239 |
}
|
|
|
|
| 284 |
});
|
| 285 |
|
| 286 |
// =========================
|
| 287 |
+
// --- Interactions
|
| 288 |
// =========================
|
| 289 |
let isDragging = false; // déplacement du modèle
|
| 290 |
let rotateMode = false; // rotation 2 doigts (tactile) ou souris
|
|
|
|
| 301 |
isDragging = false;
|
| 302 |
}
|
| 303 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
// Déplacement (input XR) — ne démarre que si pas de 2 doigts
|
| 305 |
app.xr.input.on("add", (inputSource) => {
|
| 306 |
inputSource.on("selectstart", () => {
|
|
|
|
| 330 |
});
|
| 331 |
});
|
| 332 |
|
| 333 |
+
// =========================
|
| 334 |
+
// --- Gestes tactiles NATIFS via DOM Overlay (MODIF)
|
| 335 |
+
// =========================
|
| 336 |
+
// On n'utilise PAS app.touch ici : on écoute le canvas en DOM Overlay XR
|
| 337 |
+
const RAD2DEG = 180 / Math.PI;
|
| 338 |
+
let lastTwoFingerAngle = null;
|
| 339 |
+
let rotationAlertShown = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
|
| 341 |
+
function getAngleTwoTouches(touches) {
|
| 342 |
+
if (touches.length < 2) return null;
|
| 343 |
+
const t0 = touches[0], t1 = touches[1];
|
| 344 |
+
return Math.atan2(t1.clientY - t0.clientY, t1.clientX - t0.clientX);
|
| 345 |
+
}
|
|
|
|
| 346 |
|
| 347 |
+
// Démarre un geste : si 2 doigts, activer rotation et couper la translation
|
| 348 |
+
const onTouchStart = (e) => {
|
| 349 |
+
if (!app.xr.active) return; // on s'intéresse seulement au mode AR
|
| 350 |
+
if (e.touches.length >= 2) {
|
| 351 |
+
multiTouchActive = true;
|
| 352 |
+
rotateMode = true;
|
| 353 |
+
cancelAllTransientHitTests();
|
| 354 |
+
lastTwoFingerAngle = getAngleTwoTouches(e.touches);
|
| 355 |
|
| 356 |
+
if (!rotationAlertShown) {
|
| 357 |
+
rotationAlertShown = true;
|
| 358 |
+
console.log("[AR] Rotation 2 doigts détectée — touches:", e.touches.length);
|
| 359 |
+
try { alert("Rotation à 2 doigts détectée"); } catch(_) {}
|
| 360 |
}
|
| 361 |
+
}
|
| 362 |
+
};
|
| 363 |
+
|
| 364 |
+
const onTouchMove = (e) => {
|
| 365 |
+
if (!app.xr.active) return;
|
| 366 |
+
if (rotateMode && multiTouchActive && modelRoot.enabled && e.touches.length >= 2) {
|
| 367 |
+
const ang = getAngleTwoTouches(e.touches);
|
| 368 |
+
if (ang !== null && lastTwoFingerAngle !== null) {
|
| 369 |
+
let delta = ang - lastTwoFingerAngle;
|
| 370 |
+
if (delta > Math.PI) delta -= 2 * Math.PI;
|
| 371 |
+
if (delta < -Math.PI) delta += 2 * Math.PI;
|
| 372 |
|
| 373 |
+
const eul = modelRoot.getEulerAngles();
|
| 374 |
+
modelRoot.setEulerAngles(eul.x, eul.y + (delta * RAD2DEG), eul.z);
|
| 375 |
|
| 376 |
+
lastTwoFingerAngle = ang;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
}
|
| 378 |
+
// empêcher le scroll/zoom navigateur & gestes par défaut
|
| 379 |
+
e.preventDefault();
|
| 380 |
+
}
|
| 381 |
+
};
|
| 382 |
|
| 383 |
+
const onTouchEnd = (e) => {
|
| 384 |
+
if (!app.xr.active) return;
|
| 385 |
+
if (e.touches.length < 2) {
|
| 386 |
rotateMode = false;
|
| 387 |
multiTouchActive = false;
|
| 388 |
lastTwoFingerAngle = null;
|
| 389 |
+
rotationAlertShown = false;
|
| 390 |
+
}
|
| 391 |
+
};
|
|
|
|
| 392 |
|
| 393 |
+
// IMPORTANT : listeners non-passifs pour pouvoir appeler preventDefault
|
| 394 |
+
canvas.addEventListener("touchstart", onTouchStart, { passive: false });
|
| 395 |
+
canvas.addEventListener("touchmove", onTouchMove, { passive: false });
|
| 396 |
+
canvas.addEventListener("touchend", onTouchEnd, { passive: false });
|
| 397 |
+
canvas.addEventListener("touchcancel", onTouchEnd, { passive: false });
|
| 398 |
+
|
| 399 |
+
// =========================
|
| 400 |
+
// Souris (pour desktop)
|
| 401 |
+
// =========================
|
| 402 |
app.mouse.on("mousedown", (e) => {
|
| 403 |
if (!app.xr.active || !placedOnce) return;
|
| 404 |
|
|
|
|
| 448 |
lastTwoFingerAngle = null;
|
| 449 |
rotationAlertShown = false;
|
| 450 |
cancelAllTransientHitTests();
|
| 451 |
+
|
| 452 |
+
// Nettoyage des listeners DOM (au cas où)
|
| 453 |
+
canvas.removeEventListener("touchstart", onTouchStart);
|
| 454 |
+
canvas.removeEventListener("touchmove", onTouchMove);
|
| 455 |
+
canvas.removeEventListener("touchend", onTouchEnd);
|
| 456 |
+
canvas.removeEventListener("touchcancel", onTouchEnd);
|
| 457 |
});
|
| 458 |
|
| 459 |
app.xr.on(`available:${pc.XRTYPE_AR}`, (available) => {
|