MikaFil commited on
Commit
5ad9c19
·
verified ·
1 Parent(s): b6a8b79

Update viewer.js

Browse files
Files changed (1) hide show
  1. viewer.js +75 -290
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,151 +13,95 @@ let minZoom, maxZoom, minAngle, maxAngle, minAzimuth, maxAzimuth, minPivotY, min
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
- // Platform detection
23
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
24
  const isMobile = isIOS || /Android/i.test(navigator.userAgent);
25
 
26
- // Config/params
27
  plyUrl = config.ply_url;
28
  glbUrl = config.glb_url;
29
- minZoom = parseFloat(config.minZoom || "1");
30
- maxZoom = parseFloat(config.maxZoom || "20");
31
- minAngle = parseFloat(config.minAngle || "-45");
32
- maxAngle = parseFloat(config.maxAngle || "90");
33
- minAzimuth = (config.minAzimuth !== undefined) ? parseFloat(config.minAzimuth) : -360;
34
- maxAzimuth = (config.maxAzimuth !== undefined) ? parseFloat(config.maxAzimuth) : 360;
35
- minPivotY = parseFloat(config.minPivotY || "0");
36
- minY = (config.minY !== undefined) ? parseFloat(config.minY) : 0;
37
-
38
- modelX = (config.modelX !== undefined) ? parseFloat(config.modelX) : 0;
39
- modelY = (config.modelY !== undefined) ? parseFloat(config.modelY) : 0;
40
- modelZ = (config.modelZ !== undefined) ? parseFloat(config.modelZ) : 0;
41
- modelScale = (config.modelScale !== undefined) ? parseFloat(config.modelScale) : 1;
42
  modelRotationX = (config.modelRotationX !== undefined) ? parseFloat(config.modelRotationX) : 0;
43
  modelRotationY = (config.modelRotationY !== undefined) ? parseFloat(config.modelRotationY) : 0;
44
  modelRotationZ = (config.modelRotationZ !== undefined) ? parseFloat(config.modelRotationZ) : 0;
45
 
46
- const cameraX = (config.cameraX !== undefined) ? parseFloat(config.cameraX) : 0;
47
- const cameraY = (config.cameraY !== undefined) ? parseFloat(config.cameraY) : 2;
48
- const cameraZ = (config.cameraZ !== undefined) ? parseFloat(config.cameraZ) : 5;
49
- const cameraXPhone = (config.cameraXPhone !== undefined) ? parseFloat(config.cameraXPhone) : cameraX;
50
- const cameraYPhone = (config.cameraYPhone !== undefined) ? parseFloat(config.cameraYPhone) : cameraY;
51
- const cameraZPhone = (config.cameraZPhone !== undefined) ? parseFloat(config.cameraZPhone) : (cameraZ * 1.5);
52
 
53
  chosenCameraX = isMobile ? cameraXPhone : cameraX;
54
  chosenCameraY = isMobile ? cameraYPhone : cameraY;
55
  chosenCameraZ = isMobile ? cameraZPhone : cameraZ;
56
 
57
- // 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
- // Create 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.width = "100%";
71
  canvas.style.height = "100%";
72
- canvas.setAttribute('tabindex', '0');
73
  viewerContainer.insertBefore(canvas, progressDialog);
74
 
75
- // iOS touch fixes
76
  canvas.style.touchAction = "none";
77
  canvas.style.webkitTouchCallout = "none";
78
- canvas.addEventListener('gesturestart', e => e.preventDefault());
79
- canvas.addEventListener('gesturechange', e => e.preventDefault());
80
- canvas.addEventListener('gestureend', e => e.preventDefault());
81
- canvas.addEventListener('dblclick', e => e.preventDefault());
82
- canvas.addEventListener('touchstart', function(e) {
83
- if (e.touches.length > 1) e.preventDefault();
84
- }, { passive: false });
85
-
86
- // Wheel: allow orbit zoom on desktop (Script B)
87
- canvas.addEventListener('wheel', e => {
88
- if (cameraEntity && cameraEntity.script && cameraEntity.script.orbitCamera) {
89
- const orbitCam = cameraEntity.script.orbitCamera;
90
- const sens = (cameraEntity.script.orbitCameraInputMouse?.distanceSensitivity) || 0.4;
91
- if (cameraEntity.camera.projection === pc.PROJECTION_PERSPECTIVE) {
92
- orbitCam.distance -= e.deltaY * 0.01 * sens * (orbitCam.distance * 0.1);
93
- } else {
94
- orbitCam.orthoHeight -= e.deltaY * 0.01 * sens * (orbitCam.orthoHeight * 0.1);
95
- }
96
- e.preventDefault();
97
- e.stopPropagation();
98
- }
99
- }, { passive: false });
100
 
101
  progressDialog.style.display = 'block';
102
 
103
- // Import PlayCanvas
104
  if (!pc) {
105
  pc = await import("https://esm.run/playcanvas");
106
  window.pc = pc;
107
  }
108
 
109
- // ----------- iOS PLY loader patch --------------
110
- // If on iOS, or window.PLY_FORCE_ARRAYBUFFER, force .ply load as arraybuffer (Script A trick)
111
- if (isIOS || window.PLY_FORCE_ARRAYBUFFER) {
112
- const origRequest = pc.Http.request;
113
- pc.Http.request = function(options) {
114
- if (options && options.url && options.url.endsWith('.ply')) {
115
- options.responseType = 'arraybuffer';
116
- }
117
- return origRequest.call(this, options);
118
- };
119
- }
120
- // -----------------------------------------------
 
121
 
122
- // Graphics device & AppOptions
123
  const device = await pc.createGraphicsDevice(canvas, {
124
  deviceTypes: ["webgl2"],
125
- glslangUrl: "https://playcanvas.vercel.app/static/lib/glslang/glslang.js",
126
- twgslUrl: "https://playcanvas.vercel.app/static/lib/twgsl/twgsl.js",
127
- antialias: false
128
  });
129
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
130
 
131
  const opts = new pc.AppOptions();
132
  opts.graphicsDevice = device;
133
-
134
- // --- Input: Script B logic on desktop/Android, iOS input on document.body (Script A fix) ---
135
- // Always use document.body for mouse/touch (Script A), fixes iOS input propagation issues!
136
  opts.mouse = new pc.Mouse(document.body);
137
  opts.touch = new pc.TouchDevice(document.body);
138
- // ------------------------------------------------------------------------------------------
139
-
140
- opts.componentSystems = [
141
- pc.RenderComponentSystem,
142
- pc.CameraComponentSystem,
143
- pc.LightComponentSystem,
144
- pc.ScriptComponentSystem,
145
- pc.GSplatComponentSystem,
146
- pc.CollisionComponentSystem,
147
- pc.RigidbodyComponentSystem
148
- ];
149
- opts.resourceHandlers = [
150
- pc.TextureHandler,
151
- pc.ContainerHandler,
152
- pc.ScriptHandler,
153
- pc.GSplatHandler
154
- ];
155
 
156
  app = new pc.Application(canvas, opts);
157
  app.setCanvasFillMode(pc.FILLMODE_NONE);
158
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
159
 
160
- // Resize observer
161
  resizeObserver = new ResizeObserver(entries => {
162
  for (const entry of entries) {
163
  app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
@@ -165,220 +109,61 @@ export async function initializeViewer(config, instanceId) {
165
  });
166
  resizeObserver.observe(viewerContainer);
167
 
168
- window.addEventListener('resize', () => {
169
- if (app) app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
170
- });
171
-
172
- app.on('destroy', () => {
173
- if (resizeObserver) resizeObserver.disconnect();
174
- });
175
-
176
- // 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
- // Progress bar
187
- let lastProg = 0;
188
- assets.model.on('load', () => { progressDialog.style.display = 'none'; });
189
- assets.model.on('error', err => {
190
- progressDialog.innerHTML = `<p style="color: red">Error loading model: ${err}</p>`;
191
- });
192
- const progCheck = setInterval(() => {
193
- if (assets.model.resource) {
194
- progressIndicator.value = 100;
195
- clearInterval(progCheck);
196
- progressDialog.style.display = 'none';
197
- } else if (assets.model.loading) {
198
- lastProg = Math.min(lastProg + 2, 90);
199
- progressIndicator.value = lastProg;
200
- }
201
- }, 100);
202
-
203
- // Asset loader
204
- const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
205
- loader.load(async () => {
206
- app.start();
207
- app.scene.envAtlas = assets.hdr.resource;
208
-
209
- // Model entity (PLY)
210
  modelEntity = new pc.Entity('model');
211
  modelEntity.addComponent('gsplat', { asset: assets.model });
212
  modelEntity.setLocalPosition(modelX, modelY, modelZ);
213
  modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
214
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
215
  app.root.addChild(modelEntity);
 
 
216
 
217
- // Light
218
- const dirLight = new pc.Entity('Cascaded Light');
219
- dirLight.addComponent('light', {
220
- type: 'directional',
221
- color: pc.Color.WHITE,
222
- shadowBias: 0.3,
223
- normalOffsetBias: 0.2,
224
- intensity: 1.0,
225
- soft: true,
226
- shadowResolution: 4096,
227
- penumbraSize: 7,
228
- penumbraFalloff: 1.5,
229
- shadowSamples: 128,
230
- shadowBlockerSamples: 16,
231
- castShadows: true,
232
- shadowType: pc.SHADOW_PCSS_32F,
233
- shadowDistance: 1000
234
- });
235
- dirLight.setLocalEulerAngles(0, 0, 0);
236
- app.root.addChild(dirLight);
237
-
238
- // Gallery GLB (optional)
239
- if (assets.galerie && assets.galerie.resource && assets.galerie.resource.instantiateRenderEntity) {
240
- const galleryEntity = assets.galerie.resource.instantiateRenderEntity();
241
- app.root.addChild(galleryEntity);
242
  }
 
243
 
244
- // Camera
245
- cameraEntity = new pc.Entity('camera');
246
- cameraEntity.addComponent('camera', {
247
- clearColor: config.canvas_background
248
- ? new pc.Color(
249
- parseInt(config.canvas_background.substr(1, 2), 16) / 255,
250
- parseInt(config.canvas_background.substr(3, 2), 16) / 255,
251
- parseInt(config.canvas_background.substr(5, 2), 16) / 255,
252
- 1
253
- )
254
- : new pc.Color(0.2, 0.2, 0.2, 1)
255
- });
256
- cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
257
- cameraEntity.lookAt(modelEntity.getPosition());
258
- cameraEntity.addComponent('script');
259
- cameraEntity.script.create('orbitCamera', {
260
- attributes: {
261
- inertiaFactor: 0.2,
262
- focusEntity: modelEntity,
263
- distanceMax: maxZoom,
264
- distanceMin: minZoom,
265
- pitchAngleMax: maxAngle,
266
- pitchAngleMin: minAngle,
267
- yawAngleMax: maxAzimuth,
268
- yawAngleMin: minAzimuth,
269
- minPivotY: minPivotY,
270
- minY: minY,
271
- frameOnStart: false
272
- }
273
- });
274
- cameraEntity.script.create('orbitCameraInputMouse', {
275
- attributes: {
276
- orbitSensitivity: isMobile ? 0.6 : 0.3,
277
- distanceSensitivity: isMobile ? 0.5 : 0.4
278
- }
279
- });
280
- cameraEntity.script.create('orbitCameraInputTouch', {
281
- attributes: {
282
- orbitSensitivity: 0.6,
283
- distanceSensitivity: 0.5
284
- }
285
- });
286
- app.root.addChild(cameraEntity);
287
-
288
- // On first update, reset camera (Script B)
289
- app.once('update', () => { resetViewerCamera(); });
290
-
291
- // Clamp Y so camera never goes below floor
292
- app.on('update', dt => {
293
- if (cameraEntity) {
294
- const pos = cameraEntity.getPosition();
295
- if (pos.y < minY) cameraEntity.setPosition(pos.x, minY, pos.z);
296
- }
297
- });
298
-
299
- // Final resize
300
- app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
301
 
302
- // Tooltips
303
- if (config.tooltips_url) {
304
- try {
305
- const tooltipsModule = await import('./tooltips.js');
306
- await tooltipsModule.initializeTooltips({
307
- app,
308
- cameraEntity,
309
- modelEntity,
310
- tooltipsUrl: config.tooltips_url,
311
- defaultVisible: !!config.showTooltipsDefault,
312
- moveDuration: config.tooltipMoveDuration || 0.6
313
- });
314
- } catch (e) { /* silent */ }
315
- }
316
 
317
- progressDialog.style.display = 'none';
318
- viewerInitialized = true;
319
- });
320
  }
321
 
322
- // Reset camera as in Script B (accurate positioning!)
323
  export function resetViewerCamera() {
324
- try {
325
- if (!cameraEntity || !modelEntity || !app) return;
326
- const orbitCam = cameraEntity.script.orbitCamera;
327
- if (!orbitCam) return;
328
-
329
- const modelPos = modelEntity.getPosition();
330
- const tempEnt = new pc.Entity();
331
- tempEnt.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
332
- tempEnt.lookAt(modelPos);
333
-
334
- const dist = new pc.Vec3().sub2(
335
- new pc.Vec3(chosenCameraX, chosenCameraY, chosenCameraZ),
336
- modelPos
337
- ).length();
338
-
339
- cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
340
- cameraEntity.lookAt(modelPos);
341
-
342
- orbitCam.pivotPoint = modelPos.clone();
343
- orbitCam._targetDistance = dist;
344
- orbitCam._distance = dist;
345
-
346
- const rot = tempEnt.getRotation();
347
- const fwd = new pc.Vec3();
348
- rot.transformVector(pc.Vec3.FORWARD, fwd);
349
-
350
- const yaw = Math.atan2(-fwd.x, -fwd.z) * pc.math.RAD_TO_DEG;
351
- const yawQuat = new pc.Quat().setFromEulerAngles(0, -yaw, 0);
352
- const rotNoYaw = new pc.Quat().mul2(yawQuat, rot);
353
- const fNoYaw = new pc.Vec3();
354
- rotNoYaw.transformVector(pc.Vec3.FORWARD, fNoYaw);
355
- const pitch = Math.atan2(fNoYaw.y, -fNoYaw.z) * pc.math.RAD_TO_DEG;
356
-
357
- orbitCam._targetYaw = yaw;
358
- orbitCam._yaw = yaw;
359
- orbitCam._targetPitch = pitch;
360
- orbitCam._pitch = pitch;
361
- if (orbitCam._updatePosition) orbitCam._updatePosition();
362
-
363
- tempEnt.destroy();
364
- } catch (e) {
365
- // Silent fail
366
  }
367
  }
368
 
369
- // Optional cleanup for hot-reload
370
  export function cleanupViewer() {
371
- if (app) {
372
- try {
373
- app.destroy();
374
- } catch {}
375
- app = null;
376
- }
377
- cameraEntity = null;
378
- modelEntity = null;
379
  viewerInitialized = false;
380
- if (resizeObserver) {
381
- resizeObserver.disconnect();
382
- resizeObserver = null;
383
- }
384
  }
 
1
  // viewer.js
2
  // ==============================
3
 
4
+ let pc;
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
  if (viewerInitialized) return;
18
 
 
19
  const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
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 = parseFloat(config.cameraX || "0");
42
+ const cameraY = parseFloat(config.cameraY || "2");
43
+ const cameraZ = parseFloat(config.cameraZ || "5");
44
+ const cameraXPhone = parseFloat(config.cameraXPhone || cameraX);
45
+ const cameraYPhone = parseFloat(config.cameraYPhone || cameraY);
46
+ const cameraZPhone = 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 viewerContainer = document.getElementById('viewer-container-' + instanceId);
 
 
55
 
 
56
  let oldCanvas = document.getElementById(canvasId);
57
  if (oldCanvas) oldCanvas.remove();
58
+
59
  const canvas = document.createElement('canvas');
60
  canvas.id = canvasId;
61
  canvas.className = 'ply-canvas';
 
62
  canvas.style.width = "100%";
63
  canvas.style.height = "100%";
 
64
  viewerContainer.insertBefore(canvas, progressDialog);
65
 
 
66
  canvas.style.touchAction = "none";
67
  canvas.style.webkitTouchCallout = "none";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  progressDialog.style.display = 'block';
70
 
 
71
  if (!pc) {
72
  pc = await import("https://esm.run/playcanvas");
73
  window.pc = pc;
74
  }
75
 
76
+ // Explicitly ensure arraybuffer for PLY on iOS Safari
77
+ const plyAsset = new pc.Asset('gsplat', 'gsplat', {
78
+ url: plyUrl,
79
+ responseType: 'arraybuffer' // Crucial explicit fix
80
+ });
81
+
82
+ const assets = {
83
+ model: plyAsset,
84
+ galerie: new pc.Asset('galerie', 'container', { url: glbUrl }),
85
+ hdr: new pc.Asset('hdr', 'texture', {
86
+ url: "https://huggingface.co/datasets/bilca/ply_files/resolve/main/galeries/blanc.png"
87
+ }, { type: pc.TEXTURETYPE_RGBP, mipmaps: false })
88
+ };
89
 
 
90
  const device = await pc.createGraphicsDevice(canvas, {
91
  deviceTypes: ["webgl2"],
92
+ antialias: false
 
 
93
  });
94
  device.maxPixelRatio = Math.min(window.devicePixelRatio, 2);
95
 
96
  const opts = new pc.AppOptions();
97
  opts.graphicsDevice = device;
 
 
 
98
  opts.mouse = new pc.Mouse(document.body);
99
  opts.touch = new pc.TouchDevice(document.body);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  app = new pc.Application(canvas, opts);
102
  app.setCanvasFillMode(pc.FILLMODE_NONE);
103
  app.setCanvasResolution(pc.RESOLUTION_AUTO);
104
 
 
105
  resizeObserver = new ResizeObserver(entries => {
106
  for (const entry of entries) {
107
  app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
 
109
  });
110
  resizeObserver.observe(viewerContainer);
111
 
112
+ app.assets.add(assets.model);
113
+ app.assets.add(assets.galerie);
114
+ app.assets.add(assets.hdr);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
+ assets.model.ready(() => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  modelEntity = new pc.Entity('model');
118
  modelEntity.addComponent('gsplat', { asset: assets.model });
119
  modelEntity.setLocalPosition(modelX, modelY, modelZ);
120
  modelEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
121
  modelEntity.setLocalScale(modelScale, modelScale, modelScale);
122
  app.root.addChild(modelEntity);
123
+ progressDialog.style.display = 'none';
124
+ });
125
 
126
+ app.start();
127
+
128
+ cameraEntity = new pc.Entity('camera');
129
+ cameraEntity.addComponent('camera', { clearColor: new pc.Color(0.2, 0.2, 0.2, 1) });
130
+ cameraEntity.setPosition(chosenCameraX, chosenCameraY, chosenCameraZ);
131
+ cameraEntity.lookAt(pc.Vec3.ZERO);
132
+
133
+ cameraEntity.addComponent('script');
134
+ cameraEntity.script.create('orbitCamera', {
135
+ attributes: {
136
+ inertiaFactor: 0.2,
137
+ focusEntity: modelEntity,
138
+ distanceMax: maxZoom,
139
+ distanceMin: minZoom,
140
+ pitchAngleMax: maxAngle,
141
+ pitchAngleMin: minAngle,
142
+ yawAngleMax: maxAzimuth,
143
+ yawAngleMin: minAzimuth,
144
+ minPivotY: minPivotY,
145
+ minY: minY,
146
+ frameOnStart: true
 
 
 
 
147
  }
148
+ });
149
 
150
+ cameraEntity.script.create('orbitCameraInputMouse');
151
+ cameraEntity.script.create('orbitCameraInputTouch');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
+ app.root.addChild(cameraEntity);
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
+ viewerInitialized = true;
 
 
156
  }
157
 
 
158
  export function resetViewerCamera() {
159
+ if (cameraEntity && cameraEntity.script.orbitCamera) {
160
+ cameraEntity.script.orbitCamera.reset();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  }
162
  }
163
 
 
164
  export function cleanupViewer() {
165
+ if (app) app.destroy();
166
+ app = null;
 
 
 
 
 
 
167
  viewerInitialized = false;
168
+ if (resizeObserver) resizeObserver.disconnect();
 
 
 
169
  }