MikaFil commited on
Commit
74fc7a3
·
verified ·
1 Parent(s): 896ff5e

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +41 -320
viewer.js CHANGED
@@ -1,140 +1,67 @@
1
- // viewer.js
2
- // ==============================
3
 
4
  let pc;
5
  export let app = null;
6
- let cameraEntity = null;
7
- let modelEntity = null;
8
- let tubeEntity = null;
9
- let bouchonEntity = null;
10
- let viewerInitialized = false;
11
- let resizeObserver = null;
12
-
13
- // These will hold references to the material(s) you want to update.
14
- let glossyRedMat = null;
15
-
16
- let chosenCameraX, chosenCameraY, chosenCameraZ;
17
- let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, minY;
18
- let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
19
- let glbUrl, glbUrl2, glbUrl3, aoUrl, opacityUrl, thicknessUrl;
20
 
21
  export async function initializeViewer(config, instanceId) {
22
- if (viewerInitialized) return;
23
-
24
- const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
25
- const isMobile = isIOS || /Android/i.test(navigator.userAgent);
26
-
27
- glbUrl = config.glb_url;
28
- glbUrl2 = config.glb_url_2;
29
- glbUrl3 = config.glb_url_3;
30
- aoUrl = config.ao_url;
31
- thicknessUrl = config.thickness_url;
32
- opacityUrl = config.opacity_url;
33
- minZoom = parseFloat(config.minZoom || "1");
34
- maxZoom = parseFloat(config.maxZoom || "20");
35
- minAngle = parseFloat(config.minAngle || "-75");
36
- maxAngle = parseFloat(config.maxAngle || "180");
37
- minAzimuth = (config.minAzimuth !== undefined) ? parseFloat(config.minAzimuth) : -360;
38
- maxAzimuth = (config.maxAzimuth !== undefined) ? parseFloat(config.maxAzimuth) : 360;
39
- minPivotY = parseFloat(config.minPivotY || "0");
40
- minY = -10;
41
-
42
- modelX = (config.modelX !== undefined) ? parseFloat(config.modelX) : 0;
43
- modelY = (config.modelY !== undefined) ? parseFloat(config.modelY) : 0;
44
- modelZ = (config.modelZ !== undefined) ? parseFloat(config.modelZ) : 0;
45
- modelScale = (config.modelScale !== undefined) ? parseFloat(config.modelScale) : 1;
46
- modelRotationX = (config.modelRotationX !== undefined) ? parseFloat(config.modelRotationX) : 0;
47
- modelRotationY = (config.modelRotationY !== undefined) ? parseFloat(config.modelRotationY) : 0;
48
- modelRotationZ = (config.modelRotationZ !== undefined) ? parseFloat(config.modelRotationZ) : 0;
49
-
50
- const cameraX = (config.cameraX !== undefined) ? parseFloat(config.cameraX) : 0;
51
- const cameraY = (config.cameraY !== undefined) ? parseFloat(config.cameraY) : 2;
52
- const cameraZ = (config.cameraZ !== undefined) ? parseFloat(config.cameraZ) : 5;
53
- const cameraXPhone = (config.cameraXPhone !== undefined) ? parseFloat(config.cameraXPhone) : cameraX;
54
- const cameraYPhone = (config.cameraYPhone !== undefined) ? parseFloat(config.cameraYPhone) : cameraY;
55
- const cameraZPhone = (config.cameraZPhone !== undefined) ? parseFloat(config.cameraZPhone) : (cameraZ * 1.5);
56
 
57
- chosenCameraX = isMobile ? cameraXPhone : cameraX;
58
- chosenCameraY = isMobile ? cameraYPhone : cameraY;
59
- chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
60
 
61
- const canvasId = 'canvas-' + instanceId;
62
- const progressDialog = document.getElementById('progress-dialog-' + instanceId);
63
- const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
64
  const viewerContainer = document.getElementById('viewer-container-' + instanceId);
65
 
66
- let oldCanvas = document.getElementById(canvasId);
 
67
  if (oldCanvas) oldCanvas.remove();
68
 
69
  const canvas = document.createElement('canvas');
70
- canvas.id = canvasId;
71
- canvas.className = 'ply-canvas';
72
  canvas.style.width = "100%";
73
  canvas.style.height = "100%";
74
- canvas.setAttribute('tabindex', '0');
75
- // Safer insert:
76
- if (progressDialog) {
77
- viewerContainer.insertBefore(canvas, progressDialog);
78
- } else {
79
- viewerContainer.appendChild(canvas);
80
- }
81
-
82
- canvas.style.touchAction = "none";
83
- canvas.style.webkitTouchCallout = "none";
84
- canvas.addEventListener('gesturestart', e => e.preventDefault());
85
- canvas.addEventListener('gesturechange', e => e.preventDefault());
86
- canvas.addEventListener('gestureend', e => e.preventDefault());
87
- canvas.addEventListener('dblclick', e => e.preventDefault());
88
- canvas.addEventListener('touchstart', e => { if (e.touches.length > 1) e.preventDefault(); }, { passive: false });
89
-
90
- // --- The following line attaches mouse wheel suppression to canvas only ---
91
- canvas.addEventListener('wheel', (e) => {
92
- e.preventDefault(); // Only block page scroll if mouse is over viewer
93
- }, { passive: false });
94
-
95
- if (progressDialog) progressDialog.style.display = 'block';
96
 
 
97
  if (!pc) {
98
  pc = await import("https://esm.run/playcanvas");
99
  window.pc = pc;
100
  }
101
 
102
- // Create app first
103
  const device = await pc.createGraphicsDevice(canvas, {
104
  deviceTypes: ["webgl2"],
105
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
106
- twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
107
- antialias: false
108
  });
 
109
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
110
 
111
  const opts = new pc.AppOptions();
112
  opts.graphicsDevice = device;
113
- // Attach input only to canvas
114
  opts.mouse = new pc.Mouse(canvas);
115
  opts.touch = new pc.TouchDevice(canvas);
116
  opts.componentSystems = [
117
  pc.RenderComponentSystem,
118
  pc.CameraComponentSystem,
119
  pc.LightComponentSystem,
120
- pc.ScriptComponentSystem,
121
- pc.GSplatComponentSystem,
122
- pc.CollisionComponentSystem,
123
- pc.RigidbodyComponentSystem
124
  ];
125
  opts.resourceHandlers = [
126
  pc.TextureHandler,
127
  pc.ContainerHandler,
128
- pc.ScriptHandler,
129
- pc.GSplatHandler
130
  ];
131
 
132
  app = new pc.Application(canvas, opts);
133
  app.setCanvasFillMode(pc.FILLMODE_NONE);
134
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
135
 
136
- // ******** KHR GLB EXTENSIONS SUPPORT (inserted here) ********
137
- // This ensures KHR_materials_transmission, KHR_materials_volume, KHR_materials_ior, etc. are handled!
 
 
 
138
  const containerHandler = app.loader.getHandler('container');
139
  if (containerHandler) {
140
  containerHandler.pcMaterialOptions = {
@@ -146,242 +73,36 @@ export async function initializeViewer(config, instanceId) {
146
  processVariants: true
147
  };
148
  }
149
- // ***********************************************************
150
 
151
- resizeObserver = new ResizeObserver(entries => {
152
- entries.forEach(entry => {
153
- app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
154
- });
155
- });
156
- resizeObserver.observe(viewerContainer);
157
-
158
- window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
159
- app.on('destroy', () => resizeObserver.disconnect());
160
-
161
- // Assets after app exists
162
- const assets = {
163
- orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
164
- model: new pc.Asset('glb', 'container', { url: glbUrl }),
165
- tube: new pc.Asset('glb', 'container', {url: glbUrl2}),
166
- bouchon: new pc.Asset('glb', 'container', {url: glbUrl3}),
167
- ao_map: new pc.Asset('color', 'texture', {url: aoUrl}),
168
- op_map: new pc.Asset('color', 'texture', {url: opacityUrl}),
169
- thickness_map: new pc.Asset('color', 'texture', {url: thicknessUrl}),
170
- helipad: new pc.Asset(
171
- 'helipad-env-atlas',
172
- 'texture',
173
- { url: "https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/_rsc/hdr/citrus_orchard_road_puresky_1k.png" },
174
- { type: pc.TEXTURETYPE_RGBP, mipmaps: false }
175
- )
176
- };
177
- for (const key in assets) app.assets.add(assets[key]);
178
-
179
- const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
180
- loader.load(() => {
181
- app.start();
182
- if (progressDialog) progressDialog.style.display = 'none';
183
-
184
- // --- Add the GLB entity ---
185
- modelEntity = assets.model.resource.instantiateRenderEntity();
186
- app.root.addChild(modelEntity);
187
-
188
- tubeEntity = assets.tube.resource.instantiateRenderEntity();
189
- app.root.addChild(tubeEntity);
190
-
191
- bouchonEntity = assets.bouchon.resource.instantiateRenderEntity();
192
- app.root.addChild(bouchonEntity);
193
-
194
- // --- Add a light ---
195
- const light = new pc.Entity("mainLight");
196
- light.addComponent('light',{
197
  type: "directional",
198
  color: new pc.Color(1, 1, 1),
199
- intensity:0.9,
200
  });
201
- light.setPosition(0, 1, -1);
202
  light.lookAt(0, 0, 0);
203
  app.root.addChild(light);
204
 
205
- // --- Glossy Red Material (exposed globally for changeColor)
206
- /*glossyRedMat = new pc.StandardMaterial();
207
- const ao = assets.ao_map.resource;
208
- const op = assets.op_map.resource;
209
- const thickness = assets.thickness_map.resource;
210
- glossyRedMat.blendType = pc.BLEND_NORMAL;
211
- glossyRedMat.diffuse = new pc.Color(0.7, 0.05, 0.05);
212
- glossyRedMat.specular = new pc.Color(0.01, 0.01, 0.01);
213
- glossyRedMat.shininess = 90;
214
- glossyRedMat.gloss = 1;
215
- glossyRedMat.metalness = 0.0;
216
- glossyRedMat.useMetalness = true;
217
- glossyRedMat.useDynamicRefraction = true;
218
- glossyRedMat.refraction= 0.8;
219
- glossyRedMat.refractionIndex = 0.5;
220
- glossyRedMat.thickness = 5;
221
- glossyRedMat.thicknessMap = thickness;
222
- glossyRedMat.opacityMap = op;
223
- glossyRedMat.opacityMapChannel = "r";
224
- glossyRedMat.opacity = 0.975;
225
- glossyRedMat.emissive = new pc.Color(0.372, 0.03, 0.003);
226
- glossyRedMat.emissiveMap = ao;
227
- glossyRedMat.emissiveIntensity = 1;
228
- glossyRedMat.update();
229
-
230
- const tubeEmit = new pc.StandardMaterial();
231
- tubeEmit.emissive = new pc.Color(0.372, 0.2, 0.2);
232
- tubeEmit.emissiveIntensity = 5;
233
- tubeEmit.opacity = 0.99;
234
-
235
- // RECURSIVE MATERIAL ASSIGNMENT (replace forEach with traverse)
236
- function traverse(entity, callback) {
237
- callback(entity);
238
- entity.children.forEach(child => traverse(child, callback));
239
- }
240
- // Assign main model material for color switching
241
- traverse(modelEntity, node => {
242
- if (node.render && node.render.meshInstances) {
243
- for (let mi of node.render.meshInstances) {
244
- mi.material = glossyRedMat;
245
- }
246
- }
247
- });
248
- // Tube
249
- traverse(tubeEntity, node => {
250
- if (node.render && node.render.meshInstances) {
251
- for (let mi of node.render.meshInstances) {
252
- mi.material = tubeEmit;
253
- }
254
- }
255
- });*/
256
-
257
- // Set model transform
258
- modelEntity.setPosition(modelX, modelY, modelZ);
259
- modelEntity.setLocalScale(modelScale, modelScale, modelScale);
260
- modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
261
-
262
- tubeEntity.setPosition(modelX, modelY, modelZ);
263
- tubeEntity.setLocalScale(modelScale, modelScale, modelScale);
264
- tubeEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
265
-
266
- cameraEntity = new pc.Entity('camera');
267
- cameraEntity.addComponent('camera', { clearColor: new pc.Color(0.95, 0.95, 0.95, 1) });
268
- cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
269
- cameraEntity.lookAt(modelEntity.getPosition());
270
- cameraEntity.addComponent('script');
271
-
272
- // Setup skydome
273
- app.scene.envAtlas = assets.helipad.resource;
274
- app.scene.skyboxRotation = new pc.Quat().setFromEulerAngles(30, 70, 0);
275
- app.scene.skyboxIntensity = 2;
276
- app.scene.skyboxMip = 0;
277
-
278
- // Hide skybox layer so HDR is not rendered as background
279
- const LAYERID_SKYBOX = app.scene.layers.getLayerByName("Skybox").id;
280
- //app.scene.layers.getLayerById(LAYERID_SKYBOX).enabled = false;
281
-
282
- // Pass ALL relevant attributes including minY
283
- cameraEntity.script.create('orbitCamera', {
284
- attributes: {
285
- focusEntity: modelEntity,
286
- inertiaFactor: 0.2,
287
- distanceMax: maxZoom,
288
- distanceMin: minZoom,
289
- pitchAngleMax: maxAngle,
290
- pitchAngleMin: minAngle,
291
- yawAngleMax: maxAzimuth,
292
- yawAngleMin: minAzimuth,
293
- minY: minY,
294
- frameOnStart: false
295
- }
296
- });
297
- cameraEntity.script.create('orbitCameraInputMouse');
298
- cameraEntity.script.create('orbitCameraInputTouch');
299
- app.root.addChild(cameraEntity);
300
 
301
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
302
-
303
- app.once('update', () => resetViewerCamera());
304
-
305
- // Tooltips supported if tooltips_url set
306
- try {
307
- if (config.tooltips_url) {
308
- import('./tooltips.js').then(tooltipsModule => {
309
- tooltipsModule.initializeTooltips({
310
- app,
311
- cameraEntity,
312
- modelEntity,
313
- tooltipsUrl: config.tooltips_url,
314
- defaultVisible: !!config.showTooltipsDefault,
315
- moveDuration: config.tooltipMoveDuration || 0.6
316
- });
317
- }).catch(e => {
318
- // Tooltips optional: fail silently if missing
319
- });
320
- }
321
- } catch (e) {
322
- // Tooltips optional, fail silently
323
- }
324
-
325
- viewerInitialized = true;
326
  });
327
- }
328
-
329
- export function resetViewerCamera() {
330
- try {
331
- if (!cameraEntity || !modelEntity || !app) return;
332
- const orbitCam = cameraEntity.script.orbitCamera;
333
- if (!orbitCam) return;
334
-
335
- const modelPos = modelEntity.getPosition();
336
- const tempEnt = new pc.Entity();
337
- tempEnt.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
338
- tempEnt.lookAt(modelPos);
339
-
340
- const dist = new pc.Vec3().sub2(
341
- new pc.Vec3(chosenCameraX, chosenCameraY, chosenCameraZ),
342
- modelPos
343
- ).length();
344
-
345
- cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
346
- cameraEntity.lookAt(modelPos);
347
-
348
- orbitCam.pivotPoint = modelPos.clone();
349
- orbitCam._targetDistance = dist;
350
- orbitCam._distance = dist;
351
-
352
- const rot = tempEnt.getRotation();
353
- const fwd = new pc.Vec3();
354
- rot.transformVector(pc.Vec3.FORWARD, fwd);
355
-
356
- const yaw = Math.atan2(-fwd.x, -fwd.z) * pc.math.RAD_TO_DEG;
357
- const yawQuat = new pc.Quat().setFromEulerAngles(0, -yaw, 0);
358
- const rotNoYaw = new pc.Quat().mul2(yawQuat, rot);
359
- const fNoYaw = new pc.Vec3();
360
- rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
361
- const pitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
362
-
363
- orbitCam._targetYaw = yaw;
364
- orbitCam._yaw = yaw;
365
- orbitCam._targetPitch = pitch;
366
- orbitCam._pitch = pitch;
367
- if (orbitCam._updatePosition) orbitCam._updatePosition();
368
-
369
- tempEnt.destroy();
370
- } catch (e) {
371
- // Silent fail
372
- }
373
- }
374
-
375
- /**
376
- * Change the main model's diffuse color in real time.
377
- * r,g,b are floats [0,1]. This is a reusable function for new colors.
378
- */
379
- export function changeColor(dr, dg, db, a, er, eg, eb) {
380
- // Wait until glossyRedMat is loaded (null before model load).
381
- if (!glossyRedMat) return;
382
 
383
- glossyRedMat.diffuse.set(dr, dg, db);
384
- glossyRedMat.emissive.set(er, eg, eb);
385
- glossyRedMat.opacity = a;
386
- glossyRedMat.update();
387
  }
 
1
+ // Minimal viewer.js (for one GLB with KHR_materials_transmission)
 
2
 
3
  let pc;
4
  export let app = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  export async function initializeViewer(config, instanceId) {
7
+ if (app) return; // Only allow one instance for this minimal demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ // Get URLs from config
10
+ const glbUrl = config.glb_url;
 
11
 
12
+ // Get the container
 
 
13
  const viewerContainer = document.getElementById('viewer-container-' + instanceId);
14
 
15
+ // Prepare the canvas
16
+ let oldCanvas = document.getElementById('canvas-' + instanceId);
17
  if (oldCanvas) oldCanvas.remove();
18
 
19
  const canvas = document.createElement('canvas');
20
+ canvas.id = 'canvas-' + instanceId;
 
21
  canvas.style.width = "100%";
22
  canvas.style.height = "100%";
23
+ viewerContainer.appendChild(canvas);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ // Load PlayCanvas if not loaded yet
26
  if (!pc) {
27
  pc = await import("https://esm.run/playcanvas");
28
  window.pc = pc;
29
  }
30
 
31
+ // Create graphics device
32
  const device = await pc.createGraphicsDevice(canvas, {
33
  deviceTypes: ["webgl2"],
34
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
35
+ twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js"
 
36
  });
37
+
38
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
39
 
40
  const opts = new pc.AppOptions();
41
  opts.graphicsDevice = device;
 
42
  opts.mouse = new pc.Mouse(canvas);
43
  opts.touch = new pc.TouchDevice(canvas);
44
  opts.componentSystems = [
45
  pc.RenderComponentSystem,
46
  pc.CameraComponentSystem,
47
  pc.LightComponentSystem,
48
+ pc.ScriptComponentSystem
 
 
 
49
  ];
50
  opts.resourceHandlers = [
51
  pc.TextureHandler,
52
  pc.ContainerHandler,
53
+ pc.ScriptHandler
 
54
  ];
55
 
56
  app = new pc.Application(canvas, opts);
57
  app.setCanvasFillMode(pc.FILLMODE_NONE);
58
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
59
 
60
+ // GLB asset
61
+ const glbAsset = new pc.Asset('model', 'container', { url: glbUrl });
62
+ app.assets.add(glbAsset);
63
+
64
+ // Optional: set up KHR extension flags (not always required, but safe)
65
  const containerHandler = app.loader.getHandler('container');
66
  if (containerHandler) {
67
  containerHandler.pcMaterialOptions = {
 
73
  processVariants: true
74
  };
75
  }
 
76
 
77
+ // Wait for GLB to load
78
+ glbAsset.ready(() => {
79
+ // Instantiate as-is (DO NOT TOUCH MATERIALS)
80
+ const entity = glbAsset.resource.instantiateRenderEntity();
81
+ app.root.addChild(entity);
82
+
83
+ // Add a camera
84
+ const camera = new pc.Entity('camera');
85
+ camera.addComponent('camera', { clearColor: new pc.Color(0.95, 0.95, 0.95, 1) });
86
+ camera.setPosition(0, 1, 4);
87
+ camera.lookAt(0, 0, 0);
88
+ app.root.addChild(camera);
89
+
90
+ // Add a light
91
+ const light = new pc.Entity('mainLight');
92
+ light.addComponent('light', {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  type: "directional",
94
  color: new pc.Color(1, 1, 1),
95
+ intensity: 1.0
96
  });
97
+ light.setPosition(0, 2, 2);
98
  light.lookAt(0, 0, 0);
99
  app.root.addChild(light);
100
 
101
+ // Optionally: set skybox, env map, etc, as you like
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
104
+ window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
+ app.assets.load(glbAsset);
 
 
 
108
  }