MikaFil commited on
Commit
cc93f09
·
verified ·
1 Parent(s): dbe53fb

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +56 -69
viewer.js CHANGED
@@ -1,19 +1,19 @@
1
- // viewer.js
2
 
3
- let pc; // PlayCanvas
4
  export let app = null;
5
  let cameraEntity = null;
6
  let modelEntity = null;
7
  let viewerInitialized = false;
8
  let resizeObserver = null;
9
- let loadTimeout = null; // For iOS load fallback
10
 
 
11
  let chosenCameraX, chosenCameraY, chosenCameraZ;
12
  let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, minY;
13
  let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
14
  let plyUrl, glbUrl;
15
 
16
- // iOS detection
17
  function isIOS() {
18
  return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
19
  }
@@ -25,7 +25,6 @@ function logAndAlert(msg) {
25
  export async function initializeViewer(config, instanceId) {
26
  if (viewerInitialized) return;
27
 
28
- // Config
29
  plyUrl = config.ply_url;
30
  glbUrl = config.glb_url;
31
  minZoom = parseFloat(config.minZoom || "1");
@@ -57,7 +56,7 @@ export async function initializeViewer(config, instanceId) {
57
  chosenCameraY = mobile ? cameraYPhone : cameraY;
58
  chosenCameraZ = mobile ? cameraZPhone : cameraZ;
59
 
60
- // DOM
61
  const canvasId = 'canvas-' + instanceId;
62
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
63
  const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
@@ -69,8 +68,11 @@ export async function initializeViewer(config, instanceId) {
69
  canvas.id = canvasId;
70
  canvas.className = 'ply-canvas';
71
  canvas.style.zIndex = "1";
 
 
72
  viewerContainer.insertBefore(canvas, progressDialog);
73
 
 
74
  canvas.addEventListener('wheel', e => {
75
  if (cameraEntity && cameraEntity.script && cameraEntity.script.orbitCamera) {
76
  const orbitCam = cameraEntity.script.orbitCamera;
@@ -87,9 +89,9 @@ export async function initializeViewer(config, instanceId) {
87
 
88
  progressDialog.style.display = 'block';
89
 
90
- // PlayCanvas
91
  if (!pc) {
92
- pc = await import("https://esm.run/playcanvas");
93
  window.pc = pc;
94
  logAndAlert('[viewer.js] PlayCanvas module loaded.');
95
  }
@@ -97,14 +99,11 @@ export async function initializeViewer(config, instanceId) {
97
  try {
98
  logAndAlert('[viewer.js] Creating graphics device...');
99
  const device = await pc.createGraphicsDevice(canvas, {
100
- deviceTypes: ["webgl2"],
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
- preserveDrawingBuffer: isIOS() ? true : false,
105
  alpha: false
106
  });
107
- logAndAlert('[viewer.js] Graphics device created!');
108
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
109
 
110
  const opts = new pc.AppOptions();
@@ -127,18 +126,23 @@ export async function initializeViewer(config, instanceId) {
127
  pc.GSplatHandler
128
  ];
129
 
 
130
  app = new pc.Application(canvas, opts);
131
  app.setCanvasFillMode(pc.FILLMODE_NONE);
132
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
133
 
 
 
 
 
 
134
  logAndAlert('[viewer.js] PlayCanvas application created!');
135
 
 
136
  resizeObserver = new ResizeObserver(entries => {
137
  for (const entry of entries) {
138
  const { width, height } = entry.contentRect;
139
- if (app) {
140
- app.resizeCanvas(width, height);
141
- }
142
  }
143
  });
144
  resizeObserver.observe(viewerContainer);
@@ -150,26 +154,27 @@ export async function initializeViewer(config, instanceId) {
150
  window.removeEventListener('resize', resizeCanvas);
151
  });
152
 
153
- // Assets
 
154
  const assets = {
155
  model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
156
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
157
  galerie: new pc.Asset('galerie', 'container', { url: glbUrl }),
158
  hdr: new pc.Asset('hdr', 'texture', {
159
- url: `https://huggingface.co/datasets/bilca/ply_files/resolve/main/galeries/blanc.png`
160
- }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false })
161
  };
162
 
 
163
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
164
  let lastProg = 0;
165
 
166
- // iOS PLY load timeout/fallback
167
  if (isIOS()) {
168
- // 90MB is a big PLY!
169
  loadTimeout = setTimeout(() => {
170
  alert("⚠️ 3D model loading is taking unusually long on your device. Large PLY files (>40MB) may exceed memory limits on iOS Safari and cannot be shown. Try a smaller model, or use a desktop browser for best results.");
171
  progressDialog.innerHTML = `<p style="color:red">Loading failed or too slow. Try a smaller model, or use a desktop browser.</p>`;
172
- }, 20000); // 20 seconds
173
  }
174
 
175
  assets.model.on('load', () => {
@@ -204,9 +209,18 @@ export async function initializeViewer(config, instanceId) {
204
  if (loadTimeout) { clearTimeout(loadTimeout); loadTimeout = null; }
205
  logAndAlert('[viewer.js] All assets loaded; starting app.');
206
  app.start();
207
- app.scene.envAtlas = assets.hdr.resource;
208
 
209
- // Model entity
 
 
 
 
 
 
 
 
 
 
210
  modelEntity = new pc.Entity('model');
211
  try {
212
  modelEntity.addComponent('gsplat', { asset: assets.model });
@@ -214,33 +228,23 @@ export async function initializeViewer(config, instanceId) {
214
  modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
215
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
216
  app.root.addChild(modelEntity);
217
- logAndAlert('[viewer.js] Model entity added to scene!');
218
  } catch (e) {
219
  logAndAlert('[viewer.js] Error adding model entity: ' + e.message);
220
  }
221
 
222
- // Light
223
- const dirLight = new pc.Entity('Cascaded Light');
224
- dirLight.addComponent('light', {
225
- type: 'directional',
226
- color: pc.Color.WHITE,
227
- shadowBias: 0.3,
228
- normalOffsetBias: 0.2,
229
- intensity: 1.0,
230
- soft: true,
231
- shadowResolution: 4096,
232
- penumbraSize: 7,
233
- penumbraFalloff: 1.5,
234
- shadowSamples: 128,
235
- shadowBlockerSamples: 16,
236
- castShadows: true,
237
- shadowType: pc.SHADOW_PCSS_32F,
238
- shadowDistance: 1000
239
  });
240
- dirLight.setLocalEulerAngles(0, 0, 0);
241
- app.root.addChild(dirLight);
242
 
243
- // Gallery GLB
244
  if (assets.galerie && assets.galerie.resource) {
245
  try {
246
  const galleryEntity = assets.galerie.resource.instantiateRenderEntity();
@@ -251,12 +255,13 @@ export async function initializeViewer(config, instanceId) {
251
  }
252
  }
253
 
254
- // Camera setup
255
  cameraEntity = new pc.Entity('camera');
256
  cameraEntity.addComponent('camera', {
257
- clearColor: config.canvas_background
258
- ? parseInt(config.canvas_background.substr(1, 2), 16) / 255
259
- : 0,
 
260
  });
261
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
262
  cameraEntity.lookAt(modelEntity.getPosition());
@@ -294,24 +299,6 @@ export async function initializeViewer(config, instanceId) {
294
  app.root.addChild(cameraEntity);
295
  logAndAlert('[viewer.js] Camera entity added to scene!');
296
 
297
- // Reset & constrain updates
298
- app.once('update', () => {
299
- logAndAlert('[viewer.js] First app update! Resetting camera.');
300
- resetViewerCamera();
301
- });
302
- app.on('update', dt => {
303
- if (cameraEntity) {
304
- const pos = cameraEntity.getPosition();
305
- if (pos.y < minY) {
306
- cameraEntity.setPosition(pos.x, minY, pos.z);
307
- }
308
- }
309
- if (!window._pcFirstUpdate) {
310
- window._pcFirstUpdate = true;
311
- logAndAlert('[viewer.js] Entered update/render loop!');
312
- }
313
- });
314
-
315
  // Final resize
316
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
317
 
@@ -403,4 +390,4 @@ export function cleanupViewer() {
403
  resizeObserver.disconnect();
404
  resizeObserver = null;
405
  }
406
- }
 
1
+ // viewer.js - iOS/Android/Desktop GSplat+GLB viewer, robust drop-in
2
 
3
+ let pc;
4
  export let app = null;
5
  let cameraEntity = null;
6
  let modelEntity = null;
7
  let viewerInitialized = false;
8
  let resizeObserver = null;
9
+ let loadTimeout = null;
10
 
11
+ // Camera/model config
12
  let chosenCameraX, chosenCameraY, chosenCameraZ;
13
  let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, minY;
14
  let modelX, modelY, modelZ, modelScale, modelRotationX, modelRotationY, modelRotationZ;
15
  let plyUrl, glbUrl;
16
 
 
17
  function isIOS() {
18
  return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
19
  }
 
25
  export async function initializeViewer(config, instanceId) {
26
  if (viewerInitialized) return;
27
 
 
28
  plyUrl = config.ply_url;
29
  glbUrl = config.glb_url;
30
  minZoom = parseFloat(config.minZoom || "1");
 
56
  chosenCameraY = mobile ? cameraYPhone : cameraY;
57
  chosenCameraZ = mobile ? cameraZPhone : cameraZ;
58
 
59
+ // DOM setup
60
  const canvasId = 'canvas-' + instanceId;
61
  const progressDialog = document.getElementById('progress-dialog-' + instanceId);
62
  const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
 
68
  canvas.id = canvasId;
69
  canvas.className = 'ply-canvas';
70
  canvas.style.zIndex = "1";
71
+ canvas.style.display = "block";
72
+ canvas.style.background = "#222"; // Ensure not transparent
73
  viewerContainer.insertBefore(canvas, progressDialog);
74
 
75
+ // Scroll/wheel zoom (all platforms)
76
  canvas.addEventListener('wheel', e => {
77
  if (cameraEntity && cameraEntity.script && cameraEntity.script.orbitCamera) {
78
  const orbitCam = cameraEntity.script.orbitCamera;
 
89
 
90
  progressDialog.style.display = 'block';
91
 
92
+ // PlayCanvas import
93
  if (!pc) {
94
+ pc = await import("https://esm.run/playcanvas@1.68.4"); // Stick to a known-good, stable version!
95
  window.pc = pc;
96
  logAndAlert('[viewer.js] PlayCanvas module loaded.');
97
  }
 
99
  try {
100
  logAndAlert('[viewer.js] Creating graphics device...');
101
  const device = await pc.createGraphicsDevice(canvas, {
102
+ deviceTypes: ["webgl2", "webgl1"],
103
+ antialias: true,
104
+ preserveDrawingBuffer: isIOS(), // True on iOS, false elsewhere
 
 
105
  alpha: false
106
  });
 
107
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
108
 
109
  const opts = new pc.AppOptions();
 
126
  pc.GSplatHandler
127
  ];
128
 
129
+ // Use new Application constructor only!
130
  app = new pc.Application(canvas, opts);
131
  app.setCanvasFillMode(pc.FILLMODE_NONE);
132
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
133
 
134
+ // iOS: ensure clearColor is NOT transparent, and toneMapping is standard
135
+ app.scene.exposure = 1.1;
136
+ app.scene.toneMapping = pc.TONEMAP_ACES;
137
+ app.scene.gammaCorrection = pc.GAMMA_SRGB;
138
+
139
  logAndAlert('[viewer.js] PlayCanvas application created!');
140
 
141
+ // Resize support
142
  resizeObserver = new ResizeObserver(entries => {
143
  for (const entry of entries) {
144
  const { width, height } = entry.contentRect;
145
+ if (app) app.resizeCanvas(width, height);
 
 
146
  }
147
  });
148
  resizeObserver.observe(viewerContainer);
 
154
  window.removeEventListener('resize', resizeCanvas);
155
  });
156
 
157
+ // ASSETS
158
+ // Use a neutral skybox/texture; iOS has issues with "RGBP" PNG as HDR
159
  const assets = {
160
  model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
161
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
162
  galerie: new pc.Asset('galerie', 'container', { url: glbUrl }),
163
  hdr: new pc.Asset('hdr', 'texture', {
164
+ url: `https://cdn.jsdelivr.net/gh/playcanvas/playcanvas.github.io@master/static/assets/textures/skybox/skybox_hdr_03.hdr`
165
+ }, { type: pc.TEXTURETYPE_RGBM, mipmaps: false })
166
  };
167
 
168
+ // Loader
169
  const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
170
  let lastProg = 0;
171
 
172
+ // iOS fallback: PLYs can hang
173
  if (isIOS()) {
 
174
  loadTimeout = setTimeout(() => {
175
  alert("⚠️ 3D model loading is taking unusually long on your device. Large PLY files (>40MB) may exceed memory limits on iOS Safari and cannot be shown. Try a smaller model, or use a desktop browser for best results.");
176
  progressDialog.innerHTML = `<p style="color:red">Loading failed or too slow. Try a smaller model, or use a desktop browser.</p>`;
177
+ }, 20000);
178
  }
179
 
180
  assets.model.on('load', () => {
 
209
  if (loadTimeout) { clearTimeout(loadTimeout); loadTimeout = null; }
210
  logAndAlert('[viewer.js] All assets loaded; starting app.');
211
  app.start();
 
212
 
213
+ // If HDR loads, set as skybox/envAtlas; else fallback to default lighting
214
+ if (assets.hdr.resource) {
215
+ try {
216
+ app.scene.envAtlas = assets.hdr.resource;
217
+ logAndAlert('[viewer.js] EnvAtlas set.');
218
+ } catch (e) {
219
+ logAndAlert('[viewer.js] Failed to set envAtlas: ' + e.message);
220
+ }
221
+ }
222
+
223
+ // Model
224
  modelEntity = new pc.Entity('model');
225
  try {
226
  modelEntity.addComponent('gsplat', { asset: assets.model });
 
228
  modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
229
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
230
  app.root.addChild(modelEntity);
231
+ logAndAlert(`[viewer.js] Model entity added at [${modelX}, ${modelY}, ${modelZ}] scale ${modelScale}`);
232
  } catch (e) {
233
  logAndAlert('[viewer.js] Error adding model entity: ' + e.message);
234
  }
235
 
236
+ // Add strong ambient light (fixes "black" on iOS if env map fails)
237
+ const ambient = new pc.Entity("ambient-light");
238
+ ambient.addComponent("light", {
239
+ type: "directional",
240
+ intensity: 1.1,
241
+ color: new pc.Color(1,1,1),
242
+ castShadows: false
 
 
 
 
 
 
 
 
 
 
243
  });
244
+ ambient.setLocalEulerAngles(45, 30, 0);
245
+ app.root.addChild(ambient);
246
 
247
+ // Scene GLB container (optional)
248
  if (assets.galerie && assets.galerie.resource) {
249
  try {
250
  const galleryEntity = assets.galerie.resource.instantiateRenderEntity();
 
255
  }
256
  }
257
 
258
+ // Camera
259
  cameraEntity = new pc.Entity('camera');
260
  cameraEntity.addComponent('camera', {
261
+ clearColor: [0.133,0.133,0.133,1.0], // Never transparent
262
+ fov: 60,
263
+ nearClip: 0.1,
264
+ farClip: 1000
265
  });
266
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
267
  cameraEntity.lookAt(modelEntity.getPosition());
 
299
  app.root.addChild(cameraEntity);
300
  logAndAlert('[viewer.js] Camera entity added to scene!');
301
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  // Final resize
303
  app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
304
 
 
390
  resizeObserver.disconnect();
391
  resizeObserver = null;
392
  }
393
+ }