MikaFil commited on
Commit
6d033e9
·
verified ·
1 Parent(s): e704083

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +144 -292
viewer.js CHANGED
@@ -1,301 +1,174 @@
1
  // viewer.js
2
  // ==============================
3
 
4
- let pc;
5
  export let app = null;
6
  let cameraEntity = null;
7
  let modelEntity = null;
8
  let viewerInitialized = false;
9
  let resizeObserver = null;
10
- let galleryEntity = null;
11
 
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
- // Utility: log and alert (for iPhone debug)
18
- function safeLog(msg) {
19
- if (typeof msg !== 'string') {
20
- try { msg = JSON.stringify(msg); } catch {}
21
- }
22
- console.log(msg);
23
- try { alert(msg); } catch {}
24
- }
25
-
26
  export async function initializeViewer(config, instanceId) {
27
- if (viewerInitialized) {
28
- safeLog("Viewer already initialized");
29
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
31
 
32
- try {
33
- const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
34
- const isMobile = isIOS || /Android/i.test(navigator.userAgent);
35
-
36
- plyUrl = config.ply_url;
37
- glbUrl = config.glb_url;
38
- minZoom = parseFloat(config.minZoom || "1");
39
- maxZoom = parseFloat(config.maxZoom || "20");
40
- minAngle = parseFloat(config.minAngle || "-45");
41
- maxAngle = parseFloat(config.maxAngle || "90");
42
- minAzimuth = (config.minAzimuth !== undefined) ? parseFloat(config.minAzimuth) : -360;
43
- maxAzimuth = (config.maxAzimuth !== undefined) ? parseFloat(config.maxAzimuth) : 360;
44
- minPivotY = parseFloat(config.minPivotY || "0");
45
- minY = (config.minY !== undefined) ? parseFloat(config.minY) : 0;
46
-
47
- modelX = (config.modelX !== undefined) ? parseFloat(config.modelX) : 0;
48
- modelY = (config.modelY !== undefined) ? parseFloat(config.modelY) : 0;
49
- modelZ = (config.modelZ !== undefined) ? parseFloat(config.modelZ) : 0;
50
- modelScale = (config.modelScale !== undefined) ? parseFloat(config.modelScale) : 1;
51
- modelRotationX = (config.modelRotationX !== undefined) ? parseFloat(config.modelRotationX) : 0;
52
- modelRotationY = (config.modelRotationY !== undefined) ? parseFloat(config.modelRotationY) : 0;
53
- modelRotationZ = (config.modelRotationZ !== undefined) ? parseFloat(config.modelRotationZ) : 0;
54
-
55
- const cameraX = (config.cameraX !== undefined) ? parseFloat(config.cameraX) : 0;
56
- const cameraY = (config.cameraY !== undefined) ? parseFloat(config.cameraY) : 2;
57
- const cameraZ = (config.cameraZ !== undefined) ? parseFloat(config.cameraZ) : 5;
58
- const cameraXPhone = (config.cameraXPhone !== undefined) ? parseFloat(config.cameraXPhone) : cameraX;
59
- const cameraYPhone = (config.cameraYPhone !== undefined) ? parseFloat(config.cameraYPhone) : cameraY;
60
- const cameraZPhone = (config.cameraZPhone !== undefined) ? parseFloat(config.cameraZPhone) : (cameraZ * 1.5);
61
-
62
- chosenCameraX = isMobile ? cameraXPhone : cameraX;
63
- chosenCameraY = isMobile ? cameraYPhone : cameraY;
64
- chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
65
-
66
- const canvasId = 'canvas-' + instanceId;
67
- const progressDialog = document.getElementById('progress-dialog-' + instanceId);
68
- const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
69
- const viewerContainer = document.getElementById('viewer-container-' + instanceId);
70
-
71
- let oldCanvas = document.getElementById(canvasId);
72
- if (oldCanvas) oldCanvas.remove();
73
-
74
- const canvas = document.createElement('canvas');
75
- canvas.id = canvasId;
76
- canvas.className = 'ply-canvas';
77
- canvas.style.width = "100%";
78
- canvas.style.height = "100%";
79
- canvas.setAttribute('tabindex', '0');
80
- viewerContainer.insertBefore(canvas, progressDialog);
81
-
82
- canvas.style.touchAction = "none";
83
- canvas.style.webkitTouchCallout = "none";
84
- canvas.addEventListener('gesturestart', e => e.preventDefault());
85
- canvas.addEventListener('gesturechange', e => e.preventDefault());
86
- canvas.addEventListener('gestureend', e => e.preventDefault());
87
- canvas.addEventListener('dblclick', e => e.preventDefault());
88
- canvas.addEventListener('touchstart', e => { if (e.touches.length > 1) e.preventDefault(); }, { passive: false });
89
-
90
- progressDialog.style.display = 'block';
91
-
92
- if (!pc) {
93
- try {
94
- pc = await import("https://esm.run/playcanvas");
95
- window.pc = pc;
96
- safeLog("PlayCanvas loaded!");
97
- } catch (e) {
98
- safeLog("PlayCanvas import failed: " + e);
99
- throw e;
100
- }
101
- }
102
-
103
- let device;
104
- try {
105
- device = await pc.createGraphicsDevice(canvas, {
106
- deviceTypes: ["webgl2"],
107
- glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
108
- twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
109
- antialias: false
110
- });
111
- } catch (e) {
112
- safeLog("createGraphicsDevice failed: " + e);
113
- throw e;
114
- }
115
- device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
116
-
117
- const opts = new pc.AppOptions();
118
- opts.graphicsDevice = device;
119
- opts.mouse = new pc.Mouse(document.body);
120
- opts.touch = new pc.TouchDevice(document.body);
121
- opts.componentSystems = [
122
- pc.RenderComponentSystem,
123
- pc.CameraComponentSystem,
124
- pc.LightComponentSystem,
125
- pc.ScriptComponentSystem,
126
- pc.GSplatComponentSystem,
127
- pc.CollisionComponentSystem,
128
- pc.RigidbodyComponentSystem
129
- ];
130
- opts.resourceHandlers = [
131
- pc.TextureHandler,
132
- pc.ContainerHandler,
133
- pc.ScriptHandler,
134
- pc.GSplatHandler
135
- ];
136
-
137
- app = new pc.Application(canvas, opts);
138
- app.setCanvasFillMode(pc.FILLMODE_NONE);
139
- app.setCanvasResolution(pc.RESOLUTION_AUTO);
140
-
141
- resizeObserver = new ResizeObserver(entries => {
142
- entries.forEach(entry => {
143
- app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
144
- });
145
- });
146
- resizeObserver.observe(viewerContainer);
147
-
148
- window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
149
- app.on('destroy', () => resizeObserver.disconnect());
150
-
151
- // --- Assets: Add GLB and HDR support ---
152
- const assets = {
153
- model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
154
- orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
155
- galerie: glbUrl ? new pc.Asset('galerie', 'container', { url: glbUrl }) : null,
156
- hdr: new pc.Asset('hdr', 'texture', { url: "https://huggingface.co/datasets/bilca/ply_files/resolve/main/galeries/blanc.png" }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false })
157
- };
158
- const assetArray = Object.values(assets).filter(a => !!a);
159
-
160
- const loader = new pc.AssetListLoader(assetArray, app.assets);
161
-
162
- // Asset error handling
163
- assets.model.on('error', err => {
164
- safeLog("GSplat model failed: " + err);
165
- });
166
- if (assets.galerie) {
167
- assets.galerie.on('error', err => {
168
- safeLog("GLB failed: " + err);
169
- });
170
- }
171
- assets.hdr.on('error', err => {
172
- safeLog("HDR failed: " + err);
173
  });
174
-
175
- loader.load(async () => {
176
- safeLog("Assets loaded, starting app...");
177
- app.start();
178
- progressDialog.style.display = 'none';
179
-
180
- // Add GSplat (PLY) first
181
- try {
182
- modelEntity = new pc.Entity('model');
183
- modelEntity.addComponent('gsplat', { asset: assets.model });
184
- modelEntity.setLocalPosition(modelX, modelY, modelZ);
185
- modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
186
- modelEntity.setLocalScale(modelScale, modelScale, modelScale);
187
- app.root.addChild(modelEntity);
188
- safeLog("PLY/GSplat added");
189
- } catch (e) {
190
- safeLog("Failed to add PLY/GSplat: " + e);
191
- }
192
-
193
- // Then add GLB gallery (if available)
194
- try {
195
- if (assets.galerie && assets.galerie.resource && assets.galerie.resource.instantiateRenderEntity) {
196
- galleryEntity = assets.galerie.resource.instantiateRenderEntity();
197
- app.root.addChild(galleryEntity);
198
- safeLog("GLB gallery added to scene");
199
- } else if (assets.galerie) {
200
- safeLog("GLB asset loaded, but no instantiateRenderEntity");
201
- }
202
- } catch (e) {
203
- safeLog("Failed to add GLB gallery: " + e);
 
 
204
  }
 
 
 
 
205
 
206
- // Set HDR environment (after app.start)
207
- try {
208
- if (assets.hdr && assets.hdr.resource) {
209
- app.scene.envAtlas = assets.hdr.resource;
210
- safeLog("HDR environment applied");
211
- }
212
- } catch (e) {
213
- safeLog("Failed to set HDR environment: " + e);
214
- }
215
 
216
- // Camera
217
- try {
218
- cameraEntity = new pc.Entity('camera');
219
- cameraEntity.addComponent('camera', { clearColor: new pc.Color(0.2, 0.2, 0.2, 1) });
220
- cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
221
- cameraEntity.lookAt(modelEntity.getPosition());
222
- cameraEntity.addComponent('script');
223
- cameraEntity.script.create('orbitCamera', {
224
- attributes: {
225
- focusEntity: modelEntity,
226
- inertiaFactor: 0.2,
227
- distanceMax: maxZoom,
228
- distanceMin: minZoom,
229
- pitchAngleMax: maxAngle,
230
- pitchAngleMin: minAngle,
231
- yawAngleMax: maxAzimuth,
232
- yawAngleMin: minAzimuth,
233
- minPivotY: minPivotY,
234
- frameOnStart: false
235
- }
236
- });
237
- cameraEntity.script.create('orbitCameraInputMouse');
238
- cameraEntity.script.create('orbitCameraInputTouch');
239
- app.root.addChild(cameraEntity);
240
- safeLog("Camera set up");
241
- } catch (e) {
242
- safeLog("Failed to set up camera: " + e);
243
- }
244
 
245
- app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
246
-
247
- // Use Script B's robust camera reset logic after everything is ready
248
- app.once('update', () => {
249
- try {
250
- resetViewerCamera();
251
- safeLog("Camera reset after init");
252
- } catch (e) {
253
- safeLog("Camera reset failed: " + e);
254
- }
255
- });
256
-
257
- // Tooltips (if config.tooltips_url and tooltips.js available)
258
- try {
259
- if (config.tooltips_url) {
260
- const tooltipsModule = await import('./tooltips.js');
261
- tooltipsModule.initializeTooltips({
262
- app,
263
- cameraEntity,
264
- modelEntity,
265
- tooltipsUrl: config.tooltips_url,
266
- defaultVisible: !!config.showTooltipsDefault,
267
- moveDuration: config.tooltipMoveDuration || 0.6
268
- });
269
- safeLog("Tooltips loaded and initialized");
270
- }
271
- } catch (e) {
272
- safeLog("Tooltips load/init error: " + e);
273
- }
274
-
275
- viewerInitialized = true;
276
- safeLog("Viewer initialized OK");
277
- });
278
- } catch (err) {
279
- safeLog("Fatal init error: " + err);
280
- const progressDialog = document.getElementById('progress-dialog-' + instanceId);
281
- if (progressDialog) {
282
- progressDialog.innerHTML = `<p style="color: red">Error loading viewer: ${err && err.message ? err.message : err}</p>`;
283
- }
284
- }
285
  }
286
 
287
- // Camera reset logic from Script B
288
  export function resetViewerCamera() {
289
  try {
290
- if (!cameraEntity || !modelEntity || !app) {
291
- safeLog("resetViewerCamera: missing entity or app");
292
- return;
293
- }
294
  const orbitCam = cameraEntity.script.orbitCamera;
295
- if (!orbitCam) {
296
- safeLog("resetViewerCamera: orbitCamera script missing");
297
- return;
298
- }
299
 
300
  const modelPos = modelEntity.getPosition();
301
  const tempEnt = new pc.Entity();
@@ -332,28 +205,7 @@ export function resetViewerCamera() {
332
  if (orbitCam._updatePosition) orbitCam._updatePosition();
333
 
334
  tempEnt.destroy();
335
- safeLog("Camera reset complete");
336
  } catch (e) {
337
- safeLog("resetViewerCamera exception: " + e);
338
  // Silent fail
339
  }
340
  }
341
-
342
- export function cleanupViewer() {
343
- try {
344
- if (app) {
345
- try { app.destroy(); } catch (e) { safeLog("App destroy error: " + e); }
346
- app = null;
347
- }
348
- cameraEntity = null;
349
- modelEntity = null;
350
- viewerInitialized = false;
351
- if (resizeObserver) {
352
- resizeObserver.disconnect();
353
- resizeObserver = null;
354
- }
355
- safeLog("Viewer cleaned up");
356
- } catch (e) {
357
- safeLog("cleanupViewer error: " + e);
358
- }
359
- }
 
1
  // viewer.js
2
  // ==============================
3
 
4
+ let pc;
5
  export let app = null;
6
  let cameraEntity = null;
7
  let modelEntity = null;
8
  let viewerInitialized = false;
9
  let resizeObserver = null;
 
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
  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
+ plyUrl = config.ply_url;
23
+ glbUrl = config.glb_url;
24
+ minZoom = parseFloat(config.minZoom || "1");
25
+ maxZoom = parseFloat(config.maxZoom || "20");
26
+ minAngle = parseFloat(config.minAngle || "-45");
27
+ maxAngle = parseFloat(config.maxAngle || "90");
28
+ minAzimuth = (config.minAzimuth !== undefined) ? parseFloat(config.minAzimuth) : -360;
29
+ maxAzimuth = (config.maxAzimuth !== undefined) ? parseFloat(config.maxAzimuth) : 360;
30
+ minPivotY = parseFloat(config.minPivotY || "0");
31
+ minY = (config.minY !== undefined) ? parseFloat(config.minY) : 0;
32
+
33
+ modelX = (config.modelX !== undefined) ? parseFloat(config.modelX) : 0;
34
+ modelY = (config.modelY !== undefined) ? parseFloat(config.modelY) : 0;
35
+ modelZ = (config.modelZ !== undefined) ? parseFloat(config.modelZ) : 0;
36
+ modelScale = (config.modelScale !== undefined) ? parseFloat(config.modelScale) : 1;
37
+ modelRotationX = (config.modelRotationX !== undefined) ? parseFloat(config.modelRotationX) : 0;
38
+ modelRotationY = (config.modelRotationY !== undefined) ? parseFloat(config.modelRotationY) : 0;
39
+ modelRotationZ = (config.modelRotationZ !== undefined) ? parseFloat(config.modelRotationZ) : 0;
40
+
41
+ const cameraX = (config.cameraX !== undefined) ? parseFloat(config.cameraX) : 0;
42
+ const cameraY = (config.cameraY !== undefined) ? parseFloat(config.cameraY) : 2;
43
+ const cameraZ = (config.cameraZ !== undefined) ? parseFloat(config.cameraZ) : 5;
44
+ const cameraXPhone = (config.cameraXPhone !== undefined) ? parseFloat(config.cameraXPhone) : cameraX;
45
+ const cameraYPhone = (config.cameraYPhone !== undefined) ? parseFloat(config.cameraYPhone) : cameraY;
46
+ const cameraZPhone = (config.cameraZPhone !== undefined) ? parseFloat(config.cameraZPhone) : (cameraZ * 1.5);
47
+
48
+ chosenCameraX = isMobile ? cameraXPhone : cameraX;
49
+ chosenCameraY = isMobile ? cameraYPhone : cameraY;
50
+ chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
51
+
52
+ const canvasId = 'canvas-' + instanceId;
53
+ const progressDialog = document.getElementById('progress-dialog-' + instanceId);
54
+ const progressIndicator = document.getElementById('progress-indicator-' + instanceId);
55
+ const viewerContainer = document.getElementById('viewer-container-' + instanceId);
56
+
57
+ let oldCanvas = document.getElementById(canvasId);
58
+ if (oldCanvas) oldCanvas.remove();
59
+
60
+ const canvas = document.createElement('canvas');
61
+ canvas.id = canvasId;
62
+ canvas.className = 'ply-canvas';
63
+ canvas.style.width = "100%";
64
+ canvas.style.height = "100%";
65
+ canvas.setAttribute('tabindex', '0');
66
+ viewerContainer.insertBefore(canvas, progressDialog);
67
+
68
+ canvas.style.touchAction = "none";
69
+ canvas.style.webkitTouchCallout = "none";
70
+ canvas.addEventListener('gesturestart', e => e.preventDefault());
71
+ canvas.addEventListener('gesturechange', e => e.preventDefault());
72
+ canvas.addEventListener('gestureend', e => e.preventDefault());
73
+ canvas.addEventListener('dblclick', e => e.preventDefault());
74
+ canvas.addEventListener('touchstart', e => { if (e.touches.length > 1) e.preventDefault(); }, { passive: false });
75
+
76
+ progressDialog.style.display = 'block';
77
+
78
+ if (!pc) {
79
+ pc = await import("https://esm.run/playcanvas");
80
+ window.pc = pc;
81
  }
82
 
83
+ const device = await pc.createGraphicsDevice(canvas, {
84
+ deviceTypes: ["webgl2"],
85
+ glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
86
+ twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
87
+ antialias: false
88
+ });
89
+ device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
90
+
91
+ const opts = new pc.AppOptions();
92
+ opts.graphicsDevice = device;
93
+ opts.mouse = new pc.Mouse(document.body);
94
+ opts.touch = new pc.TouchDevice(document.body);
95
+ opts.componentSystems = [
96
+ pc.RenderComponentSystem,
97
+ pc.CameraComponentSystem,
98
+ pc.LightComponentSystem,
99
+ pc.ScriptComponentSystem,
100
+ pc.GSplatComponentSystem,
101
+ pc.CollisionComponentSystem,
102
+ pc.RigidbodyComponentSystem
103
+ ];
104
+ opts.resourceHandlers = [
105
+ pc.TextureHandler,
106
+ pc.ContainerHandler,
107
+ pc.ScriptHandler,
108
+ pc.GSplatHandler
109
+ ];
110
+
111
+ app = new pc.Application(canvas, opts);
112
+ app.setCanvasFillMode(pc.FILLMODE_NONE);
113
+ app.setCanvasResolution(pc.RESOLUTION_AUTO);
114
+
115
+ resizeObserver = new ResizeObserver(entries => {
116
+ entries.forEach(entry => {
117
+ app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  });
119
+ });
120
+ resizeObserver.observe(viewerContainer);
121
+
122
+ window.addEventListener('resize', () => app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight));
123
+ app.on('destroy', () => resizeObserver.disconnect());
124
+
125
+ const assets = {
126
+ model: new pc.Asset('gsplat', 'gsplat', { url: plyUrl }),
127
+ orbit: new pc.Asset('script', 'script', { url: "https://mikafil-viewer-gs.static.hf.space/orbit-camera.js" }),
128
+ };
129
+
130
+ const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
131
+ loader.load(() => {
132
+ app.start();
133
+ progressDialog.style.display = 'none';
134
+
135
+ modelEntity = new pc.Entity('model');
136
+ modelEntity.addComponent('gsplat', { asset: assets.model });
137
+ modelEntity.setLocalPosition(modelX, modelY, modelZ);
138
+ modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
139
+ modelEntity.setLocalScale(modelScale, modelScale, modelScale);
140
+ app.root.addChild(modelEntity);
141
+
142
+ cameraEntity = new pc.Entity('camera');
143
+ cameraEntity.addComponent('camera', { clearColor: new pc.Color(0.2, 0.2, 0.2, 1) });
144
+ cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
145
+ cameraEntity.lookAt(modelEntity.getPosition());
146
+ cameraEntity.addComponent('script');
147
+ cameraEntity.script.create('orbitCamera', {
148
+ attributes: {
149
+ focusEntity: modelEntity, inertiaFactor: 0.2, distanceMax: maxZoom, distanceMin: minZoom,
150
+ pitchAngleMax: maxAngle, pitchAngleMin: minAngle, frameOnStart: false
151
  }
152
+ });
153
+ cameraEntity.script.create('orbitCameraInputMouse');
154
+ cameraEntity.script.create('orbitCameraInputTouch');
155
+ app.root.addChild(cameraEntity);
156
 
157
+ app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
 
 
 
 
 
 
 
 
158
 
159
+ // Use Script B's robust camera reset logic after everything is ready
160
+ app.once('update', () => resetViewerCamera());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
+ viewerInitialized = true;
163
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  }
165
 
166
+ // --------- This is the only modification (Script B logic) ---------
167
  export function resetViewerCamera() {
168
  try {
169
+ if (!cameraEntity || !modelEntity || !app) return;
 
 
 
170
  const orbitCam = cameraEntity.script.orbitCamera;
171
+ if (!orbitCam) return;
 
 
 
172
 
173
  const modelPos = modelEntity.getPosition();
174
  const tempEnt = new pc.Entity();
 
205
  if (orbitCam._updatePosition) orbitCam._updatePosition();
206
 
207
  tempEnt.destroy();
 
208
  } catch (e) {
 
209
  // Silent fail
210
  }
211
  }