MikaFil commited on
Commit
28721f6
·
verified ·
1 Parent(s): 237e5e4

Update viewer_ar.js

Browse files
Files changed (1) hide show
  1. viewer_ar.js +50 -80
viewer_ar.js CHANGED
@@ -1,12 +1,13 @@
1
  /* script_ar.js — AR PlayCanvas + GLB HuggingFace
2
  - Hit-test AR (horizontaux uniquement) + placement + drag
3
- - Slider custom (360° en haut 0° en bas), knob centré, rail plein
4
  - Bloque toute interaction scène quand on tape la box grise du slider
5
  */
6
 
7
  (() => {
8
  const GLB_URL = "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/AR/tests/fraisier.glb";
9
 
 
10
  const PC_VERSION = "2.11.7";
11
  const PC_URLS = {
12
  esm: [`https://cdn.jsdelivr.net/npm/playcanvas@${PC_VERSION}/build/playcanvas.min.mjs`],
@@ -21,6 +22,7 @@
21
  try{ return esmFirst ? await tryESM() : await tryUMD(); }catch{ return esmFirst ? await tryUMD() : await tryESM(); }
22
  }
23
 
 
24
  const css = `
25
  .pc-ar-msg{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;z-index:2;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}
26
  #xr-overlay-root{position:fixed;inset:0;z-index:9999;pointer-events:none}
@@ -29,16 +31,16 @@
29
 
30
  .ar-ui .label{font-size:12px;text-align:center;opacity:.95}
31
 
32
- /* Grande zone tactile (box grise) */
33
  .rotY-wrap{position:relative;width:48px;height:200px;display:flex;align-items:center;justify-content:center;touch-action:none;overflow:visible;pointer-events:auto}
34
 
35
- /* Rail PLEIN (de haut en bas), centré */
36
  .rotY-rail{position:absolute;left:50%;transform:translateX(-50%);width:4px;height:100%;background:rgba(255,255,255,.35);border-radius:2px}
37
 
38
- /* Knob custom bien centré */
39
  .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}
40
 
41
- /* Input range caché (pour accessibilité/valeur) */
42
  .ar-ui input[type="range"].rotY{position:absolute;opacity:0;pointer-events:none;width:0;height:0}
43
  .ar-ui .val{font-size:12px;opacity:.95}
44
  `;
@@ -105,16 +107,15 @@
105
  const modelRoot = new pc.Entity("ModelRoot"); modelRoot.enabled = false; app.root.addChild(modelRoot);
106
  let modelLoaded=false, placedOnce=false;
107
 
108
- // Euler de base (évite les inversions)
109
  let baseEulerX=0, baseEulerZ=0;
110
 
111
  // Rotation Y (clamp 0..360) — 360 en haut, 0 en bas
112
  let rotationYDeg = 0;
113
  const clamp360 = d => Math.max(0, Math.min(360, d));
114
 
115
- // Met à jour modèle + UI (⚠ inversion : 360 en haut)
116
  function updateKnobFromY(yDeg){
117
- const t = 1 - (yDeg / 360); // y=360 -> t=0 (haut), y=0 -> t=1 (bas)
118
  rotKnob.style.top = `${t*100}%`;
119
  rotYInput.value = String(Math.round(yDeg));
120
  rotYVal.textContent = `${Math.round(yDeg)}°`;
@@ -142,51 +143,64 @@
142
 
143
  if (!app.xr.supported){ message("WebXR n’est pas supporté sur cet appareil."); return; }
144
 
145
- // ===== Empêcher les “fuites” : activer uiInteracting en CAPTURE sur la box grise =====
146
- let uiInteracting=false, draggingWrap=false;
147
- const stop = e => { e.stopPropagation(); e.preventDefault(); };
148
-
149
- // Capture globale : si on clique/touche la box grise, on active uiInteracting IMMÉDIATEMENT
150
- const isInsideWrap = (target) => rotWrap.contains(target);
151
- const onGlobalPointerDownCapture = (e) => { if (isInsideWrap(e.target)) uiInteracting = true; };
152
- const onGlobalTouchStartCapture = (e) => { if (isInsideWrap(e.target)) uiInteracting = true; };
153
 
 
 
 
 
154
  document.addEventListener("pointerdown", onGlobalPointerDownCapture, true);
155
- document.addEventListener("touchstart", onGlobalTouchStartCapture, true);
156
 
157
- // Et on nettoie à la fin du geste
158
- const onGlobalPointerUpCapture = () => { if (!draggingWrap) uiInteracting = false; };
159
- const onGlobalTouchEndCapture = () => { if (!draggingWrap) uiInteracting = false; };
160
  document.addEventListener("pointerup", onGlobalPointerUpCapture, true);
161
- document.addEventListener("touchend", onGlobalTouchEndCapture, true);
162
 
163
- // Bloque la propagation normale depuis le panneau / knob / input
164
- ["pointerdown","pointermove","pointerup","pointercancel","touchstart","touchmove","touchend","touchcancel","mousedown","mousemove","mouseup","wheel","click"].forEach(evt=>{
 
165
  ui.addEventListener(evt, stop, {passive:false});
166
  rotYInput.addEventListener(evt, stop, {passive:false});
167
  rotKnob.addEventListener(evt, stop, {passive:false});
168
  });
169
 
170
- // Drag custom sur le wrapper vertical — 360 en haut, 0 en bas
171
  function valueFromWrapEvent(ev){
172
  const rect = rotWrap.getBoundingClientRect();
173
- const clientY = (ev.touches && ev.touches[0]) ? ev.touches[0].clientY : ev.clientY;
174
- const ratio = (clientY - rect.top) / rect.height; // 0 en haut 1 en bas
175
  const t = Math.max(0, Math.min(1, ratio));
176
- return (1 - t) * 360; // inversion : haut=360, bas=0
177
  }
178
- function beginWrapDrag(e){ draggingWrap=true; stop(e); rotWrap.setPointerCapture?.(e.pointerId||1); applyRotationY(valueFromWrapEvent(e)); }
179
- function moveWrapDrag(e){ if(!draggingWrap) return; stop(e); applyRotationY(valueFromWrapEvent(e)); }
180
- function endWrapDrag(e){ if(!draggingWrap) return; stop(e); draggingWrap=false; uiInteracting=false; try{ rotWrap.releasePointerCapture?.(e.pointerId||1);}catch{} }
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  rotWrap.addEventListener("pointerdown", beginWrapDrag, {passive:false});
183
  rotWrap.addEventListener("pointermove", moveWrapDrag, {passive:false});
184
  rotWrap.addEventListener("pointerup", endWrapDrag, {passive:false});
185
  rotWrap.addEventListener("pointercancel",endWrapDrag, {passive:false});
186
- rotWrap.addEventListener("touchstart", beginWrapDrag, {passive:false});
187
- rotWrap.addEventListener("touchmove", moveWrapDrag, {passive:false});
188
- rotWrap.addEventListener("touchend", endWrapDrag, {passive:false});
189
- rotWrap.addEventListener("touchcancel", endWrapDrag, {passive:false});
190
 
191
  // --- Démarrage AR
192
  const activateAR = () => {
@@ -230,7 +244,6 @@
230
  modelRoot.setPosition(pos);
231
 
232
  const e = new pc.Vec3(); rot.getEulerAngles(e);
233
- // normalise 0..360 (puis UI inversée affichera 360 en haut)
234
  const y0 = ((e.y % 360) + 360) % 360;
235
  applyRotationY(y0);
236
 
@@ -243,50 +256,7 @@
243
  });
244
  });
245
 
246
- // Déplacement XR (drag) — ignoré si on interagit avec la box grise
247
  let isDragging=false;
248
  app.xr.input.on("add", (inputSource) => {
249
- inputSource.on("selectstart", () => {
250
- if (uiInteracting) return;
251
- if (!placedOnce || !modelLoaded) return;
252
-
253
- inputSource.hitTestStart({
254
- entityTypes: [pc.XRTRACKABLE_POINT, pc.XRTRACKABLE_PLANE],
255
- callback: (err, transientSource) => {
256
- if (err) return;
257
- isDragging = true;
258
-
259
- transientSource.on("result", (pos, rot) => {
260
- if (!isDragging) return;
261
- if (!isHorizontalUpFacing(rot)) return;
262
- modelRoot.setPosition(pos);
263
- });
264
-
265
- transientSource.once("remove", () => { isDragging = false; });
266
- }
267
- });
268
- });
269
- inputSource.on("selectend", () => { isDragging = false; });
270
- });
271
-
272
- // Desktop : rotation souris conservée
273
- let rotateMode=false, lastMouseX=0; const ROTATE_SENSITIVITY=0.25;
274
- app.mouse.on("mousedown",(e)=>{ if(!app.xr.active||!placedOnce||uiInteracting) return; if(e.button===0 && !e.shiftKey) isDragging=true; else if(e.button===2 || (e.button===0 && e.shiftKey)){ rotateMode=true; lastMouseX=e.x; }});
275
- app.mouse.on("mousemove",(e)=>{ if(!app.xr.active||!placedOnce||uiInteracting) return; if(isDragging){ if(reticle.enabled) modelRoot.setPosition(reticle.getPosition()); } else if(rotateMode && modelRoot.enabled){ const dx=e.x-lastMouseX; lastMouseX=e.x; applyRotationY(rotationYDeg + dx*ROTATE_SENSITIVITY); }});
276
- app.mouse.on("mouseup",()=>{ isDragging=false; rotateMode=false; });
277
- window.addEventListener("contextmenu",(e)=>e.preventDefault());
278
-
279
- // Slider (au cas où via clavier)
280
- rotYInput.disabled = true;
281
- rotYInput.addEventListener("input",(e)=>{ if(!modelRoot.enabled) return; applyRotationY(parseFloat(e.target.value||"0")); },{passive:true});
282
-
283
- // AR events
284
- app.xr.on("start",()=>{ message("Session AR démarrée. Visez le sol pour détecter un plan…"); reticle.enabled=true; });
285
- app.xr.on("end",()=>{ message("Session AR terminée."); reticle.enabled=false; isDragging=false; rotateMode=false; rotYInput.disabled=true; });
286
- app.xr.on(`available:${pc.XRTYPE_AR}`,(a)=>{ if(!a) message("AR immersive indisponible."); else if(!app.xr.hitTest.supported) message("AR Hit Test non supporté."); else message(modelLoaded?"Touchez l’écran pour démarrer l’AR.":"Chargement du modèle…"); });
287
-
288
- if(!app.xr.isAvailable(pc.XRTYPE_AR)) message("AR immersive indisponible.");
289
- else if(!app.xr.hitTest.supported) message("AR Hit Test non supporté.");
290
- else message("Chargement du modèle…");
291
- }
292
- })();
 
1
  /* script_ar.js — AR PlayCanvas + GLB HuggingFace
2
  - Hit-test AR (horizontaux uniquement) + placement + drag
3
+ - Slider custom robuste (Pointer Events only) — 360° en haut, 0° en bas
4
  - Bloque toute interaction scène quand on tape la box grise du slider
5
  */
6
 
7
  (() => {
8
  const GLB_URL = "https://huggingface.co/datasets/MikaFil/viewer_gs/resolve/main/AR/tests/fraisier.glb";
9
 
10
+ // ===== PlayCanvas (version verrouillée) =====
11
  const PC_VERSION = "2.11.7";
12
  const PC_URLS = {
13
  esm: [`https://cdn.jsdelivr.net/npm/playcanvas@${PC_VERSION}/build/playcanvas.min.mjs`],
 
22
  try{ return esmFirst ? await tryESM() : await tryUMD(); }catch{ return esmFirst ? await tryUMD() : await tryESM(); }
23
  }
24
 
25
+ // ===== UI / Overlay =====
26
  const css = `
27
  .pc-ar-msg{position:fixed;left:50%;transform:translateX(-50%);bottom:16px;z-index:2;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}
28
  #xr-overlay-root{position:fixed;inset:0;z-index:9999;pointer-events:none}
 
31
 
32
  .ar-ui .label{font-size:12px;text-align:center;opacity:.95}
33
 
34
+ /* Box grise cliquable (grande hitbox) */
35
  .rotY-wrap{position:relative;width:48px;height:200px;display:flex;align-items:center;justify-content:center;touch-action:none;overflow:visible;pointer-events:auto}
36
 
37
+ /* Rail plein, centré */
38
  .rotY-rail{position:absolute;left:50%;transform:translateX(-50%);width:4px;height:100%;background:rgba(255,255,255,.35);border-radius:2px}
39
 
40
+ /* Knob centré */
41
  .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}
42
 
43
+ /* Input range caché (valeur/accès clavier) */
44
  .ar-ui input[type="range"].rotY{position:absolute;opacity:0;pointer-events:none;width:0;height:0}
45
  .ar-ui .val{font-size:12px;opacity:.95}
46
  `;
 
107
  const modelRoot = new pc.Entity("ModelRoot"); modelRoot.enabled = false; app.root.addChild(modelRoot);
108
  let modelLoaded=false, placedOnce=false;
109
 
110
+ // Euler de base
111
  let baseEulerX=0, baseEulerZ=0;
112
 
113
  // Rotation Y (clamp 0..360) — 360 en haut, 0 en bas
114
  let rotationYDeg = 0;
115
  const clamp360 = d => Math.max(0, Math.min(360, d));
116
 
 
117
  function updateKnobFromY(yDeg){
118
+ const t = 1 - (yDeg / 360); // y=360 -> haut ; y=0 -> bas
119
  rotKnob.style.top = `${t*100}%`;
120
  rotYInput.value = String(Math.round(yDeg));
121
  rotYVal.textContent = `${Math.round(yDeg)}°`;
 
143
 
144
  if (!app.xr.supported){ message("WebXR n’est pas supporté sur cet appareil."); return; }
145
 
146
+ // ===== Slider robuste : Pointer Events only =====
147
+ let uiInteracting=false, draggingWrap=false, activePointerId=null;
 
 
 
 
 
 
148
 
149
+ // Marquer TOUT DE SUITE si on tape dans la box grise (phase capture)
150
+ const onGlobalPointerDownCapture = (e) => {
151
+ if (rotWrap.contains(e.target)) uiInteracting = true;
152
+ };
153
  document.addEventListener("pointerdown", onGlobalPointerDownCapture, true);
 
154
 
155
+ const onGlobalPointerUpCapture = () => {
156
+ if (!draggingWrap) uiInteracting = false;
157
+ };
158
  document.addEventListener("pointerup", onGlobalPointerUpCapture, true);
 
159
 
160
+ // Bloque la propagation depuis le panneau/knob/input (bubbling)
161
+ const stop = e => { e.stopPropagation(); e.preventDefault(); };
162
+ ["pointerdown","pointermove","pointerup","pointercancel","mousedown","mousemove","mouseup","wheel","click"].forEach(evt=>{
163
  ui.addEventListener(evt, stop, {passive:false});
164
  rotYInput.addEventListener(evt, stop, {passive:false});
165
  rotKnob.addEventListener(evt, stop, {passive:false});
166
  });
167
 
168
+ // Drag sur la box grise
169
  function valueFromWrapEvent(ev){
170
  const rect = rotWrap.getBoundingClientRect();
171
+ const y = ev.clientY;
172
+ const ratio = (y - rect.top) / rect.height; // 0 en haut -> 1 en bas
173
  const t = Math.max(0, Math.min(1, ratio));
174
+ return (1 - t) * 360; // 360 en haut -> 0 en bas
175
  }
 
 
 
176
 
177
+ function beginWrapDrag(e){
178
+ uiInteracting = true;
179
+ draggingWrap = true;
180
+ activePointerId = e.pointerId;
181
+ stop(e);
182
+ try { rotWrap.setPointerCapture(e.pointerId); } catch(_) {}
183
+ applyRotationY(valueFromWrapEvent(e));
184
+ }
185
+ function moveWrapDrag(e){
186
+ if (!draggingWrap || e.pointerId !== activePointerId) return;
187
+ stop(e);
188
+ applyRotationY(valueFromWrapEvent(e));
189
+ }
190
+ function endWrapDrag(e){
191
+ if (e.pointerId !== activePointerId) return;
192
+ stop(e);
193
+ draggingWrap = false;
194
+ activePointerId = null;
195
+ uiInteracting = false;
196
+ try { rotWrap.releasePointerCapture(e.pointerId); } catch(_) {}
197
+ }
198
+
199
+ // Pointer events uniquement (fiable Android/Chrome)
200
  rotWrap.addEventListener("pointerdown", beginWrapDrag, {passive:false});
201
  rotWrap.addEventListener("pointermove", moveWrapDrag, {passive:false});
202
  rotWrap.addEventListener("pointerup", endWrapDrag, {passive:false});
203
  rotWrap.addEventListener("pointercancel",endWrapDrag, {passive:false});
 
 
 
 
204
 
205
  // --- Démarrage AR
206
  const activateAR = () => {
 
244
  modelRoot.setPosition(pos);
245
 
246
  const e = new pc.Vec3(); rot.getEulerAngles(e);
 
247
  const y0 = ((e.y % 360) + 360) % 360;
248
  applyRotationY(y0);
249
 
 
256
  });
257
  });
258
 
259
+ // Déplacement XR — ignoré si on touche le slider
260
  let isDragging=false;
261
  app.xr.input.on("add", (inputSource) => {
262
+ inputSource.on("selectstart", () =>