3dtiles-inspector 0.2.3 → 0.2.4

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.
@@ -0,0 +1,730 @@
1
+ import {
2
+ MathUtils,
3
+ Matrix4,
4
+ OrthographicCamera,
5
+ PerspectiveCamera,
6
+ Quaternion,
7
+ Vector3,
8
+ } from 'three';
9
+ import { CAMERA_CENTER_MODE_DISTANCE_SQ } from '../config.js';
10
+
11
+ const _matrix = new Matrix4();
12
+ const _matrix1 = new Matrix4();
13
+ const _flyDirectionStart = new Vector3();
14
+ const _flyDirectionEnd = new Vector3();
15
+ const _flyDirection = new Vector3();
16
+ const _flyAxis = new Vector3();
17
+ const _flyWorldNorth = new Vector3(0, 0, 1);
18
+ const _flyWorldRight = new Vector3(1, 0, 0);
19
+ const _flyCenterNorth = new Vector3(0, 1, 0);
20
+ const _flyCenterUp = new Vector3(0, 0, 1);
21
+ const _flyWorldUp = new Vector3(0, 1, 0);
22
+ const _flyUp = new Vector3();
23
+ const _flyEast = new Vector3();
24
+ const _flyNorth = new Vector3();
25
+ const _flyForward = new Vector3();
26
+ const _flyRight = new Vector3();
27
+ const _flyBackward = new Vector3();
28
+ const _flyCameraUp = new Vector3();
29
+ const _flyCameraRight = new Vector3();
30
+ const _flyReferenceUp = new Vector3();
31
+ const _flyReferenceRight = new Vector3();
32
+ const _flyQuaternion = new Quaternion();
33
+ const _ellipsoidRadii = new Vector3(
34
+ 6378137.0,
35
+ 6378137.0,
36
+ 6356752.3142451793,
37
+ );
38
+ const _oneOverRadiiSquared = new Vector3(
39
+ 1 / (_ellipsoidRadii.x * _ellipsoidRadii.x),
40
+ 1 / (_ellipsoidRadii.y * _ellipsoidRadii.y),
41
+ 1 / (_ellipsoidRadii.z * _ellipsoidRadii.z),
42
+ );
43
+ const _geoEast = new Vector3();
44
+ const _geoNorth = new Vector3();
45
+ const _geoUp = new Vector3();
46
+ const UPRIGHT_ROLL_THRESHOLD = MathUtils.degToRad(5);
47
+ const ROLL_UNDEFINED_DOT_THRESHOLD = Math.cos(MathUtils.degToRad(5));
48
+ const HEADING_RIGHT_DEGENERATE_EPSILON = 1e-6;
49
+
50
+ function eastNorthUpToFixedFrame(origin) {
51
+ _geoUp.copy(origin).multiply(_oneOverRadiiSquared).normalize();
52
+ _geoEast.set(0, 0, 1).cross(_geoUp).normalize();
53
+ _geoNorth.copy(_geoUp).cross(_geoEast).normalize();
54
+
55
+ return _matrix.set(
56
+ _geoEast.x,
57
+ _geoNorth.x,
58
+ _geoUp.x,
59
+ origin.x,
60
+ _geoEast.y,
61
+ _geoNorth.y,
62
+ _geoUp.y,
63
+ origin.y,
64
+ _geoEast.z,
65
+ _geoNorth.z,
66
+ _geoUp.z,
67
+ origin.z,
68
+ 0,
69
+ 0,
70
+ 0,
71
+ 1,
72
+ );
73
+ }
74
+
75
+ export function createCameraFlight(
76
+ camera,
77
+ position,
78
+ target,
79
+ options = {},
80
+ ) {
81
+ const duration = options.duration ?? 2500;
82
+ const endPosition = position.clone();
83
+ const endQuaternion = getEndQuaternion(endPosition, target, options);
84
+ const endPose = getUprightHeadingPitchAtPose(endPosition, endQuaternion);
85
+ const endRoll = getRollAtPose(endPosition, endQuaternion);
86
+ const endZoom =
87
+ camera instanceof OrthographicCamera
88
+ ? Math.max(options.endZoom ?? camera.zoom, 1e-6)
89
+ : null;
90
+ return buildFlightState(camera, {
91
+ duration,
92
+ endPosition,
93
+ endQuaternion,
94
+ endZoom,
95
+ endHeading: endPose.heading,
96
+ endPitch: endPose.pitch,
97
+ endRoll,
98
+ });
99
+ }
100
+
101
+ export function createCameraPoseFlight(
102
+ camera,
103
+ position,
104
+ options,
105
+ ) {
106
+ const duration = options.duration ?? 2500;
107
+ const endPosition = position.clone();
108
+ const endQuaternion = getHeadingPitchRollQuaternion(
109
+ endPosition,
110
+ options.heading,
111
+ options.pitch,
112
+ options.roll ?? 0,
113
+ );
114
+ const endRoll = options.roll ?? getRollAtPose(endPosition, endQuaternion);
115
+ const endZoom =
116
+ camera instanceof OrthographicCamera
117
+ ? Math.max(options.endZoom ?? camera.zoom, 1e-6)
118
+ : null;
119
+ return buildFlightState(camera, {
120
+ duration,
121
+ endPosition,
122
+ endQuaternion,
123
+ endZoom,
124
+ endHeading: options.heading,
125
+ endPitch: options.pitch,
126
+ endRoll,
127
+ });
128
+ }
129
+
130
+ export function getFlyToParamsFromBoundingSphere(
131
+ camera,
132
+ target,
133
+ radius,
134
+ options = {},
135
+ ) {
136
+ const safeRadius = Math.max(radius, 1);
137
+ let offsetDistance = safeRadius;
138
+ let endZoom = options.endZoom;
139
+ if (camera instanceof PerspectiveCamera) {
140
+ const verticalFov = MathUtils.degToRad(camera.fov);
141
+ const horizontalFov =
142
+ 2 * Math.atan(Math.tan(verticalFov / 2) * camera.aspect);
143
+ const minHalfFov = Math.max(0.1, Math.min(verticalFov, horizontalFov) / 2);
144
+ offsetDistance = safeRadius / Math.sin(minHalfFov) + safeRadius * 0.75;
145
+ } else if (camera instanceof OrthographicCamera) {
146
+ const visibleHeight = Math.max(safeRadius * 2.8, 1);
147
+ endZoom = (camera.top - camera.bottom) / visibleHeight;
148
+ offsetDistance = Math.max(safeRadius * 2, visibleHeight * 0.5);
149
+ }
150
+
151
+ const position = getBoundingSphereFlyToPosition(
152
+ camera,
153
+ target,
154
+ offsetDistance,
155
+ options,
156
+ );
157
+
158
+ return {
159
+ position,
160
+ target: target.clone(),
161
+ options: {
162
+ ...options,
163
+ endZoom,
164
+ },
165
+ };
166
+ }
167
+
168
+ export function flyTo(
169
+ camera,
170
+ flight,
171
+ time,
172
+ ) {
173
+ if (flight.startTime === null) {
174
+ flight.startTime = time;
175
+ }
176
+
177
+ const rawT = MathUtils.clamp(
178
+ (time - flight.startTime) / flight.duration,
179
+ 0,
180
+ 1,
181
+ );
182
+ const easedT =
183
+ rawT < 0.5 ? 4 * rawT * rawT * rawT : 1 - Math.pow(-2 * rawT + 2, 3) / 2;
184
+
185
+ const position = getFlyToPosition(flight, easedT);
186
+ const quaternion = flight.maintainUpright
187
+ ? getUprightInterpolatedQuaternion(flight, position, easedT)
188
+ : _flyQuaternion.slerpQuaternions(
189
+ flight.startQuaternion,
190
+ flight.endQuaternion,
191
+ easedT,
192
+ );
193
+ const zoom =
194
+ flight.startZoom !== null && flight.endZoom !== null
195
+ ? MathUtils.lerp(flight.startZoom, flight.endZoom, easedT)
196
+ : null;
197
+
198
+ applyFlyToPose(camera, position, quaternion, zoom);
199
+ return rawT === 1;
200
+ }
201
+
202
+ function getFlyToPosition(flight, t) {
203
+ const { startPosition, endPosition, arcHeight } = flight;
204
+ const startLength = startPosition.length();
205
+ const endLength = endPosition.length();
206
+
207
+ if (startLength < 1e-6 || endLength < 1e-6) {
208
+ return _flyDirection.lerpVectors(startPosition, endPosition, t);
209
+ }
210
+
211
+ _flyDirectionStart.copy(startPosition).divideScalar(startLength);
212
+ _flyDirectionEnd.copy(endPosition).divideScalar(endLength);
213
+
214
+ const angle = _flyDirectionStart.angleTo(_flyDirectionEnd);
215
+ if (angle > 1e-5) {
216
+ if (angle < Math.PI - 1e-5) {
217
+ _flyAxis.crossVectors(_flyDirectionStart, _flyDirectionEnd).normalize();
218
+ } else {
219
+ _flyAxis.crossVectors(_flyDirectionStart, _flyWorldUp);
220
+ if (_flyAxis.lengthSq() < 1e-6) {
221
+ _flyAxis.crossVectors(_flyDirectionStart, _flyWorldNorth);
222
+ }
223
+ _flyAxis.normalize();
224
+ }
225
+ _flyDirection
226
+ .copy(_flyDirectionStart)
227
+ .applyAxisAngle(_flyAxis, angle * t)
228
+ .normalize();
229
+ } else {
230
+ _flyDirection.copy(_flyDirectionStart);
231
+ }
232
+
233
+ const radius =
234
+ MathUtils.lerp(startLength, endLength, t) +
235
+ Math.sin(Math.PI * t) * arcHeight;
236
+ return _flyDirection.multiplyScalar(radius);
237
+ }
238
+
239
+ function getUprightInterpolatedQuaternion(
240
+ flight,
241
+ position,
242
+ t,
243
+ ) {
244
+ const heading = lerpAngle(flight.startHeading, flight.endHeading, t);
245
+ const pitch = MathUtils.lerp(flight.startPitch, flight.endPitch, t);
246
+ return getHeadingPitchRollQuaternion(position, heading, pitch, 0);
247
+ }
248
+
249
+ function buildFlightState(
250
+ camera,
251
+ {
252
+ duration,
253
+ endPosition,
254
+ endQuaternion,
255
+ endZoom,
256
+ endHeading,
257
+ endPitch,
258
+ endRoll,
259
+ },
260
+ ) {
261
+ const startPosition = camera.position.clone();
262
+ const startQuaternion = camera.quaternion.clone();
263
+ const startPose = getUprightHeadingPitchAtPose(
264
+ startPosition,
265
+ startQuaternion,
266
+ );
267
+ const startRoll = getRollAtPose(startPosition, startQuaternion);
268
+ const startZoom = camera instanceof OrthographicCamera ? camera.zoom : null;
269
+
270
+ if (
271
+ isImmediateFlight(
272
+ duration,
273
+ startPosition,
274
+ endPosition,
275
+ startQuaternion,
276
+ endQuaternion,
277
+ startZoom,
278
+ endZoom,
279
+ )
280
+ ) {
281
+ applyFlyToPose(camera, endPosition, endQuaternion, endZoom);
282
+ return null;
283
+ }
284
+
285
+ return {
286
+ startTime: null,
287
+ duration,
288
+ startPosition,
289
+ endPosition,
290
+ startQuaternion,
291
+ endQuaternion,
292
+ startZoom,
293
+ endZoom,
294
+ arcHeight: getArcHeight(startPosition, endPosition),
295
+ maintainUpright: shouldMaintainUpright(startRoll, endRoll),
296
+ startHeading: startPose.heading,
297
+ endHeading,
298
+ startPitch: startPose.pitch,
299
+ endPitch,
300
+ };
301
+ }
302
+
303
+ function isImmediateFlight(
304
+ duration,
305
+ startPosition,
306
+ endPosition,
307
+ startQuaternion,
308
+ endQuaternion,
309
+ startZoom,
310
+ endZoom,
311
+ ) {
312
+ return (
313
+ duration <= 0 ||
314
+ (startPosition.distanceToSquared(endPosition) < 1e-6 &&
315
+ startQuaternion.angleTo(endQuaternion) < 1e-6 &&
316
+ (startZoom === null ||
317
+ endZoom === null ||
318
+ Math.abs(startZoom - endZoom) < 1e-6))
319
+ );
320
+ }
321
+
322
+ function getArcHeight(startPosition, endPosition) {
323
+ const chordLength = startPosition.distanceTo(endPosition);
324
+ return Math.max(
325
+ chordLength * 0.35,
326
+ Math.abs(endPosition.length() - startPosition.length()) * 0.5,
327
+ 500,
328
+ );
329
+ }
330
+
331
+ function shouldMaintainUpright(startRoll, endRoll) {
332
+ return (
333
+ Math.abs(startRoll) <= UPRIGHT_ROLL_THRESHOLD &&
334
+ Math.abs(endRoll) <= UPRIGHT_ROLL_THRESHOLD
335
+ );
336
+ }
337
+
338
+ function getLookAtQuaternion(position, target) {
339
+ if (isCenterModePosition(position)) {
340
+ _matrix.lookAt(position, target, _flyCenterUp);
341
+ return new Quaternion().setFromRotationMatrix(_matrix);
342
+ }
343
+
344
+ _flyUp.copy(target);
345
+ if (_flyUp.lengthSq() < 1e-6) {
346
+ _flyUp.copy(_flyWorldUp);
347
+ } else {
348
+ _flyUp.normalize();
349
+ }
350
+
351
+ _flyEast.crossVectors(_flyWorldNorth, _flyUp);
352
+ if (_flyEast.lengthSq() < 1e-6) {
353
+ _flyEast.crossVectors(_flyWorldUp, _flyUp);
354
+ }
355
+ if (_flyEast.lengthSq() < 1e-6) {
356
+ _flyEast.set(1, 0, 0);
357
+ }
358
+ _flyEast.normalize();
359
+ _flyUp.crossVectors(_flyUp, _flyEast).normalize();
360
+
361
+ _matrix.lookAt(position, target, _flyUp);
362
+ return new Quaternion().setFromRotationMatrix(_matrix);
363
+ }
364
+
365
+ function getForwardFromQuaternion(quaternion, target) {
366
+ return target.set(0, 0, -1).applyQuaternion(quaternion).normalize();
367
+ }
368
+
369
+ function getUprightHeadingPitchAtPose(
370
+ position,
371
+ quaternion,
372
+ ) {
373
+ if (isCenterModePosition(position)) {
374
+ const forward = getForwardFromQuaternion(quaternion, _flyForward);
375
+ _flyCameraRight.set(1, 0, 0).applyQuaternion(quaternion);
376
+ _flyReferenceRight.copy(_flyCameraRight).projectOnPlane(_flyCenterUp);
377
+
378
+ let heading = 0;
379
+ if (_flyReferenceRight.lengthSq() > HEADING_RIGHT_DEGENERATE_EPSILON) {
380
+ _flyReferenceRight.normalize();
381
+ heading = Math.atan2(
382
+ -_flyReferenceRight.dot(_flyCenterNorth),
383
+ _flyReferenceRight.dot(_flyWorldRight),
384
+ );
385
+ } else {
386
+ const horizontalForward = _flyDirection
387
+ .copy(forward)
388
+ .projectOnPlane(_flyCenterUp);
389
+ if (horizontalForward.lengthSq() > HEADING_RIGHT_DEGENERATE_EPSILON) {
390
+ horizontalForward.normalize();
391
+ heading = Math.atan2(
392
+ horizontalForward.dot(_flyWorldRight),
393
+ horizontalForward.dot(_flyCenterNorth),
394
+ );
395
+ }
396
+ }
397
+
398
+ return {
399
+ heading,
400
+ pitch: Math.asin(MathUtils.clamp(forward.dot(_flyCenterUp), -1, 1)),
401
+ };
402
+ }
403
+
404
+ if (position.lengthSq() < 1e-6) {
405
+ return {
406
+ heading: 0,
407
+ pitch: 0,
408
+ };
409
+ }
410
+
411
+ const forward = getForwardFromQuaternion(quaternion, _flyForward);
412
+ getLocalFrame(position);
413
+
414
+ _flyCameraRight.set(1, 0, 0).applyQuaternion(quaternion);
415
+ _flyReferenceRight.copy(_flyCameraRight).projectOnPlane(_flyUp);
416
+
417
+ let heading = 0;
418
+ if (_flyReferenceRight.lengthSq() > HEADING_RIGHT_DEGENERATE_EPSILON) {
419
+ _flyReferenceRight.normalize();
420
+ heading = Math.atan2(
421
+ -_flyReferenceRight.dot(_flyNorth),
422
+ _flyReferenceRight.dot(_flyEast),
423
+ );
424
+ } else {
425
+ const horizontalForward = _flyDirection
426
+ .copy(forward)
427
+ .projectOnPlane(_flyUp);
428
+ if (horizontalForward.lengthSq() > HEADING_RIGHT_DEGENERATE_EPSILON) {
429
+ horizontalForward.normalize();
430
+ heading = Math.atan2(
431
+ horizontalForward.dot(_flyEast),
432
+ horizontalForward.dot(_flyNorth),
433
+ );
434
+ }
435
+ }
436
+
437
+ return {
438
+ heading,
439
+ pitch: Math.asin(MathUtils.clamp(forward.dot(_flyUp), -1, 1)),
440
+ };
441
+ }
442
+
443
+ function getRollAtPose(position, quaternion) {
444
+ const forward = getForwardFromQuaternion(quaternion, _flyForward);
445
+ if (isCenterModePosition(position)) {
446
+ if (Math.abs(forward.dot(_flyCenterUp)) >= ROLL_UNDEFINED_DOT_THRESHOLD) {
447
+ return 0;
448
+ }
449
+
450
+ getReferenceBasis(
451
+ forward,
452
+ _flyWorldRight,
453
+ _flyCenterUp,
454
+ _flyReferenceRight,
455
+ _flyReferenceUp,
456
+ );
457
+ } else {
458
+ if (position.lengthSq() < 1e-6) {
459
+ return 0;
460
+ }
461
+
462
+ getLocalFrame(position);
463
+ if (Math.abs(forward.dot(_flyUp)) >= ROLL_UNDEFINED_DOT_THRESHOLD) {
464
+ return 0;
465
+ }
466
+
467
+ getReferenceBasis(
468
+ forward,
469
+ _flyEast,
470
+ _flyUp,
471
+ _flyReferenceRight,
472
+ _flyReferenceUp,
473
+ );
474
+ }
475
+
476
+ _flyReferenceUp.projectOnPlane(forward);
477
+ _flyCameraUp.set(0, 1, 0).applyQuaternion(quaternion).projectOnPlane(forward);
478
+
479
+ if (_flyReferenceUp.lengthSq() < 1e-6 || _flyCameraUp.lengthSq() < 1e-6) {
480
+ return 0;
481
+ }
482
+
483
+ _flyReferenceUp.normalize();
484
+ _flyCameraUp.normalize();
485
+
486
+ return Math.atan2(
487
+ _flyDirection.crossVectors(_flyReferenceUp, _flyCameraUp).dot(forward),
488
+ _flyReferenceUp.dot(_flyCameraUp),
489
+ );
490
+ }
491
+
492
+ function getEndQuaternion(
493
+ position,
494
+ target,
495
+ options,
496
+ ) {
497
+ const { heading, pitch, roll } = options;
498
+ if (heading === undefined && pitch === undefined && roll === undefined) {
499
+ return getLookAtQuaternion(position, target);
500
+ }
501
+
502
+ if (heading === undefined && pitch === undefined) {
503
+ const quaternion = getLookAtQuaternion(position, target);
504
+ if (roll) {
505
+ _flyForward.subVectors(target, position).normalize();
506
+ quaternion.multiply(_flyQuaternion.setFromAxisAngle(_flyForward, roll));
507
+ }
508
+ return quaternion;
509
+ }
510
+
511
+ return getHeadingPitchRollQuaternion(
512
+ isCenterModePosition(position) ? position : target,
513
+ heading ?? 0,
514
+ pitch ?? -Math.PI / 2,
515
+ roll ?? 0,
516
+ );
517
+ }
518
+
519
+ function getBoundingSphereFlyToPosition(
520
+ camera,
521
+ target,
522
+ range,
523
+ options,
524
+ ) {
525
+ const { heading, pitch } = options;
526
+ if (heading === undefined && pitch === undefined) {
527
+ const direction =
528
+ target.lengthSq() > 1e-6
529
+ ? _flyDirection.copy(target).normalize()
530
+ : camera.position.lengthSq() > 1e-6
531
+ ? _flyDirection.copy(camera.position).normalize()
532
+ : _flyDirection.set(0, -1, 0);
533
+ return direction.multiplyScalar(range).add(target);
534
+ }
535
+
536
+ const resolvedHeading = heading ?? 0;
537
+ const resolvedPitch = pitch ?? -Math.PI / 2;
538
+ const centerForward = getCenterModeHeadingPitchRollForward(
539
+ resolvedHeading,
540
+ resolvedPitch,
541
+ );
542
+ const centerPosition = _flyDirection
543
+ .copy(target)
544
+ .addScaledVector(centerForward, -range);
545
+ if (isCenterModePosition(centerPosition)) {
546
+ return centerPosition;
547
+ }
548
+
549
+ const forward = getHeadingPitchRollForward(
550
+ target,
551
+ resolvedHeading,
552
+ resolvedPitch,
553
+ );
554
+ return _flyDirection.copy(target).addScaledVector(forward, -range);
555
+ }
556
+
557
+ function getHeadingPitchRollQuaternion(
558
+ referencePoint,
559
+ heading,
560
+ pitch,
561
+ roll,
562
+ ) {
563
+ if (isCenterModePosition(referencePoint)) {
564
+ getCenterModeHeadingPitchRollBasis(heading, pitch, roll);
565
+ _matrix1.makeBasis(_flyRight, _flyUp, _flyBackward);
566
+ return new Quaternion().setFromRotationMatrix(_matrix1);
567
+ }
568
+
569
+ if (referencePoint.lengthSq() < 1e-6) {
570
+ return new Quaternion();
571
+ }
572
+
573
+ getHeadingPitchRollBasis(referencePoint, heading, pitch, roll);
574
+ _matrix1.makeBasis(_flyRight, _flyUp, _flyBackward);
575
+ return new Quaternion().setFromRotationMatrix(_matrix1);
576
+ }
577
+
578
+ function getHeadingPitchRollForward(
579
+ referencePoint,
580
+ heading,
581
+ pitch,
582
+ ) {
583
+ if (isCenterModePosition(referencePoint)) {
584
+ return getCenterModeHeadingPitchRollForward(heading, pitch);
585
+ }
586
+
587
+ if (referencePoint.lengthSq() < 1e-6) {
588
+ return _flyForward.set(0, 0, -1);
589
+ }
590
+
591
+ getLocalFrame(referencePoint);
592
+ const cosPitch = Math.cos(pitch);
593
+ const sinPitch = Math.sin(pitch);
594
+ const cosHeading = Math.cos(heading);
595
+ const sinHeading = Math.sin(heading);
596
+
597
+ _flyForward
598
+ .copy(_flyNorth)
599
+ .multiplyScalar(cosHeading * cosPitch)
600
+ .addScaledVector(_flyEast, sinHeading * cosPitch)
601
+ .addScaledVector(_flyUp, sinPitch)
602
+ .normalize();
603
+
604
+ return _flyForward;
605
+ }
606
+
607
+ function getCenterModeHeadingPitchRollForward(
608
+ heading,
609
+ pitch,
610
+ ) {
611
+ const cosPitch = Math.cos(pitch);
612
+ const sinPitch = Math.sin(pitch);
613
+ const cosHeading = Math.cos(heading);
614
+ const sinHeading = Math.sin(heading);
615
+
616
+ _flyForward.set(
617
+ sinHeading * cosPitch,
618
+ cosHeading * cosPitch,
619
+ sinPitch,
620
+ );
621
+
622
+ return _flyForward.normalize();
623
+ }
624
+
625
+ function getHeadingPitchRollBasis(
626
+ referencePoint,
627
+ heading,
628
+ pitch,
629
+ roll,
630
+ ) {
631
+ if (isCenterModePosition(referencePoint)) {
632
+ getCenterModeHeadingPitchRollBasis(heading, pitch, roll);
633
+ return;
634
+ }
635
+
636
+ getHeadingPitchRollForward(referencePoint, heading, pitch);
637
+
638
+ _flyRight
639
+ .copy(_flyEast)
640
+ .multiplyScalar(Math.cos(heading))
641
+ .addScaledVector(_flyNorth, -Math.sin(heading))
642
+ .normalize();
643
+ _flyUp.crossVectors(_flyRight, _flyForward).normalize();
644
+
645
+ if (roll !== 0) {
646
+ _flyRight.applyAxisAngle(_flyForward, roll).normalize();
647
+ _flyUp.applyAxisAngle(_flyForward, roll).normalize();
648
+ }
649
+
650
+ _flyBackward.copy(_flyForward).negate();
651
+ }
652
+
653
+ function getCenterModeHeadingPitchRollBasis(
654
+ heading,
655
+ pitch,
656
+ roll,
657
+ ) {
658
+ getCenterModeHeadingPitchRollForward(heading, pitch);
659
+
660
+ _flyRight
661
+ .copy(_flyWorldRight)
662
+ .multiplyScalar(Math.cos(heading))
663
+ .addScaledVector(_flyCenterNorth, -Math.sin(heading))
664
+ .normalize();
665
+ _flyUp.crossVectors(_flyRight, _flyForward).normalize();
666
+
667
+ if (roll !== 0) {
668
+ _flyRight.applyAxisAngle(_flyForward, roll).normalize();
669
+ _flyUp.applyAxisAngle(_flyForward, roll).normalize();
670
+ }
671
+
672
+ _flyBackward.copy(_flyForward).negate();
673
+ }
674
+
675
+ function getLocalFrame(target) {
676
+ _matrix1.copy(eastNorthUpToFixedFrame(target));
677
+ _flyEast.setFromMatrixColumn(_matrix1, 0).normalize();
678
+ _flyNorth.setFromMatrixColumn(_matrix1, 1).normalize();
679
+ _flyUp.setFromMatrixColumn(_matrix1, 2).normalize();
680
+ }
681
+
682
+ function isCenterModePosition(position) {
683
+ return position.lengthSq() <= CAMERA_CENTER_MODE_DISTANCE_SQ;
684
+ }
685
+
686
+ function getReferenceBasis(
687
+ forward,
688
+ east,
689
+ up,
690
+ rightTarget,
691
+ upTarget,
692
+ ) {
693
+ rightTarget.crossVectors(forward, up);
694
+ if (rightTarget.lengthSq() < 1e-6) {
695
+ rightTarget.copy(east).projectOnPlane(forward);
696
+ if (rightTarget.lengthSq() < 1e-6) {
697
+ rightTarget.set(1, 0, 0).projectOnPlane(forward);
698
+ }
699
+ }
700
+ rightTarget.normalize();
701
+ upTarget.crossVectors(rightTarget, forward).normalize();
702
+ }
703
+
704
+ function lerpAngle(start, end, t) {
705
+ let delta = end - start;
706
+ if (delta > Math.PI) {
707
+ delta -= Math.PI * 2;
708
+ } else if (delta < -Math.PI) {
709
+ delta += Math.PI * 2;
710
+ }
711
+
712
+ return start + delta * t;
713
+ }
714
+
715
+ function applyFlyToPose(
716
+ camera,
717
+ position,
718
+ quaternion,
719
+ zoom = null,
720
+ ) {
721
+ camera.position.copy(position);
722
+ camera.quaternion.copy(quaternion);
723
+
724
+ if (camera instanceof OrthographicCamera && zoom !== null) {
725
+ camera.zoom = Math.max(zoom, 1e-6);
726
+ camera.updateProjectionMatrix();
727
+ }
728
+
729
+ camera.updateMatrixWorld();
730
+ }