MikaFil commited on
Commit
7f9d62c
·
verified ·
1 Parent(s): 4af1760

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +218 -207
viewer.js CHANGED
@@ -10,17 +10,19 @@ let bouchonEntity = null;
10
  let viewerInitialized = false;
11
  let resizeObserver = null;
12
 
 
13
  let matTransparent = null;
14
  let matOpaque = null;
15
  let tubeTransparent = null;
16
  let tubeOpaque = null;
17
 
 
18
  let chosenCameraX, chosenCameraY, chosenCameraZ;
19
  let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, minY;
20
  let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
21
  let glbUrl, glbUrl2, glbUrl3, aoUrl, opacityUrl, thicknessUrl;
22
 
23
- // Helper for recursive traversal
24
  function traverse(entity, callback) {
25
  callback(entity);
26
  if (entity.children) {
@@ -34,9 +36,6 @@ export async function initializeViewer(config, instanceId) {
34
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
35
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
36
 
37
- alert("Step 1: isIOS = " + isIOS + ", isMobile = " + isMobile);
38
-
39
- // Parse config (hardcode for demo if needed)
40
  glbUrl = config.glb_url;
41
  glbUrl2 = config.glb_url_2;
42
  glbUrl3 = config.glb_url_3;
@@ -62,20 +61,20 @@ export async function initializeViewer(config, instanceId) {
62
 
63
  const cameraX = 0;
64
  const cameraY = 0;
65
- const cameraZ = 3;
66
  const cameraXPhone = 0;
67
  const cameraYPhone = 0;
68
- const cameraZPhone = 3;
69
 
70
  chosenCameraX = isMobile ? cameraXPhone : cameraX;
71
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
72
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
73
 
74
- alert("Step 2: Config parsed.");
75
 
76
- // Canvas creation
77
  const canvasId = 'canvas-' + instanceId;
78
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
 
79
  const viewerContainer = document.getElementById('viewer-container-' + instanceId);
80
 
81
  let oldCanvas = document.getElementById(canvasId);
@@ -87,14 +86,13 @@ export async function initializeViewer(config, instanceId) {
87
  canvas.style.width = "100%";
88
  canvas.style.height = "100%";
89
  canvas.setAttribute('tabindex', '0');
 
90
  if (progressDialog) {
91
  viewerContainer.insertBefore(canvas, progressDialog);
92
  } else {
93
  viewerContainer.appendChild(canvas);
94
  }
95
- alert("Step 3: Canvas created and inserted.");
96
 
97
- // Listeners & touch-action
98
  canvas.style.touchAction = "none";
99
  canvas.style.webkitTouchCallout = "none";
100
  canvas.addEventListener('gesturestart', e => e.preventDefault());
@@ -102,8 +100,10 @@ export async function initializeViewer(config, instanceId) {
102
  canvas.addEventListener('gestureend', e => e.preventDefault());
103
  canvas.addEventListener('dblclick', e => e.preventDefault());
104
  canvas.addEventListener('touchstart', e => { if (e.touches.length > 1) e.preventDefault(); }, { passive: false });
105
- canvas.addEventListener('wheel', (e) => { e.preventDefault(); }, { passive: false });
106
- alert("Step 4: Canvas listeners added.");
 
 
107
 
108
  if (progressDialog) progressDialog.style.display = 'block';
109
 
@@ -111,67 +111,60 @@ export async function initializeViewer(config, instanceId) {
111
  pc = await import("https://esm.run/playcanvas");
112
  window.pc = pc;
113
  }
114
- alert("Step 5: PlayCanvas imported.");
115
-
116
- // --- iOS: Prefer WebGL1 ---
117
- let deviceTypes = ["webgl2", "webgl1"];
118
- if (isIOS) deviceTypes = ["webgl1", "webgl2"]; // Prefer WebGL1 first
119
- // Use AppBase + .init() for compatibility
120
- const gfxOptions = {
121
- deviceTypes: deviceTypes,
122
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
123
- twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js"
124
- };
125
- const device = await pc.createGraphicsDevice(canvas, gfxOptions);
126
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
127
- alert("Step 6: Graphics device created.");
128
 
129
- // AppOptions with minimal systems (NO GSplat on iOS)
130
- const appOptions = new pc.AppOptions();
131
- appOptions.graphicsDevice = device;
132
- appOptions.mouse = new pc.Mouse(document.body); // Use body like example
133
- appOptions.touch = new pc.TouchDevice(document.body);
134
- appOptions.keyboard = new pc.Keyboard(document.body);
135
-
136
- appOptions.componentSystems = [
137
  pc.RenderComponentSystem,
138
  pc.CameraComponentSystem,
139
  pc.LightComponentSystem,
140
- pc.ScriptComponentSystem
141
- // (GSplat, Collision, RigidBody omitted for iOS safety)
 
 
142
  ];
143
- appOptions.resourceHandlers = [
144
  pc.TextureHandler,
145
  pc.ContainerHandler,
146
- pc.ScriptHandler
147
- // (GSplatHandler omitted for iOS safety)
148
  ];
149
 
150
- app = new pc.AppBase(canvas);
151
- app.init(appOptions);
152
-
153
- // Fill window, auto-res
154
- app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
155
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
156
 
157
- // Resize listener
158
- const resize = () => app.resizeCanvas();
159
- window.addEventListener('resize', resize);
160
- app.on('destroy', () => {
161
- window.removeEventListener('resize', resize);
162
  });
163
- alert("Step 7: PlayCanvas app initialized.");
 
 
 
164
 
165
- // Assets
166
  const assets = {
167
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
168
- model: new pc.Asset('model', 'container', { url: glbUrl }),
169
- tube: new pc.Asset('tube', 'container', { url: glbUrl2 }),
170
- bouchon: new pc.Asset('bouchon', 'container', { url: glbUrl3 }),
171
- ao_map: new pc.Asset('ao_map', 'texture', { url: aoUrl }),
172
- op_map: new pc.Asset('op_map', 'texture', { url: opacityUrl }),
173
- bg_tex: new pc.Asset('bg_tex', 'texture', { url: "https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/banniere_earcare.png" }),
174
- thickness_map: new pc.Asset('thickness_map', 'texture', { url: thicknessUrl }),
175
  helipad: new pc.Asset(
176
  'helipad-env-atlas',
177
  'texture',
@@ -181,18 +174,17 @@ export async function initializeViewer(config, instanceId) {
181
  };
182
 
183
  for (const key in assets) app.assets.add(assets[key]);
184
- alert("Step 9: Assets registered.");
185
 
186
- // Load env HDR first
187
  assets.helipad.ready(() => {
188
  app.scene.envAtlas = assets.helipad.resource;
189
  app.scene.skyboxRotation = new pc.Quat().setFromEulerAngles(30, 20, 0);
190
  app.scene.skyboxIntensity = 4;
191
  app.scene.skyboxMip = 0;
192
- alert("Step 10: Environment HDR loaded.");
193
 
194
- // Asset loader
195
- const assetList = [
 
196
  assets.orbit,
197
  assets.model,
198
  assets.tube,
@@ -201,112 +193,119 @@ export async function initializeViewer(config, instanceId) {
201
  assets.op_map,
202
  assets.bg_tex,
203
  assets.thickness_map
204
- ];
205
- const loader = new pc.AssetListLoader(assetList, app.assets);
206
  loader.load(() => {
207
- alert("Step 11: All assets loaded, app started.");
208
  app.start();
 
209
 
210
- // Depth layer reorder (as in example)
211
  const depthLayer = app.scene.layers.getLayerById(pc.LAYERID_DEPTH);
212
  app.scene.layers.remove(depthLayer);
213
  app.scene.layers.insertOpaque(depthLayer, 2);
214
- alert("Step 12: Depth layer reordered.");
215
 
216
- // Instantiate models
217
  modelEntity = assets.model.resource.instantiateRenderEntity();
218
  tubeEntity = assets.tube.resource.instantiateRenderEntity();
219
  bouchonEntity = assets.bouchon.resource.instantiateRenderEntity();
220
 
221
  app.root.addChild(modelEntity);
222
  app.root.addChild(tubeEntity);
223
- // app.root.addChild(bouchonEntity);
224
- alert("Step 13: Entities instantiated and added.");
225
-
226
- // --- iOS: SKIP all custom material assignment ---
227
- if (!isIOS) {
228
- try {
229
- // The 2 materials for the main model:
230
- const ao = assets.ao_map.resource;
231
- const op = assets.op_map.resource;
232
- const thickness = assets.thickness_map.resource;
233
-
234
- matTransparent = new pc.StandardMaterial();
235
- matTransparent.blendType = pc.BLEND_NORMAL;
236
- matTransparent.diffuse = new pc.Color(0.7, 0.05, 0.05);
237
- matTransparent.specular = new pc.Color(0.01, 0.01, 0.01);
238
- matTransparent.shininess = 90;
239
- matTransparent.gloss = 1;
240
- matTransparent.metalness = 0.0;
241
- matTransparent.useMetalness = true;
242
- matTransparent.useDynamicRefraction = true;
243
- matTransparent.depthWrite = true;
244
- matTransparent.refraction= 0.8;
245
- matTransparent.refractionIndex = 1;
246
- matTransparent.thickness = 0.02;
247
- matTransparent.thicknessMap = thickness;
248
- matTransparent.opacityMap = op;
249
- matTransparent.opacityMapChannel = "r";
250
- matTransparent.opacity = 0.97;
251
- matTransparent.emissive = new pc.Color(0.372, 0.03, 0.003);
252
- matTransparent.emissiveMap = ao;
253
- matTransparent.emissiveIntensity = 0.1;
254
- matTransparent.update();
255
-
256
- matOpaque = new pc.StandardMaterial();
257
- matOpaque.blendType = pc.BLEND_NORMAL;
258
- matOpaque.diffuse = new pc.Color(0.7, 0.05, 0.05);
259
- matOpaque.specular = new pc.Color(0.01, 0.01, 0.01);
260
- matOpaque.shininess = 90;
261
- matOpaque.gloss = 1;
262
- matOpaque.metalness = 0.0;
263
- matOpaque.opacityMap = op;
264
- matOpaque.opacityMapChannel = "r";
265
- matOpaque.opacity = 1;
266
- matOpaque.emissive = new pc.Color(0.372, 0.03, 0.003);
267
- matOpaque.emissiveMap = ao;
268
- matOpaque.emissiveIntensity = 2;
269
- matOpaque.update();
270
-
271
- tubeTransparent = new pc.StandardMaterial();
272
- tubeTransparent.diffuse = new pc.Color(1, 1, 1);
273
- tubeTransparent.blendType = pc.BLEND_NORMAL;
274
- tubeTransparent.opacity = 0.1;
275
- tubeTransparent.depthTest = false;
276
- tubeTransparent.depthWrite = false;
277
- tubeTransparent.useMetalness = true;
278
- tubeTransparent.useDynamicRefraction = true;
279
- tubeTransparent.thickness = 4;
280
- tubeTransparent.update();
281
-
282
- tubeOpaque = new pc.StandardMaterial();
283
- tubeOpaque.diffuse = new pc.Color(1, 1, 1);
284
- tubeOpaque.opacity = 0.9;
285
- tubeOpaque.update();
286
-
287
- traverse(modelEntity, node => {
288
- if (node.render && node.render.meshInstances) {
289
- for (let mi of node.render.meshInstances) {
290
- mi.material = matOpaque;
291
- }
292
- }
293
- });
294
- traverse(tubeEntity, node => {
295
- if (node.render && node.render.meshInstances) {
296
- for (let mi of node.render.meshInstances) {
297
- mi.material = tubeOpaque;
298
- }
299
- }
300
- });
301
- alert("Step 14: Materials created and assigned.");
302
- } catch (err) {
303
- alert("Material assignment failed: " + err);
304
  }
305
- } else {
306
- alert("Step 14: Custom materials skipped for iOS. Using GLB built-ins.");
307
- }
 
 
 
 
 
 
 
 
 
 
 
308
 
309
- // Transforms
310
  modelEntity.setPosition(modelX, modelY, modelZ);
311
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
312
  modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
@@ -318,25 +317,20 @@ export async function initializeViewer(config, instanceId) {
318
  bouchonEntity.setPosition(modelX, modelY, modelZ);
319
  bouchonEntity.setLocalScale(modelScale, modelScale, modelScale);
320
  bouchonEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
321
- alert("Step 15: Transforms set.");
322
 
323
  // 4. Camera & orbit
 
324
  cameraEntity = new pc.Entity('camera');
325
  cameraEntity.addComponent('camera', {
326
- clearColor: new pc.Color(0, 1, 1, 1), // Turquoise for debug
327
  toneMapping: pc.TONEMAP_NEUTRAL
328
  });
329
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
330
  cameraEntity.lookAt(modelEntity.getPosition());
331
  cameraEntity.addComponent('script');
332
- // Only do requestSceneColorMap if NOT on iOS
333
- if (!isIOS) {
334
- try {
335
- cameraEntity.camera.requestSceneColorMap(true);
336
- } catch (err) {
337
- alert("requestSceneColorMap failed: " + err);
338
- }
339
- }
340
 
341
  cameraEntity.script.create('orbitCamera', {
342
  attributes: {
@@ -355,27 +349,29 @@ export async function initializeViewer(config, instanceId) {
355
  cameraEntity.script.create('orbitCameraInputMouse');
356
  cameraEntity.script.create('orbitCameraInputTouch');
357
  app.root.addChild(cameraEntity);
358
- alert("Step 16: Camera & orbit script added.");
359
 
360
- // 5. Light
361
- const light = new pc.Entity("mainLight");
362
- light.addComponent('light', {
363
- type: "directional",
364
- color: new pc.Color(1, 1, 1),
365
- intensity: 0.9,
366
- });
367
- light.setPosition(0, 1, -1);
368
- light.lookAt(0, 0, 0);
369
- app.root.addChild(light);
370
- alert("Step 17: Light added.");
371
 
372
- // Optional: Add debug/test cube (parent to camera for visibility)
373
  const bgTex = assets.bg_tex.resource;
374
  const bgPlane = new pc.Entity("Plane");
375
  bgPlane.addComponent("model", { type: "plane" });
376
- bgPlane.setLocalPosition(0, 0, -10);
 
 
377
  bgPlane.setLocalScale(11, 1, 5.5);
378
  bgPlane.setLocalEulerAngles(90, 0, 0);
 
 
379
  const mat = new pc.StandardMaterial();
380
  mat.diffuse = new pc.Color(1, 1, 1);
381
  mat.diffuseMap = bgTex;
@@ -385,13 +381,24 @@ export async function initializeViewer(config, instanceId) {
385
  mat.useLighting = false;
386
  mat.update();
387
  bgPlane.model.material = mat;
 
 
388
  cameraEntity.addChild(bgPlane);
389
- alert("Test cube added to scene!");
390
-
391
- app.once('update', () => {
392
- resetViewerCamera();
393
- alert("Step 18: Camera reset after update.");
 
 
394
  });
 
 
 
 
 
 
 
395
 
396
  // Tooltips supported if tooltips_url set
397
  try {
@@ -412,15 +419,14 @@ export async function initializeViewer(config, instanceId) {
412
  } catch (e) {
413
  // Tooltips optional, fail silently
414
  }
 
415
  viewerInitialized = true;
416
- alert("Step 20: Viewer successfully initialized!");
417
  });
418
  });
419
 
420
  app.assets.load(assets.helipad); // Start by loading the HDR
421
- }
422
 
423
- // --- Camera reset and color change (unchanged from yours, left for completeness) ---
424
 
425
  export function resetViewerCamera() {
426
  try {
@@ -443,7 +449,7 @@ export function resetViewerCamera() {
443
 
444
  orbitCam.pivotPoint = modelPos.clone();
445
  orbitCam._targetDistance = dist;
446
- orbitCam._distance = dist;
447
 
448
  const rot = tempEnt.getRotation();
449
  const fwd = new pc.Vec3();
@@ -456,10 +462,10 @@ export function resetViewerCamera() {
456
  rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
457
  const pitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
458
 
459
- orbitCam._targetYaw = yaw;
460
- orbitCam._yaw = yaw;
461
  orbitCam._targetPitch = pitch;
462
- orbitCam._pitch = pitch;
463
  if (orbitCam._updatePosition) orbitCam._updatePosition();
464
 
465
  tempEnt.destroy();
@@ -468,9 +474,14 @@ export function resetViewerCamera() {
468
  }
469
  }
470
 
471
- // Color function (no changes)
 
 
 
472
  export function changeColor(dr, dg, db, er, eg, eb, ei, op, boolTrans) {
473
- if (boolTrans) {
 
 
474
  if (!matTransparent) return;
475
 
476
  matTransparent.diffuse.set(dr, dg, db);
@@ -483,21 +494,21 @@ export function changeColor(dr, dg, db, er, eg, eb, ei, op, boolTrans) {
483
  tubeTransparent.update();
484
 
485
  traverse(modelEntity, node => {
486
- if (node.render && node.render.meshInstances) {
487
- for (let mi of node.render.meshInstances) {
488
- mi.material = matTransparent;
 
489
  }
490
- }
491
  });
492
 
493
  traverse(tubeEntity, node => {
494
- if (node.render && node.render.meshInstances) {
495
- for (let mi of node.render.meshInstances) {
496
- mi.material = tubeTransparent;
 
497
  }
498
- }
499
  });
500
- } else {
501
  if (!matOpaque) return;
502
 
503
  matOpaque.diffuse.set(dr, dg, db);
@@ -506,19 +517,19 @@ export function changeColor(dr, dg, db, er, eg, eb, ei, op, boolTrans) {
506
  matOpaque.update();
507
 
508
  traverse(modelEntity, node => {
509
- if (node.render && node.render.meshInstances) {
510
- for (let mi of node.render.meshInstances) {
511
- mi.material = matOpaque;
 
512
  }
513
- }
514
  });
515
 
516
  traverse(tubeEntity, node => {
517
- if (node.render && node.render.meshInstances) {
518
- for (let mi of node.render.meshInstances) {
519
- mi.material = tubeOpaque;
 
520
  }
521
- }
522
  });
523
  }
524
  }
 
10
  let viewerInitialized = false;
11
  let resizeObserver = null;
12
 
13
+ // These will hold references to the material(s) you want to update.
14
  let matTransparent = null;
15
  let matOpaque = null;
16
  let tubeTransparent = null;
17
  let tubeOpaque = null;
18
 
19
+
20
  let chosenCameraX, chosenCameraY, chosenCameraZ;
21
  let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, minY;
22
  let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
23
  let glbUrl, glbUrl2, glbUrl3, aoUrl, opacityUrl, thicknessUrl;
24
 
25
+ // RECURSIVE ENTITY TRAVERSAL (needed for material assignment)
26
  function traverse(entity, callback) {
27
  callback(entity);
28
  if (entity.children) {
 
36
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
37
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
38
 
 
 
 
39
  glbUrl = config.glb_url;
40
  glbUrl2 = config.glb_url_2;
41
  glbUrl3 = config.glb_url_3;
 
61
 
62
  const cameraX = 0;
63
  const cameraY = 0;
64
+ const cameraZ = 1;
65
  const cameraXPhone = 0;
66
  const cameraYPhone = 0;
67
+ const cameraZPhone = 1;
68
 
69
  chosenCameraX = isMobile ? cameraXPhone : cameraX;
70
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
71
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
72
 
73
+
74
 
 
75
  const canvasId = 'canvas-' + instanceId;
76
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
77
+ const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
78
  const viewerContainer = document.getElementById('viewer-container-' + instanceId);
79
 
80
  let oldCanvas = document.getElementById(canvasId);
 
86
  canvas.style.width = "100%";
87
  canvas.style.height = "100%";
88
  canvas.setAttribute('tabindex', '0');
89
+ // Safer insert:
90
  if (progressDialog) {
91
  viewerContainer.insertBefore(canvas, progressDialog);
92
  } else {
93
  viewerContainer.appendChild(canvas);
94
  }
 
95
 
 
96
  canvas.style.touchAction = "none";
97
  canvas.style.webkitTouchCallout = "none";
98
  canvas.addEventListener('gesturestart', e => e.preventDefault());
 
100
  canvas.addEventListener('gestureend', e => e.preventDefault());
101
  canvas.addEventListener('dblclick', e => e.preventDefault());
102
  canvas.addEventListener('touchstart', e => { if (e.touches.length > 1) e.preventDefault(); }, { passive: false });
103
+
104
+ canvas.addEventListener('wheel', (e) => {
105
+ e.preventDefault();
106
+ }, { passive: false });
107
 
108
  if (progressDialog) progressDialog.style.display = 'block';
109
 
 
111
  pc = await import("https://esm.run/playcanvas");
112
  window.pc = pc;
113
  }
114
+
115
+ // Create app first
116
+ const device = await pc.createGraphicsDevice(canvas, {
117
+ deviceTypes: ["webgl2"],
 
 
 
 
118
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
119
+ twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
120
+ antialias: false
121
+ });
122
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
 
123
 
124
+ const opts = new pc.AppOptions();
125
+ opts.graphicsDevice = device;
126
+ opts.mouse = new pc.Mouse(canvas);
127
+ opts.touch = new pc.TouchDevice(canvas);
128
+ opts.componentSystems = [
 
 
 
129
  pc.RenderComponentSystem,
130
  pc.CameraComponentSystem,
131
  pc.LightComponentSystem,
132
+ pc.ScriptComponentSystem,
133
+ pc.GSplatComponentSystem,
134
+ pc.CollisionComponentSystem,
135
+ pc.RigidbodyComponentSystem
136
  ];
137
+ opts.resourceHandlers = [
138
  pc.TextureHandler,
139
  pc.ContainerHandler,
140
+ pc.ScriptHandler,
141
+ pc.GSplatHandler
142
  ];
143
 
144
+ app = new pc.Application(canvas, opts);
145
+ app.setCanvasFillMode(pc.FILLMODE_NONE);
 
 
 
146
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
147
 
148
+ resizeObserver = new ResizeObserver(entries => {
149
+ entries.forEach(entry => {
150
+ app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
151
+ });
 
152
  });
153
+ resizeObserver.observe(viewerContainer);
154
+
155
+ window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
156
+ app.on('destroy', () => resizeObserver.disconnect());
157
 
158
+ // --- Assets ---
159
  const assets = {
160
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
161
+ model: new pc.Asset('glb', 'container', { url: glbUrl }),
162
+ tube: new pc.Asset('glb', 'container', { url: glbUrl2 }),
163
+ bouchon: new pc.Asset('glb', 'container', { url: glbUrl3 }),
164
+ ao_map: new pc.Asset('color', 'texture', { url: aoUrl }),
165
+ op_map: new pc.Asset('color', 'texture', { url: opacityUrl }),
166
+ bg_tex: new pc.Asset('color', 'texture', {url: "https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/banniere_earcare.png"}),
167
+ thickness_map: new pc.Asset('color', 'texture', { url: thicknessUrl }),
168
  helipad: new pc.Asset(
169
  'helipad-env-atlas',
170
  'texture',
 
174
  };
175
 
176
  for (const key in assets) app.assets.add(assets[key]);
 
177
 
178
+ // Load HDR *before* loading any models
179
  assets.helipad.ready(() => {
180
  app.scene.envAtlas = assets.helipad.resource;
181
  app.scene.skyboxRotation = new pc.Quat().setFromEulerAngles(30, 20, 0);
182
  app.scene.skyboxIntensity = 4;
183
  app.scene.skyboxMip = 0;
 
184
 
185
+
186
+ // Now load all GLBs and other assets
187
+ const loader = new pc.AssetListLoader([
188
  assets.orbit,
189
  assets.model,
190
  assets.tube,
 
193
  assets.op_map,
194
  assets.bg_tex,
195
  assets.thickness_map
196
+ ], app.assets);
197
+
198
  loader.load(() => {
 
199
  app.start();
200
+ if (progressDialog) progressDialog.style.display = 'none';
201
 
202
+ // Reorder depth layer for transmission
203
  const depthLayer = app.scene.layers.getLayerById(pc.LAYERID_DEPTH);
204
  app.scene.layers.remove(depthLayer);
205
  app.scene.layers.insertOpaque(depthLayer, 2);
 
206
 
207
+ // 1. Models
208
  modelEntity = assets.model.resource.instantiateRenderEntity();
209
  tubeEntity = assets.tube.resource.instantiateRenderEntity();
210
  bouchonEntity = assets.bouchon.resource.instantiateRenderEntity();
211
 
212
  app.root.addChild(modelEntity);
213
  app.root.addChild(tubeEntity);
214
+ //app.root.addChild(bouchonEntity);
215
+
216
+ // 2. Materials for main model & tube
217
+
218
+
219
+ //the 2 materials for the main model :
220
+ const ao = assets.ao_map.resource;
221
+ const op = assets.op_map.resource;
222
+ const thickness = assets.thickness_map.resource;
223
+
224
+ //mat transparent
225
+ matTransparent = new pc.StandardMaterial();
226
+ matTransparent.blendType = pc.BLEND_NORMAL;
227
+ matTransparent.diffuse = new pc.Color(0.7, 0.05, 0.05);
228
+ matTransparent.specular = new pc.Color(0.01, 0.01, 0.01);
229
+ matTransparent.shininess = 90;
230
+ matTransparent.gloss = 1;
231
+ matTransparent.metalness = 0.0;
232
+ matTransparent.useMetalness = true;
233
+ matTransparent.useDynamicRefraction = true;
234
+ matTransparent.depthWrite = true;
235
+ matTransparent.refraction= 0.8;
236
+ matTransparent.refractionIndex = 1;
237
+ matTransparent.thickness = 0.02;
238
+ matTransparent.thicknessMap = thickness;
239
+ matTransparent.opacityMap = op;
240
+ matTransparent.opacityMapChannel = "r";
241
+ matTransparent.opacity = 0.97;
242
+ matTransparent.emissive = new pc.Color(0.372, 0.03, 0.003);
243
+ matTransparent.emissiveMap = ao;
244
+ matTransparent.emissiveIntensity = 0.1;
245
+ matTransparent.update();
246
+
247
+
248
+ //mat opaque
249
+ matOpaque = new pc.StandardMaterial();
250
+ matOpaque.blendType = pc.BLEND_NORMAL;
251
+ matOpaque.diffuse = new pc.Color(0.7, 0.05, 0.05);
252
+ matOpaque.specular = new pc.Color(0.01, 0.01, 0.01);
253
+ matOpaque.shininess = 90;
254
+ matOpaque.gloss = 1;
255
+ matOpaque.metalness = 0.0;
256
+ matOpaque.opacityMap = op;
257
+ matOpaque.opacityMapChannel = "r";
258
+ matOpaque.opacity = 1;
259
+ matOpaque.emissive = new pc.Color(0.372, 0.03, 0.003);
260
+ matOpaque.emissiveMap = ao;
261
+ matOpaque.emissiveIntensity = 2;
262
+ matOpaque.update();
263
+
264
+
265
+
266
+
267
+ //material for the tube
268
+ //transparent
269
+ tubeTransparent = new pc.StandardMaterial();
270
+ tubeTransparent.diffuse = new pc.Color(1, 1, 1);
271
+ tubeTransparent.blendType = pc.BLEND_NORMAL;
272
+ tubeTransparent.opacity = 0.1;
273
+ tubeTransparent.depthTest = false;
274
+ tubeTransparent.depthWrite = false;
275
+ tubeTransparent.useMetalness = true;
276
+ tubeTransparent.useDynamicRefraction = true;
277
+ tubeTransparent.thickness = 4;
278
+ tubeTransparent.update();
279
+
280
+
281
+ //opaque
282
+ tubeOpaque = new pc.StandardMaterial();
283
+ tubeOpaque.diffuse = new pc.Color(1, 1, 1);
284
+ tubeOpaque.opacity = 0.9;
285
+ tubeOpaque.update();
286
+
287
+ traverse(modelEntity, node => {
288
+ if (node.render && node.render.meshInstances) {
289
+ for (let mi of node.render.meshInstances) {
290
+ mi.material = matOpaque;
291
+ }
 
 
 
292
  }
293
+ });
294
+
295
+
296
+ traverse(tubeEntity, node => {
297
+ if (node.render && node.render.meshInstances) {
298
+ for (let mi of node.render.meshInstances) {
299
+ mi.material = tubeOpaque;
300
+ }
301
+ }
302
+ });
303
+
304
+
305
+
306
+ // Do NOT override materials for bouchonEntity!
307
 
308
+ // 3. Position/scale/orientation
309
  modelEntity.setPosition(modelX, modelY, modelZ);
310
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
311
  modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
 
317
  bouchonEntity.setPosition(modelX, modelY, modelZ);
318
  bouchonEntity.setLocalScale(modelScale, modelScale, modelScale);
319
  bouchonEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
 
320
 
321
  // 4. Camera & orbit
322
+
323
  cameraEntity = new pc.Entity('camera');
324
  cameraEntity.addComponent('camera', {
325
+ clearColor: new pc.Color(1, 1, 1, 1),
326
  toneMapping: pc.TONEMAP_NEUTRAL
327
  });
328
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
329
  cameraEntity.lookAt(modelEntity.getPosition());
330
  cameraEntity.addComponent('script');
331
+
332
+ // --- **CRITICAL** for KHR_materials_transmission! ---
333
+ cameraEntity.camera.requestSceneColorMap(true);
 
 
 
 
 
334
 
335
  cameraEntity.script.create('orbitCamera', {
336
  attributes: {
 
349
  cameraEntity.script.create('orbitCameraInputMouse');
350
  cameraEntity.script.create('orbitCameraInputTouch');
351
  app.root.addChild(cameraEntity);
 
352
 
353
+ // Remove the Skybox layer from the camera
354
+ const skyboxLayer = app.scene.layers.getLayerByName("Skybox");
355
+ const camLayers = cameraEntity.camera.layers.slice();
356
+ const idx = camLayers.indexOf(skyboxLayer.id);
357
+ if (idx !== -1) {
358
+ camLayers.splice(idx, 1);
359
+ cameraEntity.camera.layers = camLayers;
360
+ }
361
+
362
+ // --- Add this block for a cube parented to the camera ---
363
+ // Create the cube
364
 
 
365
  const bgTex = assets.bg_tex.resource;
366
  const bgPlane = new pc.Entity("Plane");
367
  bgPlane.addComponent("model", { type: "plane" });
368
+ // Set the cube's local position relative to the camera
369
+ bgPlane.setLocalPosition(0, 0, -10); // 2 units in front of camera
370
+ // Optionally, scale or color the cube
371
  bgPlane.setLocalScale(11, 1, 5.5);
372
  bgPlane.setLocalEulerAngles(90, 0, 0);
373
+
374
+ // Simple material for visibility
375
  const mat = new pc.StandardMaterial();
376
  mat.diffuse = new pc.Color(1, 1, 1);
377
  mat.diffuseMap = bgTex;
 
381
  mat.useLighting = false;
382
  mat.update();
383
  bgPlane.model.material = mat;
384
+
385
+ // Parent to the camera
386
  cameraEntity.addChild(bgPlane);
387
+
388
+ // 5. Light
389
+ const light = new pc.Entity("mainLight");
390
+ light.addComponent('light',{
391
+ type: "directional",
392
+ color: new pc.Color(1, 1, 1),
393
+ intensity: 0.9,
394
  });
395
+ light.setPosition(0, 1, -1);
396
+ light.lookAt(0, 0, 0);
397
+ app.root.addChild(light);
398
+
399
+ app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
400
+
401
+ app.once('update', () => resetViewerCamera());
402
 
403
  // Tooltips supported if tooltips_url set
404
  try {
 
419
  } catch (e) {
420
  // Tooltips optional, fail silently
421
  }
422
+
423
  viewerInitialized = true;
 
424
  });
425
  });
426
 
427
  app.assets.load(assets.helipad); // Start by loading the HDR
 
428
 
429
+ }
430
 
431
  export function resetViewerCamera() {
432
  try {
 
449
 
450
  orbitCam.pivotPoint = modelPos.clone();
451
  orbitCam._targetDistance = dist;
452
+ orbitCam._distance = dist;
453
 
454
  const rot = tempEnt.getRotation();
455
  const fwd = new pc.Vec3();
 
462
  rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
463
  const pitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
464
 
465
+ orbitCam._targetYaw = yaw;
466
+ orbitCam._yaw = yaw;
467
  orbitCam._targetPitch = pitch;
468
+ orbitCam._pitch = pitch;
469
  if (orbitCam._updatePosition) orbitCam._updatePosition();
470
 
471
  tempEnt.destroy();
 
474
  }
475
  }
476
 
477
+ /**
478
+ * Change the main model's diffuse color in real time.
479
+ * r,g,b are floats [0,1]. This is a reusable function for new colors.
480
+ */
481
  export function changeColor(dr, dg, db, er, eg, eb, ei, op, boolTrans) {
482
+
483
+ //cameraEntity.camera.clearColor = new pc.Color(colorBgR, colorBgG, colorBgB);
484
+ if(boolTrans){
485
  if (!matTransparent) return;
486
 
487
  matTransparent.diffuse.set(dr, dg, db);
 
494
  tubeTransparent.update();
495
 
496
  traverse(modelEntity, node => {
497
+ if (node.render && node.render.meshInstances) {
498
+ for (let mi of node.render.meshInstances) {
499
+ mi.material = matTransparent;
500
+ }
501
  }
 
502
  });
503
 
504
  traverse(tubeEntity, node => {
505
+ if (node.render && node.render.meshInstances) {
506
+ for (let mi of node.render.meshInstances) {
507
+ mi.material = tubeTransparent;
508
+ }
509
  }
 
510
  });
511
+ }else{
512
  if (!matOpaque) return;
513
 
514
  matOpaque.diffuse.set(dr, dg, db);
 
517
  matOpaque.update();
518
 
519
  traverse(modelEntity, node => {
520
+ if (node.render && node.render.meshInstances) {
521
+ for (let mi of node.render.meshInstances) {
522
+ mi.material = matOpaque;
523
+ }
524
  }
 
525
  });
526
 
527
  traverse(tubeEntity, node => {
528
+ if (node.render && node.render.meshInstances) {
529
+ for (let mi of node.render.meshInstances) {
530
+ mi.material = tubeOpaque;
531
+ }
532
  }
 
533
  });
534
  }
535
  }