MikaFil commited on
Commit
253d9fc
·
verified ·
1 Parent(s): 351fe1b

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +156 -120
viewer.js CHANGED
@@ -23,12 +23,19 @@ async function loadImageAsTexture(url, app) {
23
  let pc;
24
  export let app = null;
25
  let cameraEntity = null;
 
26
  let modelEntity = null;
27
  let tubeEntity = null;
28
  let filtreEntity = null;
 
 
29
  let viewerInitialized = false;
30
  let resizeObserver = null;
31
 
 
 
 
 
32
  // Materials (for real-time switching)
33
  let matTransparent = null;
34
  let matOpaque = null;
@@ -134,7 +141,7 @@ export async function initializeViewer(config, instanceId) {
134
  app.setCanvasFillMode(pc.FILLMODE_NONE);
135
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
136
 
137
- // Canvas responsive resize
138
  resizeObserver = new ResizeObserver(entries => {
139
  entries.forEach(entry => {
140
  app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
@@ -145,12 +152,11 @@ export async function initializeViewer(config, instanceId) {
145
  app.on('destroy', () => resizeObserver.disconnect());
146
 
147
  // ----- Load all images as Textures (async, safe for CORS) -----
148
- // All of these can fail (network, CORS), so wrap with try/catch if needed.
149
  const hdrTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/hdr/ciel_nuageux_1k.png', app);
150
  const emitTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/emit_map_1k.png', app);
151
  const opTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/op_map_1k.png', app);
152
  const thicknessTex= await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/thickness_map_1k.png', app);
153
- const bgTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/images/banniere_earcare.png', app);
154
 
155
  // ----- GLB asset definition -----
156
  const assets = {
@@ -167,102 +173,134 @@ export async function initializeViewer(config, instanceId) {
167
  app.scene.skyboxIntensity = 4;
168
  app.scene.skyboxMip = 0;
169
 
170
- // ----- Load GLBs -----
171
- const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
172
- loader.load(() => {
173
- app.start();
174
- if (progressDialog) progressDialog.style.display = 'none';
175
-
176
- const texture = new pc.Texture(app.graphicsDevice, {
177
- width: 512,
178
- height: 256,
179
  format: pc.PIXELFORMAT_SRGBA8,
180
- mipmaps: true,
181
- addressU: pc.ADDRESS_CLAMP_TO_EDGE,
182
- addressV: pc.ADDRESS_CLAMP_TO_EDGE
183
  });
184
- const renderTarget = new pc.RenderTarget({
 
 
 
 
 
185
  name: 'RT',
186
- colorBuffer: texture,
187
  depth: true,
188
- //flipY: !app.grapexcludeLayerhicsDevice.isWebGPU,
189
- samples: 2
190
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
192
- const excludedLayer = new pc.Layer({name: 'Excluded'});
 
193
  app.scene.layers.insert(excludedLayer, 1);
194
 
195
- // get existing layers
196
- const worldLayer = app.scene.layers.getLayerByName('World');
197
  const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
198
- const uiLayer = app.scene.layers.getLayerByName('UI');
199
 
200
- // Reorder depth layer for transmission
201
  const depthLayer = app.scene.layers.getLayerById(pc.LAYERID_DEPTH);
202
  app.scene.layers.remove(depthLayer);
203
  app.scene.layers.insertOpaque(depthLayer, 2);
204
 
205
- // Instantiate GLB entities
206
- modelEntity = assets.model.resource.instantiateRenderEntity();
207
- tubeEntity = assets.tube.resource.instantiateRenderEntity();
208
  filtreEntity = assets.filtre.resource.instantiateRenderEntity();
209
 
210
- traverse(tubeEntity, (node) => {
211
- if (node.render) node.render.layers = [excludedLayer.id];
212
- });
213
- traverse(filtreEntity, (node) => {
214
- if (node.render) node.render.layers = [excludedLayer.id];
215
- });
216
 
217
  app.root.addChild(modelEntity);
218
  app.root.addChild(tubeEntity);
219
  app.root.addChild(filtreEntity);
220
 
221
-
222
  // ----- Materials Setup -----
223
  // Transparent material (main model)
224
- {matTransparent = new pc.StandardMaterial();
225
- matTransparent.blendType = pc.BLEND_NORMAL;
226
- matTransparent.diffuse = new pc.Color(1, 1, 1);
227
- matTransparent.specular = new pc.Color(0.01, 0.01, 0.01);
228
- matTransparent.gloss = 1;
229
- matTransparent.metalness = 0;
230
- matTransparent.useMetalness = true;
231
- matTransparent.useDynamicRefraction = true;
232
- matTransparent.depthWrite = true;
233
- matTransparent.refraction = 0.8;
234
- matTransparent.refractionIndex = 1;
235
- matTransparent.thickness = 0.02;
236
- matTransparent.thicknessMap = thicknessTex;
237
- matTransparent.opacityMap = opTex;
238
- matTransparent.opacityMapChannel = "r";
239
- matTransparent.opacity = 0.97;
240
- matTransparent.emissive = new pc.Color(1, 1, 1);
241
- matTransparent.emissiveMap = emitTex;
242
- matTransparent.emissiveIntensity = 0.1;
243
- matTransparent.update();
 
244
  }
245
  // Opaque material (main model)
246
- {matOpaque = new pc.StandardMaterial();
247
- matOpaque.blendType = pc.BLEND_NORMAL;
248
- matOpaque.diffuse = new pc.Color(0.7, 0.05, 0.05);
249
- matOpaque.specular = new pc.Color(0.01, 0.01, 0.01);
250
- matOpaque.specularityFactor = 1;
251
- matOpaque.gloss = 1;
252
- matOpaque.metalness = 0;
253
- matOpaque.opacityMap = opTex;
254
- matOpaque.opacityMapChannel = "r";
255
- matOpaque.opacity = 1;
256
- matOpaque.emissive = new pc.Color(0.372, 0.03, 0.003);
257
- matOpaque.emissiveMap = emitTex;
258
- matOpaque.emissiveIntensity = 2;
259
- matOpaque.update();
260
- }
 
261
  // Transparent material (tube)
262
- {tubeTransparent = new pc.StandardMaterial();
 
263
  tubeTransparent.diffuse = new pc.Color(1, 1, 1);
264
  tubeTransparent.blendType = pc.BLEND_NORMAL;
265
- tubeTransparent.opacity = 0.95;
266
  tubeTransparent.depthTest = false;
267
  tubeTransparent.depthWrite = false;
268
  tubeTransparent.useMetalness = true;
@@ -271,11 +309,13 @@ export async function initializeViewer(config, instanceId) {
271
  tubeTransparent.update();
272
  }
273
  // Opaque material (tube)
274
- {tubeOpaque = new pc.StandardMaterial();
 
275
  tubeOpaque.diffuse = new pc.Color(1, 1, 1);
276
- tubeOpaque.opacity = 0.9;
277
  tubeOpaque.update();
278
  }
 
279
  // ----- Assign materials to meshes -----
280
  traverse(modelEntity, node => {
281
  if (node.render && node.render.meshInstances) {
@@ -305,7 +345,7 @@ export async function initializeViewer(config, instanceId) {
305
  filtreEntity.setLocalScale(modelScale, modelScale, modelScale);
306
  filtreEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
307
 
308
- // ----- Camera + Orbit Controls -----
309
  cameraEntity = new pc.Entity('camera');
310
  cameraEntity.addComponent('camera', {
311
  clearColor: new pc.Color(0.8, 0.8, 0.8, 1),
@@ -335,60 +375,39 @@ export async function initializeViewer(config, instanceId) {
335
  cameraEntity.script.create('orbitCameraInputTouch');
336
  app.root.addChild(cameraEntity);
337
 
338
- // Remove Skybox layer from camera
339
- //const skyboxLayer = app.scene.layers.getLayerByName("Skybox");
340
- /*if (skyboxLayer) {
341
- const camLayers = cameraEntity.camera.layers.slice();
342
- const idx = camLayers.indexOf(skyboxLayer.id);
343
- if (idx !== -1) {
344
- camLayers.splice(idx, 1);
345
- cameraEntity.camera.layers = camLayers;
346
- }
347
- }*/
348
 
349
- const textureCamera = new pc.Entity('TextureCamera');
350
  textureCamera.addComponent('camera', {
351
- clearColor: new pc.Color(0.8, 0.8, 0.8, 1),
352
- layers: [excludedLayer.id, /*skyboxLayer.id*/],
353
  toneMapping: pc.TONEMAP_NEUTRAL,
354
-
355
- // set the priority of textureCamera to lower number than the priority of the main camera (which is at default 0)
356
- // to make it rendered first each frame
357
- priority: -1,
358
-
359
- // this camera renders into texture target
360
- renderTarget: renderTarget
361
  });
 
362
  cameraEntity.addChild(textureCamera);
 
 
363
 
364
- /*if (skyboxLayer) {
365
- const camLayers = textureCamera.camera.layers.slice();
366
- const idx = camLayers.indexOf(skyboxLayer.id);
367
- if (idx !== -1) {
368
- camLayers.splice(idx, 1);
369
- textureCamera.camera.layers = camLayers;
370
- }
371
- }*/
372
 
 
 
 
 
 
 
 
 
 
373
 
374
- // ----- Camera-attached background plane -----
375
- const bgPlane = new pc.Entity("Plane");
376
- bgPlane.addComponent("model", { type: "plane" });
377
- bgPlane.setLocalPosition(0, 0, -10);
378
- bgPlane.setLocalScale(11, 1, 5.5);
379
- bgPlane.setLocalEulerAngles(90, 0, 0);
380
- // Simple material for the banner
381
- const mat = new pc.StandardMaterial();
382
- mat.emissive = new pc.Color(1, 1, 1);
383
- mat.emissiveMap = texture;
384
- mat.emissiveIntensity = 1;
385
- mat.useLighting = false;
386
- mat.cull = pc.CULLFACE_NONE;
387
- mat.update();
388
- bgPlane.model.material = mat;
389
- cameraEntity.addChild(bgPlane);
390
-
391
-
392
 
393
  // ----- Lighting -----
394
  const light = new pc.Entity("mainLight");
@@ -401,6 +420,23 @@ export async function initializeViewer(config, instanceId) {
401
  light.lookAt(0, 0, 0);
402
  app.root.addChild(light);
403
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
405
  app.once('update', () => resetViewerCamera());
406
 
 
23
  let pc;
24
  export let app = null;
25
  let cameraEntity = null;
26
+ let textureCamera = null;
27
  let modelEntity = null;
28
  let tubeEntity = null;
29
  let filtreEntity = null;
30
+ let bgPlane = null;
31
+ let bgMat = null;
32
  let viewerInitialized = false;
33
  let resizeObserver = null;
34
 
35
+ // Render target globals (pour resize)
36
+ let rtTexture = null;
37
+ let renderTarget = null;
38
+
39
  // Materials (for real-time switching)
40
  let matTransparent = null;
41
  let matOpaque = null;
 
141
  app.setCanvasFillMode(pc.FILLMODE_NONE);
142
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
143
 
144
+ // Canvas responsive resize (déclenchera app.on('resize'))
145
  resizeObserver = new ResizeObserver(entries => {
146
  entries.forEach(entry => {
147
  app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
 
152
  app.on('destroy', () => resizeObserver.disconnect());
153
 
154
  // ----- Load all images as Textures (async, safe for CORS) -----
155
+ // Note : envAtlas ci-dessous utilise une simple image comme placeholder.
156
  const hdrTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/hdr/ciel_nuageux_1k.png', app);
157
  const emitTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/emit_map_1k.png', app);
158
  const opTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/op_map_1k.png', app);
159
  const thicknessTex= await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/thickness_map_1k.png', app);
 
160
 
161
  // ----- GLB asset definition -----
162
  const assets = {
 
173
  app.scene.skyboxIntensity = 4;
174
  app.scene.skyboxMip = 0;
175
 
176
+ // ----- RT helpers -----
177
+ function createRT(w, h) {
178
+ if (rtTexture) {
179
+ rtTexture.destroy();
180
+ rtTexture = null;
181
+ }
182
+ const tex = new pc.Texture(app.graphicsDevice, {
183
+ width: w,
184
+ height: h,
185
  format: pc.PIXELFORMAT_SRGBA8,
186
+ mipmaps: false // pas de mips pour une RT dynamique plein écran
 
 
187
  });
188
+ tex.minFilter = pc.FILTER_LINEAR;
189
+ tex.magFilter = pc.FILTER_LINEAR;
190
+ tex.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
191
+ tex.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
192
+
193
+ const rt = new pc.RenderTarget({
194
  name: 'RT',
195
+ colorBuffer: tex,
196
  depth: true,
197
+ samples: 1 // évite un resolve MSAA pour la réutiliser comme texture
 
198
  });
199
+ return { tex, rt };
200
+ }
201
+
202
+ function copyCam(src, dst) {
203
+ dst.camera.projection = src.camera.projection; // (perspective par défaut)
204
+ dst.camera.fov = src.camera.fov;
205
+ dst.camera.nearClip = src.camera.nearClip;
206
+ dst.camera.farClip = src.camera.farClip;
207
+ dst.camera.rect.set(src.camera.rect.x, src.camera.rect.y, src.camera.rect.z, src.camera.rect.w);
208
+ }
209
+
210
+ function fitFullscreenPlane(camEntity, planeEntity, d = 0.5) {
211
+ const cam = camEntity.camera;
212
+ const aspect = app.graphicsDevice.width / app.graphicsDevice.height;
213
+ const fovRad = cam.fov * pc.math.DEG_TO_RAD;
214
+ const h = 2 * d * Math.tan(fovRad / 2);
215
+ const w = h * aspect;
216
+
217
+ // en espace caméra, face caméra
218
+ planeEntity.reparent(camEntity);
219
+ planeEntity.setLocalPosition(0, 0, -d);
220
+ planeEntity.setLocalEulerAngles(-90, 0, 0); // plane (normale +Y) -> face -Z
221
+ planeEntity.setLocalScale(w, h, 1);
222
+ }
223
+
224
+ // ----- Load GLBs -----
225
+ const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
226
+ loader.load(() => {
227
+ app.start();
228
+ if (progressDialog) progressDialog.style.display = 'none';
229
 
230
+ // --- Layers ---
231
+ const excludedLayer = new pc.Layer({ name: 'Excluded' });
232
  app.scene.layers.insert(excludedLayer, 1);
233
 
234
+ // get existing layers
235
+ const worldLayer = app.scene.layers.getLayerByName('World');
236
  const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
237
+ const uiLayer = app.scene.layers.getLayerByName('UI');
238
 
239
+ // Reorder depth layer for transmission (si utile)
240
  const depthLayer = app.scene.layers.getLayerById(pc.LAYERID_DEPTH);
241
  app.scene.layers.remove(depthLayer);
242
  app.scene.layers.insertOpaque(depthLayer, 2);
243
 
244
+ // --- Instantiate GLB entities ---
245
+ modelEntity = assets.model.resource.instantiateRenderEntity();
246
+ tubeEntity = assets.tube.resource.instantiateRenderEntity();
247
  filtreEntity = assets.filtre.resource.instantiateRenderEntity();
248
 
249
+ // mettre tube/filtre dans la couche Excluded
250
+ traverse(tubeEntity, (node) => { if (node.render) node.render.layers = [excludedLayer.id]; });
251
+ traverse(filtreEntity, (node) => { if (node.render) node.render.layers = [excludedLayer.id]; });
 
 
 
252
 
253
  app.root.addChild(modelEntity);
254
  app.root.addChild(tubeEntity);
255
  app.root.addChild(filtreEntity);
256
 
 
257
  // ----- Materials Setup -----
258
  // Transparent material (main model)
259
+ {
260
+ matTransparent = new pc.StandardMaterial();
261
+ matTransparent.blendType = pc.BLEND_NORMAL;
262
+ matTransparent.diffuse = new pc.Color(1, 1, 1);
263
+ matTransparent.specular = new pc.Color(0.01, 0.01, 0.01);
264
+ matTransparent.gloss = 1;
265
+ matTransparent.metalness = 0;
266
+ matTransparent.useMetalness = true;
267
+ matTransparent.useDynamicRefraction = true;
268
+ matTransparent.depthWrite = true;
269
+ matTransparent.refraction = 0.8;
270
+ matTransparent.refractionIndex = 1;
271
+ matTransparent.thickness = 0.02;
272
+ matTransparent.thicknessMap = thicknessTex;
273
+ matTransparent.opacityMap = opTex;
274
+ matTransparent.opacityMapChannel = "r";
275
+ matTransparent.opacity = 0.97;
276
+ matTransparent.emissive = new pc.Color(1, 1, 1);
277
+ matTransparent.emissiveMap = emitTex;
278
+ matTransparent.emissiveIntensity = 0.1;
279
+ matTransparent.update();
280
  }
281
  // Opaque material (main model)
282
+ {
283
+ matOpaque = new pc.StandardMaterial();
284
+ matOpaque.blendType = pc.BLEND_NORMAL;
285
+ matOpaque.diffuse = new pc.Color(0.7, 0.05, 0.05);
286
+ matOpaque.specular = new pc.Color(0.01, 0.01, 0.01);
287
+ matOpaque.specularityFactor = 1;
288
+ matOpaque.gloss = 1;
289
+ matOpaque.metalness = 0;
290
+ matOpaque.opacityMap = opTex;
291
+ matOpaque.opacityMapChannel = "r";
292
+ matOpaque.opacity = 1;
293
+ matOpaque.emissive = new pc.Color(0.372, 0.03, 0.003);
294
+ matOpaque.emissiveMap = emitTex;
295
+ matOpaque.emissiveIntensity = 2;
296
+ matOpaque.update();
297
+ }
298
  // Transparent material (tube)
299
+ {
300
+ tubeTransparent = new pc.StandardMaterial();
301
  tubeTransparent.diffuse = new pc.Color(1, 1, 1);
302
  tubeTransparent.blendType = pc.BLEND_NORMAL;
303
+ tubeTransparent.opacity = 0.95;
304
  tubeTransparent.depthTest = false;
305
  tubeTransparent.depthWrite = false;
306
  tubeTransparent.useMetalness = true;
 
309
  tubeTransparent.update();
310
  }
311
  // Opaque material (tube)
312
+ {
313
+ tubeOpaque = new pc.StandardMaterial();
314
  tubeOpaque.diffuse = new pc.Color(1, 1, 1);
315
+ tubeOpaque.opacity = 0.9;
316
  tubeOpaque.update();
317
  }
318
+
319
  // ----- Assign materials to meshes -----
320
  traverse(modelEntity, node => {
321
  if (node.render && node.render.meshInstances) {
 
345
  filtreEntity.setLocalScale(modelScale, modelScale, modelScale);
346
  filtreEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
347
 
348
+ // ----- Main camera + Orbit Controls -----
349
  cameraEntity = new pc.Entity('camera');
350
  cameraEntity.addComponent('camera', {
351
  clearColor: new pc.Color(0.8, 0.8, 0.8, 1),
 
375
  cameraEntity.script.create('orbitCameraInputTouch');
376
  app.root.addChild(cameraEntity);
377
 
378
+ // ----- Texture camera (rend Excluded vers RT) -----
379
+ ({ tex: rtTexture, rt: renderTarget } = createRT(app.graphicsDevice.width, app.graphicsDevice.height));
 
 
 
 
 
 
 
 
380
 
381
+ textureCamera = new pc.Entity('TextureCamera');
382
  textureCamera.addComponent('camera', {
383
+ clearColor: new pc.Color(0, 0, 0, 0),
384
+ layers: [excludedLayer.id /*, skyboxLayer.id*/],
385
  toneMapping: pc.TONEMAP_NEUTRAL,
386
+ priority: -1, // rendre avant la caméra principale
387
+ renderTarget: renderTarget // sortie vers la RT
 
 
 
 
 
388
  });
389
+ // on veut la même pose → enfant de la caméra principale
390
  cameraEntity.addChild(textureCamera);
391
+ // et la même projection
392
+ copyCam(cameraEntity, textureCamera);
393
 
394
+ // ----- Background plane plein écran (espace caméra) -----
395
+ bgPlane = new pc.Entity("Plane");
396
+ bgPlane.addComponent("model", { type: "plane" });
 
 
 
 
 
397
 
398
+ bgMat = new pc.StandardMaterial();
399
+ bgMat.useLighting = false;
400
+ bgMat.emissive = new pc.Color(1, 1, 1);
401
+ bgMat.emissiveMap = rtTexture;
402
+ // si l'image apparaît inversée verticalement, décommente ces 2 lignes :
403
+ // bgMat.emissiveMapTiling.set(1, -1);
404
+ // bgMat.emissiveMapOffset.set(0, 1);
405
+ bgMat.cull = pc.CULLFACE_NONE; // visible des deux côtés (sécurité)
406
+ bgMat.update();
407
 
408
+ bgPlane.model.material = bgMat;
409
+ // placement plein écran devant la caméra (et parentée à la caméra)
410
+ fitFullscreenPlane(cameraEntity, bgPlane, 0.5);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
 
412
  // ----- Lighting -----
413
  const light = new pc.Entity("mainLight");
 
420
  light.lookAt(0, 0, 0);
421
  app.root.addChild(light);
422
 
423
+ // ----- Resize handling -----
424
+ const onAppResize = () => {
425
+ // resize RT à la nouvelle résolution
426
+ ({ tex: rtTexture, rt: renderTarget } = createRT(app.graphicsDevice.width, app.graphicsDevice.height));
427
+ textureCamera.camera.renderTarget = renderTarget;
428
+
429
+ // rebrancher la nouvelle texture sur le matériau
430
+ bgMat.emissiveMap = rtTexture;
431
+ bgMat.update();
432
+
433
+ // s'assurer que la projection/cadre correspondent toujours
434
+ copyCam(cameraEntity, textureCamera);
435
+ // refaire le fit plein écran
436
+ fitFullscreenPlane(cameraEntity, bgPlane, 0.5);
437
+ };
438
+ app.on('resize', onAppResize);
439
+
440
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
441
  app.once('update', () => resetViewerCamera());
442