MikaFil commited on
Commit
5ca7928
·
verified ·
1 Parent(s): abad121

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +92 -217
viewer.js CHANGED
@@ -23,19 +23,12 @@ async function loadImageAsTexture(url, app) {
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;
@@ -56,73 +49,6 @@ function traverse(entity, callback) {
56
  }
57
  }
58
 
59
- // ----- Helpers: viewport size / copy cam / RT / fullscreen plane -----
60
- function camViewportSize(cam) {
61
- const gd = app.graphicsDevice;
62
- const r = cam.rect; // x, y, width, height in [0..1]
63
- return {
64
- w: Math.max(1, Math.round(gd.width * r.z)),
65
- h: Math.max(1, Math.round(gd.height * r.w))
66
- };
67
- }
68
-
69
- function copyCam(src, dst) {
70
- dst.camera.projection = src.camera.projection;
71
- dst.camera.horizontalFov = src.camera.horizontalFov;
72
- dst.camera.fov = src.camera.fov;
73
- dst.camera.nearClip = src.camera.nearClip;
74
- dst.camera.farClip = src.camera.farClip;
75
- dst.camera.rect.set(src.camera.rect.x, src.camera.rect.y, src.camera.rect.z, src.camera.rect.w);
76
- }
77
-
78
- function createRTFor(cam) {
79
- const { w, h } = camViewportSize(cam);
80
- if (rtTexture) {
81
- rtTexture.destroy();
82
- rtTexture = null;
83
- }
84
- const tex = new pc.Texture(app.graphicsDevice, {
85
- width: w,
86
- height: h,
87
- format: pc.PIXELFORMAT_SRGBA8,
88
- mipmaps: false
89
- });
90
- tex.minFilter = pc.FILTER_LINEAR;
91
- tex.magFilter = pc.FILTER_LINEAR;
92
- tex.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
93
- tex.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
94
-
95
- const rt = new pc.RenderTarget({
96
- name: 'RT',
97
- colorBuffer: tex,
98
- depth: true,
99
- samples: 1
100
- });
101
- return { tex, rt };
102
- }
103
-
104
- function fitFullscreenPlane(camEntity, planeEntity, d = 0.5) {
105
- const cam = camEntity.camera;
106
- const { w: vpW, h: vpH } = camViewportSize(cam);
107
- const aspect = vpW / vpH;
108
-
109
- // fov en radians (vertical si horizontalFov === false)
110
- let vFovRad = cam.fov * pc.math.DEG_TO_RAD;
111
- if (cam.horizontalFov) {
112
- const hFovRad = vFovRad;
113
- vFovRad = 2 * Math.atan(Math.tan(hFovRad / 2) / aspect);
114
- }
115
-
116
- const h = 2 * d * Math.tan(vFovRad / 2);
117
- const w = h * aspect;
118
-
119
- // plein écran en espace caméra
120
- planeEntity.reparent(camEntity);
121
- planeEntity.setLocalPosition(0, 0, -d);
122
- planeEntity.setLocalEulerAngles(-90, 0, 0); // plane (normale +Y) -> face -Z
123
- planeEntity.setLocalScale(w, h, 1);
124
- }
125
-
126
  // ----- Main Viewer Initialization -----
127
  export async function initializeViewer(config, instanceId) {
128
  if (viewerInitialized) return; // Prevent double-init
@@ -208,7 +134,7 @@ export async function initializeViewer(config, instanceId) {
208
  app.setCanvasFillMode(pc.FILLMODE_NONE);
209
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
210
 
211
- // Canvas responsive resize (déclenchera app.on('resize'))
212
  resizeObserver = new ResizeObserver(entries => {
213
  entries.forEach(entry => {
214
  app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
@@ -219,11 +145,12 @@ export async function initializeViewer(config, instanceId) {
219
  app.on('destroy', () => resizeObserver.disconnect());
220
 
221
  // ----- Load all images as Textures (async, safe for CORS) -----
222
- // (envAtlas ci-dessous utilise une simple image placeholder)
223
- const hdrTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/hdr/ciel_nuageux_1k.png', app);
224
- const emitTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/emit_map_1k.png', app);
225
- const opTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/op_map_1k.png', app);
226
- const thicknessTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/thickness_map_1k.png', app);
 
227
 
228
  // ----- GLB asset definition -----
229
  const assets = {
@@ -246,97 +173,76 @@ export async function initializeViewer(config, instanceId) {
246
  app.start();
247
  if (progressDialog) progressDialog.style.display = 'none';
248
 
249
- // --- Layers ---
250
- const backgroundLayer = new pc.Layer({ name: 'Background' });
251
- app.scene.layers.insert(backgroundLayer, 0); // tout au début
252
-
253
- const excludedLayer = new pc.Layer({ name: 'Excluded' });
254
- app.scene.layers.insert(excludedLayer, 1);
255
-
256
- // get existing layers
257
- const worldLayer = app.scene.layers.getLayerByName('World');
258
- const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
259
- const uiLayer = app.scene.layers.getLayerByName('UI');
260
-
261
- // Reorder depth layer for transmission (si utile)
262
  const depthLayer = app.scene.layers.getLayerById(pc.LAYERID_DEPTH);
263
  app.scene.layers.remove(depthLayer);
264
  app.scene.layers.insertOpaque(depthLayer, 2);
265
 
266
- // --- Instantiate GLB entities ---
267
- modelEntity = assets.model.resource.instantiateRenderEntity();
268
- tubeEntity = assets.tube.resource.instantiateRenderEntity();
269
  filtreEntity = assets.filtre.resource.instantiateRenderEntity();
270
 
271
- // mettre tube/filtre dans la couche Excluded
272
- traverse(tubeEntity, (node) => { if (node.render) node.render.layers = [excludedLayer.id]; });
273
- traverse(filtreEntity, (node) => { if (node.render) node.render.layers = [excludedLayer.id]; });
274
-
275
  app.root.addChild(modelEntity);
276
  app.root.addChild(tubeEntity);
277
  app.root.addChild(filtreEntity);
278
 
279
  // ----- Materials Setup -----
280
  // Transparent material (main model)
281
- {
282
- matTransparent = new pc.StandardMaterial();
283
- matTransparent.blendType = pc.BLEND_NORMAL;
284
- matTransparent.diffuse = new pc.Color(1, 1, 1);
285
- matTransparent.specular = new pc.Color(0.01, 0.01, 0.01);
286
- matTransparent.gloss = 1;
287
- matTransparent.metalness = 0;
288
- matTransparent.useMetalness = true;
289
- matTransparent.useDynamicRefraction = true;
290
- matTransparent.depthWrite = true;
291
- matTransparent.refraction = 0.8;
292
- matTransparent.refractionIndex = 1;
293
- matTransparent.thickness = 0.02;
294
- matTransparent.thicknessMap = thicknessTex;
295
- matTransparent.opacityMap = opTex;
296
- matTransparent.opacityMapChannel = "r";
297
- matTransparent.opacity = 0.97;
298
- matTransparent.emissive = new pc.Color(1, 1, 1);
299
- matTransparent.emissiveMap = emitTex;
300
- matTransparent.emissiveIntensity = 0.1;
301
- matTransparent.update();
302
- }
303
  // Opaque material (main model)
304
- {
305
- matOpaque = new pc.StandardMaterial();
306
- matOpaque.blendType = pc.BLEND_NORMAL;
307
- matOpaque.diffuse = new pc.Color(0.7, 0.05, 0.05);
308
- matOpaque.specular = new pc.Color(0.01, 0.01, 0.01);
309
- matOpaque.specularityFactor = 1;
310
- matOpaque.gloss = 1;
311
- matOpaque.metalness = 0;
312
- matOpaque.opacityMap = opTex;
313
- matOpaque.opacityMapChannel = "r";
314
- matOpaque.opacity = 1;
315
- matOpaque.emissive = new pc.Color(0.372, 0.03, 0.003);
316
- matOpaque.emissiveIntensity = 2;
317
- matOpaque.emissiveMap = emitTex;
318
- matOpaque.update();
319
- }
320
  // Transparent material (tube)
321
- {
322
- tubeTransparent = new pc.StandardMaterial();
323
- tubeTransparent.diffuse = new pc.Color(1, 1, 1);
324
- tubeTransparent.blendType = pc.BLEND_NORMAL;
325
- tubeTransparent.opacity = 0.95;
326
- tubeTransparent.depthTest = false;
327
- tubeTransparent.depthWrite = false;
328
- tubeTransparent.useMetalness = true;
329
- tubeTransparent.useDynamicRefraction = true;
330
- tubeTransparent.thickness = 4;
331
- tubeTransparent.update();
332
- }
333
  // Opaque material (tube)
334
- {
335
- tubeOpaque = new pc.StandardMaterial();
336
- tubeOpaque.diffuse = new pc.Color(1, 1, 1);
337
- tubeOpaque.opacity = 0.9;
338
- tubeOpaque.update();
339
- }
340
 
341
  // ----- Assign materials to meshes -----
342
  traverse(modelEntity, node => {
@@ -367,17 +273,12 @@ export async function initializeViewer(config, instanceId) {
367
  filtreEntity.setLocalScale(modelScale, modelScale, modelScale);
368
  filtreEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
369
 
370
- // ----- Main camera + Orbit Controls -----
371
  cameraEntity = new pc.Entity('camera');
372
  cameraEntity.addComponent('camera', {
373
  clearColor: new pc.Color(0.8, 0.8, 0.8, 1),
374
- // Important : Background d'abord, puis World, puis UI et Depth
375
- layers: [backgroundLayer.id, worldLayer.id, /*skyboxLayer.id,*/ uiLayer.id, depthLayer.id],
376
  toneMapping: pc.TONEMAP_NEUTRAL
377
  });
378
- // Option : forcer FOV vertical partout
379
- cameraEntity.camera.horizontalFov = false;
380
-
381
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
382
  cameraEntity.lookAt(modelEntity.getPosition());
383
  cameraEntity.addComponent('script');
@@ -401,45 +302,35 @@ export async function initializeViewer(config, instanceId) {
401
  cameraEntity.script.create('orbitCameraInputTouch');
402
  app.root.addChild(cameraEntity);
403
 
404
- // ----- Texture camera (rend Excluded vers RT) -----
405
- ({ tex: rtTexture, rt: renderTarget } = createRTFor(cameraEntity.camera));
406
-
407
- textureCamera = new pc.Entity('TextureCamera');
408
- textureCamera.addComponent('camera', {
409
- clearColor: new pc.Color(0, 0, 0, 0),
410
- layers: [excludedLayer.id], // PAS Background/World
411
- toneMapping: pc.TONEMAP_NEUTRAL,
412
- priority: -1, // rendu en premier
413
- renderTarget: renderTarget
414
- });
415
- cameraEntity.addChild(textureCamera);
416
- textureCamera.camera.horizontalFov = cameraEntity.camera.horizontalFov;
417
- copyCam(cameraEntity, textureCamera);
418
-
419
- // ----- Background plane plein écran (espace caméra) -----
420
- bgPlane = new pc.Entity("Plane");
421
  bgPlane.addComponent("model", { type: "plane" });
422
-
423
- bgMat = new pc.StandardMaterial();
424
- bgMat.useLighting = false;
425
- bgMat.emissive = new pc.Color(1, 1, 1);
426
- bgMat.emissiveMap = rtTexture;
427
- // si l’image est inversée verticalement, décommente :
428
- // bgMat.emissiveMapTiling.set(1, -1);
429
- // bgMat.emissiveMapOffset.set(0, 1);
430
-
431
- // fond non-occlusif (toujours derrière le World)
432
- bgMat.depthTest = false;
433
- bgMat.depthWrite = false;
434
- bgMat.cull = pc.CULLFACE_NONE;
435
- bgMat.update();
436
-
437
- bgPlane.model.material = bgMat;
438
- // plane rendu dans la layer Background (rendu AVANT World)
439
- bgPlane.model.layers = [backgroundLayer.id];
440
- // placement plein écran devant la caméra (mais dans Background)
441
- fitFullscreenPlane(cameraEntity, bgPlane, 0.5);
442
-
443
  // ----- Lighting -----
444
  const light = new pc.Entity("mainLight");
445
  light.addComponent('light', {
@@ -451,22 +342,6 @@ export async function initializeViewer(config, instanceId) {
451
  light.lookAt(0, 0, 0);
452
  app.root.addChild(light);
453
 
454
- // ----- Resize / viewport / FOV change handling -----
455
- const onAppResize = () => {
456
- ({ tex: rtTexture, rt: renderTarget } = createRTFor(cameraEntity.camera));
457
- textureCamera.camera.renderTarget = renderTarget;
458
-
459
- // rebrancher la nouvelle texture sur le matériau
460
- bgMat.emissiveMap = rtTexture;
461
- bgMat.update();
462
-
463
- // même projection / viewport
464
- copyCam(cameraEntity, textureCamera);
465
- // refaire le fit plein écran (FOV/aspect)
466
- fitFullscreenPlane(cameraEntity, bgPlane, 0.5);
467
- };
468
- app.on('resize', onAppResize);
469
-
470
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
471
  app.once('update', () => resetViewerCamera());
472
 
@@ -562,7 +437,7 @@ export function changeColor(dr, dg, db, er, eg, eb, ei, op, boolTrans) {
562
  traverse(tubeEntity, node => {
563
  if (node.render && node.render.meshInstances) {
564
  for (let mi of node.render.meshInstances) {
565
- mi.material = matTransparent;
566
  }
567
  }
568
  });
 
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;
 
49
  }
50
  }
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  // ----- Main Viewer Initialization -----
53
  export async function initializeViewer(config, instanceId) {
54
  if (viewerInitialized) return; // Prevent double-init
 
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
  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 = {
 
173
  app.start();
174
  if (progressDialog) progressDialog.style.display = 'none';
175
 
176
+ // Reorder depth layer for transmission
 
 
 
 
 
 
 
 
 
 
 
 
177
  const depthLayer = app.scene.layers.getLayerById(pc.LAYERID_DEPTH);
178
  app.scene.layers.remove(depthLayer);
179
  app.scene.layers.insertOpaque(depthLayer, 2);
180
 
181
+ // Instantiate GLB entities
182
+ modelEntity = assets.model.resource.instantiateRenderEntity();
183
+ tubeEntity = assets.tube.resource.instantiateRenderEntity();
184
  filtreEntity = assets.filtre.resource.instantiateRenderEntity();
185
 
 
 
 
 
186
  app.root.addChild(modelEntity);
187
  app.root.addChild(tubeEntity);
188
  app.root.addChild(filtreEntity);
189
 
190
  // ----- Materials Setup -----
191
  // Transparent material (main model)
192
+ matTransparent = new pc.StandardMaterial();
193
+ matTransparent.blendType = pc.BLEND_NORMAL;
194
+ matTransparent.diffuse = new pc.Color(1, 1, 1);
195
+ matTransparent.specular = new pc.Color(0.01, 0.01, 0.01);
196
+ matTransparent.gloss = 1;
197
+ matTransparent.metalness = 0;
198
+ matTransparent.useMetalness = true;
199
+ matTransparent.useDynamicRefraction = true;
200
+ matTransparent.depthWrite = true;
201
+ matTransparent.refraction = 0.8;
202
+ matTransparent.refractionIndex = 1;
203
+ matTransparent.thickness = 0.02;
204
+ matTransparent.thicknessMap = thicknessTex;
205
+ matTransparent.opacityMap = opTex;
206
+ matTransparent.opacityMapChannel = "r";
207
+ matTransparent.opacity = 0.97;
208
+ matTransparent.emissive = new pc.Color(1, 1, 1);
209
+ matTransparent.emissiveMap = emitTex;
210
+ matTransparent.emissiveIntensity = 0.1;
211
+ matTransparent.update();
212
+
 
213
  // Opaque material (main model)
214
+ matOpaque = new pc.StandardMaterial();
215
+ matOpaque.blendType = pc.BLEND_NORMAL;
216
+ matOpaque.diffuse = new pc.Color(0.7, 0.05, 0.05);
217
+ matOpaque.specular = new pc.Color(0.01, 0.01, 0.01);
218
+ matOpaque.specularityFactor = 1;
219
+ matOpaque.gloss = 1;
220
+ matOpaque.metalness = 0;
221
+ matOpaque.opacityMap = opTex;
222
+ matOpaque.opacityMapChannel = "r";
223
+ matOpaque.opacity = 1;
224
+ matOpaque.emissive = new pc.Color(0.372, 0.03, 0.003);
225
+ matOpaque.emissiveMap = emitTex;
226
+ matOpaque.emissiveIntensity = 2;
227
+ matOpaque.update();
228
+
 
229
  // Transparent material (tube)
230
+ tubeTransparent = new pc.StandardMaterial();
231
+ tubeTransparent.diffuse = new pc.Color(1, 1, 1);
232
+ tubeTransparent.blendType = pc.BLEND_NORMAL;
233
+ tubeTransparent.opacity = 0.05;
234
+ tubeTransparent.depthTest = false;
235
+ tubeTransparent.depthWrite = false;
236
+ tubeTransparent.useMetalness = true;
237
+ tubeTransparent.useDynamicRefraction = true;
238
+ tubeTransparent.thickness = 4;
239
+ tubeTransparent.update();
240
+
 
241
  // Opaque material (tube)
242
+ tubeOpaque = new pc.StandardMaterial();
243
+ tubeOpaque.diffuse = new pc.Color(1, 1, 1);
244
+ tubeOpaque.opacity = 0.9;
245
+ tubeOpaque.update();
 
 
246
 
247
  // ----- Assign materials to meshes -----
248
  traverse(modelEntity, node => {
 
273
  filtreEntity.setLocalScale(modelScale, modelScale, modelScale);
274
  filtreEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
275
 
276
+ // ----- Camera + Orbit Controls -----
277
  cameraEntity = new pc.Entity('camera');
278
  cameraEntity.addComponent('camera', {
279
  clearColor: new pc.Color(0.8, 0.8, 0.8, 1),
 
 
280
  toneMapping: pc.TONEMAP_NEUTRAL
281
  });
 
 
 
282
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
283
  cameraEntity.lookAt(modelEntity.getPosition());
284
  cameraEntity.addComponent('script');
 
302
  cameraEntity.script.create('orbitCameraInputTouch');
303
  app.root.addChild(cameraEntity);
304
 
305
+ // Remove Skybox layer from camera
306
+ const skyboxLayer = app.scene.layers.getLayerByName("Skybox");
307
+ if (skyboxLayer) {
308
+ const camLayers = cameraEntity.camera.layers.slice();
309
+ const idx = camLayers.indexOf(skyboxLayer.id);
310
+ if (idx !== -1) {
311
+ camLayers.splice(idx, 1);
312
+ cameraEntity.camera.layers = camLayers;
313
+ }
314
+ }
315
+ /*
316
+ // ----- Camera-attached background plane -----
317
+ const bgPlane = new pc.Entity("Plane");
 
 
 
 
318
  bgPlane.addComponent("model", { type: "plane" });
319
+ bgPlane.setLocalPosition(0, 0, -10);
320
+ bgPlane.setLocalScale(11, 1, 5.5);
321
+ bgPlane.setLocalEulerAngles(90, 0, 0);
322
+ // Simple material for the banner
323
+ const mat = new pc.StandardMaterial();
324
+ mat.diffuse = new pc.Color(1, 1, 1);
325
+ mat.diffuseMap = bgTex;
326
+ mat.emissive = new pc.Color(1, 1, 1);
327
+ mat.emissiveMap = bgTex;
328
+ mat.emissiveIntensity = 1;
329
+ mat.useLighting = false;
330
+ mat.update();
331
+ bgPlane.model.material = mat;
332
+ cameraEntity.addChild(bgPlane);
333
+ */
 
 
 
 
 
 
334
  // ----- Lighting -----
335
  const light = new pc.Entity("mainLight");
336
  light.addComponent('light', {
 
342
  light.lookAt(0, 0, 0);
343
  app.root.addChild(light);
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
346
  app.once('update', () => resetViewerCamera());
347
 
 
437
  traverse(tubeEntity, node => {
438
  if (node.render && node.render.meshInstances) {
439
  for (let mi of node.render.meshInstances) {
440
+ mi.material = tubeTransparent;
441
  }
442
  }
443
  });