MikaFil commited on
Commit
76d4858
·
verified ·
1 Parent(s): ce1135b

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +64 -51
viewer.js CHANGED
@@ -11,38 +11,38 @@ let resizeObserver = null;
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 sogsUrl, glbUrl;
15
 
16
  export async function initializeViewer(config, instanceId) {
17
  if (viewerInitialized) return;
18
 
19
- // ==== MOBILE DETECTION EXACTLY AS IN PLY VIEWER ====
20
- const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
21
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
22
 
23
- // ==== ASSET URLS (SOGS/GSPLAT INSTEAD OF PLY) ====
24
- sogsUrl = config.sogs_json_url;
25
- glbUrl = config.glb_url;
26
- minZoom = parseFloat(config.minZoom || "1");
27
- maxZoom = parseFloat(config.maxZoom || "20");
28
- minAngle = parseFloat(config.minAngle || "-45");
29
- maxAngle = parseFloat(config.maxAngle || "90");
30
- minAzimuth = (config.minAzimuth !== undefined) ? parseFloat(config.minAzimuth) : -360;
31
- maxAzimuth = (config.maxAzimuth !== undefined) ? parseFloat(config.maxAzimuth) : 360;
 
32
  minPivotY = parseFloat(config.minPivotY || "0");
33
- minY = (config.minY !== undefined) ? parseFloat(config.minY) : 0;
34
-
35
- modelX = (config.modelX !== undefined) ? parseFloat(config.modelX) : 0;
36
- modelY = (config.modelY !== undefined) ? parseFloat(config.modelY) : 0;
37
- modelZ = (config.modelZ !== undefined) ? parseFloat(config.modelZ) : 0;
38
- modelScale = (config.modelScale !== undefined) ? parseFloat(config.modelScale) : 1;
39
- modelRotationX = (config.modelRotationX !== undefined) ? parseFloat(config.modelRotationX) : 0;
40
- modelRotationY = (config.modelRotationY !== undefined) ? parseFloat(config.modelRotationY) : 0;
41
- modelRotationZ = (config.modelRotationZ !== undefined) ? parseFloat(config.modelRotationZ) : 0;
42
-
43
- const cameraX = (config.cameraX !== undefined) ? parseFloat(config.cameraX) : 0;
44
- const cameraY = (config.cameraY !== undefined) ? parseFloat(config.cameraY) : 2;
45
- const cameraZ = (config.cameraZ !== undefined) ? parseFloat(config.cameraZ) : 5;
46
  const cameraXPhone = (config.cameraXPhone !== undefined) ? parseFloat(config.cameraXPhone) : cameraX;
47
  const cameraYPhone = (config.cameraYPhone !== undefined) ? parseFloat(config.cameraYPhone) : cameraY;
48
  const cameraZPhone = (config.cameraZPhone !== undefined) ? parseFloat(config.cameraZPhone) : (cameraZ * 1.5);
@@ -51,10 +51,10 @@ export async function initializeViewer(config, instanceId) {
51
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
52
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
53
 
54
- const canvasId = 'canvas-' + instanceId;
55
- const progressDialog = document.getElementById('progress-dialog-' + instanceId);
56
- const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
57
- const viewerContainer = document.getElementById('viewer-container-' + instanceId);
58
 
59
  let oldCanvas = document.getElementById(canvasId);
60
  if (oldCanvas) oldCanvas.remove();
@@ -67,6 +67,9 @@ export async function initializeViewer(config, instanceId) {
67
  canvas.setAttribute('tabindex', '0');
68
  viewerContainer.insertBefore(canvas, progressDialog);
69
 
 
 
 
70
  canvas.style.touchAction = "none";
71
  canvas.style.webkitTouchCallout = "none";
72
  canvas.addEventListener('gesturestart', e => e.preventDefault());
@@ -75,22 +78,23 @@ export async function initializeViewer(config, instanceId) {
75
  canvas.addEventListener('dblclick', e => e.preventDefault());
76
  canvas.addEventListener('touchstart', e => { if (e.touches.length > 1) e.preventDefault(); }, { passive: false });
77
 
78
- // Mouse wheel suppression to canvas only
79
  canvas.addEventListener('wheel', (e) => {
80
- e.preventDefault();
81
  }, { passive: false });
82
 
83
  progressDialog.style.display = 'block';
84
 
85
  if (!pc) {
86
- pc = await import("https://esm.run/playcanvas");
87
  window.pc = pc;
88
  }
89
 
90
- // === EXACT iOS/Android/Windows HANDLING AS WORKING PLY VIEWER ===
91
  const device = await pc.createGraphicsDevice(canvas, {
92
- glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
93
- twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
 
94
  antialias: false
95
  });
96
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
@@ -104,6 +108,7 @@ export async function initializeViewer(config, instanceId) {
104
  pc.CameraComponentSystem,
105
  pc.LightComponentSystem,
106
  pc.ScriptComponentSystem,
 
107
  pc.GSplatComponentSystem,
108
  pc.CollisionComponentSystem,
109
  pc.RigidbodyComponentSystem
@@ -112,6 +117,7 @@ export async function initializeViewer(config, instanceId) {
112
  pc.TextureHandler,
113
  pc.ContainerHandler,
114
  pc.ScriptHandler,
 
115
  pc.GSplatHandler
116
  ];
117
 
@@ -129,9 +135,9 @@ export async function initializeViewer(config, instanceId) {
129
  window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
130
  app.on('destroy', () => resizeObserver.disconnect());
131
 
132
- // === SOGS (not PLY) asset ===
133
  const assets = {
134
- model: new pc.Asset('gsplat', 'gsplat', { url: sogsUrl }),
135
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-sgos.static.hf.space/orbit-camera.js" }),
136
  glb: new pc.Asset('glb', 'container', { url: glbUrl }),
137
  };
@@ -142,36 +148,42 @@ export async function initializeViewer(config, instanceId) {
142
  app.start();
143
  progressDialog.style.display = 'none';
144
 
145
- // Add GSplat model
146
  modelEntity = new pc.Entity('model');
147
- modelEntity.addComponent('gsplat', { asset: assets.model });
148
  modelEntity.setLocalPosition(modelX, modelY, modelZ);
149
  modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
150
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
151
  app.root.addChild(modelEntity);
152
 
153
- // --- Add the GLB entity ---
154
  if (assets.glb && assets.glb.resource && assets.glb.resource.instantiateRenderEntity) {
155
  const glbEntity = assets.glb.resource.instantiateRenderEntity();
156
  app.root.addChild(glbEntity);
157
  }
158
 
 
159
  cameraEntity = new pc.Entity('camera');
160
- // Default background color (white) or config value
161
- let bgColor = new pc.Color(1, 1, 1, 1);
162
- if (config.canvas_background && /^#[A-Fa-f0-9]{6}$/.test(config.canvas_background)) {
163
- let hex = config.canvas_background.replace("#", "");
164
- bgColor = new pc.Color(
165
- parseInt(hex.substr(0,2),16)/255,
166
- parseInt(hex.substr(2,2),16)/255,
167
- parseInt(hex.substr(4,2),16)/255
168
- );
 
 
 
 
169
  }
170
- cameraEntity.addComponent('camera', { clearColor: bgColor });
171
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
172
  cameraEntity.lookAt(modelEntity.getPosition());
173
  cameraEntity.addComponent('script');
174
 
 
175
  cameraEntity.script.create('orbitCamera', {
176
  attributes: {
177
  focusEntity: modelEntity,
@@ -194,7 +206,7 @@ export async function initializeViewer(config, instanceId) {
194
 
195
  app.once('update', () => resetViewerCamera());
196
 
197
- // Tooltips supported if tooltips_url set
198
  try {
199
  if (config.tooltips_url) {
200
  import('https://mikafil-viewer-sgos.static.hf.space/tooltips.js').then(tooltipsModule => {
@@ -214,6 +226,7 @@ export async function initializeViewer(config, instanceId) {
214
  });
215
  }
216
 
 
217
  export function resetViewerCamera() {
218
  try {
219
  if (!cameraEntity || !modelEntity || !app) return;
 
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 sogsUrl, glbUrl, canvasBg;
15
 
16
  export async function initializeViewer(config, instanceId) {
17
  if (viewerInitialized) return;
18
 
19
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
 
20
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
21
 
22
+ // Parse config fields
23
+ sogsUrl = config.sogs_json_url;
24
+ glbUrl = config.glb_url;
25
+ canvasBg = config.canvas_background || "#ffffff";
26
+ minZoom = parseFloat(config.minZoom || "1");
27
+ maxZoom = parseFloat(config.maxZoom || "20");
28
+ minAngle = parseFloat(config.minAngle || "-45");
29
+ maxAngle = parseFloat(config.maxAngle || "90");
30
+ minAzimuth= (config.minAzimuth !== undefined) ? parseFloat(config.minAzimuth) : -360;
31
+ maxAzimuth= (config.maxAzimuth !== undefined) ? parseFloat(config.maxAzimuth) : 360;
32
  minPivotY = parseFloat(config.minPivotY || "0");
33
+ minY = (config.minY !== undefined) ? parseFloat(config.minY) : 0;
34
+
35
+ modelX = (config.modelX !== undefined) ? parseFloat(config.modelX) : 0;
36
+ modelY = (config.modelY !== undefined) ? parseFloat(config.modelY) : 0;
37
+ modelZ = (config.modelZ !== undefined) ? parseFloat(config.modelZ) : 0;
38
+ modelScale = (config.modelScale !== undefined) ? parseFloat(config.modelScale) : 1;
39
+ modelRotationX = (config.modelRotationX !== undefined) ? parseFloat(config.modelRotationX) : 0;
40
+ modelRotationY = (config.modelRotationY !== undefined) ? parseFloat(config.modelRotationY) : 0;
41
+ modelRotationZ = (config.modelRotationZ !== undefined) ? parseFloat(config.modelRotationZ) : 0;
42
+
43
+ const cameraX = (config.cameraX !== undefined) ? parseFloat(config.cameraX) : 0;
44
+ const cameraY = (config.cameraY !== undefined) ? parseFloat(config.cameraY) : 2;
45
+ const cameraZ = (config.cameraZ !== undefined) ? parseFloat(config.cameraZ) : 5;
46
  const cameraXPhone = (config.cameraXPhone !== undefined) ? parseFloat(config.cameraXPhone) : cameraX;
47
  const cameraYPhone = (config.cameraYPhone !== undefined) ? parseFloat(config.cameraYPhone) : cameraY;
48
  const cameraZPhone = (config.cameraZPhone !== undefined) ? parseFloat(config.cameraZPhone) : (cameraZ * 1.5);
 
51
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
52
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
53
 
54
+ const canvasId = 'canvas-' + instanceId;
55
+ const progressDialog = document.getElementById('progress-dialog-' + instanceId);
56
+ const progressIndicator= document.getElementById('progress-indicator-' + instanceId);
57
+ const viewerContainer = document.getElementById('viewer-container-' + instanceId);
58
 
59
  let oldCanvas = document.getElementById(canvasId);
60
  if (oldCanvas) oldCanvas.remove();
 
67
  canvas.setAttribute('tabindex', '0');
68
  viewerContainer.insertBefore(canvas, progressDialog);
69
 
70
+ // Apply background color from config if present
71
+ canvas.style.background = canvasBg;
72
+
73
  canvas.style.touchAction = "none";
74
  canvas.style.webkitTouchCallout = "none";
75
  canvas.addEventListener('gesturestart', e => e.preventDefault());
 
78
  canvas.addEventListener('dblclick', e => e.preventDefault());
79
  canvas.addEventListener('touchstart', e => { if (e.touches.length > 1) e.preventDefault(); }, { passive: false });
80
 
81
+ // --- Mouse wheel suppression
82
  canvas.addEventListener('wheel', (e) => {
83
+ e.preventDefault(); // Only block page scroll if mouse is over viewer
84
  }, { passive: false });
85
 
86
  progressDialog.style.display = 'block';
87
 
88
  if (!pc) {
89
+ pc = await import("https://cdn.jsdelivr.net/npm/playcanvas@latest/+esm"); // always use latest for SOGS/GSplat support
90
  window.pc = pc;
91
  }
92
 
93
+ // Create app and graphics device
94
  const device = await pc.createGraphicsDevice(canvas, {
95
+ deviceTypes: ["webgl2"],
96
+ glslangUrl: "https://mikafil-viewer-sgos.static.hf.space/static/lib/glslang/glslang.js",
97
+ twgslUrl: "https://mikafil-viewer-sgos.static.hf.space/static/lib/twgsl/twgsl.js",
98
  antialias: false
99
  });
100
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
 
108
  pc.CameraComponentSystem,
109
  pc.LightComponentSystem,
110
  pc.ScriptComponentSystem,
111
+ // "GSplat" component supports SOGS and GSplat
112
  pc.GSplatComponentSystem,
113
  pc.CollisionComponentSystem,
114
  pc.RigidbodyComponentSystem
 
117
  pc.TextureHandler,
118
  pc.ContainerHandler,
119
  pc.ScriptHandler,
120
+ // GSplatHandler handles GSplat and SOGS
121
  pc.GSplatHandler
122
  ];
123
 
 
135
  window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
136
  app.on('destroy', () => resizeObserver.disconnect());
137
 
138
+ // Prepare asset loading (SOGS as 'gsplat')
139
  const assets = {
140
+ sogs: new pc.Asset('gsplat', 'gsplat', { url: sogsUrl }),
141
  orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-sgos.static.hf.space/orbit-camera.js" }),
142
  glb: new pc.Asset('glb', 'container', { url: glbUrl }),
143
  };
 
148
  app.start();
149
  progressDialog.style.display = 'none';
150
 
151
+ // Add SOGS model
152
  modelEntity = new pc.Entity('model');
153
+ modelEntity.addComponent('gsplat', { asset: assets.sogs });
154
  modelEntity.setLocalPosition(modelX, modelY, modelZ);
155
  modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
156
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
157
  app.root.addChild(modelEntity);
158
 
159
+ // Add the GLB entity if provided
160
  if (assets.glb && assets.glb.resource && assets.glb.resource.instantiateRenderEntity) {
161
  const glbEntity = assets.glb.resource.instantiateRenderEntity();
162
  app.root.addChild(glbEntity);
163
  }
164
 
165
+ // CAMERA
166
  cameraEntity = new pc.Entity('camera');
167
+ // Support background color
168
+ let bg = [1, 1, 1, 1];
169
+ if (canvasBg && /^#?[0-9a-f]{6,8}$/i.test(canvasBg.replace("#", ""))) {
170
+ // convert hex color to [r,g,b,a]
171
+ let hex = canvasBg.replace("#", "");
172
+ if (hex.length === 6) hex += "FF";
173
+ const num = parseInt(hex, 16);
174
+ bg = [
175
+ ((num >> 24) & 0xFF) / 255,
176
+ ((num >> 16) & 0xFF) / 255,
177
+ ((num >> 8) & 0xFF) / 255,
178
+ (num & 0xFF) / 255
179
+ ];
180
  }
181
+ cameraEntity.addComponent('camera', { clearColor: new pc.Color(bg[0], bg[1], bg[2], bg[3]) });
182
  cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
183
  cameraEntity.lookAt(modelEntity.getPosition());
184
  cameraEntity.addComponent('script');
185
 
186
+ // Pass all attributes to Orbit Camera script
187
  cameraEntity.script.create('orbitCamera', {
188
  attributes: {
189
  focusEntity: modelEntity,
 
206
 
207
  app.once('update', () => resetViewerCamera());
208
 
209
+ // Tooltips support (optional)
210
  try {
211
  if (config.tooltips_url) {
212
  import('https://mikafil-viewer-sgos.static.hf.space/tooltips.js').then(tooltipsModule => {
 
226
  });
227
  }
228
 
229
+ // Resets the viewer camera (as in your PLY example)
230
  export function resetViewerCamera() {
231
  try {
232
  if (!cameraEntity || !modelEntity || !app) return;