MikaFil commited on
Commit
6e435df
·
verified ·
1 Parent(s): 1d14106

Update viewer_ar_ios.js

Browse files
Files changed (1) hide show
  1. viewer_ar_ios.js +88 -59
viewer_ar_ios.js CHANGED
@@ -4,12 +4,13 @@
4
  - Android/Desktop : WebXR AR (horizontaux uniquement) + slider yaw + blob shadow
5
  - Éclairage PBR par défaut (sans WebXR light estimation)
6
  - Monté dans un conteneur choisi (data-mount="#ar-mount") pour Squarespace
 
 
7
  */
8
 
9
  (function () {
10
- // ============ Utils: script tag / config / platform ============
11
  function getCurrentScript() {
12
- // robuste même si currentScript n'est pas dispo
13
  return document.currentScript || (function () {
14
  var scripts = document.getElementsByTagName('script');
15
  return scripts[scripts.length - 1] || null;
@@ -46,6 +47,18 @@
46
  }
47
  }
48
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  // ============ PlayCanvas version fixée (Android/Desktop) ============
50
  var PC_VERSION = "2.11.7";
51
  var PC_URLS = {
@@ -105,27 +118,7 @@
105
  }
106
  }
107
 
108
- // ============ UI / Overlay commun ============
109
- var css = [
110
- ".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}",
111
- "#xr-overlay-root{position:fixed;inset:0;z-index:10001;pointer-events:none}",
112
-
113
- ".ar-ui{position:absolute;right:12px;top:50%;transform:translateY(-50%);background:rgba(0,0,0,0.55);color:#fff;padding:12px 10px;border-radius:16px;width:56px;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;pointer-events:auto;display:flex;flex-direction:column;align-items:center;gap:8px;box-shadow:0 6px 18px rgba(0,0,0,.25);backdrop-filter:blur(6px);touch-action:none}",
114
- ".ar-ui .label{font-size:12px;text-align:center;opacity:.95}",
115
- ".rotY-wrap{position:relative;width:48px;height:200px;display:flex;align-items:center;justify-content:center;touch-action:none;overflow:visible;pointer-events:auto}",
116
- ".rotY-rail{position:absolute;left:50%;transform:translateX(-50%);width:4px;height:100%;background:rgba(255,255,255,.35);border-radius:2px;pointer-events:none}",
117
- ".rotY-knob{position:absolute;left:50%;width:22px;height:22px;border-radius:50%;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.35);transform:translate(-50%,-50%);top:50%;will-change:top;touch-action:none;pointer-events:none}",
118
- ".ar-ui input[type=\"range\"].rotY{position:absolute;opacity:0;pointer-events:none;width:0;height:0}",
119
- ".ar-ui .val{font-size:12px;opacity:.95}",
120
-
121
- /* iOS Quick Look button */
122
- "#ios-quicklook-btn{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;z-index:10003;display:inline-block;pointer-events:auto}",
123
- "#ios-quicklook-btn img{display:block;height:44px;width:auto}"
124
- ].join("\n");
125
- var styleTag = document.createElement("style");
126
- styleTag.textContent = css;
127
- document.head.appendChild(styleTag);
128
-
129
  function ensureOverlayRoot() {
130
  var r = document.getElementById("xr-overlay-root");
131
  if (!r) {
@@ -161,43 +154,21 @@
161
  }
162
  }
163
 
164
- function ensureQuickLookButton(USDZ_URL) {
165
- var btn = document.getElementById("ios-quicklook-btn");
166
- if (btn) return btn;
167
-
168
- var anchor = document.createElement("a");
169
- anchor.id = "ios-quicklook-btn";
170
- anchor.setAttribute("rel", "ar");
171
- anchor.setAttribute("href", buildQuickLookHref(USDZ_URL));
172
-
173
- var img = document.createElement("img");
174
- img.alt = "Voir en AR";
175
- img.src =
176
- "data:image/svg+xml;charset=utf-8," +
177
- encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="160" height="44"><rect rx="8" ry="8" width="160" height="44" fill="black"/><text x="80" y="28" font-size="14" text-anchor="middle" fill="white" font-family="system-ui, -apple-system, Segoe UI, Roboto">Voir en AR</text></svg>');
178
-
179
- anchor.appendChild(img);
180
- document.body.appendChild(anchor);
181
- return anchor;
182
- }
183
-
184
- // ============ Canvas monté dans un conteneur contrôlé (Squarespace) ============
185
  function ensureCanvas() {
186
  var scriptEl = getCurrentScript();
187
  var mountSel = scriptEl && scriptEl.getAttribute('data-mount');
188
  var desiredHeight = (scriptEl && scriptEl.getAttribute('data-height')) || '70vh';
189
 
190
- // Trouve le conteneur cible
191
  var mountEl = null;
192
  if (mountSel) mountEl = document.querySelector(mountSel);
193
  if (!mountEl) {
194
- // fallback : tout en haut du body (pour éviter "sous le footer")
195
  mountEl = document.createElement('div');
196
  mountEl.id = 'ar-mount-fallback';
197
  document.body.insertBefore(mountEl, document.body.firstChild);
198
  }
199
 
200
- // Style du conteneur
201
  var mountStyle = mountEl.style;
202
  if (!mountStyle.position) mountStyle.position = 'relative';
203
  mountStyle.width = mountStyle.width || '100%';
@@ -205,7 +176,6 @@
205
  mountStyle.touchAction = mountStyle.touchAction || 'manipulation';
206
  mountStyle.webkitTapHighlightColor = 'transparent';
207
 
208
- // Réutilise ou crée le canvas
209
  var canvas = mountEl.querySelector('#application-canvas');
210
  if (!canvas) {
211
  canvas = document.createElement('canvas');
@@ -213,7 +183,7 @@
213
  mountEl.appendChild(canvas);
214
  }
215
 
216
- // Le canvas remplit le conteneur
217
  var cs = canvas.style;
218
  cs.position = 'absolute';
219
  cs.left = '0';
@@ -222,7 +192,6 @@
222
  cs.height = '100%';
223
  cs.display = 'block';
224
 
225
- // Optionnel : ramener le viewport sur le viewer
226
  try {
227
  mountEl.scrollIntoView({ behavior: 'instant', block: 'start' });
228
  } catch (_) {}
@@ -247,8 +216,41 @@
247
  return p;
248
  }
249
 
250
- // ============ Boot : charge config, route iOS vs Android/Desktop ============
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  (async function () {
 
 
 
 
252
  var cfgUrl = findConfigUrl();
253
  var cfg = await loadConfigJson(cfgUrl);
254
  var GLB_URL = (cfg && typeof cfg.glb_url === "string" && cfg.glb_url) ?
@@ -258,28 +260,50 @@
258
  cfg.usdz_url :
259
  null;
260
 
 
 
 
 
 
261
  if (isIOS()) {
262
  if (USDZ_URL) {
263
- ensureQuickLookButton(USDZ_URL);
264
- message("iOS détecté : utilisez le bouton « Voir en AR » (AR Quick Look).");
 
 
 
 
265
  } else {
266
  message("iOS détecté, mais aucun 'usdz_url' dans config.json.");
 
 
267
  }
268
  return;
269
  }
270
 
 
271
  try {
272
  await loadPlayCanvasRobust({ esmFirst: true, loadTimeoutMs: 15000 });
273
  } catch (e) {
274
  console.error("Chargement PlayCanvas échoué ->", e);
275
  message("Impossible de charger PlayCanvas (réseau/CDN). Réessaie plus tard.");
 
 
276
  return;
277
  }
278
- initARApp(GLB_URL);
 
 
 
 
 
 
 
 
279
  })();
280
 
281
  // ============ App Android/Desktop (WebXR) ============
282
- function initARApp(GLB_URL) {
283
  var pc = window.pc;
284
  var canvas = ensureCanvas();
285
  var ui = ensureSliderUI();
@@ -395,7 +419,7 @@
395
  return e;
396
  }
397
 
398
- // Euler de base (évite inversions)
399
  var baseEulerX = 0, baseEulerZ = 0;
400
 
401
  // Rotation via slider (0..360, 360 en haut / 0 en bas)
@@ -448,7 +472,7 @@
448
  baseEulerX = initE.x; baseEulerZ = initE.z;
449
 
450
  modelLoaded = true;
451
- message("Modèle chargé. Touchez l’écran pour démarrer l’AR.");
452
  });
453
 
454
  if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
@@ -502,7 +526,7 @@
502
  document.addEventListener("pointerup", endDrag, { capture: true, passive: false });
503
  document.addEventListener("pointercancel", endDrag, { capture: true, passive: false });
504
 
505
- // --- Démarrage AR (Android/Desktop)
506
  function activateAR() {
507
  if (!app.xr.isAvailable(pc.XRTYPE_AR)) { message("AR immersive indisponible sur cet appareil."); return; }
508
  if (!app.xr.domOverlay) app.xr.domOverlay = {};
@@ -518,6 +542,11 @@
518
  }
519
  });
520
  }
 
 
 
 
 
521
  app.mouse.on("mousedown", function () { if (!app.xr.active && !uiInteracting) activateAR(); });
522
  if (app.touch) {
523
  app.touch.on("touchend", function (evt) {
@@ -640,7 +669,7 @@
640
  app.xr.on("available:" + pc.XRTYPE_AR, function (a) {
641
  if (!a) message("AR immersive indisponible.");
642
  else if (!app.xr.hitTest.supported) message("AR Hit Test non supporté.");
643
- else message(modelLoaded ? "Touchez l’écran pour démarrer l’AR." : "Chargement du modèle…");
644
  });
645
 
646
  if (!app.xr.isAvailable(pc.XRTYPE_AR)) message("AR immersive indisponible.");
 
4
  - Android/Desktop : WebXR AR (horizontaux uniquement) + slider yaw + blob shadow
5
  - Éclairage PBR par défaut (sans WebXR light estimation)
6
  - Monté dans un conteneur choisi (data-mount="#ar-mount") pour Squarespace
7
+ - Bouton "Lancer l’AR" (Android & iOS)
8
+ - CSS externalisé : https://huggingface.co/spaces/MikaFil/VR/resolve/main/css/style.css
9
  */
10
 
11
  (function () {
12
+ // ============ Utils: script tag / config / platform / CSS ============
13
  function getCurrentScript() {
 
14
  return document.currentScript || (function () {
15
  var scripts = document.getElementsByTagName('script');
16
  return scripts[scripts.length - 1] || null;
 
47
  }
48
  }
49
 
50
+ // Charge un CSS externe une seule fois
51
+ function ensureCssLink(href) {
52
+ if (!href) return;
53
+ var existing = Array.prototype.slice.call(document.querySelectorAll('link[rel="stylesheet"]'))
54
+ .some(function (link) { return link.href === href; });
55
+ if (existing) return;
56
+ var linkEl = document.createElement('link');
57
+ linkEl.rel = 'stylesheet';
58
+ linkEl.href = href;
59
+ document.head.appendChild(linkEl);
60
+ }
61
+
62
  // ============ PlayCanvas version fixée (Android/Desktop) ============
63
  var PC_VERSION = "2.11.7";
64
  var PC_URLS = {
 
118
  }
119
  }
120
 
121
+ // ============ Overlay commun (structure DOM) ============
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  function ensureOverlayRoot() {
123
  var r = document.getElementById("xr-overlay-root");
124
  if (!r) {
 
154
  }
155
  }
156
 
157
+ // Canvas monté dans un conteneur (Squarespace-friendly)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  function ensureCanvas() {
159
  var scriptEl = getCurrentScript();
160
  var mountSel = scriptEl && scriptEl.getAttribute('data-mount');
161
  var desiredHeight = (scriptEl && scriptEl.getAttribute('data-height')) || '70vh';
162
 
 
163
  var mountEl = null;
164
  if (mountSel) mountEl = document.querySelector(mountSel);
165
  if (!mountEl) {
 
166
  mountEl = document.createElement('div');
167
  mountEl.id = 'ar-mount-fallback';
168
  document.body.insertBefore(mountEl, document.body.firstChild);
169
  }
170
 
171
+ // Styles minimaux structurels (pas de "design", laissé au CSS externe)
172
  var mountStyle = mountEl.style;
173
  if (!mountStyle.position) mountStyle.position = 'relative';
174
  mountStyle.width = mountStyle.width || '100%';
 
176
  mountStyle.touchAction = mountStyle.touchAction || 'manipulation';
177
  mountStyle.webkitTapHighlightColor = 'transparent';
178
 
 
179
  var canvas = mountEl.querySelector('#application-canvas');
180
  if (!canvas) {
181
  canvas = document.createElement('canvas');
 
183
  mountEl.appendChild(canvas);
184
  }
185
 
186
+ // Positionne le canvas en plein dans son conteneur
187
  var cs = canvas.style;
188
  cs.position = 'absolute';
189
  cs.left = '0';
 
192
  cs.height = '100%';
193
  cs.display = 'block';
194
 
 
195
  try {
196
  mountEl.scrollIntoView({ behavior: 'instant', block: 'start' });
197
  } catch (_) {}
 
216
  return p;
217
  }
218
 
219
+ // Bouton "Lancer l'AR" + (iOS) ancre rel=ar cachée
220
+ function ensureLaunchControls(USDZ_URL) {
221
+ var btn = document.getElementById('ar-launch-btn');
222
+ if (!btn) {
223
+ btn = document.createElement('button');
224
+ btn.id = 'ar-launch-btn';
225
+ btn.type = 'button';
226
+ btn.textContent = 'Lancer l’AR';
227
+ // Ne laisse pas le bouton déclencher des interactions scène
228
+ btn.addEventListener('pointerdown', function (e) { e.stopPropagation(); }, { passive: true });
229
+ btn.addEventListener('click', function (e) { e.stopPropagation(); }, false);
230
+ document.body.appendChild(btn);
231
+ }
232
+
233
+ var anchor = document.getElementById('ios-quicklook-anchor');
234
+ if (isIOS()) {
235
+ if (!anchor) {
236
+ anchor = document.createElement('a');
237
+ anchor.id = 'ios-quicklook-anchor';
238
+ anchor.setAttribute('rel', 'ar');
239
+ document.body.appendChild(anchor);
240
+ }
241
+ if (USDZ_URL) {
242
+ anchor.setAttribute('href', buildQuickLookHref(USDZ_URL));
243
+ }
244
+ }
245
+ return { btn: btn, anchor: anchor || null };
246
+ }
247
+
248
+ // ============ Boot : charge CSS, config, route iOS vs Android/Desktop ============
249
  (async function () {
250
+ // CSS externe
251
+ ensureCssLink("https://huggingface.co/spaces/MikaFil/VR/resolve/main/css/style.css");
252
+
253
+ // Config
254
  var cfgUrl = findConfigUrl();
255
  var cfg = await loadConfigJson(cfgUrl);
256
  var GLB_URL = (cfg && typeof cfg.glb_url === "string" && cfg.glb_url) ?
 
260
  cfg.usdz_url :
261
  null;
262
 
263
+ // Bouton (Android & iOS) + ancre QuickLook (iOS)
264
+ var controls = ensureLaunchControls(USDZ_URL);
265
+ var launchBtn = controls.btn;
266
+ var iosAnchor = controls.anchor;
267
+
268
  if (isIOS()) {
269
  if (USDZ_URL) {
270
+ message("iOS détecté : appuyez sur « Lancer l’AR » pour ouvrir Quick Look.");
271
+ launchBtn.onclick = function (e) {
272
+ e.preventDefault();
273
+ e.stopPropagation();
274
+ if (iosAnchor && iosAnchor.href) iosAnchor.click();
275
+ };
276
  } else {
277
  message("iOS détecté, mais aucun 'usdz_url' dans config.json.");
278
+ launchBtn.disabled = true;
279
+ launchBtn.style.opacity = '0.5';
280
  }
281
  return;
282
  }
283
 
284
+ // Android / Desktop : charger PlayCanvas et initialiser l’app
285
  try {
286
  await loadPlayCanvasRobust({ esmFirst: true, loadTimeoutMs: 15000 });
287
  } catch (e) {
288
  console.error("Chargement PlayCanvas échoué ->", e);
289
  message("Impossible de charger PlayCanvas (réseau/CDN). Réessaie plus tard.");
290
+ launchBtn.disabled = true;
291
+ launchBtn.style.opacity = '0.5';
292
  return;
293
  }
294
+
295
+ var activateARRef = { fn: null };
296
+ initARApp(GLB_URL, activateARRef);
297
+
298
+ launchBtn.onclick = function (e) {
299
+ e.preventDefault();
300
+ e.stopPropagation();
301
+ if (typeof activateARRef.fn === 'function') activateARRef.fn();
302
+ };
303
  })();
304
 
305
  // ============ App Android/Desktop (WebXR) ============
306
+ function initARApp(GLB_URL, activateARRef) {
307
  var pc = window.pc;
308
  var canvas = ensureCanvas();
309
  var ui = ensureSliderUI();
 
419
  return e;
420
  }
421
 
422
+ // Euler de base
423
  var baseEulerX = 0, baseEulerZ = 0;
424
 
425
  // Rotation via slider (0..360, 360 en haut / 0 en bas)
 
472
  baseEulerX = initE.x; baseEulerZ = initE.z;
473
 
474
  modelLoaded = true;
475
+ message("Modèle chargé. Appuyez sur « Lancer l’AR » pour démarrer.");
476
  });
477
 
478
  if (!app.xr.supported) { message("WebXR n’est pas supporté sur cet appareil."); return; }
 
526
  document.addEventListener("pointerup", endDrag, { capture: true, passive: false });
527
  document.addEventListener("pointercancel", endDrag, { capture: true, passive: false });
528
 
529
+ // --- Démarrage AR (Android/Desktop) — exposé via activateARRef.fn et bouton
530
  function activateAR() {
531
  if (!app.xr.isAvailable(pc.XRTYPE_AR)) { message("AR immersive indisponible sur cet appareil."); return; }
532
  if (!app.xr.domOverlay) app.xr.domOverlay = {};
 
542
  }
543
  });
544
  }
545
+ if (activateARRef && typeof activateARRef === 'object') {
546
+ activateARRef.fn = activateAR;
547
+ }
548
+
549
+ // Tap écran (optionnel, on le conserve)
550
  app.mouse.on("mousedown", function () { if (!app.xr.active && !uiInteracting) activateAR(); });
551
  if (app.touch) {
552
  app.touch.on("touchend", function (evt) {
 
669
  app.xr.on("available:" + pc.XRTYPE_AR, function (a) {
670
  if (!a) message("AR immersive indisponible.");
671
  else if (!app.xr.hitTest.supported) message("AR Hit Test non supporté.");
672
+ else message(modelLoaded ? "Touchez l’écran ou « Lancer l’AR »." : "Chargement du modèle…");
673
  });
674
 
675
  if (!app.xr.isAvailable(pc.XRTYPE_AR)) message("AR immersive indisponible.");