MikaFil commited on
Commit
e87f3a1
·
verified ·
1 Parent(s): 21ac916

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +103 -75
viewer.js CHANGED
@@ -1,7 +1,7 @@
1
  // viewer.js
2
  // ==============================
3
 
4
- let pc; // PlayCanvas module
5
  export let app = null;
6
  let cameraEntity = null;
7
  let modelEntity = null;
@@ -13,17 +13,18 @@ let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, min
13
  let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
14
  let plyUrl, glbUrl;
15
 
16
- // Platform flags
17
- const isIOS = typeof navigator !== "undefined" && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
18
- const isMobile = isIOS || (typeof navigator !== "undefined" && /Android/i.test(navigator.userAgent));
19
-
20
- /**
21
- * initializeViewer(config, instanceId)
22
- */
23
  export async function initializeViewer(config, instanceId) {
24
- if (viewerInitialized) return;
 
 
 
 
 
 
 
 
25
 
26
- // 1. Config
27
  plyUrl = config.ply_url;
28
  glbUrl = config.glb_url;
29
  minZoom = parseFloat(config.minZoom || "1");
@@ -54,39 +55,41 @@ export async function initializeViewer(config, instanceId) {
54
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
55
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
56
 
57
- // 2. DOM
58
  const canvasId = 'canvas-' + instanceId;
59
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
60
  const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
61
  const viewerContainer = document.getElementById('viewer-container-' + instanceId);
62
 
63
- // 3. Canvas
 
 
64
  let oldCanvas = document.getElementById(canvasId);
65
  if (oldCanvas) oldCanvas.remove();
66
  const canvas = document.createElement('canvas');
67
  canvas.id = canvasId;
68
  canvas.className = 'ply-canvas';
69
  canvas.style.zIndex = "1";
70
- canvas.style.display = 'block';
71
- canvas.style.width = "100%";
72
- canvas.style.height = "100%";
73
- canvas.setAttribute('tabindex', '-1');
74
- // Prevents iOS/Chrome pinch-zoom
75
- canvas.style.touchAction = 'none';
76
  viewerContainer.insertBefore(canvas, progressDialog);
 
77
 
78
- // 3b. iOS fix: force correct initial size on load
79
- function setCanvasSizeToParent() {
80
- canvas.width = viewerContainer.clientWidth * window.devicePixelRatio;
81
- canvas.height = viewerContainer.clientHeight * window.devicePixelRatio;
82
- canvas.style.width = "100%";
83
- canvas.style.height = "100%";
84
- }
85
- setCanvasSizeToParent();
86
-
87
- // 4. Wheel and touch event prevention (for zoom gesture hijack)
88
- // Wheel = desktop, touchmove = iOS
 
 
 
 
89
  canvas.addEventListener('wheel', e => {
 
90
  if (cameraEntity && cameraEntity.script && cameraEntity.script.orbitCamera) {
91
  const orbitCam = cameraEntity.script.orbitCamera;
92
  const sens = (cameraEntity.script.orbitCameraInputMouse?.distanceSensitivity) || 0.4;
@@ -100,23 +103,23 @@ export async function initializeViewer(config, instanceId) {
100
  }
101
  }, { passive: false });
102
 
103
- // Prevent iOS Safari double-finger pinch/zoom on canvas
104
- canvas.addEventListener('touchmove', function(e) {
105
- if (e.touches.length > 1) e.preventDefault();
106
- }, { passive: false });
107
-
108
- // 5. Show progress
109
  progressDialog.style.display = 'block';
 
110
 
111
- // 6. Import PlayCanvas
112
  if (!pc) {
113
- pc = await import("https://esm.run/playcanvas");
114
- window.pc = pc;
 
 
 
 
 
 
115
  }
116
 
117
  try {
118
- // 7. Setup device & app
119
- setCanvasSizeToParent(); // ensure correct size before context creation
120
  const device = await pc.createGraphicsDevice(canvas, {
121
  deviceTypes: ["webgl2"],
122
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
@@ -149,41 +152,48 @@ export async function initializeViewer(config, instanceId) {
149
  app.setCanvasFillMode(pc.FILLMODE_NONE);
150
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
151
 
152
- // 7b. Attach ResizeObserver to keep canvas in sync with container size
153
- function resizeCanvasToParent() {
154
- if (!viewerContainer) return;
155
- setCanvasSizeToParent();
156
- app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
157
- }
158
- resizeObserver = new window.ResizeObserver(resizeCanvasToParent);
 
 
159
  resizeObserver.observe(viewerContainer);
160
 
161
- // Also on window resize (iOS orientation change)
162
- window.addEventListener('resize', resizeCanvasToParent);
 
163
 
164
  app.on('destroy', () => {
165
- window.removeEventListener('resize', resizeCanvasToParent);
166
- resizeObserver && resizeObserver.disconnect();
167
  });
168
 
169
- // 8. Assets
 
 
170
  const assets = {
171
  model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
172
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
173
- galerie: glbUrl ? new pc.Asset('galerie', 'container', { url: glbUrl }) : null,
174
  hdr: new pc.Asset('hdr', 'texture', {
175
  url: "https://huggingface.co/datasets/bilca/ply_files/resolve/main/galeries/blanc.png"
176
  }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false })
177
  };
178
 
179
- // Remove null assets
180
- const toLoad = Object.values(assets).filter(Boolean);
181
- const loader = new pc.AssetListLoader(toLoad, app.assets);
182
 
 
183
  let lastProg = 0;
184
- assets.model.on('load', () => progressDialog.style.display = 'none');
 
 
 
185
  assets.model.on('error', err => {
186
  progressDialog.innerHTML = `<p style="color: red">Error loading model: ${err}</p>`;
 
187
  });
188
 
189
  const progCheck = setInterval(() => {
@@ -198,8 +208,10 @@ export async function initializeViewer(config, instanceId) {
198
  }, 100);
199
 
200
  loader.load(async () => {
 
201
  app.start();
202
  app.scene.envAtlas = assets.hdr.resource;
 
203
 
204
  // Model entity
205
  modelEntity = new pc.Entity('model');
@@ -208,6 +220,7 @@ export async function initializeViewer(config, instanceId) {
208
  modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
209
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
210
  app.root.addChild(modelEntity);
 
211
 
212
  // Light
213
  const dirLight = new pc.Entity('Cascaded Light');
@@ -229,12 +242,13 @@ export async function initializeViewer(config, instanceId) {
229
  });
230
  dirLight.setLocalEulerAngles(0, 0, 0);
231
  app.root.addChild(dirLight);
 
232
 
233
- // Gallery GLB (floor) only added if it loads, never touched by UI
234
- let galleryEntity = null;
235
  if (assets.galerie && assets.galerie.resource && assets.galerie.resource.instantiateRenderEntity) {
236
- galleryEntity = assets.galerie.resource.instantiateRenderEntity();
237
  app.root.addChild(galleryEntity);
 
238
  }
239
 
240
  // Camera setup
@@ -259,7 +273,6 @@ export async function initializeViewer(config, instanceId) {
259
  yawAngleMax: maxAzimuth,
260
  yawAngleMin: minAzimuth,
261
  minPivotY: minPivotY,
262
- minY: minY,
263
  frameOnStart: false
264
  }
265
  });
@@ -269,9 +282,10 @@ export async function initializeViewer(config, instanceId) {
269
  distanceSensitivity: isMobile ? 0.5 : 0.4
270
  }
271
  });
272
- // Prevent PlayCanvas from handling default mousewheel (which can bubble)
273
  if (cameraEntity.script.orbitCameraInputMouse) {
274
- cameraEntity.script.orbitCameraInputMouse.onMouseWheel = function() {};
 
 
275
  }
276
  cameraEntity.script.create('orbitCameraInputTouch', {
277
  attributes: {
@@ -280,23 +294,30 @@ export async function initializeViewer(config, instanceId) {
280
  }
281
  });
282
  app.root.addChild(cameraEntity);
 
283
 
284
  // Reset & constrain updates
285
- app.once('update', () => resetViewerCamera());
 
 
 
286
  app.on('update', dt => {
287
  if (cameraEntity) {
288
  const pos = cameraEntity.getPosition();
289
- if (pos.y < minY) cameraEntity.setPosition(pos.x, minY, pos.z);
 
 
290
  }
291
  });
292
 
293
- // Final resize after all entities
294
- resizeCanvasToParent();
 
295
 
296
- // Tooltips (only ever affect tooltip spheres, not floor/GLB)
297
  try {
298
  const tooltipsModule = await import('./tooltips.js');
299
- tooltipsModule.initializeTooltips({
300
  app,
301
  cameraEntity,
302
  modelEntity,
@@ -304,22 +325,24 @@ export async function initializeViewer(config, instanceId) {
304
  defaultVisible: !!config.showTooltipsDefault,
305
  moveDuration: config.tooltipMoveDuration || 0.6
306
  });
307
- } catch (e) {}
 
 
 
308
 
309
  progressDialog.style.display = 'none';
 
310
  viewerInitialized = true;
311
  });
312
 
313
- // iOS-specific: sometimes context loss (suspend on background)
314
- // PlayCanvas auto-recovers, but we need to restore canvas size
315
- canvas.addEventListener('webglcontextrestored', setCanvasSizeToParent, false);
316
-
317
  } catch (error) {
318
  progressDialog.innerHTML = `<p style="color: red">Error loading viewer: ${error && error.message ? error.message : error}</p>`;
 
319
  }
320
  }
321
 
322
  export function resetViewerCamera() {
 
323
  try {
324
  if (!cameraEntity || !modelEntity || !app) return;
325
  const orbitCam = cameraEntity.script.orbitCamera;
@@ -360,10 +383,14 @@ export function resetViewerCamera() {
360
  if (orbitCam._updatePosition) orbitCam._updatePosition();
361
 
362
  tempEnt.destroy();
363
- } catch (e) {}
 
 
 
364
  }
365
 
366
  export function cleanupViewer() {
 
367
  if (app) {
368
  try {
369
  app.destroy();
@@ -378,4 +405,5 @@ export function cleanupViewer() {
378
  resizeObserver.disconnect();
379
  resizeObserver = null;
380
  }
 
381
  }
 
1
  // viewer.js
2
  // ==============================
3
 
4
+ let pc; // will hold the PlayCanvas module once imported
5
  export let app = null;
6
  let cameraEntity = null;
7
  let modelEntity = null;
 
13
  let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
14
  let plyUrl, glbUrl;
15
 
 
 
 
 
 
 
 
16
  export async function initializeViewer(config, instanceId) {
17
+ alert("[viewer.js] initializeViewer called");
18
+
19
+ if (viewerInitialized) {
20
+ alert("[viewer.js] Already initialized, skipping.");
21
+ return;
22
+ }
23
+
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
+ alert("[viewer.js] DOM elements grabbed");
65
+
66
+ // 3. Create <canvas>
67
  let oldCanvas = document.getElementById(canvasId);
68
  if (oldCanvas) oldCanvas.remove();
69
  const canvas = document.createElement('canvas');
70
  canvas.id = canvasId;
71
  canvas.className = 'ply-canvas';
72
  canvas.style.zIndex = "1";
 
 
 
 
 
 
73
  viewerContainer.insertBefore(canvas, progressDialog);
74
+ alert("[viewer.js] Canvas inserted");
75
 
76
+ // Prevent iOS Safari from zooming the whole page via gestures on canvas
77
+ canvas.addEventListener('touchstart', function(e) {
78
+ if (e.touches.length > 1) e.preventDefault();
79
+ }, { passive: false });
80
+ canvas.addEventListener('gesturestart', function(e) {
81
+ e.preventDefault();
82
+ });
83
+ canvas.addEventListener('gesturechange', function(e) {
84
+ e.preventDefault();
85
+ });
86
+ canvas.addEventListener('gestureend', function(e) {
87
+ e.preventDefault();
88
+ });
89
+
90
+ // 4. Wheel listener
91
  canvas.addEventListener('wheel', e => {
92
+ alert("[viewer.js] Wheel event on canvas");
93
  if (cameraEntity && cameraEntity.script && cameraEntity.script.orbitCamera) {
94
  const orbitCam = cameraEntity.script.orbitCamera;
95
  const sens = (cameraEntity.script.orbitCameraInputMouse?.distanceSensitivity) || 0.4;
 
103
  }
104
  }, { passive: false });
105
 
 
 
 
 
 
 
106
  progressDialog.style.display = 'block';
107
+ alert("[viewer.js] Canvas ready, loading PlayCanvas...");
108
 
109
+ // 5. Import PlayCanvas
110
  if (!pc) {
111
+ try {
112
+ pc = await import("https://esm.run/playcanvas");
113
+ window.pc = pc;
114
+ alert("[viewer.js] PlayCanvas loaded");
115
+ } catch (err) {
116
+ alert("[viewer.js] ERROR loading PlayCanvas: " + err);
117
+ return;
118
+ }
119
  }
120
 
121
  try {
122
+ // 6. Setup device & app
 
123
  const device = await pc.createGraphicsDevice(canvas, {
124
  deviceTypes: ["webgl2"],
125
  glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
 
152
  app.setCanvasFillMode(pc.FILLMODE_NONE);
153
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
154
 
155
+ // Attach ResizeObserver to keep canvas in sync with container size
156
+ resizeObserver = new ResizeObserver(entries => {
157
+ for (const entry of entries) {
158
+ const { width, height } = entry.contentRect;
159
+ if (app) {
160
+ app.resizeCanvas(width, height);
161
+ }
162
+ }
163
+ });
164
  resizeObserver.observe(viewerContainer);
165
 
166
+ window.addEventListener('resize', () => {
167
+ if (app) app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
168
+ });
169
 
170
  app.on('destroy', () => {
171
+ window.removeEventListener('resize', resizeCanvas);
 
172
  });
173
 
174
+ alert("[viewer.js] PlayCanvas app initialized");
175
+
176
+ // 7. Assets
177
  const assets = {
178
  model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
179
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
180
+ galerie: new pc.Asset('galerie', 'container', { url: glbUrl }),
181
  hdr: new pc.Asset('hdr', 'texture', {
182
  url: "https://huggingface.co/datasets/bilca/ply_files/resolve/main/galeries/blanc.png"
183
  }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false })
184
  };
185
 
186
+ alert("[viewer.js] Assets declared");
 
 
187
 
188
+ const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
189
  let lastProg = 0;
190
+ assets.model.on('load', () => {
191
+ progressDialog.style.display = 'none';
192
+ alert("[viewer.js] Main model loaded");
193
+ });
194
  assets.model.on('error', err => {
195
  progressDialog.innerHTML = `<p style="color: red">Error loading model: ${err}</p>`;
196
+ alert("[viewer.js] ERROR loading main model: " + err);
197
  });
198
 
199
  const progCheck = setInterval(() => {
 
208
  }, 100);
209
 
210
  loader.load(async () => {
211
+ alert("[viewer.js] Asset loader complete. Starting app...");
212
  app.start();
213
  app.scene.envAtlas = assets.hdr.resource;
214
+ alert("[viewer.js] Scene envAtlas set");
215
 
216
  // Model entity
217
  modelEntity = new pc.Entity('model');
 
220
  modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
221
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
222
  app.root.addChild(modelEntity);
223
+ alert("[viewer.js] Main model entity added");
224
 
225
  // Light
226
  const dirLight = new pc.Entity('Cascaded Light');
 
242
  });
243
  dirLight.setLocalEulerAngles(0, 0, 0);
244
  app.root.addChild(dirLight);
245
+ alert("[viewer.js] Directional light entity added");
246
 
247
+ // Gallery GLB
 
248
  if (assets.galerie && assets.galerie.resource && assets.galerie.resource.instantiateRenderEntity) {
249
+ const galleryEntity = assets.galerie.resource.instantiateRenderEntity();
250
  app.root.addChild(galleryEntity);
251
+ alert("[viewer.js] Gallery GLB entity added");
252
  }
253
 
254
  // Camera setup
 
273
  yawAngleMax: maxAzimuth,
274
  yawAngleMin: minAzimuth,
275
  minPivotY: minPivotY,
 
276
  frameOnStart: false
277
  }
278
  });
 
282
  distanceSensitivity: isMobile ? 0.5 : 0.4
283
  }
284
  });
 
285
  if (cameraEntity.script.orbitCameraInputMouse) {
286
+ cameraEntity.script.orbitCameraInputMouse.onMouseWheel = function() {
287
+ alert("[viewer.js] onMouseWheel (custom suppressed)");
288
+ };
289
  }
290
  cameraEntity.script.create('orbitCameraInputTouch', {
291
  attributes: {
 
294
  }
295
  });
296
  app.root.addChild(cameraEntity);
297
+ alert("[viewer.js] Camera entity added and set up");
298
 
299
  // Reset & constrain updates
300
+ app.once('update', () => {
301
+ alert("[viewer.js] First app update (reset camera)");
302
+ resetViewerCamera();
303
+ });
304
  app.on('update', dt => {
305
  if (cameraEntity) {
306
  const pos = cameraEntity.getPosition();
307
+ if (pos.y < minY) {
308
+ cameraEntity.setPosition(pos.x, minY, pos.z);
309
+ }
310
  }
311
  });
312
 
313
+ // Final resize
314
+ app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
315
+ alert("[viewer.js] Canvas resized to viewer container");
316
 
317
+ // Tooltips
318
  try {
319
  const tooltipsModule = await import('./tooltips.js');
320
+ await tooltipsModule.initializeTooltips({
321
  app,
322
  cameraEntity,
323
  modelEntity,
 
325
  defaultVisible: !!config.showTooltipsDefault,
326
  moveDuration: config.tooltipMoveDuration || 0.6
327
  });
328
+ alert("[viewer.js] Tooltips initialized");
329
+ } catch (e) {
330
+ alert("[viewer.js] Tooltips.js import/init failed: " + e);
331
+ }
332
 
333
  progressDialog.style.display = 'none';
334
+ alert("[viewer.js] App fully loaded!");
335
  viewerInitialized = true;
336
  });
337
 
 
 
 
 
338
  } catch (error) {
339
  progressDialog.innerHTML = `<p style="color: red">Error loading viewer: ${error && error.message ? error.message : error}</p>`;
340
+ alert("[viewer.js] ERROR in PlayCanvas app setup: " + (error && error.message ? error.message : error));
341
  }
342
  }
343
 
344
  export function resetViewerCamera() {
345
+ alert("[viewer.js] resetViewerCamera called");
346
  try {
347
  if (!cameraEntity || !modelEntity || !app) return;
348
  const orbitCam = cameraEntity.script.orbitCamera;
 
383
  if (orbitCam._updatePosition) orbitCam._updatePosition();
384
 
385
  tempEnt.destroy();
386
+ alert("[viewer.js] resetViewerCamera completed");
387
+ } catch (e) {
388
+ alert("[viewer.js] ERROR in resetViewerCamera: " + e);
389
+ }
390
  }
391
 
392
  export function cleanupViewer() {
393
+ alert("[viewer.js] cleanupViewer called");
394
  if (app) {
395
  try {
396
  app.destroy();
 
405
  resizeObserver.disconnect();
406
  resizeObserver = null;
407
  }
408
+ alert("[viewer.js] cleanupViewer completed");
409
  }