MikaFil commited on
Commit
28e8e3c
·
verified ·
1 Parent(s): 1e036fa

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +112 -191
viewer.js CHANGED
@@ -1,7 +1,5 @@
1
  // viewer.js
2
  // ==============================
3
- // PlayCanvas viewer: iOS/Safari deep step-by-step debug edition
4
- // ==============================
5
 
6
  let pc; // will hold the PlayCanvas module once imported
7
  export let app = null;
@@ -15,27 +13,18 @@ let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, min
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,13 +55,13 @@ export async function initializeViewer(config, instanceId) {
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,13 +70,6 @@ export async function initializeViewer(config, instanceId) {
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) {
@@ -109,20 +91,19 @@ export async function initializeViewer(config, instanceId) {
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("Attemptiong 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;
@@ -148,8 +129,6 @@ export async function initializeViewer(config, instanceId) {
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) {
@@ -180,12 +159,8 @@ export async function initializeViewer(config, instanceId) {
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("[viewer.js] model 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
  });
@@ -202,170 +177,118 @@ export async function initializeViewer(config, instanceId) {
202
  }, 100);
203
 
204
  loader.load(async () => {
205
- try {
206
- if (isIOS || isSafari) alert("[viewer.js] loader.load callback: assets ready!");
207
-
208
- app.start();
209
- if (isIOS || isSafari) alert("[viewer.js] app.start() called");
210
-
211
- app.scene.envAtlas = assets.hdr.resource;
212
- if (isIOS || isSafari) alert("[viewer.js] HDR environment applied");
213
-
214
- // Model entity
215
- modelEntity = new pc.Entity('model');
216
- modelEntity.addComponent('gsplat', { asset: assets.model });
217
- modelEntity.setLocalPosition(modelX, modelY, modelZ);
218
- modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
219
- modelEntity.setLocalScale(modelScale, modelScale, modelScale);
220
- app.root.addChild(modelEntity);
221
- if (isIOS || isSafari) alert("[viewer.js] Model entity created and added to scene");
222
-
223
- // Light
224
- const dirLight = new pc.Entity('Cascaded Light');
225
- dirLight.addComponent('light', {
226
- type: 'directional',
227
- color: pc.Color.WHITE,
228
- shadowBias: 0.3,
229
- normalOffsetBias: 0.2,
230
- intensity: 1.0,
231
- soft: true,
232
- shadowResolution: 4096,
233
- penumbraSize: 7,
234
- penumbraFalloff: 1.5,
235
- shadowSamples: 128,
236
- shadowBlockerSamples: 16,
237
- castShadows: true,
238
- shadowType: pc.SHADOW_PCSS_32F,
239
- shadowDistance: 1000
240
- });
241
- dirLight.setLocalEulerAngles(0, 0, 0);
242
- app.root.addChild(dirLight);
243
- if (isIOS || isSafari) alert("[viewer.js] Directional light added");
244
-
245
- // Gallery GLB
246
- const galleryEntity = assets.galerie.resource.instantiateRenderEntity();
247
- app.root.addChild(galleryEntity);
248
- if (isIOS || isSafari) alert("[viewer.js] Gallery (GLB) entity added");
249
-
250
- // Camera setup
251
- cameraEntity = new pc.Entity('camera');
252
- cameraEntity.addComponent('camera', {
253
- clearColor: config.canvas_background
254
- ? parseInt(config.canvas_background.substr(1, 2), 16) / 255
255
- : 0,
256
- });
257
- cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
258
- cameraEntity.lookAt(modelEntity.getPosition());
259
-
260
- cameraEntity.addComponent('script');
261
- cameraEntity.script.create('orbitCamera', {
262
- attributes: {
263
- inertiaFactor: 0.2,
264
- focusEntity: modelEntity,
265
- distanceMax: maxZoom,
266
- distanceMin: minZoom,
267
- pitchAngleMax: maxAngle,
268
- pitchAngleMin: minAngle,
269
- yawAngleMax: maxAzimuth,
270
- yawAngleMin: minAzimuth,
271
- minPivotY: minPivotY,
272
- frameOnStart: false
273
- }
274
- });
275
- cameraEntity.script.create('orbitCameraInputMouse', {
276
- attributes: {
277
- orbitSensitivity: isMobile ? 0.6 : 0.3,
278
- distanceSensitivity: isMobile ? 0.5 : 0.4
279
- }
280
- });
281
- if (cameraEntity.script.orbitCameraInputMouse) {
282
- cameraEntity.script.orbitCameraInputMouse.onMouseWheel = function() {};
283
  }
284
- cameraEntity.script.create('orbitCameraInputTouch', {
285
- attributes: {
286
- orbitSensitivity: 0.6,
287
- distanceSensitivity: 0.5
288
- }
289
- });
290
- app.root.addChild(cameraEntity);
291
- if (isIOS || isSafari) alert("[viewer.js] Camera entity created, configured and added to scene");
292
-
293
- // Camera reset/scene visible
294
- app.once('update', () => {
295
- resetViewerCamera();
296
- if (isIOS || isSafari) alert("[viewer.js] Camera reset to defaults after first update");
297
- });
298
-
299
- // --- MAIN: Insert debug 'prerender' hook (fires before *every* frame render) ---
300
- let prerenderAlerted = false;
301
- app.on('prerender', function () {
302
- if ((isIOS || isSafari) && !prerenderAlerted) {
303
- prerenderAlerted = true;
304
- const gl = app.graphicsDevice.gl;
305
- let glInfo = "";
306
- try {
307
- glInfo = gl ? "WebGL context exists, viewport: " + gl.drawingBufferWidth + "x" + gl.drawingBufferHeight : "No GL context";
308
- } catch (e) {}
309
- alert("[viewer.js] prerender: FIRST draw! " + glInfo);
310
- console.log("[viewer.js] prerender: FIRST draw!", glInfo);
311
- }
312
- });
313
-
314
- app.on('update', dt => {
315
- if (cameraEntity) {
316
- const pos = cameraEntity.getPosition();
317
- if (pos.y < minY) {
318
- cameraEntity.setPosition(pos.x, minY, pos.z);
319
- }
320
- }
321
- });
322
-
323
- // --- iOS/Safari workaround: force resize after delay in case initial canvas size is 0x0 ---
324
- if (isIOS || isSafari) {
325
- setTimeout(() => {
326
- try {
327
- canvas.width = viewerContainer.clientWidth || 320;
328
- canvas.height = viewerContainer.clientHeight || 240;
329
- app.resizeCanvas(canvas.width, canvas.height);
330
- console.log("[viewer.js] iOS canvas forced resize:", canvas.width, canvas.height);
331
- } catch (e) {
332
- console.warn("[viewer.js] iOS resize workaround failed:", e);
333
- }
334
- }, 350);
335
- } else {
336
- app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
337
  }
338
-
339
- // Tooltips
340
- try {
341
- const tooltipsModule = await import('./tooltips.js');
342
- tooltipsModule.initializeTooltips({
343
- app,
344
- cameraEntity,
345
- modelEntity,
346
- tooltipsUrl: config.tooltips_url,
347
- defaultVisible: !!config.showTooltipsDefault,
348
- moveDuration: config.tooltipMoveDuration || 0.6
349
- });
350
- if (isIOS || isSafari) alert("[viewer.js] Tooltips loaded");
351
- } catch (e) {
352
- if (isIOS || isSafari) alert("[viewer.js] Error loading tooltips.js: " + e);
353
- console.error("Error loading tooltips.js:", e);
 
 
 
 
354
  }
 
355
 
356
- progressDialog.style.display = 'none';
357
- viewerInitialized = true;
358
- if (isIOS || isSafari) alert("[viewer.js] Viewer is fully initialized!");
359
 
360
- } catch (innerErr) {
361
- alert("[viewer.js] ERROR during scene setup: " + (innerErr && innerErr.message ? innerErr.message : innerErr));
362
- console.error("[viewer.js] Scene setup error:", innerErr);
363
- throw innerErr;
 
 
 
 
 
 
 
 
 
364
  }
 
 
 
365
  });
366
 
367
  } catch (error) {
368
- alert("Error initializing PlayCanvas viewer: " + error);
369
  console.error("Error initializing PlayCanvas viewer:", error);
370
  progressDialog.innerHTML = `<p style="color: red">Error loading viewer: ${error.message}</p>`;
371
  }
@@ -412,9 +335,7 @@ export function resetViewerCamera() {
412
  orbitCam._updatePosition && orbitCam._updatePosition();
413
 
414
  tempEnt.destroy();
415
- if (isIOSorSafari()) alert("[viewer.js] Camera reset to defaults");
416
  } catch (e) {
417
- alert("[viewer.js] Error resetting camera: " + e);
418
  console.error("Error resetting camera:", e);
419
  }
420
  }
 
1
  // viewer.js
2
  // ==============================
 
 
3
 
4
  let pc; // will hold the PlayCanvas module once imported
5
  export let app = null;
 
13
  let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
14
  let plyUrl, glbUrl;
15
 
 
 
 
 
 
 
 
 
 
16
  /**
17
  * initializeViewer(config, instanceId)
18
  */
19
  export async function initializeViewer(config, instanceId) {
20
  if (viewerInitialized) return;
21
 
22
+ // Detect iOS and Safari
23
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
24
  const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
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) {
 
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
+ // ---- CRUCIAL FOR iOS/Safari BLANK SCREEN BUG: ----
104
+ preserveDrawingBuffer: isIOS || isSafari ? true : false
105
  });
106
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
 
107
 
108
  const opts = new pc.AppOptions();
109
  opts.graphicsDevice = device;
 
129
  app.setCanvasFillMode(pc.FILLMODE_NONE);
130
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
131
 
 
 
132
  // Attach ResizeObserver to keep canvas in sync with container size
133
  resizeObserver = new ResizeObserver(entries => {
134
  for (const entry of entries) {
 
159
 
160
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
161
  let lastProg = 0;
162
+ assets.model.on('load', () => progressDialog.style.display = 'none');
 
 
 
163
  assets.model.on('error', err => {
 
164
  console.error("Error loading PLY file:", err);
165
  progressDialog.innerHTML = `<p style="color: red">Error loading model: ${err}</p>`;
166
  });
 
177
  }, 100);
178
 
179
  loader.load(async () => {
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);
187
+ modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
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',
195
+ color: pc.Color.WHITE,
196
+ shadowBias: 0.3,
197
+ normalOffsetBias: 0.2,
198
+ intensity: 1.0,
199
+ soft: true,
200
+ shadowResolution: 4096,
201
+ penumbraSize: 7,
202
+ penumbraFalloff: 1.5,
203
+ shadowSamples: 128,
204
+ shadowBlockerSamples: 16,
205
+ castShadows: true,
206
+ shadowType: pc.SHADOW_PCSS_32F,
207
+ shadowDistance: 1000
208
+ });
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
+ });
223
+ cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
224
+ cameraEntity.lookAt(modelEntity.getPosition());
225
+
226
+ cameraEntity.addComponent('script');
227
+ cameraEntity.script.create('orbitCamera', {
228
+ attributes: {
229
+ inertiaFactor: 0.2,
230
+ focusEntity: modelEntity,
231
+ distanceMax: maxZoom,
232
+ distanceMin: minZoom,
233
+ pitchAngleMax: maxAngle,
234
+ pitchAngleMin: minAngle,
235
+ yawAngleMax: maxAzimuth,
236
+ yawAngleMin: minAzimuth,
237
+ minPivotY: minPivotY,
238
+ frameOnStart: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  }
240
+ });
241
+ cameraEntity.script.create('orbitCameraInputMouse', {
242
+ attributes: {
243
+ orbitSensitivity: isMobile ? 0.6 : 0.3,
244
+ distanceSensitivity: isMobile ? 0.5 : 0.4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  }
246
+ });
247
+ if (cameraEntity.script.orbitCameraInputMouse) {
248
+ cameraEntity.script.orbitCameraInputMouse.onMouseWheel = function() {};
249
+ }
250
+ cameraEntity.script.create('orbitCameraInputTouch', {
251
+ attributes: {
252
+ orbitSensitivity: 0.6,
253
+ distanceSensitivity: 0.5
254
+ }
255
+ });
256
+ app.root.addChild(cameraEntity);
257
+
258
+ // Reset & constrain updates
259
+ app.once('update', () => resetViewerCamera());
260
+ app.on('update', dt => {
261
+ if (cameraEntity) {
262
+ const pos = cameraEntity.getPosition();
263
+ if (pos.y < minY) {
264
+ cameraEntity.setPosition(pos.x, minY, pos.z);
265
+ }
266
  }
267
+ });
268
 
269
+ // Final resize
270
+ app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
 
271
 
272
+ // Tooltips
273
+ try {
274
+ const tooltipsModule = await import('./tooltips.js');
275
+ tooltipsModule.initializeTooltips({
276
+ app,
277
+ cameraEntity,
278
+ modelEntity,
279
+ tooltipsUrl: config.tooltips_url,
280
+ defaultVisible: !!config.showTooltipsDefault,
281
+ moveDuration: config.tooltipMoveDuration || 0.6
282
+ });
283
+ } catch (e) {
284
+ console.error("Error loading tooltips.js:", e);
285
  }
286
+
287
+ progressDialog.style.display = 'none';
288
+ viewerInitialized = true;
289
  });
290
 
291
  } catch (error) {
 
292
  console.error("Error initializing PlayCanvas viewer:", error);
293
  progressDialog.innerHTML = `<p style="color: red">Error loading viewer: ${error.message}</p>`;
294
  }
 
335
  orbitCam._updatePosition && orbitCam._updatePosition();
336
 
337
  tempEnt.destroy();
 
338
  } catch (e) {
 
339
  console.error("Error resetting camera:", e);
340
  }
341
  }