Update viewer_ar_ios.js
Browse files- viewer_ar_ios.js +43 -32
viewer_ar_ios.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
| 4 |
- Android/Desktop : WebXR AR (plans HORIZONTAUX uniquement) + slider yaw + blob shadow
|
| 5 |
- Éclairage PBR par défaut (sans WebXR light estimation)
|
| 6 |
- Bouton "Lancer l’AR" (Android & iOS)
|
| 7 |
-
- CSS EXTERNE via data-css
|
| 8 |
- Montable dans un conteneur dédié via data-mount et data-height (Squarespace-friendly)
|
| 9 |
*/
|
| 10 |
|
|
@@ -17,20 +17,34 @@
|
|
| 17 |
})();
|
| 18 |
}
|
| 19 |
|
| 20 |
-
// Injecte
|
| 21 |
-
function
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
}
|
| 35 |
|
| 36 |
function findConfigUrl() {
|
|
@@ -204,7 +218,6 @@
|
|
| 204 |
btn.id = 'ar-launch-btn';
|
| 205 |
btn.type = 'button';
|
| 206 |
btn.textContent = 'Lancer l’AR';
|
| 207 |
-
// Évite que le clic déclenche d’autres gestuelles
|
| 208 |
btn.addEventListener('pointerdown', function (e) { e.stopPropagation(); }, { passive: true });
|
| 209 |
btn.addEventListener('click', function (e) { e.stopPropagation(); }, false);
|
| 210 |
document.body.appendChild(btn);
|
|
@@ -223,15 +236,16 @@
|
|
| 223 |
return { btn: btn, anchor: anchor || null };
|
| 224 |
}
|
| 225 |
|
| 226 |
-
// ========== Boot :
|
| 227 |
(async function () {
|
| 228 |
-
// A) CSS externe depuis data-css (ou valeur par défaut)
|
| 229 |
var scriptEl = getCurrentScript();
|
| 230 |
var cssHref = (scriptEl && scriptEl.getAttribute('data-css'))
|
| 231 |
|| "https://huggingface.co/spaces/MikaFil/VR/resolve/main/css/style.css";
|
| 232 |
-
ensureCssLink(cssHref);
|
| 233 |
|
| 234 |
-
//
|
|
|
|
|
|
|
|
|
|
| 235 |
var cfgUrl = findConfigUrl();
|
| 236 |
var cfg = await loadConfigJson(cfgUrl);
|
| 237 |
var GLB_URL = (cfg && typeof cfg.glb_url === "string" && cfg.glb_url)
|
|
@@ -239,12 +253,12 @@
|
|
| 239 |
: "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/AR/tests/danae_no_metallic.glb";
|
| 240 |
var USDZ_URL = (cfg && typeof cfg.usdz_url === "string" && cfg.usdz_url) ? cfg.usdz_url : null;
|
| 241 |
|
| 242 |
-
//
|
| 243 |
var controls = ensureLaunchControls(USDZ_URL);
|
| 244 |
var launchBtn = controls.btn;
|
| 245 |
var iosAnchor = controls.anchor;
|
| 246 |
|
| 247 |
-
//
|
| 248 |
if (isIOS()) {
|
| 249 |
if (USDZ_URL) {
|
| 250 |
message("iOS détecté : appuyez sur « Lancer l’AR » pour ouvrir Quick Look.");
|
|
@@ -259,7 +273,7 @@
|
|
| 259 |
return;
|
| 260 |
}
|
| 261 |
|
| 262 |
-
//
|
| 263 |
try {
|
| 264 |
await loadPlayCanvasRobust({ esmFirst: true, loadTimeoutMs: 15000 });
|
| 265 |
} catch (e) {
|
|
@@ -340,7 +354,7 @@
|
|
| 340 |
app.root.addChild(modelRoot);
|
| 341 |
var modelLoaded = false, placedOnce = false;
|
| 342 |
|
| 343 |
-
// ===== Blob Shadow
|
| 344 |
var blob = null;
|
| 345 |
var BLOB_SIZE = 0.4;
|
| 346 |
var BLOB_OFFSET_Y = 0.005;
|
|
@@ -424,7 +438,6 @@
|
|
| 424 |
modelRoot.addChild(instance);
|
| 425 |
modelRoot.setLocalScale(1, 1, 1);
|
| 426 |
|
| 427 |
-
// Petits ajustements matériaux (éviter rendu trop sombre sans IBL)
|
| 428 |
var renders = instance.findComponents('render');
|
| 429 |
for (var ri = 0; ri < renders.length; ri++) {
|
| 430 |
var r = renders[ri];
|
|
@@ -449,7 +462,7 @@
|
|
| 449 |
|
| 450 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
| 451 |
|
| 452 |
-
// Slider fiable
|
| 453 |
var uiInteracting = false, draggingWrap = false, activePointerId = null;
|
| 454 |
function insideWrap(target) { return rotWrap.contains(target); }
|
| 455 |
function degFromPointer(e) {
|
|
@@ -483,7 +496,7 @@
|
|
| 483 |
document.addEventListener("pointerup", endDrag, { capture: true, passive: false });
|
| 484 |
document.addEventListener("pointercancel", endDrag, { capture: true, passive: false });
|
| 485 |
|
| 486 |
-
// Démarrage AR (exposé pour le bouton
|
| 487 |
function activateAR() {
|
| 488 |
if (!app.xr.isAvailable(pc.XRTYPE_AR)) { message("AR immersive indisponible sur cet appareil."); return; }
|
| 489 |
if (!app.xr.domOverlay) app.xr.domOverlay = {};
|
|
@@ -508,7 +521,7 @@
|
|
| 508 |
}
|
| 509 |
app.keyboard.on("keydown", function (evt) { if (evt.key === pc.KEY_ESCAPE && app.xr.active) app.xr.end(); });
|
| 510 |
|
| 511 |
-
// Hit-test HORIZONTAL
|
| 512 |
var TMP_IN = new pc.Vec3(0, 1, 0), TMP_OUT = new pc.Vec3();
|
| 513 |
function isHorizontalUpFacing(rot, minDot) {
|
| 514 |
minDot = (typeof minDot === "number") ? minDot : 0.75;
|
|
@@ -516,7 +529,6 @@
|
|
| 516 |
return TMP_OUT.y >= minDot;
|
| 517 |
}
|
| 518 |
|
| 519 |
-
// Hit Test global
|
| 520 |
app.xr.hitTest.on("available", function () {
|
| 521 |
app.xr.hitTest.start({
|
| 522 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
|
@@ -532,7 +544,6 @@
|
|
| 532 |
modelRoot.enabled = true;
|
| 533 |
modelRoot.setPosition(pos);
|
| 534 |
|
| 535 |
-
// Ombre de contact
|
| 536 |
blob = createBlobShadowAt(pos, rot);
|
| 537 |
|
| 538 |
var e = new pc.Vec3(); rot.getEulerAngles(e);
|
|
@@ -548,7 +559,7 @@
|
|
| 548 |
});
|
| 549 |
});
|
| 550 |
|
| 551 |
-
// Déplacement XR
|
| 552 |
var isDragging = false;
|
| 553 |
app.xr.input.on("add", function (inputSource) {
|
| 554 |
inputSource.on("selectstart", function () {
|
|
@@ -575,7 +586,7 @@
|
|
| 575 |
inputSource.on("selectend", function () { isDragging = false; });
|
| 576 |
});
|
| 577 |
|
| 578 |
-
// Desktop : rotation
|
| 579 |
var rotateMode = false, lastMouseX = 0;
|
| 580 |
var ROTATE_SENSITIVITY = 0.25;
|
| 581 |
app.mouse.on("mousedown", function (e) {
|
|
|
|
| 4 |
- Android/Desktop : WebXR AR (plans HORIZONTAUX uniquement) + slider yaw + blob shadow
|
| 5 |
- Éclairage PBR par défaut (sans WebXR light estimation)
|
| 6 |
- Bouton "Lancer l’AR" (Android & iOS)
|
| 7 |
+
- CSS EXTERNE via data-css, **attendue avant création de l’UI**
|
| 8 |
- Montable dans un conteneur dédié via data-mount et data-height (Squarespace-friendly)
|
| 9 |
*/
|
| 10 |
|
|
|
|
| 17 |
})();
|
| 18 |
}
|
| 19 |
|
| 20 |
+
// Injecte <link rel="stylesheet"> et **attend** son chargement
|
| 21 |
+
function ensureCssLinkAsync(href) {
|
| 22 |
+
return new Promise(function (resolve) {
|
| 23 |
+
if (!href) return resolve();
|
| 24 |
+
|
| 25 |
+
// Déjà injectée ?
|
| 26 |
+
var links = document.querySelectorAll('link[rel="stylesheet"]');
|
| 27 |
+
for (var i = 0; i < links.length; i++) {
|
| 28 |
+
try {
|
| 29 |
+
if (new URL(links[i].href, document.baseURI).href === new URL(href, document.baseURI).href) {
|
| 30 |
+
// Déjà présente : on considère chargée
|
| 31 |
+
return resolve();
|
| 32 |
+
}
|
| 33 |
+
} catch (_) {
|
| 34 |
+
if (links[i].href === href) return resolve();
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
var linkEl = document.createElement('link');
|
| 39 |
+
linkEl.rel = 'stylesheet';
|
| 40 |
+
linkEl.href = href;
|
| 41 |
+
linkEl.onload = function () { resolve(); };
|
| 42 |
+
linkEl.onerror = function () {
|
| 43 |
+
console.warn('[viewer_ar_ios] CSS failed to load:', href);
|
| 44 |
+
resolve(); // on continue quand même (pas de blocage)
|
| 45 |
+
};
|
| 46 |
+
document.head.appendChild(linkEl);
|
| 47 |
+
});
|
| 48 |
}
|
| 49 |
|
| 50 |
function findConfigUrl() {
|
|
|
|
| 218 |
btn.id = 'ar-launch-btn';
|
| 219 |
btn.type = 'button';
|
| 220 |
btn.textContent = 'Lancer l’AR';
|
|
|
|
| 221 |
btn.addEventListener('pointerdown', function (e) { e.stopPropagation(); }, { passive: true });
|
| 222 |
btn.addEventListener('click', function (e) { e.stopPropagation(); }, false);
|
| 223 |
document.body.appendChild(btn);
|
|
|
|
| 236 |
return { btn: btn, anchor: anchor || null };
|
| 237 |
}
|
| 238 |
|
| 239 |
+
// ========== Boot : charger **CSS**, puis config, puis brancher iOS/Android ==========
|
| 240 |
(async function () {
|
|
|
|
| 241 |
var scriptEl = getCurrentScript();
|
| 242 |
var cssHref = (scriptEl && scriptEl.getAttribute('data-css'))
|
| 243 |
|| "https://huggingface.co/spaces/MikaFil/VR/resolve/main/css/style.css";
|
|
|
|
| 244 |
|
| 245 |
+
// 1) Charger la CSS et ATTENDRE le onload avant toute création d'UI
|
| 246 |
+
await ensureCssLinkAsync(cssHref);
|
| 247 |
+
|
| 248 |
+
// 2) Lire config.json
|
| 249 |
var cfgUrl = findConfigUrl();
|
| 250 |
var cfg = await loadConfigJson(cfgUrl);
|
| 251 |
var GLB_URL = (cfg && typeof cfg.glb_url === "string" && cfg.glb_url)
|
|
|
|
| 253 |
: "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/AR/tests/danae_no_metallic.glb";
|
| 254 |
var USDZ_URL = (cfg && typeof cfg.usdz_url === "string" && cfg.usdz_url) ? cfg.usdz_url : null;
|
| 255 |
|
| 256 |
+
// 3) Construire les contrôles **après** CSS
|
| 257 |
var controls = ensureLaunchControls(USDZ_URL);
|
| 258 |
var launchBtn = controls.btn;
|
| 259 |
var iosAnchor = controls.anchor;
|
| 260 |
|
| 261 |
+
// 4) iOS → Quick Look (pas de WebXR)
|
| 262 |
if (isIOS()) {
|
| 263 |
if (USDZ_URL) {
|
| 264 |
message("iOS détecté : appuyez sur « Lancer l’AR » pour ouvrir Quick Look.");
|
|
|
|
| 273 |
return;
|
| 274 |
}
|
| 275 |
|
| 276 |
+
// 5) Android/Desktop → PlayCanvas
|
| 277 |
try {
|
| 278 |
await loadPlayCanvasRobust({ esmFirst: true, loadTimeoutMs: 15000 });
|
| 279 |
} catch (e) {
|
|
|
|
| 354 |
app.root.addChild(modelRoot);
|
| 355 |
var modelLoaded = false, placedOnce = false;
|
| 356 |
|
| 357 |
+
// ===== Blob Shadow =====
|
| 358 |
var blob = null;
|
| 359 |
var BLOB_SIZE = 0.4;
|
| 360 |
var BLOB_OFFSET_Y = 0.005;
|
|
|
|
| 438 |
modelRoot.addChild(instance);
|
| 439 |
modelRoot.setLocalScale(1, 1, 1);
|
| 440 |
|
|
|
|
| 441 |
var renders = instance.findComponents('render');
|
| 442 |
for (var ri = 0; ri < renders.length; ri++) {
|
| 443 |
var r = renders[ri];
|
|
|
|
| 462 |
|
| 463 |
if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
|
| 464 |
|
| 465 |
+
// Slider fiable
|
| 466 |
var uiInteracting = false, draggingWrap = false, activePointerId = null;
|
| 467 |
function insideWrap(target) { return rotWrap.contains(target); }
|
| 468 |
function degFromPointer(e) {
|
|
|
|
| 496 |
document.addEventListener("pointerup", endDrag, { capture: true, passive: false });
|
| 497 |
document.addEventListener("pointercancel", endDrag, { capture: true, passive: false });
|
| 498 |
|
| 499 |
+
// Démarrage AR (exposé pour le bouton)
|
| 500 |
function activateAR() {
|
| 501 |
if (!app.xr.isAvailable(pc.XRTYPE_AR)) { message("AR immersive indisponible sur cet appareil."); return; }
|
| 502 |
if (!app.xr.domOverlay) app.xr.domOverlay = {};
|
|
|
|
| 521 |
}
|
| 522 |
app.keyboard.on("keydown", function (evt) { if (evt.key === pc.KEY_ESCAPE && app.xr.active) app.xr.end(); });
|
| 523 |
|
| 524 |
+
// Hit-test HORIZONTAL
|
| 525 |
var TMP_IN = new pc.Vec3(0, 1, 0), TMP_OUT = new pc.Vec3();
|
| 526 |
function isHorizontalUpFacing(rot, minDot) {
|
| 527 |
minDot = (typeof minDot === "number") ? minDot : 0.75;
|
|
|
|
| 529 |
return TMP_OUT.y >= minDot;
|
| 530 |
}
|
| 531 |
|
|
|
|
| 532 |
app.xr.hitTest.on("available", function () {
|
| 533 |
app.xr.hitTest.start({
|
| 534 |
entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
|
|
|
|
| 544 |
modelRoot.enabled = true;
|
| 545 |
modelRoot.setPosition(pos);
|
| 546 |
|
|
|
|
| 547 |
blob = createBlobShadowAt(pos, rot);
|
| 548 |
|
| 549 |
var e = new pc.Vec3(); rot.getEulerAngles(e);
|
|
|
|
| 559 |
});
|
| 560 |
});
|
| 561 |
|
| 562 |
+
// Déplacement XR
|
| 563 |
var isDragging = false;
|
| 564 |
app.xr.input.on("add", function (inputSource) {
|
| 565 |
inputSource.on("selectstart", function () {
|
|
|
|
| 586 |
inputSource.on("selectend", function () { isDragging = false; });
|
| 587 |
});
|
| 588 |
|
| 589 |
+
// Desktop : rotation souris
|
| 590 |
var rotateMode = false, lastMouseX = 0;
|
| 591 |
var ROTATE_SENSITIVITY = 0.25;
|
| 592 |
app.mouse.on("mousedown", function (e) {
|