MikaFil commited on
Commit
22e060d
·
verified ·
1 Parent(s): a5c8c5c

Update viewer_ar.js

Browse files
Files changed (1) hide show
  1. viewer_ar.js +81 -7
viewer_ar.js CHANGED
@@ -3,6 +3,7 @@
3
  - Slider custom Yaw (360° en haut → 0° en bas), knob centré, rail plein
4
  - Blocage total des interactions scène quand on touche le slider
5
  - Éclairage PBR par défaut (sans WebXR light estimation)
 
6
  */
7
 
8
  (() => {
@@ -70,7 +71,7 @@
70
  }
71
 
72
  // ===== Boot =====
73
- (async()=>{ try{ await loadPlayCanvasRobust({esmFirst:true,loadTimeoutMs:15000}); }catch(e){ console.error("Chargement PlayCanvas échoué ->",e); message("Impossible de charger PlayCanvas (réseau/CDN). Réessaie plus tard."); return; } initARApp(); })();
74
 
75
  // ===== App =====
76
  function initARApp(){
@@ -96,7 +97,7 @@
96
  const onResize = () => app.resizeCanvas(); window.addEventListener("resize", onResize); app.on("destroy",()=>window.removeEventListener("resize",onResize));
97
  app.start();
98
 
99
- // ===== Rendu / PBR defaults (évite l'objet noir) =====
100
  app.scene.gammaCorrection = pc.GAMMA_SRGB;
101
  app.scene.toneMapping = pc.TONEMAP_ACES;
102
  app.scene.exposure = 1; // ajuste 0.9–1.8 si besoin
@@ -109,7 +110,7 @@
109
 
110
  const light = new pc.Entity("Light");
111
  light.addComponent("light", { type: "directional", intensity: 1.0, castShadows: true, color: new pc.Color(1,1,1) });
112
- light.setLocalEulerAngles(45, 30, 0); // direction lisible par défaut
113
  app.root.addChild(light);
114
 
115
  // Réticule
@@ -120,6 +121,53 @@
120
  const modelRoot = new pc.Entity("ModelRoot"); modelRoot.enabled = false; app.root.addChild(modelRoot);
121
  let modelLoaded=false, placedOnce=false;
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  // Euler de base (évite inversions)
124
  let baseEulerX=0, baseEulerZ=0;
125
 
@@ -140,6 +188,16 @@
140
  updateKnobFromY(y);
141
  }
142
 
 
 
 
 
 
 
 
 
 
 
143
  // Chargement GLB + "fix matériaux"
144
  app.assets.loadFromUrl(GLB_URL, "container", (err, asset) => {
145
  if (err){ console.error(err); message("Échec du chargement du modèle GLB."); return; }
@@ -147,16 +205,17 @@
147
  modelRoot.addChild(instance);
148
  modelRoot.setLocalScale(1,1,1);
149
 
150
- // Fix matériaux (évite l'objet noir si diffuse 1 / IBL off)
151
  const renders = instance.findComponents('render');
152
  for (const r of renders) {
 
153
  for (const mi of r.meshInstances) {
154
  const m = mi.material;
155
  if (!m) continue;
156
  if (m.diffuse && (m.diffuse.r !== 1 || m.diffuse.g !== 1 || m.diffuse.b !== 1)) {
157
  m.diffuse.set(1,1,1);
158
  }
159
- if ('useSkybox' in m) m.useSkybox = true; // exploite l'ambiant/scene lighting
160
  m.update();
161
  }
162
  }
@@ -221,7 +280,6 @@
221
  app.xr.domOverlay.root = document.getElementById("xr-overlay-root");
222
  camera.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
223
  requiredFeatures: ["hit-test","dom-overlay"],
224
- // PAS de "light-estimation" ici (demandé)
225
  domOverlay: { root: app.xr.domOverlay.root },
226
  callback: (err) => { if (err){ console.error("Échec du démarrage AR :", err); message(`Échec du démarrage AR : ${err.message||err}`); } }
227
  });
@@ -253,6 +311,9 @@
253
  modelRoot.enabled = true;
254
  modelRoot.setPosition(pos);
255
 
 
 
 
256
  const e = new pc.Vec3(); rot.getEulerAngles(e);
257
  // normalise yaw initiale 0..360
258
  const y0 = ((e.y % 360)+360)%360;
@@ -284,6 +345,7 @@
284
  if (!isDragging) return;
285
  if (!isHorizontalUpFacing(rot)) return;
286
  modelRoot.setPosition(pos);
 
287
  });
288
 
289
  transientSource.once("remove", () => { isDragging = false; });
@@ -296,7 +358,19 @@
296
  // Desktop : rotation souris conservée (ignore si UI)
297
  let rotateMode=false, lastMouseX=0; const ROTATE_SENSITIVITY=0.25;
298
  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; }});
299
- 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); }});
 
 
 
 
 
 
 
 
 
 
 
 
300
  app.mouse.on("mouseup",()=>{ isDragging=false; rotateMode=false; });
301
  window.addEventListener("contextmenu",(e)=>e.preventDefault());
302
 
 
3
  - Slider custom Yaw (360° en haut → 0° en bas), knob centré, rail plein
4
  - Blocage total des interactions scène quand on touche le slider
5
  - Éclairage PBR par défaut (sans WebXR light estimation)
6
+ - Blob Shadow (ombre de contact) sous l’objet
7
  */
8
 
9
  (() => {
 
71
  }
72
 
73
  // ===== Boot =====
74
+ (async()=>{ try{ await loadPlayCanvasRobust({esmFirst:true,loadTimeoutMs=15000}); }catch(e){ console.error("Chargement PlayCanvas échoué ->",e); message("Impossible de charger PlayCanvas (réseau/CDN). Réessaie plus tard."); return; } initARApp(); })();
75
 
76
  // ===== App =====
77
  function initARApp(){
 
97
  const onResize = () => app.resizeCanvas(); window.addEventListener("resize", onResize); app.on("destroy",()=>window.removeEventListener("resize",onResize));
98
  app.start();
99
 
100
+ // ===== Rendu / PBR defaults =====
101
  app.scene.gammaCorrection = pc.GAMMA_SRGB;
102
  app.scene.toneMapping = pc.TONEMAP_ACES;
103
  app.scene.exposure = 1; // ajuste 0.9–1.8 si besoin
 
110
 
111
  const light = new pc.Entity("Light");
112
  light.addComponent("light", { type: "directional", intensity: 1.0, castShadows: true, color: new pc.Color(1,1,1) });
113
+ light.setLocalEulerAngles(45, 30, 0);
114
  app.root.addChild(light);
115
 
116
  // Réticule
 
121
  const modelRoot = new pc.Entity("ModelRoot"); modelRoot.enabled = false; app.root.addChild(modelRoot);
122
  let modelLoaded=false, placedOnce=false;
123
 
124
+ // ===== Blob Shadow =====
125
+ let blob = null; // entité plane de l’ombre
126
+ const BLOB_SIZE = 0.7; // diamètre approx. de l’ombre (m)
127
+ const BLOB_OFFSET_Y = 0.005; // petit décalage au-dessus du sol pour éviter le z-fighting
128
+
129
+ function makeBlobTexture(app, size = 256) {
130
+ const cvs = document.createElement('canvas');
131
+ cvs.width = cvs.height = size;
132
+ const ctx = cvs.getContext('2d');
133
+ const r = size * 0.45;
134
+ const grd = ctx.createRadialGradient(size/2, size/2, r*0.2, size/2, size/2, r);
135
+ grd.addColorStop(0, 'rgba(0,0,0,0.35)'); // centre sombre
136
+ grd.addColorStop(1, 'rgba(0,0,0,0.0)'); // bords transparents
137
+ ctx.fillStyle = grd;
138
+ ctx.fillRect(0, 0, size, size);
139
+
140
+ const tex = new pc.Texture(app.graphicsDevice, {
141
+ width: size, height: size, format: pc.PIXELFORMAT_R8_G8_B8_A8,
142
+ mipmaps: true, magFilter: pc.FILTER_LINEAR, minFilter: pc.FILTER_LINEAR_MIPMAP_LINEAR
143
+ });
144
+ tex.setSource(cvs);
145
+ return tex;
146
+ }
147
+
148
+ function createBlobShadowAt(pos, rot) {
149
+ const tex = makeBlobTexture(app, 256);
150
+ const blobMat = new pc.StandardMaterial();
151
+ blobMat.diffuse.set(0,0,0);
152
+ blobMat.opacity = 1.0; // la transparence vient de la texture
153
+ blobMat.blendType = pc.BLEND_PREMULTIPLIED;
154
+ blobMat.depthWrite = false; // pour ne pas masquer l'objet
155
+ blobMat.diffuseMap = tex;
156
+ blobMat.update();
157
+
158
+ const e = new pc.Entity("BlobShadow");
159
+ e.addComponent("render", { type: "plane", castShadows: false, receiveShadows: false });
160
+ e.render.material = blobMat;
161
+
162
+ // place légèrement au-dessus du sol, orienté comme le plan détecté (horizontal)
163
+ e.setPosition(pos.x, pos.y + BLOB_OFFSET_Y, pos.z);
164
+ e.setRotation(rot);
165
+ e.setLocalScale(BLOB_SIZE, 1, BLOB_SIZE);
166
+
167
+ app.root.addChild(e);
168
+ return e;
169
+ }
170
+
171
  // Euler de base (évite inversions)
172
  let baseEulerX=0, baseEulerZ=0;
173
 
 
188
  updateKnobFromY(y);
189
  }
190
 
191
+ // Helper : met à jour aussi l’ombre quand on déplace l’objet
192
+ function updateBlobPositionUnder(pos, rotLikePlane = null) {
193
+ if (!blob) return;
194
+ // on suit la position XY du modèle, et on s'aligne sur la rotation du plan si fournie
195
+ blob.setPosition(pos.x, pos.y + BLOB_OFFSET_Y, pos.z);
196
+ if (rotLikePlane) {
197
+ blob.setRotation(rotLikePlane);
198
+ }
199
+ }
200
+
201
  // Chargement GLB + "fix matériaux"
202
  app.assets.loadFromUrl(GLB_URL, "container", (err, asset) => {
203
  if (err){ console.error(err); message("Échec du chargement du modèle GLB."); return; }
 
205
  modelRoot.addChild(instance);
206
  modelRoot.setLocalScale(1,1,1);
207
 
208
+ // Fix matériaux (au cas où) : s’assure d’un rendu PBR correct
209
  const renders = instance.findComponents('render');
210
  for (const r of renders) {
211
+ r.castShadows = true; // l’objet projette des ombres (utile si tu actives de vraies ombres)
212
  for (const mi of r.meshInstances) {
213
  const m = mi.material;
214
  if (!m) continue;
215
  if (m.diffuse && (m.diffuse.r !== 1 || m.diffuse.g !== 1 || m.diffuse.b !== 1)) {
216
  m.diffuse.set(1,1,1);
217
  }
218
+ if ('useSkybox' in m) m.useSkybox = true;
219
  m.update();
220
  }
221
  }
 
280
  app.xr.domOverlay.root = document.getElementById("xr-overlay-root");
281
  camera.camera.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {
282
  requiredFeatures: ["hit-test","dom-overlay"],
 
283
  domOverlay: { root: app.xr.domOverlay.root },
284
  callback: (err) => { if (err){ console.error("Échec du démarrage AR :", err); message(`Échec du démarrage AR : ${err.message||err}`); } }
285
  });
 
311
  modelRoot.enabled = true;
312
  modelRoot.setPosition(pos);
313
 
314
+ // Crée l’ombre de contact au même endroit/orientation
315
+ blob = createBlobShadowAt(pos, rot);
316
+
317
  const e = new pc.Vec3(); rot.getEulerAngles(e);
318
  // normalise yaw initiale 0..360
319
  const y0 = ((e.y % 360)+360)%360;
 
345
  if (!isDragging) return;
346
  if (!isHorizontalUpFacing(rot)) return;
347
  modelRoot.setPosition(pos);
348
+ updateBlobPositionUnder(pos, rot); // <— l’ombre suit le modèle
349
  });
350
 
351
  transientSource.once("remove", () => { isDragging = false; });
 
358
  // Desktop : rotation souris conservée (ignore si UI)
359
  let rotateMode=false, lastMouseX=0; const ROTATE_SENSITIVITY=0.25;
360
  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; }});
361
+ app.mouse.on("mousemove",(e)=>{
362
+ if(!app.xr.active||!placedOnce||uiInteracting) return;
363
+ if(isDragging){
364
+ if(reticle.enabled) {
365
+ const p = reticle.getPosition();
366
+ modelRoot.setPosition(p);
367
+ updateBlobPositionUnder(p); // <— suit aussi en drag souris via réticule
368
+ }
369
+ } else if(rotateMode && modelRoot.enabled){
370
+ const dx=e.x-lastMouseX; lastMouseX=e.x;
371
+ applyRotationY(rotationYDeg + dx*ROTATE_SENSITIVITY);
372
+ }
373
+ });
374
  app.mouse.on("mouseup",()=>{ isDragging=false; rotateMode=false; });
375
  window.addEventListener("contextmenu",(e)=>e.preventDefault());
376