@chocozhang/three-model-render 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,37 +1,5 @@
1
1
  import * as THREE from 'three';
2
2
 
3
- /******************************************************************************
4
- Copyright (c) Microsoft Corporation.
5
-
6
- Permission to use, copy, modify, and/or distribute this software for any
7
- purpose with or without fee is hereby granted.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
- PERFORMANCE OF THIS SOFTWARE.
16
- ***************************************************************************** */
17
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
18
-
19
-
20
- function __awaiter(thisArg, _arguments, P, generator) {
21
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
22
- return new (P || (P = Promise))(function (resolve, reject) {
23
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
24
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
25
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
26
- step((generator = generator.apply(thisArg, _arguments || [])).next());
27
- });
28
- }
29
-
30
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
31
- var e = new Error(message);
32
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
33
- };
34
-
35
3
  /**
36
4
  * @file exploder.ts
37
5
  * @description
@@ -98,205 +66,197 @@ class GroupExploder {
98
66
  * @param newSet The new set of meshes
99
67
  * @param contextId Optional context ID to distinguish business scenarios
100
68
  */
101
- setMeshes(newSet, options) {
102
- return __awaiter(this, void 0, void 0, function* () {
103
- var _a, _b;
104
- const autoRestorePrev = (_a = options === null || options === void 0 ? void 0 : options.autoRestorePrev) !== null && _a !== void 0 ? _a : true;
105
- const restoreDuration = (_b = options === null || options === void 0 ? void 0 : options.restoreDuration) !== null && _b !== void 0 ? _b : 300;
106
- this.log(`setMeshes called. newSetSize=${newSet ? newSet.size : 0}, autoRestorePrev=${autoRestorePrev}`);
107
- // If the newSet is null and currentSet is null -> nothing
108
- if (!newSet && !this.currentSet) {
109
- this.log('setMeshes: both newSet and currentSet are null, nothing to do');
110
- return;
111
- }
112
- // If both exist and are the same reference, we still must detect content changes.
113
- const sameReference = this.currentSet === newSet;
114
- // Prepare prevSet snapshot (we copy current to prev)
115
- if (this.currentSet) {
116
- this.prevSet = this.currentSet;
117
- this.prevStateMap = new Map(this.stateMap);
118
- this.log(`setMeshes: backed up current->prev prevSetSize=${this.prevSet.size}`);
119
- }
120
- else {
121
- this.prevSet = null;
122
- this.prevStateMap = new Map();
123
- }
124
- // If we used to be exploded and need to restore prevSet, do that first (await)
125
- if (this.prevSet && autoRestorePrev && this.isExploded) {
126
- this.log('setMeshes: need to restore prevSet before applying newSet');
127
- yield this.restoreSet(this.prevSet, this.prevStateMap, restoreDuration, { debug: true });
128
- this.log('setMeshes: prevSet restore done');
129
- this.prevStateMap.clear();
130
- this.prevSet = null;
131
- }
132
- // Now register newSet: we clear and rebuild stateMap carefully.
133
- // But we must handle the case where caller reuses same Set object and just mutated elements.
134
- // We will compute additions and removals.
135
- const oldSet = this.currentSet;
136
- this.currentSet = newSet;
137
- // If newSet is null -> simply clear stateMap
138
- if (!this.currentSet) {
139
- this.stateMap.clear();
140
- this.log('setMeshes: newSet is null -> cleared stateMap');
141
- this.isExploded = false;
69
+ async setMeshes(newSet, options) {
70
+ const autoRestorePrev = options?.autoRestorePrev ?? true;
71
+ const restoreDuration = options?.restoreDuration ?? 300;
72
+ this.log(`setMeshes called. newSetSize=${newSet ? newSet.size : 0}, autoRestorePrev=${autoRestorePrev}`);
73
+ // If the newSet is null and currentSet is null -> nothing
74
+ if (!newSet && !this.currentSet) {
75
+ this.log('setMeshes: both newSet and currentSet are null, nothing to do');
76
+ return;
77
+ }
78
+ // If both exist and are the same reference, we still must detect content changes.
79
+ const sameReference = this.currentSet === newSet;
80
+ // Prepare prevSet snapshot (we copy current to prev)
81
+ if (this.currentSet) {
82
+ this.prevSet = this.currentSet;
83
+ this.prevStateMap = new Map(this.stateMap);
84
+ this.log(`setMeshes: backed up current->prev prevSetSize=${this.prevSet.size}`);
85
+ }
86
+ else {
87
+ this.prevSet = null;
88
+ this.prevStateMap = new Map();
89
+ }
90
+ // If we used to be exploded and need to restore prevSet, do that first (await)
91
+ if (this.prevSet && autoRestorePrev && this.isExploded) {
92
+ this.log('setMeshes: need to restore prevSet before applying newSet');
93
+ await this.restoreSet(this.prevSet, this.prevStateMap, restoreDuration, { debug: true });
94
+ this.log('setMeshes: prevSet restore done');
95
+ this.prevStateMap.clear();
96
+ this.prevSet = null;
97
+ }
98
+ // Now register newSet: we clear and rebuild stateMap carefully.
99
+ // But we must handle the case where caller reuses same Set object and just mutated elements.
100
+ // We will compute additions and removals.
101
+ const oldSet = this.currentSet;
102
+ this.currentSet = newSet;
103
+ // If newSet is null -> simply clear stateMap
104
+ if (!this.currentSet) {
105
+ this.stateMap.clear();
106
+ this.log('setMeshes: newSet is null -> cleared stateMap');
107
+ this.isExploded = false;
108
+ return;
109
+ }
110
+ // If we have oldSet (could be same reference) then compute diffs
111
+ if (oldSet) {
112
+ // If same reference but size or content differs -> handle diffs
113
+ const wasSameRef = sameReference;
114
+ let added = [];
115
+ let removed = [];
116
+ // Build maps of membership
117
+ const oldMembers = new Set(Array.from(oldSet));
118
+ const newMembers = new Set(Array.from(this.currentSet));
119
+ // find removals
120
+ oldMembers.forEach((m) => {
121
+ if (!newMembers.has(m))
122
+ removed.push(m);
123
+ });
124
+ // find additions
125
+ newMembers.forEach((m) => {
126
+ if (!oldMembers.has(m))
127
+ added.push(m);
128
+ });
129
+ if (wasSameRef && added.length === 0 && removed.length === 0) {
130
+ // truly identical (no content changes)
131
+ this.log('setMeshes: same reference and identical contents -> nothing to update');
142
132
  return;
143
133
  }
144
- // If we have oldSet (could be same reference) then compute diffs
145
- if (oldSet) {
146
- // If same reference but size or content differs -> handle diffs
147
- const wasSameRef = sameReference;
148
- let added = [];
149
- let removed = [];
150
- // Build maps of membership
151
- const oldMembers = new Set(Array.from(oldSet));
152
- const newMembers = new Set(Array.from(this.currentSet));
153
- // find removals
154
- oldMembers.forEach((m) => {
155
- if (!newMembers.has(m))
156
- removed.push(m);
157
- });
158
- // find additions
159
- newMembers.forEach((m) => {
160
- if (!oldMembers.has(m))
161
- added.push(m);
162
- });
163
- if (wasSameRef && added.length === 0 && removed.length === 0) {
164
- // truly identical (no content changes)
165
- this.log('setMeshes: same reference and identical contents -> nothing to update');
166
- return;
134
+ this.log(`setMeshes: diff detected -> added=${added.length}, removed=${removed.length}`);
135
+ // Remove snapshots for removed meshes
136
+ removed.forEach((m) => {
137
+ if (this.stateMap.has(m)) {
138
+ this.stateMap.delete(m);
167
139
  }
168
- this.log(`setMeshes: diff detected -> added=${added.length}, removed=${removed.length}`);
169
- // Remove snapshots for removed meshes
170
- removed.forEach((m) => {
171
- if (this.stateMap.has(m)) {
172
- this.stateMap.delete(m);
173
- }
174
- });
175
- // Ensure snapshots exist for current set members (create for newly added meshes)
176
- yield this.ensureSnapshotsForSet(this.currentSet);
177
- this.log(`setMeshes: after diff handling, stateMap size=${this.stateMap.size}`);
178
- this.isExploded = false;
179
- return;
180
- }
181
- else {
182
- // no oldSet -> brand new registration
183
- this.stateMap.clear();
184
- yield this.ensureSnapshotsForSet(this.currentSet);
185
- this.log(`setMeshes: recorded stateMap entries for newSet size=${this.stateMap.size}`);
186
- this.isExploded = false;
187
- return;
188
- }
189
- });
140
+ });
141
+ // Ensure snapshots exist for current set members (create for newly added meshes)
142
+ await this.ensureSnapshotsForSet(this.currentSet);
143
+ this.log(`setMeshes: after diff handling, stateMap size=${this.stateMap.size}`);
144
+ this.isExploded = false;
145
+ return;
146
+ }
147
+ else {
148
+ // no oldSet -> brand new registration
149
+ this.stateMap.clear();
150
+ await this.ensureSnapshotsForSet(this.currentSet);
151
+ this.log(`setMeshes: recorded stateMap entries for newSet size=${this.stateMap.size}`);
152
+ this.isExploded = false;
153
+ return;
154
+ }
190
155
  }
191
156
  /**
192
157
  * ensureSnapshotsForSet: for each mesh in set, ensure stateMap has an entry.
193
158
  * If missing, record current matrixWorld as originalMatrixWorld (best-effort).
194
159
  */
195
- ensureSnapshotsForSet(set) {
196
- return __awaiter(this, void 0, void 0, function* () {
197
- set.forEach((m) => {
160
+ async ensureSnapshotsForSet(set) {
161
+ set.forEach((m) => {
162
+ try {
163
+ m.updateMatrixWorld(true);
164
+ }
165
+ catch { }
166
+ if (!this.stateMap.has(m)) {
198
167
  try {
199
- m.updateMatrixWorld(true);
168
+ this.stateMap.set(m, {
169
+ originalParent: m.parent || null,
170
+ originalMatrixWorld: (m.matrixWorld && m.matrixWorld.clone()) || new THREE.Matrix4().copy(m.matrix),
171
+ });
172
+ // Also store in userData for extra resilience
173
+ m.userData.__originalMatrixWorld = this.stateMap.get(m).originalMatrixWorld.clone();
200
174
  }
201
- catch (_a) { }
202
- if (!this.stateMap.has(m)) {
203
- try {
204
- this.stateMap.set(m, {
205
- originalParent: m.parent || null,
206
- originalMatrixWorld: (m.matrixWorld && m.matrixWorld.clone()) || new THREE.Matrix4().copy(m.matrix),
207
- });
208
- // Also store in userData for extra resilience
209
- m.userData.__originalMatrixWorld = this.stateMap.get(m).originalMatrixWorld.clone();
210
- }
211
- catch (e) {
212
- this.log(`ensureSnapshotsForSet: failed to snapshot mesh ${m.name || m.id}: ${e.message}`);
213
- }
175
+ catch (e) {
176
+ this.log(`ensureSnapshotsForSet: failed to snapshot mesh ${m.name || m.id}: ${e.message}`);
214
177
  }
215
- });
178
+ }
216
179
  });
217
180
  }
218
181
  /**
219
182
  * explode: compute targets first, compute targetBound using targets + mesh radii,
220
183
  * animate camera to that targetBound, then animate meshes to targets.
221
184
  */
222
- explode(opts) {
223
- return __awaiter(this, void 0, void 0, function* () {
224
- var _a;
225
- if (!this.currentSet || this.currentSet.size === 0) {
226
- this.log('explode: empty currentSet, nothing to do');
227
- return;
185
+ async explode(opts) {
186
+ if (!this.currentSet || this.currentSet.size === 0) {
187
+ this.log('explode: empty currentSet, nothing to do');
188
+ return;
189
+ }
190
+ const { spacing = 2, duration = 1000, lift = 0.5, cameraPadding = 1.5, mode = 'spiral', dimOthers = { enabled: true, opacity: 0.25 }, debug = false, } = opts || {};
191
+ this.log(`explode called. setSize=${this.currentSet.size}, mode=${mode}, spacing=${spacing}, duration=${duration}, lift=${lift}, dim=${dimOthers.enabled}`);
192
+ this.cancelAnimations();
193
+ const meshes = Array.from(this.currentSet);
194
+ // ensure snapshots exist for any meshes that may have been added after initial registration
195
+ await this.ensureSnapshotsForSet(this.currentSet);
196
+ // compute center/radius from current meshes (fallback)
197
+ const initial = this.computeBoundingSphereForMeshes(meshes);
198
+ const center = initial.center;
199
+ const baseRadius = Math.max(1, initial.radius);
200
+ this.log(`explode: initial center=${center.toArray().map((n) => n.toFixed(3))}, baseRadius=${baseRadius.toFixed(3)}`);
201
+ // compute targets (pure calculation)
202
+ const targets = this.computeTargetsByMode(meshes, center, baseRadius + spacing, { lift, mode });
203
+ this.log(`explode: computed ${targets.length} target positions`);
204
+ // compute target-based bounding sphere (targets + per-mesh radius)
205
+ const targetBound = this.computeBoundingSphereForPositionsAndMeshes(targets, meshes);
206
+ this.log(`explode: targetBound center=${targetBound.center.toArray().map((n) => n.toFixed(3))}, radius=${targetBound.radius.toFixed(3)}`);
207
+ await this.animateCameraToFit(targetBound.center, targetBound.radius, { duration: Math.min(600, duration), padding: cameraPadding });
208
+ this.log('explode: camera animation to target bound completed');
209
+ // apply dim if needed with context id
210
+ const contextId = dimOthers?.enabled ? this.applyDimToOthers(meshes, dimOthers.opacity ?? 0.25, { debug }) : null;
211
+ if (contextId)
212
+ this.log(`explode: applied dim for context ${contextId}`);
213
+ // capture starts after camera move
214
+ const starts = meshes.map((m) => {
215
+ const v = new THREE.Vector3();
216
+ try {
217
+ m.getWorldPosition(v);
228
218
  }
229
- const { spacing = 2, duration = 1000, lift = 0.5, cameraPadding = 1.5, mode = 'spiral', dimOthers = { enabled: true, opacity: 0.25 }, debug = false, } = opts || {};
230
- this.log(`explode called. setSize=${this.currentSet.size}, mode=${mode}, spacing=${spacing}, duration=${duration}, lift=${lift}, dim=${dimOthers.enabled}`);
231
- this.cancelAnimations();
232
- const meshes = Array.from(this.currentSet);
233
- // ensure snapshots exist for any meshes that may have been added after initial registration
234
- yield this.ensureSnapshotsForSet(this.currentSet);
235
- // compute center/radius from current meshes (fallback)
236
- const initial = this.computeBoundingSphereForMeshes(meshes);
237
- const center = initial.center;
238
- const baseRadius = Math.max(1, initial.radius);
239
- this.log(`explode: initial center=${center.toArray().map((n) => n.toFixed(3))}, baseRadius=${baseRadius.toFixed(3)}`);
240
- // compute targets (pure calculation)
241
- const targets = this.computeTargetsByMode(meshes, center, baseRadius + spacing, { lift, mode });
242
- this.log(`explode: computed ${targets.length} target positions`);
243
- // compute target-based bounding sphere (targets + per-mesh radius)
244
- const targetBound = this.computeBoundingSphereForPositionsAndMeshes(targets, meshes);
245
- this.log(`explode: targetBound center=${targetBound.center.toArray().map((n) => n.toFixed(3))}, radius=${targetBound.radius.toFixed(3)}`);
246
- yield this.animateCameraToFit(targetBound.center, targetBound.radius, { duration: Math.min(600, duration), padding: cameraPadding });
247
- this.log('explode: camera animation to target bound completed');
248
- // apply dim if needed with context id
249
- const contextId = (dimOthers === null || dimOthers === void 0 ? void 0 : dimOthers.enabled) ? this.applyDimToOthers(meshes, (_a = dimOthers.opacity) !== null && _a !== void 0 ? _a : 0.25, { debug }) : null;
250
- if (contextId)
251
- this.log(`explode: applied dim for context ${contextId}`);
252
- // capture starts after camera move
253
- const starts = meshes.map((m) => {
254
- const v = new THREE.Vector3();
255
- try {
256
- m.getWorldPosition(v);
257
- }
258
- catch (_a) {
259
- // fallback to originalMatrixWorld if available
260
- const st = this.stateMap.get(m);
261
- if (st)
262
- v.setFromMatrixPosition(st.originalMatrixWorld);
263
- }
264
- return v;
265
- });
266
- const startTime = performance.now();
267
- const total = Math.max(1, duration);
268
- const tick = (now) => {
269
- const t = Math.min(1, (now - startTime) / total);
270
- const eased = easeInOutQuad(t);
271
- for (let i = 0; i < meshes.length; i++) {
272
- const m = meshes[i];
273
- const s = starts[i];
274
- const tar = targets[i];
275
- const cur = s.clone().lerp(tar, eased);
276
- if (m.parent) {
277
- const local = cur.clone();
278
- m.parent.worldToLocal(local);
279
- m.position.copy(local);
280
- }
281
- else {
282
- m.position.copy(cur);
283
- }
284
- m.updateMatrix();
285
- }
286
- if (this.controls && typeof this.controls.update === 'function')
287
- this.controls.update();
288
- if (t < 1) {
289
- this.animId = requestAnimationFrame(tick);
219
+ catch {
220
+ // fallback to originalMatrixWorld if available
221
+ const st = this.stateMap.get(m);
222
+ if (st)
223
+ v.setFromMatrixPosition(st.originalMatrixWorld);
224
+ }
225
+ return v;
226
+ });
227
+ const startTime = performance.now();
228
+ const total = Math.max(1, duration);
229
+ const tick = (now) => {
230
+ const t = Math.min(1, (now - startTime) / total);
231
+ const eased = easeInOutQuad(t);
232
+ for (let i = 0; i < meshes.length; i++) {
233
+ const m = meshes[i];
234
+ const s = starts[i];
235
+ const tar = targets[i];
236
+ const cur = s.clone().lerp(tar, eased);
237
+ if (m.parent) {
238
+ const local = cur.clone();
239
+ m.parent.worldToLocal(local);
240
+ m.position.copy(local);
290
241
  }
291
242
  else {
292
- this.animId = null;
293
- this.isExploded = true;
294
- this.log(`explode: completed. contextId=${contextId !== null && contextId !== void 0 ? contextId : 'none'}`);
243
+ m.position.copy(cur);
295
244
  }
296
- };
297
- this.animId = requestAnimationFrame(tick);
298
- return;
299
- });
245
+ m.updateMatrix();
246
+ }
247
+ if (this.controls && typeof this.controls.update === 'function')
248
+ this.controls.update();
249
+ if (t < 1) {
250
+ this.animId = requestAnimationFrame(tick);
251
+ }
252
+ else {
253
+ this.animId = null;
254
+ this.isExploded = true;
255
+ this.log(`explode: completed. contextId=${contextId ?? 'none'}`);
256
+ }
257
+ };
258
+ this.animId = requestAnimationFrame(tick);
259
+ return;
300
260
  }
301
261
  /**
302
262
  * Restore all exploded meshes to their original transform:
@@ -319,7 +279,7 @@ class GroupExploder {
319
279
  */
320
280
  restoreSet(set, stateMap, duration = 400, opts) {
321
281
  if (!set || set.size === 0) {
322
- if (opts === null || opts === void 0 ? void 0 : opts.debug)
282
+ if (opts?.debug)
323
283
  this.log('restoreSet: empty set, nothing to restore');
324
284
  return Promise.resolve();
325
285
  }
@@ -332,12 +292,12 @@ class GroupExploder {
332
292
  try {
333
293
  m.updateMatrixWorld(true);
334
294
  }
335
- catch (_a) { }
295
+ catch { }
336
296
  const s = new THREE.Vector3();
337
297
  try {
338
298
  m.getWorldPosition(s);
339
299
  }
340
- catch (_b) {
300
+ catch {
341
301
  s.set(0, 0, 0);
342
302
  }
343
303
  starts.push(s);
@@ -435,7 +395,7 @@ class GroupExploder {
435
395
  });
436
396
  }
437
397
  // material dim with context id
438
- applyDimToOthers(explodingMeshes, opacity = 0.25, opts) {
398
+ applyDimToOthers(explodingMeshes, opacity = 0.25, _opts) {
439
399
  const contextId = `ctx_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
440
400
  const explodingSet = new Set(explodingMeshes);
441
401
  const touched = new Set();
@@ -446,11 +406,10 @@ class GroupExploder {
446
406
  if (explodingSet.has(mesh))
447
407
  return;
448
408
  const applyMat = (mat) => {
449
- var _a;
450
409
  if (!this.materialSnaps.has(mat)) {
451
410
  this.materialSnaps.set(mat, {
452
411
  transparent: !!mat.transparent,
453
- opacity: (_a = mat.opacity) !== null && _a !== void 0 ? _a : 1,
412
+ opacity: mat.opacity ?? 1,
454
413
  depthWrite: mat.depthWrite,
455
414
  });
456
415
  }
@@ -477,7 +436,7 @@ class GroupExploder {
477
436
  return contextId;
478
437
  }
479
438
  // clean contexts for meshes (restore materials whose contexts are removed)
480
- cleanContextsForMeshes(meshes) {
439
+ cleanContextsForMeshes(_meshes) {
481
440
  // conservative strategy: for each context we created, delete it and restore materials accordingly
482
441
  for (const [contextId, mats] of Array.from(this.contextMaterials.entries())) {
483
442
  mats.forEach((mat) => {
@@ -591,7 +550,7 @@ class GroupExploder {
591
550
  }
592
551
  }
593
552
  }
594
- catch (_a) {
553
+ catch {
595
554
  radius = 0;
596
555
  }
597
556
  if (!isFinite(radius) || radius < 0 || radius > 1e8)
@@ -614,10 +573,9 @@ class GroupExploder {
614
573
  }
615
574
  // computeTargetsByMode (unchanged logic but pure function)
616
575
  computeTargetsByMode(meshes, center, baseRadius, opts) {
617
- var _a, _b;
618
576
  const n = meshes.length;
619
- const lift = (_a = opts.lift) !== null && _a !== void 0 ? _a : 0.5;
620
- const mode = (_b = opts.mode) !== null && _b !== void 0 ? _b : 'ring';
577
+ const lift = opts.lift ?? 0.5;
578
+ const mode = opts.mode ?? 'ring';
621
579
  const targets = [];
622
580
  if (mode === 'ring') {
623
581
  for (let i = 0; i < n; i++) {
@@ -659,9 +617,8 @@ class GroupExploder {
659
617
  return targets;
660
618
  }
661
619
  animateCameraToFit(targetCenter, targetRadius, opts) {
662
- var _a, _b, _c, _d;
663
- const duration = (_a = opts === null || opts === void 0 ? void 0 : opts.duration) !== null && _a !== void 0 ? _a : 600;
664
- const padding = (_b = opts === null || opts === void 0 ? void 0 : opts.padding) !== null && _b !== void 0 ? _b : 1.5;
620
+ const duration = opts?.duration ?? 600;
621
+ const padding = opts?.padding ?? 1.5;
665
622
  if (!(this.camera instanceof THREE.PerspectiveCamera)) {
666
623
  if (this.controls && this.controls.target) {
667
624
  // Fallback for non-PerspectiveCamera
@@ -673,14 +630,13 @@ class GroupExploder {
673
630
  const endPos = endTarget.clone().add(dir.multiplyScalar(dist));
674
631
  const startTime = performance.now();
675
632
  const tick = (now) => {
676
- var _a;
677
633
  const t = Math.min(1, (now - startTime) / duration);
678
634
  const k = easeInOutQuad(t);
679
635
  if (this.controls && this.controls.target) {
680
636
  this.controls.target.lerpVectors(startTarget, endTarget, k);
681
637
  }
682
638
  this.camera.position.lerpVectors(startPos, endPos, k);
683
- if ((_a = this.controls) === null || _a === void 0 ? void 0 : _a.update)
639
+ if (this.controls?.update)
684
640
  this.controls.update();
685
641
  if (t < 1) {
686
642
  this.cameraAnimId = requestAnimationFrame(tick);
@@ -703,8 +659,8 @@ class GroupExploder {
703
659
  const distH = targetRadius / Math.sin(Math.min(fov, fov * aspect) / 2); // approximate
704
660
  const dist = Math.max(distV, distH) * padding;
705
661
  const startPos = this.camera.position.clone();
706
- const startTarget = ((_c = this.controls) === null || _c === void 0 ? void 0 : _c.target) ? this.controls.target.clone() : new THREE.Vector3(); // assumption
707
- if (!((_d = this.controls) === null || _d === void 0 ? void 0 : _d.target)) {
662
+ const startTarget = this.controls?.target ? this.controls.target.clone() : new THREE.Vector3(); // assumption
663
+ if (!this.controls?.target) {
708
664
  this.camera.getWorldDirection(startTarget);
709
665
  startTarget.add(startPos);
710
666
  }
@@ -717,13 +673,12 @@ class GroupExploder {
717
673
  return new Promise((resolve) => {
718
674
  const startTime = performance.now();
719
675
  const tick = (now) => {
720
- var _a, _b;
721
676
  const t = Math.min(1, (now - startTime) / duration);
722
677
  const k = easeInOutQuad(t);
723
678
  this.camera.position.lerpVectors(startPos, endPos, k);
724
679
  if (this.controls && this.controls.target) {
725
680
  this.controls.target.lerpVectors(startTarget, endTarget, k);
726
- (_b = (_a = this.controls).update) === null || _b === void 0 ? void 0 : _b.call(_a);
681
+ this.controls.update?.();
727
682
  }
728
683
  else {
729
684
  this.camera.lookAt(endTarget); // simple lookAt if no controls