MikaFil commited on
Commit
3bb2289
·
verified ·
1 Parent(s): e7cb6e5

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +16 -269
viewer.js CHANGED
@@ -1,5 +1,4 @@
1
- // viewer.js
2
- // ==============================
3
 
4
  let pc;
5
  export let app = null;
@@ -10,7 +9,6 @@ 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;
@@ -59,10 +57,7 @@ export async function initializeViewer(config, instanceId) {
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
 
@@ -71,35 +66,13 @@ export async function initializeViewer(config, instanceId) {
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",
@@ -110,273 +83,47 @@ export async function initializeViewer(config, instanceId) {
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
- const containerHandler = app.loader.getHandler('container');
137
- containerHandler.pcMaterialOptions = {
138
- processTransmission: true,
139
- processVolume: true,
140
- processIOR: true,
141
- processSheen: true,
142
- processClearCoat: true,
143
- processVariants: true
144
  };
145
 
146
- resizeObserver = new ResizeObserver(entries => {
147
- entries.forEach(entry => {
148
- app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
149
- });
150
- });
151
  resizeObserver.observe(viewerContainer);
152
 
153
- window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
154
- app.on('destroy', () => resizeObserver.disconnect());
155
-
156
- // Assets after app exists
157
  const assets = {
158
- orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
159
- model: new pc.Asset('glb', 'container', { url: glbUrl }),
160
- tube: new pc.Asset('glb', 'container', {url: glbUrl2}),
161
  bouchon: new pc.Asset('glb', 'container', {url: glbUrl3}),
162
- ao_map: new pc.Asset('color', 'texture', {url: aoUrl}),
163
- op_map: new pc.Asset('color', 'texture', {url: opacityUrl}),
164
- thickness_map: new pc.Asset('color', 'texture', {url: thicknessUrl}),
165
- helipad: new pc.Asset(
166
- 'helipad-env-atlas',
167
- 'texture',
168
- { url: "https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/_rsc/hdr/citrus_orchard_road_puresky_1k.png" },
169
- { type: pc.TEXTURETYPE_RGBP, mipmaps: false }
170
- )
171
  };
172
  for (const key in assets) app.assets.add(assets[key]);
173
 
174
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
175
  loader.load(() => {
176
  app.start();
177
- if (progressDialog) progressDialog.style.display = 'none';
178
-
179
- // --- Add the GLB entity ---
180
- modelEntity = assets.model.resource.instantiateRenderEntity();
181
- app.root.addChild(modelEntity);
182
-
183
- tubeEntity = assets.tube.resource.instantiateRenderEntity();
184
- app.root.addChild(tubeEntity);
185
 
186
  bouchonEntity = assets.bouchon.resource.instantiateRenderEntity();
 
 
 
187
  app.root.addChild(bouchonEntity);
188
 
189
- // --- Add a light ---
190
- const light = new pc.Entity("mainLight");
191
- light.addComponent('light',{
192
- type: "directional",
193
- color: new pc.Color(1, 1, 1),
194
- intensity:0.9,
195
- });
196
- light.setPosition(0, 1, -1);
197
- light.lookAt(0, 0, 0);
198
- app.root.addChild(light);
199
-
200
- // --- Glossy Red Material (exposed globally for changeColor)
201
- glossyRedMat = new pc.StandardMaterial();
202
- const ao = assets.ao_map.resource;
203
- const op = assets.op_map.resource;
204
- const thickness = assets.thickness_map.resource;
205
- glossyRedMat.blendType = pc.BLEND_NORMAL;
206
- glossyRedMat.diffuse = new pc.Color(0.7, 0.05, 0.05);
207
- glossyRedMat.specular = new pc.Color(0.01, 0.01, 0.01);
208
- glossyRedMat.shininess = 90;
209
- glossyRedMat.gloss = 1;
210
- glossyRedMat.metalness = 0.0;
211
- glossyRedMat.useMetalness = true;
212
- glossyRedMat.useDynamicRefraction = true;
213
- glossyRedMat.refraction= 0.8;
214
- glossyRedMat.refractionIndex = 0.5;
215
- glossyRedMat.thickness = 5;
216
- glossyRedMat.thicknessMap = thickness;
217
- glossyRedMat.opacityMap = op;
218
- glossyRedMat.opacityMapChannel = "r";
219
- glossyRedMat.opacity = 0.975;
220
- glossyRedMat.emissive = new pc.Color(0.372, 0.03, 0.003);
221
- glossyRedMat.emissiveMap = ao;
222
- glossyRedMat.emissiveIntensity = 1;
223
- glossyRedMat.update();
224
-
225
- const tubeEmit = new pc.StandardMaterial();
226
- tubeEmit.emissive = new pc.Color(0.372, 0.2, 0.2);
227
- tubeEmit.emissiveIntensity = 5;
228
- tubeEmit.opacity = 0.99;
229
-
230
- // RECURSIVE MATERIAL ASSIGNMENT (replace forEach with traverse)
231
- function traverse(entity, callback) {
232
- callback(entity);
233
- entity.children.forEach(child => traverse(child, callback));
234
- }
235
- // Assign main model material for color switching
236
- traverse(modelEntity, node => {
237
- if (node.render && node.render.meshInstances) {
238
- for (let mi of node.render.meshInstances) {
239
- mi.material = glossyRedMat;
240
- }
241
- }
242
- });
243
- // Tube
244
- traverse(tubeEntity, node => {
245
- if (node.render && node.render.meshInstances) {
246
- for (let mi of node.render.meshInstances) {
247
- mi.material = tubeEmit;
248
- }
249
- }
250
- });
251
-
252
- // Set model transform
253
- modelEntity.setPosition(modelX, modelY, modelZ);
254
- modelEntity.setLocalScale(modelScale, modelScale, modelScale);
255
- modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
256
-
257
- tubeEntity.setPosition(modelX, modelY, modelZ);
258
- tubeEntity.setLocalScale(modelScale, modelScale, modelScale);
259
- tubeEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
260
-
261
  cameraEntity = new pc.Entity('camera');
262
- cameraEntity.addComponent('camera', { clearColor: new pc.Color(0.95, 0.95, 0.95, 1) });
263
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
264
- cameraEntity.lookAt(modelEntity.getPosition());
265
- cameraEntity.addComponent('script');
266
-
267
- // Setup skydome
268
- app.scene.envAtlas = assets.helipad.resource;
269
- app.scene.skyboxRotation = new pc.Quat().setFromEulerAngles(30, 70, 0);
270
- app.scene.skyboxIntensity = 2;
271
- app.scene.skyboxMip = 0;
272
-
273
- // Hide skybox layer so HDR is not rendered as background
274
- const LAYERID_SKYBOX = app.scene.layers.getLayerByName("Skybox").id;
275
- //app.scene.layers.getLayerById(LAYERID_SKYBOX).enabled = false;
276
-
277
- // Pass ALL relevant attributes including minY
278
- cameraEntity.script.create('orbitCamera', {
279
- attributes: {
280
- focusEntity: modelEntity,
281
- inertiaFactor: 0.2,
282
- distanceMax: maxZoom,
283
- distanceMin: minZoom,
284
- pitchAngleMax: maxAngle,
285
- pitchAngleMin: minAngle,
286
- yawAngleMax: maxAzimuth,
287
- yawAngleMin: minAzimuth,
288
- minY: minY,
289
- frameOnStart: false
290
- }
291
- });
292
- cameraEntity.script.create('orbitCameraInputMouse');
293
- cameraEntity.script.create('orbitCameraInputTouch');
294
  app.root.addChild(cameraEntity);
295
 
296
- app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
297
-
298
- app.once('update', () => resetViewerCamera());
299
-
300
- // Tooltips supported if tooltips_url set
301
- try {
302
- if (config.tooltips_url) {
303
- import('./tooltips.js').then(tooltipsModule => {
304
- tooltipsModule.initializeTooltips({
305
- app,
306
- cameraEntity,
307
- modelEntity,
308
- tooltipsUrl: config.tooltips_url,
309
- defaultVisible: !!config.showTooltipsDefault,
310
- moveDuration: config.tooltipMoveDuration || 0.6
311
- });
312
- }).catch(e => {
313
- // Tooltips optional: fail silently if missing
314
- });
315
- }
316
- } catch (e) {
317
- // Tooltips optional, fail silently
318
- }
319
-
320
  viewerInitialized = true;
321
  });
322
  }
323
-
324
- export function resetViewerCamera() {
325
- try {
326
- if (!cameraEntity || !modelEntity || !app) return;
327
- const orbitCam = cameraEntity.script.orbitCamera;
328
- if (!orbitCam) return;
329
-
330
- const modelPos = modelEntity.getPosition();
331
- const tempEnt = new pc.Entity();
332
- tempEnt.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
333
- tempEnt.lookAt(modelPos);
334
-
335
- const dist = new pc.Vec3().sub2(
336
- new pc.Vec3(chosenCameraX, chosenCameraY, chosenCameraZ),
337
- modelPos
338
- ).length();
339
-
340
- cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
341
- cameraEntity.lookAt(modelPos);
342
-
343
- orbitCam.pivotPoint = modelPos.clone();
344
- orbitCam._targetDistance = dist;
345
- orbitCam._distance = dist;
346
-
347
- const rot = tempEnt.getRotation();
348
- const fwd = new pc.Vec3();
349
- rot.transformVector(pc.Vec3.FORWARD, fwd);
350
-
351
- const yaw = Math.atan2(-fwd.x, -fwd.z) * pc.math.RAD_TO_DEG;
352
- const yawQuat = new pc.Quat().setFromEulerAngles(0, -yaw, 0);
353
- const rotNoYaw = new pc.Quat().mul2(yawQuat, rot);
354
- const fNoYaw = new pc.Vec3();
355
- rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
356
- const pitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
357
-
358
- orbitCam._targetYaw = yaw;
359
- orbitCam._yaw = yaw;
360
- orbitCam._targetPitch = pitch;
361
- orbitCam._pitch = pitch;
362
- if (orbitCam._updatePosition) orbitCam._updatePosition();
363
-
364
- tempEnt.destroy();
365
- } catch (e) {
366
- // Silent fail
367
- }
368
- }
369
-
370
- /**
371
- * Change the main model's diffuse color in real time.
372
- * r,g,b are floats [0,1]. This is a reusable function for new colors.
373
- */
374
- export function changeColor(dr, dg, db, a, er, eg, eb) {
375
- // Wait until glossyRedMat is loaded (null before model load).
376
- if (!glossyRedMat) return;
377
-
378
- glossyRedMat.diffuse.set(dr, dg, db);
379
- glossyRedMat.emissive.set(er, eg, eb);
380
- glossyRedMat.opacity = a;
381
- glossyRedMat.update();
382
- }
 
1
+ // viewer.js (updated with explicit KHR GLB extension support)
 
2
 
3
  let pc;
4
  export let app = null;
 
9
  let viewerInitialized = false;
10
  let resizeObserver = null;
11
 
 
12
  let glossyRedMat = null;
13
 
14
  let chosenCameraX, chosenCameraY, chosenCameraZ;
 
57
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
58
 
59
  const canvasId = 'canvas-' + instanceId;
 
 
60
  const viewerContainer = document.getElementById('viewer-container-' + instanceId);
 
61
  let oldCanvas = document.getElementById(canvasId);
62
  if (oldCanvas) oldCanvas.remove();
63
 
 
66
  canvas.className = 'ply-canvas';
67
  canvas.style.width = "100%";
68
  canvas.style.height = "100%";
69
+ viewerContainer.appendChild(canvas);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  if (!pc) {
72
  pc = await import("https://esm.run/playcanvas");
73
  window.pc = pc;
74
  }
75
 
 
76
  const device = await pc.createGraphicsDevice(canvas, {
77
  deviceTypes: ["webgl2"],
78
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
 
83
 
84
  const opts = new pc.AppOptions();
85
  opts.graphicsDevice = device;
 
86
  opts.mouse = new pc.Mouse(canvas);
87
  opts.touch = new pc.TouchDevice(canvas);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
  app = new pc.Application(canvas, opts);
90
  app.setCanvasFillMode(pc.FILLMODE_NONE);
91
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
92
 
93
+ // Explicitly enable KHR GLB extensions
94
+ app.loader.getHandler('container').pcMaterialOptions = {
95
+ processTransmission: true,
96
+ processVolume: true,
97
+ processIOR: true,
98
+ processSheen: true,
99
+ processClearCoat: true,
100
+ processVariants: true
101
  };
102
 
103
+ resizeObserver = new ResizeObserver(entries => entries.forEach(entry => app.resizeCanvas(entry.contentRect.width, entry.contentRect.height)));
 
 
 
 
104
  resizeObserver.observe(viewerContainer);
105
 
 
 
 
 
106
  const assets = {
 
 
 
107
  bouchon: new pc.Asset('glb', 'container', {url: glbUrl3}),
 
 
 
 
 
 
 
 
 
108
  };
109
  for (const key in assets) app.assets.add(assets[key]);
110
 
111
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
112
  loader.load(() => {
113
  app.start();
 
 
 
 
 
 
 
 
114
 
115
  bouchonEntity = assets.bouchon.resource.instantiateRenderEntity();
116
+ bouchonEntity.setLocalScale(modelScale, modelScale, modelScale);
117
+ bouchonEntity.setPosition(modelX, modelY, modelZ);
118
+ bouchonEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
119
  app.root.addChild(bouchonEntity);
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  cameraEntity = new pc.Entity('camera');
122
+ cameraEntity.addComponent('camera');
123
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
124
+ cameraEntity.lookAt(bouchonEntity.getPosition());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  app.root.addChild(cameraEntity);
126
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  viewerInitialized = true;
128
  });
129
  }