MikaFil commited on
Commit
d7ead5d
·
verified ·
1 Parent(s): fb73464

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +40 -25
viewer.js CHANGED
@@ -24,7 +24,7 @@ export async function initializeViewer(config, instanceId) {
24
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
25
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
26
 
27
- // 1. Read config
28
  plyUrl = config.ply_url;
29
  glbUrl = config.glb_url;
30
  minZoom = parseFloat(config.minZoom || "1");
@@ -55,13 +55,13 @@ export async function initializeViewer(config, instanceId) {
55
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
56
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
57
 
58
- // 2. Grab DOM
59
  const canvasId = 'canvas-' + instanceId;
60
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
61
  const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
62
  const viewerContainer = document.getElementById('viewer-container-' + instanceId);
63
 
64
- // 3. Create <canvas>
65
  let oldCanvas = document.getElementById(canvasId);
66
  if (oldCanvas) oldCanvas.remove();
67
  const canvas = document.createElement('canvas');
@@ -70,7 +70,7 @@ export async function initializeViewer(config, instanceId) {
70
  canvas.style.zIndex = "1";
71
  viewerContainer.insertBefore(canvas, progressDialog);
72
 
73
- // 4. Wheel listener
74
  canvas.addEventListener('wheel', e => {
75
  if (cameraEntity && cameraEntity.script && cameraEntity.script.orbitCamera) {
76
  const orbitCam = cameraEntity.script.orbitCamera;
@@ -87,20 +87,39 @@ export async function initializeViewer(config, instanceId) {
87
 
88
  progressDialog.style.display = 'block';
89
 
90
- // 5. Import PlayCanvas
91
  if (!pc) {
92
  pc = await import("https://esm.run/playcanvas");
93
  window.pc = pc;
94
  }
95
 
96
  try {
97
- // 6. Setup device & app
98
- const device = await pc.createGraphicsDevice(canvas, {
99
- deviceTypes: ["webgl2"],
100
- glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
101
- twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
102
- antialias: false
103
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
105
 
106
  const opts = new pc.AppOptions();
@@ -126,10 +145,8 @@ export async function initializeViewer(config, instanceId) {
126
  app = new pc.Application(canvas, opts);
127
  app.setCanvasFillMode(pc.FILLMODE_NONE);
128
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
129
- //app.scene.exposure = 0.5;
130
- //app.scene.toneMapping = pc.TONEMAP_ACES;
131
 
132
- // Attach ResizeObserver to keep canvas in sync with container size
133
  resizeObserver = new ResizeObserver(entries => {
134
  for (const entry of entries) {
135
  const { width, height } = entry.contentRect;
@@ -147,7 +164,7 @@ export async function initializeViewer(config, instanceId) {
147
  window.removeEventListener('resize', resizeCanvas);
148
  });
149
 
150
- // 7. Assets
151
  const assets = {
152
  model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
153
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
@@ -180,7 +197,7 @@ export async function initializeViewer(config, instanceId) {
180
  app.start();
181
  app.scene.envAtlas = assets.hdr.resource;
182
 
183
- // Model entity
184
  modelEntity = new pc.Entity('model');
185
  modelEntity.addComponent('gsplat', { asset: assets.model });
186
  modelEntity.setLocalPosition(modelX, modelY, modelZ);
@@ -188,7 +205,6 @@ export async function initializeViewer(config, instanceId) {
188
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
189
  app.root.addChild(modelEntity);
190
 
191
- // Light
192
  const dirLight = new pc.Entity('Cascaded Light');
193
  dirLight.addComponent('light', {
194
  type: 'directional',
@@ -209,17 +225,15 @@ export async function initializeViewer(config, instanceId) {
209
  dirLight.setLocalEulerAngles(0, 0, 0);
210
  app.root.addChild(dirLight);
211
 
212
- // Gallery GLB
213
  const galleryEntity = assets.galerie.resource.instantiateRenderEntity();
214
  app.root.addChild(galleryEntity);
215
 
216
- // Camera setup
217
  cameraEntity = new pc.Entity('camera');
218
  cameraEntity.addComponent('camera', {
219
  clearColor: config.canvas_background
220
  ? parseInt(config.canvas_background.substr(1, 2), 16) / 255
221
  : 0,
222
- //toneMapping: pc.TONEMAP_ACES
223
  });
224
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
225
  cameraEntity.lookAt(modelEntity.getPosition());
@@ -256,7 +270,7 @@ export async function initializeViewer(config, instanceId) {
256
  });
257
  app.root.addChild(cameraEntity);
258
 
259
- // Reset & constrain updates
260
  app.once('update', () => resetViewerCamera());
261
  app.on('update', dt => {
262
  if (cameraEntity) {
@@ -267,10 +281,9 @@ export async function initializeViewer(config, instanceId) {
267
  }
268
  });
269
 
270
- // Final resize
271
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
272
 
273
- // Tooltips
274
  try {
275
  const tooltipsModule = await import('./tooltips.js');
276
  tooltipsModule.initializeTooltips({
@@ -295,6 +308,9 @@ export async function initializeViewer(config, instanceId) {
295
  }
296
  }
297
 
 
 
 
298
  export function resetViewerCamera() {
299
  try {
300
  if (!cameraEntity || !modelEntity || !app) return;
@@ -352,7 +368,6 @@ export function cleanupViewer() {
352
  modelEntity = null;
353
  viewerInitialized = false;
354
 
355
- // Disconnect the ResizeObserver to avoid leaks
356
  if (resizeObserver) {
357
  resizeObserver.disconnect();
358
  resizeObserver = null;
 
24
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
25
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
26
 
27
+ // 1. Read config -------------------------------------------------------------
28
  plyUrl = config.ply_url;
29
  glbUrl = config.glb_url;
30
  minZoom = parseFloat(config.minZoom || "1");
 
55
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
56
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
57
 
58
+ // 2. Grab DOM ---------------------------------------------------------------
59
  const canvasId = 'canvas-' + instanceId;
60
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
61
  const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
62
  const viewerContainer = document.getElementById('viewer-container-' + instanceId);
63
 
64
+ // 3. Create <canvas> --------------------------------------------------------
65
  let oldCanvas = document.getElementById(canvasId);
66
  if (oldCanvas) oldCanvas.remove();
67
  const canvas = document.createElement('canvas');
 
70
  canvas.style.zIndex = "1";
71
  viewerContainer.insertBefore(canvas, progressDialog);
72
 
73
+ // 4. Wheel listener ---------------------------------------------------------
74
  canvas.addEventListener('wheel', e => {
75
  if (cameraEntity && cameraEntity.script && cameraEntity.script.orbitCamera) {
76
  const orbitCam = cameraEntity.script.orbitCamera;
 
87
 
88
  progressDialog.style.display = 'block';
89
 
90
+ // 5. Import PlayCanvas -------------------------------------------------------
91
  if (!pc) {
92
  pc = await import("https://esm.run/playcanvas");
93
  window.pc = pc;
94
  }
95
 
96
  try {
97
+ // 6. Setup device & app ----------------------------------------------------
98
+ // --- NEW: robust WebGL-2 → WebGL-1 fallback (fixes iPhone blank screen) ---
99
+ async function getGraphicsDevice(preferWebgl2 = true) {
100
+ const baseOpts = {
101
+ glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
102
+ twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
103
+ antialias: false
104
+ };
105
+ try {
106
+ return await pc.createGraphicsDevice(canvas, {
107
+ ...baseOpts,
108
+ deviceTypes: preferWebgl2 ? ["webgl2"] : ["webgl1"]
109
+ });
110
+ } catch (err) {
111
+ if (preferWebgl2) {
112
+ console.warn("[viewer] WebGL2 unavailable, retrying with WebGL1 …", err);
113
+ return await pc.createGraphicsDevice(canvas, {
114
+ ...baseOpts,
115
+ deviceTypes: ["webgl1"]
116
+ });
117
+ }
118
+ throw err;
119
+ }
120
+ }
121
+
122
+ const device = await getGraphicsDevice(true); // first try WebGL2
123
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
124
 
125
  const opts = new pc.AppOptions();
 
145
  app = new pc.Application(canvas, opts);
146
  app.setCanvasFillMode(pc.FILLMODE_NONE);
147
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
 
 
148
 
149
+ // 7. Resize observer -------------------------------------------------------
150
  resizeObserver = new ResizeObserver(entries => {
151
  for (const entry of entries) {
152
  const { width, height } = entry.contentRect;
 
164
  window.removeEventListener('resize', resizeCanvas);
165
  });
166
 
167
+ // 8. Assets ----------------------------------------------------------------
168
  const assets = {
169
  model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
170
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
 
197
  app.start();
198
  app.scene.envAtlas = assets.hdr.resource;
199
 
200
+ // 9. Scene ----------------------------------------------------------------
201
  modelEntity = new pc.Entity('model');
202
  modelEntity.addComponent('gsplat', { asset: assets.model });
203
  modelEntity.setLocalPosition(modelX, modelY, modelZ);
 
205
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
206
  app.root.addChild(modelEntity);
207
 
 
208
  const dirLight = new pc.Entity('Cascaded Light');
209
  dirLight.addComponent('light', {
210
  type: 'directional',
 
225
  dirLight.setLocalEulerAngles(0, 0, 0);
226
  app.root.addChild(dirLight);
227
 
 
228
  const galleryEntity = assets.galerie.resource.instantiateRenderEntity();
229
  app.root.addChild(galleryEntity);
230
 
231
+ // 10. Camera --------------------------------------------------------------
232
  cameraEntity = new pc.Entity('camera');
233
  cameraEntity.addComponent('camera', {
234
  clearColor: config.canvas_background
235
  ? parseInt(config.canvas_background.substr(1, 2), 16) / 255
236
  : 0,
 
237
  });
238
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
239
  cameraEntity.lookAt(modelEntity.getPosition());
 
270
  });
271
  app.root.addChild(cameraEntity);
272
 
273
+ // 11. Misc per-frame guard & resize --------------------------------------
274
  app.once('update', () => resetViewerCamera());
275
  app.on('update', dt => {
276
  if (cameraEntity) {
 
281
  }
282
  });
283
 
 
284
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
285
 
286
+ // 12. Tooltips ------------------------------------------------------------
287
  try {
288
  const tooltipsModule = await import('./tooltips.js');
289
  tooltipsModule.initializeTooltips({
 
308
  }
309
  }
310
 
311
+ // -----------------------------------------------------------------------------
312
+ // resetViewerCamera / cleanupViewer remain UNCHANGED
313
+ // -----------------------------------------------------------------------------
314
  export function resetViewerCamera() {
315
  try {
316
  if (!cameraEntity || !modelEntity || !app) return;
 
368
  modelEntity = null;
369
  viewerInitialized = false;
370
 
 
371
  if (resizeObserver) {
372
  resizeObserver.disconnect();
373
  resizeObserver = null;