MikaFil commited on
Commit
c908cc8
·
verified ·
1 Parent(s): 7b855e0

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +92 -134
viewer.js CHANGED
@@ -1,6 +1,6 @@
1
  // viewer.js
2
  // ==============================
3
- // viewer.js (iPhone/Safari context detection & debug)
4
  // ==============================
5
 
6
  let pc; // will hold the PlayCanvas module once imported
@@ -15,28 +15,27 @@ let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, min
15
  let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
16
  let plyUrl, glbUrl;
17
 
18
- function logAndAlert(msg) {
19
- console.log(msg);
20
- if (typeof window !== "undefined" && /iPhone|iPad|iPod|Safari/i.test(navigator.userAgent)) {
21
- alert(msg);
22
- }
 
 
23
  }
24
 
 
 
 
25
  export async function initializeViewer(config, instanceId) {
26
- if (viewerInitialized) {
27
- logAndAlert("Viewer already initialized");
28
- return;
29
- }
30
-
31
- const userAgent = navigator.userAgent;
32
- const isIOS = /iPad|iPhone|iPod/.test(userAgent) && !window.MSStream;
33
- const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
34
- const isMobile = isIOS || /Android/i.test(userAgent);
35
 
36
- logAndAlert(`UserAgent: ${userAgent}`);
37
- logAndAlert(`isIOS: ${isIOS} isSafari: ${isSafari}`);
 
 
38
 
39
- // 1. Read config
40
  plyUrl = config.ply_url;
41
  glbUrl = config.glb_url;
42
  minZoom = parseFloat(config.minZoom || "1");
@@ -67,13 +66,13 @@ export async function initializeViewer(config, instanceId) {
67
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
68
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
69
 
70
- // 2. Grab DOM
71
  const canvasId = 'canvas-' + instanceId;
72
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
73
  const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
74
  const viewerContainer = document.getElementById('viewer-container-' + instanceId);
75
 
76
- // 3. Create <canvas>
77
  let oldCanvas = document.getElementById(canvasId);
78
  if (oldCanvas) oldCanvas.remove();
79
  const canvas = document.createElement('canvas');
@@ -82,6 +81,13 @@ export async function initializeViewer(config, instanceId) {
82
  canvas.style.zIndex = "1";
83
  viewerContainer.insertBefore(canvas, progressDialog);
84
 
 
 
 
 
 
 
 
85
  // 4. Wheel listener
86
  canvas.addEventListener('wheel', e => {
87
  if (cameraEntity && cameraEntity.script && cameraEntity.script.orbitCamera) {
@@ -101,53 +107,24 @@ export async function initializeViewer(config, instanceId) {
101
 
102
  // 5. Import PlayCanvas
103
  if (!pc) {
104
- try {
105
- pc = await import("https://esm.run/playcanvas");
106
- window.pc = pc;
107
- logAndAlert("PlayCanvas module loaded");
108
- } catch (e) {
109
- logAndAlert("PlayCanvas module import FAILED: " + e);
110
- return;
111
- }
112
  }
113
 
114
- // 6. Setup device & app (with robust fallback)
115
- let device = null;
116
  try {
117
- try {
118
- logAndAlert("Attempting WebGL2 context creation");
119
- device = await pc.createGraphicsDevice(canvas, {
120
- deviceTypes: ["webgl2"],
121
- glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
122
- twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
123
- antialias: false
124
- });
125
- logAndAlert("WebGL2 context acquired.");
126
- } catch (err) {
127
- logAndAlert("WebGL2 context failed: " + err);
128
- logAndAlert("Falling back to WebGL1 context…");
129
- device = await pc.createGraphicsDevice(canvas, {
130
- deviceTypes: ["webgl1"],
131
- glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
132
- twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
133
- antialias: false
134
- });
135
- logAndAlert("WebGL1 context acquired.");
136
- }
137
- if (!device) {
138
- logAndAlert("No WebGL context could be created!");
139
- return;
140
- }
141
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
142
- } catch (e) {
143
- logAndAlert("Graphics device creation failed: " + e);
144
- return;
145
- }
146
 
147
- // 7. App setup
148
- let opts;
149
- try {
150
- opts = new pc.AppOptions();
151
  opts.graphicsDevice = device;
152
  opts.mouse = new pc.Mouse(canvas);
153
  opts.touch = new pc.TouchDevice(canvas);
@@ -170,14 +147,10 @@ export async function initializeViewer(config, instanceId) {
170
  app = new pc.Application(canvas, opts);
171
  app.setCanvasFillMode(pc.FILLMODE_NONE);
172
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
173
- logAndAlert("PlayCanvas Application created.");
174
- } catch (e) {
175
- logAndAlert("PlayCanvas Application creation FAILED: " + e);
176
- return;
177
- }
178
 
179
- // Attach ResizeObserver to keep canvas in sync with container size
180
- try {
 
181
  resizeObserver = new ResizeObserver(entries => {
182
  for (const entry of entries) {
183
  const { width, height } = entry.contentRect;
@@ -194,54 +167,26 @@ export async function initializeViewer(config, instanceId) {
194
  app.on('destroy', () => {
195
  window.removeEventListener('resize', resizeCanvas);
196
  });
197
- } catch (e) {
198
- logAndAlert("ResizeObserver setup failed: " + e);
199
- }
200
 
201
- // 8. Asset loader, with CORS workaround if needed
202
- let fetchAssetWithCors = async function(url) {
203
- try {
204
- const resp = await fetch(url, {mode: 'cors', credentials: 'omit', cache: 'force-cache'});
205
- if (!resp.ok) throw new Error("Failed fetch: " + url);
206
- return resp.url;
207
- } catch (e) {
208
- logAndAlert("Asset fetch CORS failed for " + url + ": " + e);
209
- return url; // fallback to original
210
- }
211
- };
212
- let plyAssetUrl = plyUrl;
213
- let glbAssetUrl = glbUrl;
214
- if (isIOS || isSafari) {
215
- plyAssetUrl = await fetchAssetWithCors(plyUrl);
216
- glbAssetUrl = await fetchAssetWithCors(glbUrl);
217
- }
218
-
219
- // 9. Assets
220
- let assets;
221
- try {
222
- assets = {
223
- model: new pc.Asset('gsplat', 'gsplat', { url: plyAssetUrl }),
224
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
225
- galerie: new pc.Asset('galerie', 'container', { url: glbAssetUrl }),
226
  hdr: new pc.Asset('hdr', 'texture', {
227
  url: `https://huggingface.co/datasets/bilca/ply_files/resolve/main/galeries/blanc.png`
228
  }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false })
229
  };
230
- } catch (e) {
231
- logAndAlert("Asset object creation failed: " + e);
232
- return;
233
- }
234
 
235
- // 10. Loader and progress
236
- let loader, lastProg = 0;
237
- try {
238
- loader = new pc.AssetListLoader(Object.values(assets), app.assets);
239
  assets.model.on('load', () => {
 
240
  progressDialog.style.display = 'none';
241
- logAndAlert("PLY asset loaded!");
242
  });
243
  assets.model.on('error', err => {
244
- logAndAlert("Error loading PLY file: " + err);
 
245
  progressDialog.innerHTML = `<p style="color: red">Error loading model: ${err}</p>`;
246
  });
247
 
@@ -255,15 +200,9 @@ export async function initializeViewer(config, instanceId) {
255
  progressIndicator.value = lastProg;
256
  }
257
  }, 100);
258
- } catch (e) {
259
- logAndAlert("AssetListLoader setup failed: " + e);
260
- return;
261
- }
262
 
263
- // 11. Start loading and build scene
264
- try {
265
  loader.load(async () => {
266
- logAndAlert("Asset loader .load() finished, starting PlayCanvas app");
267
  app.start();
268
  app.scene.envAtlas = assets.hdr.resource;
269
 
@@ -342,11 +281,28 @@ export async function initializeViewer(config, instanceId) {
342
  });
343
  app.root.addChild(cameraEntity);
344
 
345
- // Initial camera reset
346
  app.once('update', () => {
347
  resetViewerCamera();
348
- logAndAlert("First update: scene and camera should be visible now!");
349
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  app.on('update', dt => {
351
  if (cameraEntity) {
352
  const pos = cameraEntity.getPosition();
@@ -356,8 +312,21 @@ export async function initializeViewer(config, instanceId) {
356
  }
357
  });
358
 
359
- // Final resize
360
- app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
 
 
 
 
 
 
 
 
 
 
 
 
 
361
 
362
  // Tooltips
363
  try {
@@ -370,29 +339,20 @@ export async function initializeViewer(config, instanceId) {
370
  defaultVisible: !!config.showTooltipsDefault,
371
  moveDuration: config.tooltipMoveDuration || 0.6
372
  });
373
- logAndAlert("Tooltips loaded");
374
  } catch (e) {
375
- logAndAlert("Error loading tooltips.js: " + e);
376
  }
377
 
378
  progressDialog.style.display = 'none';
379
  viewerInitialized = true;
380
- logAndAlert("Viewer is fully initialized!");
381
 
382
- // DEBUG: Draw loop reach on iOS/Safari only
383
- if (isIOS || isSafari) {
384
- let firstDraw = true;
385
- app.on('frameend', function() {
386
- if (firstDraw) {
387
- alert('First draw call reached (Safari/iOS)');
388
- firstDraw = false;
389
- }
390
- });
391
- }
392
  });
393
 
394
  } catch (error) {
395
- logAndAlert("Error initializing PlayCanvas viewer: " + error);
 
396
  progressDialog.innerHTML = `<p style="color: red">Error loading viewer: ${error.message}</p>`;
397
  }
398
  }
@@ -438,9 +398,8 @@ export function resetViewerCamera() {
438
  orbitCam._updatePosition && orbitCam._updatePosition();
439
 
440
  tempEnt.destroy();
441
- logAndAlert("Camera reset to defaults.");
442
  } catch (e) {
443
- logAndAlert("Error resetting camera: " + e);
444
  }
445
  }
446
 
@@ -455,7 +414,6 @@ export function cleanupViewer() {
455
  modelEntity = null;
456
  viewerInitialized = false;
457
 
458
- // Disconnect the ResizeObserver to avoid leaks
459
  if (resizeObserver) {
460
  resizeObserver.disconnect();
461
  resizeObserver = null;
 
1
  // viewer.js
2
  // ==============================
3
+ // PlayCanvas viewer: iOS/Safari canvas/render loop debug/fix edition
4
  // ==============================
5
 
6
  let pc; // will hold the PlayCanvas module once imported
 
15
  let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
16
  let plyUrl, glbUrl;
17
 
18
+ // --- iOS/Safari detection helper
19
+ function isIOSorSafari() {
20
+ const ua = navigator.userAgent;
21
+ return (
22
+ /iPad|iPhone|iPod/.test(ua) ||
23
+ (/Safari/.test(ua) && !/Chrome/.test(ua) && !/Android/.test(ua))
24
+ );
25
  }
26
 
27
+ /**
28
+ * initializeViewer(config, instanceId)
29
+ */
30
  export async function initializeViewer(config, instanceId) {
31
+ if (viewerInitialized) return;
 
 
 
 
 
 
 
 
32
 
33
+ // 1. iOS/Safari detection for debugging
34
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
35
+ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
36
+ const isMobile = isIOS || /Android/i.test(navigator.userAgent);
37
 
38
+ // 2. Read config
39
  plyUrl = config.ply_url;
40
  glbUrl = config.glb_url;
41
  minZoom = parseFloat(config.minZoom || "1");
 
66
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
67
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
68
 
69
+ // 3. Grab DOM
70
  const canvasId = 'canvas-' + instanceId;
71
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
72
  const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
73
  const viewerContainer = document.getElementById('viewer-container-' + instanceId);
74
 
75
+ // 4. Create <canvas>
76
  let oldCanvas = document.getElementById(canvasId);
77
  if (oldCanvas) oldCanvas.remove();
78
  const canvas = document.createElement('canvas');
 
81
  canvas.style.zIndex = "1";
82
  viewerContainer.insertBefore(canvas, progressDialog);
83
 
84
+ // --- iOS canvas workaround: prevent high pixelRatio upscaling (avoid WebGL1/2 buffer mismatch)
85
+ if (isIOS || isSafari) {
86
+ canvas.style.imageRendering = 'pixelated';
87
+ canvas.width = viewerContainer.clientWidth || 320;
88
+ canvas.height = viewerContainer.clientHeight || 240;
89
+ }
90
+
91
  // 4. Wheel listener
92
  canvas.addEventListener('wheel', e => {
93
  if (cameraEntity && cameraEntity.script && cameraEntity.script.orbitCamera) {
 
107
 
108
  // 5. Import PlayCanvas
109
  if (!pc) {
110
+ pc = await import("https://esm.run/playcanvas");
111
+ window.pc = pc;
112
+ if (isIOS || isSafari) alert("PlayCanvas module loaded");
 
 
 
 
 
113
  }
114
 
 
 
115
  try {
116
+ // 6. Setup device & app
117
+ if (isIOS || isSafari) alert("Attempting WebGL2 context creation...");
118
+ const device = await pc.createGraphicsDevice(canvas, {
119
+ deviceTypes: ["webgl2"],
120
+ glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
121
+ twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
122
+ antialias: false
123
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
125
+ if (isIOS || isSafari) alert("WebGL2 context acquired");
 
 
 
126
 
127
+ const opts = new pc.AppOptions();
 
 
 
128
  opts.graphicsDevice = device;
129
  opts.mouse = new pc.Mouse(canvas);
130
  opts.touch = new pc.TouchDevice(canvas);
 
147
  app = new pc.Application(canvas, opts);
148
  app.setCanvasFillMode(pc.FILLMODE_NONE);
149
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
 
 
 
 
 
150
 
151
+ if (isIOS || isSafari) alert("PlayCanvas Application created");
152
+
153
+ // Attach ResizeObserver to keep canvas in sync with container size
154
  resizeObserver = new ResizeObserver(entries => {
155
  for (const entry of entries) {
156
  const { width, height } = entry.contentRect;
 
167
  app.on('destroy', () => {
168
  window.removeEventListener('resize', resizeCanvas);
169
  });
 
 
 
170
 
171
+ // 7. Assets
172
+ const assets = {
173
+ model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
175
+ galerie: new pc.Asset('galerie', 'container', { url: glbUrl }),
176
  hdr: new pc.Asset('hdr', 'texture', {
177
  url: `https://huggingface.co/datasets/bilca/ply_files/resolve/main/galeries/blanc.png`
178
  }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false })
179
  };
 
 
 
 
180
 
181
+ const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
182
+ let lastProg = 0;
 
 
183
  assets.model.on('load', () => {
184
+ if (isIOS || isSafari) alert("PLY asset loaded!");
185
  progressDialog.style.display = 'none';
 
186
  });
187
  assets.model.on('error', err => {
188
+ alert("Error loading PLY file: " + err);
189
+ console.error("Error loading PLY file:", err);
190
  progressDialog.innerHTML = `<p style="color: red">Error loading model: ${err}</p>`;
191
  });
192
 
 
200
  progressIndicator.value = lastProg;
201
  }
202
  }, 100);
 
 
 
 
203
 
 
 
204
  loader.load(async () => {
205
+ if (isIOS || isSafari) alert("Asset loaded .load() finished, starting PlayCanvas app...");
206
  app.start();
207
  app.scene.envAtlas = assets.hdr.resource;
208
 
 
281
  });
282
  app.root.addChild(cameraEntity);
283
 
284
+ // Camera reset/scene visible
285
  app.once('update', () => {
286
  resetViewerCamera();
287
+ if (isIOS || isSafari) alert("Camera reset to defaults");
288
  });
289
+
290
+ // --- MAIN: Insert debug 'prerender' hook (fires before *every* frame render) ---
291
+ let prerenderAlerted = false;
292
+ app.on('prerender', function () {
293
+ if ((isIOS || isSafari) && !prerenderAlerted) {
294
+ prerenderAlerted = true;
295
+ // Additional test: dump framebuffer size and context info
296
+ const gl = app.graphicsDevice.gl;
297
+ let glInfo = "";
298
+ try {
299
+ glInfo = gl ? "WebGL context exists, viewport: " + gl.drawingBufferWidth + "x" + gl.drawingBufferHeight : "No GL context";
300
+ } catch (e) {}
301
+ alert("[viewer.js] prerender: FIRST draw! " + glInfo);
302
+ console.log("[viewer.js] prerender: FIRST draw!", glInfo);
303
+ }
304
+ });
305
+
306
  app.on('update', dt => {
307
  if (cameraEntity) {
308
  const pos = cameraEntity.getPosition();
 
312
  }
313
  });
314
 
315
+ // --- iOS/Safari workaround: force resize after delay in case initial canvas size is 0x0 ---
316
+ if (isIOS || isSafari) {
317
+ setTimeout(() => {
318
+ try {
319
+ canvas.width = viewerContainer.clientWidth || 320;
320
+ canvas.height = viewerContainer.clientHeight || 240;
321
+ app.resizeCanvas(canvas.width, canvas.height);
322
+ console.log("[viewer.js] iOS canvas forced resize:", canvas.width, canvas.height);
323
+ } catch (e) {
324
+ console.warn("[viewer.js] iOS resize workaround failed:", e);
325
+ }
326
+ }, 350);
327
+ } else {
328
+ app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
329
+ }
330
 
331
  // Tooltips
332
  try {
 
339
  defaultVisible: !!config.showTooltipsDefault,
340
  moveDuration: config.tooltipMoveDuration || 0.6
341
  });
342
+ if (isIOS || isSafari) alert("Tooltips loaded");
343
  } catch (e) {
344
+ console.error("Error loading tooltips.js:", e);
345
  }
346
 
347
  progressDialog.style.display = 'none';
348
  viewerInitialized = true;
 
349
 
350
+ if (isIOS || isSafari) alert("Viewer is fully initialized!");
 
 
 
 
 
 
 
 
 
351
  });
352
 
353
  } catch (error) {
354
+ alert("Error initializing PlayCanvas viewer: " + error);
355
+ console.error("Error initializing PlayCanvas viewer:", error);
356
  progressDialog.innerHTML = `<p style="color: red">Error loading viewer: ${error.message}</p>`;
357
  }
358
  }
 
398
  orbitCam._updatePosition && orbitCam._updatePosition();
399
 
400
  tempEnt.destroy();
 
401
  } catch (e) {
402
+ console.error("Error resetting camera:", e);
403
  }
404
  }
405
 
 
414
  modelEntity = null;
415
  viewerInitialized = false;
416
 
 
417
  if (resizeObserver) {
418
  resizeObserver.disconnect();
419
  resizeObserver = null;