easy-three-utils 0.0.1

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.
Files changed (166) hide show
  1. package/package.json +12 -0
  2. package/src/common/index.ts +24 -0
  3. package/src/common/useLine2.ts +87 -0
  4. package/src/common/useLoader.ts +184 -0
  5. package/src/common/useLocationCalculator.ts +145 -0
  6. package/src/common/useMark.ts +42 -0
  7. package/src/common/useTween.ts +86 -0
  8. package/src/core/basic/camera.ts +28 -0
  9. package/src/core/basic/clock.ts +11 -0
  10. package/src/core/basic/control.ts +32 -0
  11. package/src/core/basic/index.ts +35 -0
  12. package/src/core/basic/labelRenderer.ts +26 -0
  13. package/src/core/basic/light.ts +63 -0
  14. package/src/core/basic/renderer.ts +37 -0
  15. package/src/core/basic/scene.ts +11 -0
  16. package/src/core/basic/stats.ts +16 -0
  17. package/src/core/event.ts +74 -0
  18. package/src/core/index.ts +11 -0
  19. package/src/core/main.ts +389 -0
  20. package/src/draco/README.md +32 -0
  21. package/src/draco/draco_decoder.js +34 -0
  22. package/src/draco/draco_decoder.wasm +0 -0
  23. package/src/draco/draco_encoder.js +33 -0
  24. package/src/draco/draco_wasm_wrapper.js +117 -0
  25. package/src/draco/gltf/draco_decoder.js +33 -0
  26. package/src/draco/gltf/draco_decoder.wasm +0 -0
  27. package/src/draco/gltf/draco_encoder.js +33 -0
  28. package/src/draco/gltf/draco_wasm_wrapper.js +116 -0
  29. package/src/tileRenderer/base/Tile.d.ts +50 -0
  30. package/src/tileRenderer/base/TileBase.d.ts +76 -0
  31. package/src/tileRenderer/base/TileInternal.d.ts +36 -0
  32. package/src/tileRenderer/base/TilesRendererBase.d.ts +35 -0
  33. package/src/tileRenderer/base/TilesRendererBase.js +847 -0
  34. package/src/tileRenderer/base/Tileset.d.ts +66 -0
  35. package/src/tileRenderer/base/constants.d.ts +6 -0
  36. package/src/tileRenderer/base/constants.js +16 -0
  37. package/src/tileRenderer/base/loaders/B3DMLoaderBase.d.ts +18 -0
  38. package/src/tileRenderer/base/loaders/B3DMLoaderBase.js +85 -0
  39. package/src/tileRenderer/base/loaders/CMPTLoaderBase.d.ts +22 -0
  40. package/src/tileRenderer/base/loaders/CMPTLoaderBase.js +61 -0
  41. package/src/tileRenderer/base/loaders/I3DMLoaderBase.d.ts +21 -0
  42. package/src/tileRenderer/base/loaders/I3DMLoaderBase.js +130 -0
  43. package/src/tileRenderer/base/loaders/LoaderBase.d.ts +10 -0
  44. package/src/tileRenderer/base/loaders/LoaderBase.js +73 -0
  45. package/src/tileRenderer/base/loaders/PNTSLoaderBase.d.ts +17 -0
  46. package/src/tileRenderer/base/loaders/PNTSLoaderBase.js +82 -0
  47. package/src/tileRenderer/base/plugins/ImplicitTilingPlugin.js +12 -0
  48. package/src/tileRenderer/base/traverseFunctions.js +468 -0
  49. package/src/tileRenderer/gltf.js +144 -0
  50. package/src/tileRenderer/index.d.ts +41 -0
  51. package/src/tileRenderer/index.js +44 -0
  52. package/src/tileRenderer/plugins/README.md +578 -0
  53. package/src/tileRenderer/plugins/base/ImplicitTilingPlugin.d.ts +2 -0
  54. package/src/tileRenderer/plugins/base/ImplicitTilingPlugin.js +84 -0
  55. package/src/tileRenderer/plugins/base/SUBTREELoader.js +876 -0
  56. package/src/tileRenderer/plugins/index.d.ts +17 -0
  57. package/src/tileRenderer/plugins/index.js +17 -0
  58. package/src/tileRenderer/plugins/three/CesiumIonAuthPlugin.d.ts +9 -0
  59. package/src/tileRenderer/plugins/three/CesiumIonAuthPlugin.js +175 -0
  60. package/src/tileRenderer/plugins/three/DebugTilesPlugin.d.ts +29 -0
  61. package/src/tileRenderer/plugins/three/DebugTilesPlugin.js +677 -0
  62. package/src/tileRenderer/plugins/three/GLTFExtensionsPlugin.d.ts +18 -0
  63. package/src/tileRenderer/plugins/three/GLTFExtensionsPlugin.js +86 -0
  64. package/src/tileRenderer/plugins/three/GoogleAttributionsManager.js +62 -0
  65. package/src/tileRenderer/plugins/three/GoogleCloudAuthPlugin.d.ts +5 -0
  66. package/src/tileRenderer/plugins/three/GoogleCloudAuthPlugin.js +200 -0
  67. package/src/tileRenderer/plugins/three/ReorientationPlugin.d.ts +12 -0
  68. package/src/tileRenderer/plugins/three/ReorientationPlugin.js +136 -0
  69. package/src/tileRenderer/plugins/three/TileCompressionPlugin.d.ts +18 -0
  70. package/src/tileRenderer/plugins/three/TileCompressionPlugin.js +223 -0
  71. package/src/tileRenderer/plugins/three/UpdateOnChangePlugin.d.ts +5 -0
  72. package/src/tileRenderer/plugins/three/UpdateOnChangePlugin.js +87 -0
  73. package/src/tileRenderer/plugins/three/fade/FadeManager.js +370 -0
  74. package/src/tileRenderer/plugins/three/fade/TilesFadePlugin.d.ts +9 -0
  75. package/src/tileRenderer/plugins/three/fade/TilesFadePlugin.js +318 -0
  76. package/src/tileRenderer/plugins/three/gltf/GLTFCesiumRTCExtension.d.ts +5 -0
  77. package/src/tileRenderer/plugins/three/gltf/GLTFCesiumRTCExtension.js +27 -0
  78. package/src/tileRenderer/plugins/three/gltf/GLTFMeshFeaturesExtension.d.ts +30 -0
  79. package/src/tileRenderer/plugins/three/gltf/GLTFMeshFeaturesExtension.js +76 -0
  80. package/src/tileRenderer/plugins/three/gltf/GLTFStructuralMetadataExtension.d.ts +49 -0
  81. package/src/tileRenderer/plugins/three/gltf/GLTFStructuralMetadataExtension.js +147 -0
  82. package/src/tileRenderer/plugins/three/gltf/metadata/classes/ClassProperty.js +149 -0
  83. package/src/tileRenderer/plugins/three/gltf/metadata/classes/MeshFeatures.js +215 -0
  84. package/src/tileRenderer/plugins/three/gltf/metadata/classes/PropertyAttributeAccessor.js +107 -0
  85. package/src/tileRenderer/plugins/three/gltf/metadata/classes/PropertySetAccessor.js +45 -0
  86. package/src/tileRenderer/plugins/three/gltf/metadata/classes/PropertyTableAccessor.js +209 -0
  87. package/src/tileRenderer/plugins/three/gltf/metadata/classes/PropertyTextureAccessor.js +244 -0
  88. package/src/tileRenderer/plugins/three/gltf/metadata/classes/StructuralMetadata.js +202 -0
  89. package/src/tileRenderer/plugins/three/gltf/metadata/math/Matrix2.js +55 -0
  90. package/src/tileRenderer/plugins/three/gltf/metadata/utilities/ClassPropertyHelpers.js +495 -0
  91. package/src/tileRenderer/plugins/three/gltf/metadata/utilities/TexCoordUtilities.js +72 -0
  92. package/src/tileRenderer/plugins/three/gltf/metadata/utilities/TextureReadUtility.js +154 -0
  93. package/src/tileRenderer/plugins/three/objects/EllipsoidRegionHelper.js +186 -0
  94. package/src/tileRenderer/plugins/three/objects/SphereHelper.js +55 -0
  95. package/src/tileRenderer/r3f/README.md +238 -0
  96. package/src/tileRenderer/r3f/components/CameraControls.jsx +132 -0
  97. package/src/tileRenderer/r3f/components/CameraTransition.jsx +177 -0
  98. package/src/tileRenderer/r3f/components/CanvasDOMOverlay.jsx +54 -0
  99. package/src/tileRenderer/r3f/components/CompassGizmo.jsx +260 -0
  100. package/src/tileRenderer/r3f/components/TilesAttributionOverlay.jsx +110 -0
  101. package/src/tileRenderer/r3f/components/TilesRenderer.jsx +239 -0
  102. package/src/tileRenderer/r3f/index.jsx +6 -0
  103. package/src/tileRenderer/r3f/utilities/useForceUpdate.jsx +8 -0
  104. package/src/tileRenderer/r3f/utilities/useObjectDep.jsx +59 -0
  105. package/src/tileRenderer/r3f/utilities/useOptions.jsx +143 -0
  106. package/src/tileRenderer/three/DebugTilesRenderer.d.ts +28 -0
  107. package/src/tileRenderer/three/DebugTilesRenderer.js +58 -0
  108. package/src/tileRenderer/three/TilesGroup.d.ts +9 -0
  109. package/src/tileRenderer/three/TilesGroup.js +91 -0
  110. package/src/tileRenderer/three/TilesRenderer.d.ts +37 -0
  111. package/src/tileRenderer/three/TilesRenderer.js +1049 -0
  112. package/src/tileRenderer/three/controls/CameraTransitionManager.js +305 -0
  113. package/src/tileRenderer/three/controls/EnvironmentControls.js +1295 -0
  114. package/src/tileRenderer/three/controls/GlobeControls.js +684 -0
  115. package/src/tileRenderer/three/controls/PivotPointMesh.js +104 -0
  116. package/src/tileRenderer/three/controls/PointerTracker.js +257 -0
  117. package/src/tileRenderer/three/controls/utils.js +113 -0
  118. package/src/tileRenderer/three/loaders/B3DMLoader.d.ts +26 -0
  119. package/src/tileRenderer/three/loaders/B3DMLoader.js +85 -0
  120. package/src/tileRenderer/three/loaders/CMPTLoader.d.ts +19 -0
  121. package/src/tileRenderer/three/loaders/CMPTLoader.js +97 -0
  122. package/src/tileRenderer/three/loaders/GLTFExtensionLoader.d.ts +11 -0
  123. package/src/tileRenderer/three/loaders/GLTFExtensionLoader.js +68 -0
  124. package/src/tileRenderer/three/loaders/I3DMLoader.d.ts +26 -0
  125. package/src/tileRenderer/three/loaders/I3DMLoader.js +256 -0
  126. package/src/tileRenderer/three/loaders/PNTSLoader.d.ts +25 -0
  127. package/src/tileRenderer/three/loaders/PNTSLoader.js +202 -0
  128. package/src/tileRenderer/three/loaders/gltf/GLTFCesiumRTCExtension.js +12 -0
  129. package/src/tileRenderer/three/loaders/gltf/GLTFMeshFeaturesExtension.js +12 -0
  130. package/src/tileRenderer/three/loaders/gltf/GLTFStructuralMetadataExtension.js +12 -0
  131. package/src/tileRenderer/three/math/Ellipsoid.d.ts +31 -0
  132. package/src/tileRenderer/three/math/Ellipsoid.js +337 -0
  133. package/src/tileRenderer/three/math/EllipsoidRegion.d.ts +23 -0
  134. package/src/tileRenderer/three/math/EllipsoidRegion.js +178 -0
  135. package/src/tileRenderer/three/math/ExtendedFrustum.js +65 -0
  136. package/src/tileRenderer/three/math/GeoConstants.d.ts +4 -0
  137. package/src/tileRenderer/three/math/GeoConstants.js +5 -0
  138. package/src/tileRenderer/three/math/GeoUtils.d.ts +9 -0
  139. package/src/tileRenderer/three/math/GeoUtils.js +106 -0
  140. package/src/tileRenderer/three/math/OBB.js +179 -0
  141. package/src/tileRenderer/three/math/TileBoundingVolume.js +272 -0
  142. package/src/tileRenderer/three/plugins/CesiumIonAuthPlugin.js +12 -0
  143. package/src/tileRenderer/three/plugins/DebugTilesPlugin.js +26 -0
  144. package/src/tileRenderer/three/plugins/GoogleCloudAuthPlugin.js +12 -0
  145. package/src/tileRenderer/three/raycastTraverse.js +178 -0
  146. package/src/tileRenderer/three/renderers/CesiumIonTilesRenderer.d.ts +14 -0
  147. package/src/tileRenderer/three/renderers/CesiumIonTilesRenderer.js +21 -0
  148. package/src/tileRenderer/three/renderers/GoogleTilesRenderer.d.ts +43 -0
  149. package/src/tileRenderer/three/renderers/GoogleTilesRenderer.js +48 -0
  150. package/src/tileRenderer/three/utilities.js +54 -0
  151. package/src/tileRenderer/utilities/BatchTable.d.ts +24 -0
  152. package/src/tileRenderer/utilities/BatchTable.js +82 -0
  153. package/src/tileRenderer/utilities/BatchTableHierarchyExtension.js +127 -0
  154. package/src/tileRenderer/utilities/FeatureTable.d.ts +30 -0
  155. package/src/tileRenderer/utilities/FeatureTable.js +159 -0
  156. package/src/tileRenderer/utilities/LRUCache.d.ts +8 -0
  157. package/src/tileRenderer/utilities/LRUCache.js +385 -0
  158. package/src/tileRenderer/utilities/PriorityQueue.d.ts +16 -0
  159. package/src/tileRenderer/utilities/PriorityQueue.js +137 -0
  160. package/src/tileRenderer/utilities/arrayToString.js +7 -0
  161. package/src/tileRenderer/utilities/readMagicBytes.js +29 -0
  162. package/src/tileRenderer/utilities/rgb565torgb.js +22 -0
  163. package/src/tileRenderer/utilities/urlExtension.js +34 -0
  164. package/tsconfig.json +42 -0
  165. package/tsconfig.node.json +11 -0
  166. package/typings/three.d.ts +27 -0
@@ -0,0 +1,1295 @@
1
+ import {
2
+ Matrix4,
3
+ Quaternion,
4
+ Vector2,
5
+ Vector3,
6
+ Raycaster,
7
+ Plane,
8
+ EventDispatcher,
9
+ MathUtils,
10
+ Clock,
11
+ } from 'three';
12
+ import { PivotPointMesh } from './PivotPointMesh.js';
13
+ import { PointerTracker } from './PointerTracker.js';
14
+ import { mouseToCoords, makeRotateAroundPoint, setRaycasterFromCamera } from './utils.js';
15
+
16
+ export const NONE = 0;
17
+ export const DRAG = 1;
18
+ export const ROTATE = 2;
19
+ export const ZOOM = 3;
20
+ export const WAITING = 4;
21
+
22
+ const DRAG_PLANE_THRESHOLD = 0.05;
23
+ const DRAG_UP_THRESHOLD = 0.025;
24
+ const ROT_MOMENTUM_THRESHOLD = 1e-4;
25
+ const POS_MOMENTUM_THRESHOLD = 1e-2;
26
+
27
+ const _rotMatrix = new Matrix4();
28
+ const _delta = new Vector3();
29
+ const _vec = new Vector3();
30
+ const _forward = new Vector3();
31
+ const _right = new Vector3();
32
+ const _rotationAxis = new Vector3();
33
+ const _quaternion = new Quaternion();
34
+ const _plane = new Plane();
35
+ const _localUp = new Vector3();
36
+ const _mouseBefore = new Vector3();
37
+ const _mouseAfter = new Vector3();
38
+ const _identityQuat = new Quaternion();
39
+
40
+ const _zoomPointPointer = new Vector2();
41
+ const _pointer = new Vector2();
42
+ const _prevPointer = new Vector2();
43
+ const _deltaPointer = new Vector2();
44
+ const _centerPoint = new Vector2();
45
+ const _startCenterPoint = new Vector2();
46
+
47
+ const _changeEvent = { type: 'change' };
48
+ const _startEvent = { type: 'start' };
49
+ const _endEvent = { type: 'end' };
50
+
51
+ export class EnvironmentControls extends EventDispatcher {
52
+
53
+ get enabled() {
54
+
55
+ return this._enabled;
56
+
57
+ }
58
+
59
+ set enabled( v ) {
60
+
61
+ if ( v !== this.enabled ) {
62
+
63
+ this._enabled = v;
64
+ this.resetState();
65
+ this.pointerTracker.reset();
66
+
67
+ if ( ! this.enabled ) {
68
+
69
+ this.dragInertia.set( 0, 0, 0 );
70
+ this.rotationInertia.set( 0, 0 );
71
+
72
+ }
73
+
74
+ }
75
+
76
+ }
77
+
78
+ constructor( scene = null, camera = null, domElement = null, tilesRenderer = null ) {
79
+
80
+ super();
81
+
82
+ this.isEnvironmentControls = true;
83
+
84
+ this.domElement = null;
85
+ this.camera = null;
86
+ this.scene = null;
87
+ this.tilesRenderer = null;
88
+
89
+ // settings
90
+ this._enabled = true;
91
+ this.cameraRadius = 5;
92
+ this.rotationSpeed = 1;
93
+ this.minAltitude = 0;
94
+ this.maxAltitude = 0.45 * Math.PI;
95
+ this.minDistance = 10;
96
+ this.maxDistance = Infinity;
97
+ this.minZoom = 0;
98
+ this.maxZoom = Infinity;
99
+ this.zoomSpeed = 1;
100
+ this.adjustHeight = true;
101
+ this.enableDamping = false;
102
+ this.dampingFactor = 0.15;
103
+
104
+ // settings for GlobeControls
105
+ this.reorientOnDrag = true;
106
+ this.scaleZoomOrientationAtEdges = false;
107
+
108
+ // internal state
109
+ this.state = NONE;
110
+ this.pointerTracker = new PointerTracker();
111
+ this.needsUpdate = false;
112
+ this.actionHeightOffset = 0;
113
+
114
+ this.pivotPoint = new Vector3();
115
+
116
+ this.zoomDirectionSet = false;
117
+ this.zoomPointSet = false;
118
+ this.zoomDirection = new Vector3();
119
+ this.zoomPoint = new Vector3();
120
+ this.zoomDelta = 0;
121
+
122
+ this.rotationInertia = new Vector2();
123
+ this.dragInertia = new Vector3();
124
+
125
+ this.pivotMesh = new PivotPointMesh();
126
+ this.pivotMesh.raycast = () => {};
127
+ this.pivotMesh.scale.setScalar( 0.25 );
128
+
129
+ this.raycaster = new Raycaster();
130
+ this.raycaster.firstHitOnly = true;
131
+
132
+ this.up = new Vector3( 0, 1, 0 );
133
+ this.clock = new Clock();
134
+
135
+ this.fallbackPlane = new Plane( new Vector3( 0, 1, 0 ), 0 );
136
+ this.useFallbackPlane = true;
137
+
138
+ this._detachCallback = null;
139
+ this._upInitialized = false;
140
+ this._lastUsedState = NONE;
141
+ this._zoomPointWasSet = false;
142
+
143
+ // always update the zoom target point in case the tiles are changing
144
+ this._tilesOnChangeCallback = () => this.zoomPointSet = false;
145
+
146
+ // init
147
+ if ( domElement ) this.attach( domElement );
148
+ if ( camera ) this.setCamera( camera );
149
+ if ( scene ) this.setScene( scene );
150
+ if ( tilesRenderer ) this.setTilesRenderer( tilesRenderer );
151
+
152
+ }
153
+
154
+ setScene( scene ) {
155
+
156
+ this.scene = scene;
157
+
158
+ }
159
+
160
+ setCamera( camera ) {
161
+
162
+ this.camera = camera;
163
+ this._upInitialized = false;
164
+ this.zoomDirectionSet = false;
165
+ this.zoomPointSet = false;
166
+ this.needsUpdate = true;
167
+ this.raycaster.camera = camera;
168
+ this.resetState();
169
+
170
+ }
171
+
172
+ setTilesRenderer( tilesRenderer ) {
173
+
174
+ // TODO: what if a scene has multiple tile sets?
175
+ if ( this.tilesRenderer ) {
176
+
177
+ this.tilesRenderer.removeEventListener( 'tile-visibility-change', this._tilesOnChangeCallback );
178
+
179
+ }
180
+
181
+ this.tilesRenderer = tilesRenderer;
182
+ if ( this.tilesRenderer !== null ) {
183
+
184
+ this.tilesRenderer.addEventListener( 'tile-visibility-change', this._tilesOnChangeCallback );
185
+
186
+ if ( this.scene === null ) {
187
+
188
+ this.setScene( this.tilesRenderer.group );
189
+
190
+ }
191
+
192
+ }
193
+
194
+ }
195
+
196
+ attach( domElement ) {
197
+
198
+ if ( this.domElement ) {
199
+
200
+ throw new Error( 'EnvironmentControls: Controls already attached to element' );
201
+
202
+ }
203
+
204
+ // set the touch action to none so the browser does not
205
+ // drag the page to refresh or scroll
206
+ this.domElement = domElement;
207
+ this.pointerTracker.domElement = domElement;
208
+ domElement.style.touchAction = 'none';
209
+
210
+ let shiftClicked = false;
211
+
212
+ const contextMenuCallback = e => {
213
+
214
+ e.preventDefault();
215
+
216
+ };
217
+
218
+ const keydownCallback = e => {
219
+
220
+ if ( e.key === 'Shift' ) {
221
+
222
+ shiftClicked = true;
223
+
224
+ }
225
+
226
+ };
227
+
228
+ const keyupCallback = e => {
229
+
230
+ if ( e.key === 'Shift' ) {
231
+
232
+ shiftClicked = false;
233
+
234
+ }
235
+
236
+ };
237
+
238
+ const pointerdownCallback = e => {
239
+
240
+ e.preventDefault();
241
+
242
+ const {
243
+ camera,
244
+ raycaster,
245
+ domElement,
246
+ up,
247
+ pivotMesh,
248
+ pointerTracker,
249
+ } = this;
250
+
251
+ // init the pointer
252
+ pointerTracker.addPointer( e );
253
+ this.needsUpdate = true;
254
+
255
+ // handle cases where we need to capture the pointer or
256
+ // reset state when we have too many pointers
257
+ if ( pointerTracker.isPointerTouch() ) {
258
+
259
+ pivotMesh.visible = false;
260
+
261
+ if ( pointerTracker.getPointerCount() === 0 ) {
262
+
263
+ domElement.setPointerCapture( e.pointerId );
264
+
265
+ } else if ( pointerTracker.getPointerCount() > 2 ) {
266
+
267
+ this.resetState();
268
+ return;
269
+
270
+ }
271
+
272
+ }
273
+
274
+ // the "pointer" for zooming and rotating should be based on the center point
275
+ pointerTracker.getCenterPoint( _pointer );
276
+ mouseToCoords( _pointer.x, _pointer.y, domElement, _pointer );
277
+ setRaycasterFromCamera( raycaster, _pointer, camera );
278
+
279
+ // prevent the drag distance from getting too severe by limiting the drag point
280
+ // to a reasonable angle and reasonable distance with the drag plane
281
+ const dot = Math.abs( raycaster.ray.direction.dot( up ) );
282
+ if ( dot < DRAG_PLANE_THRESHOLD || dot < DRAG_UP_THRESHOLD ) {
283
+
284
+ return;
285
+
286
+ }
287
+
288
+ // find the hit point
289
+ const hit = this._raycast( raycaster );
290
+ if ( hit ) {
291
+
292
+ // if two fingers, right click, or shift click are being used then we trigger
293
+ // a rotation action to begin
294
+ if (
295
+ pointerTracker.getPointerCount() === 2 ||
296
+ pointerTracker.isRightClicked() ||
297
+ pointerTracker.isLeftClicked() && shiftClicked
298
+ ) {
299
+
300
+ this.setState( pointerTracker.isPointerTouch() ? WAITING : ROTATE );
301
+
302
+ this.pivotPoint.copy( hit.point );
303
+ this.pivotMesh.position.copy( hit.point );
304
+ this.pivotMesh.updateMatrixWorld();
305
+ this.scene.add( this.pivotMesh );
306
+
307
+ } else if ( pointerTracker.isLeftClicked() ) {
308
+
309
+ // if the clicked point is coming from below the plane then don't perform the drag
310
+ this.setState( DRAG );
311
+ this.pivotPoint.copy( hit.point );
312
+
313
+ this.pivotMesh.position.copy( hit.point );
314
+ this.pivotMesh.updateMatrixWorld();
315
+ this.scene.add( this.pivotMesh );
316
+
317
+ }
318
+
319
+ }
320
+
321
+ };
322
+
323
+ let _pointerMoveQueued = false;
324
+ const pointermoveCallback = e => {
325
+
326
+ e.preventDefault();
327
+
328
+ // whenever the pointer moves we need to re-derive the zoom direction and point
329
+ this.zoomDirectionSet = false;
330
+ this.zoomPointSet = false;
331
+
332
+ if ( this.state !== NONE ) {
333
+
334
+ this.needsUpdate = true;
335
+
336
+ }
337
+
338
+ const { pointerTracker } = this;
339
+ pointerTracker.setHoverEvent( e );
340
+ if ( ! pointerTracker.updatePointer( e ) ) {
341
+
342
+ return;
343
+
344
+ }
345
+
346
+ if ( pointerTracker.isPointerTouch() && pointerTracker.getPointerCount() === 2 ) {
347
+
348
+ // We queue this event to ensure that all pointers have been updated
349
+ if ( ! _pointerMoveQueued ) {
350
+
351
+ _pointerMoveQueued = true;
352
+ queueMicrotask( () => {
353
+
354
+ _pointerMoveQueued = false;
355
+
356
+ // adjust the pointer position to be the center point
357
+ pointerTracker.getCenterPoint( _centerPoint );
358
+
359
+ // detect zoom transition
360
+ const startDist = pointerTracker.getStartTouchPointerDistance();
361
+ const pointerDist = pointerTracker.getTouchPointerDistance();
362
+ const separateDelta = pointerDist - startDist;
363
+ if ( this.state === NONE || this.state === WAITING ) {
364
+
365
+ // check which direction was moved in first - if the pointers are pinching then
366
+ // it's a zoom. But if they move in parallel it's a rotation
367
+ pointerTracker.getCenterPoint( _centerPoint );
368
+ pointerTracker.getStartCenterPoint( _startCenterPoint );
369
+
370
+ // adjust the drag requirement by the dpr
371
+ const dragThreshold = 2.0 * window.devicePixelRatio;
372
+ const parallelDelta = _centerPoint.distanceTo( _startCenterPoint );
373
+ if ( Math.abs( separateDelta ) > dragThreshold || parallelDelta > dragThreshold ) {
374
+
375
+ if ( Math.abs( separateDelta ) > parallelDelta ) {
376
+
377
+ this.setState( ZOOM );
378
+ this.zoomDirectionSet = false;
379
+
380
+ } else {
381
+
382
+ this.setState( ROTATE );
383
+
384
+ }
385
+
386
+ }
387
+
388
+ }
389
+
390
+ if ( this.state === ZOOM ) {
391
+
392
+ const previousDist = pointerTracker.getPreviousTouchPointerDistance();
393
+ this.zoomDelta += pointerDist - previousDist;
394
+
395
+ } else if ( this.state === ROTATE ) {
396
+
397
+ this.pivotMesh.visible = this.enabled;
398
+
399
+ }
400
+
401
+ } );
402
+
403
+ }
404
+
405
+ }
406
+
407
+ // TODO: we have the potential to fire change multiple times per frame - should we debounce?
408
+ this.dispatchEvent( _changeEvent );
409
+
410
+ };
411
+
412
+ const pointerupCallback = e => {
413
+
414
+ const { pointerTracker } = this;
415
+
416
+ pointerTracker.deletePointer( e );
417
+
418
+ if (
419
+ pointerTracker.getPointerType() === 'touch' &&
420
+ pointerTracker.getPointerCount() === 0
421
+ ) {
422
+
423
+ domElement.releasePointerCapture( e.pointerId );
424
+
425
+ }
426
+
427
+ this.resetState();
428
+ this.needsUpdate = true;
429
+
430
+ };
431
+
432
+ const wheelCallback = e => {
433
+
434
+ e.preventDefault();
435
+
436
+ const { pointerTracker } = this;
437
+ pointerTracker.setHoverEvent( e );
438
+ pointerTracker.updatePointer( e );
439
+
440
+ // TODO: do we need events here?
441
+ this.dispatchEvent( _startEvent );
442
+
443
+ let delta;
444
+ switch ( e.deltaMode ) {
445
+
446
+ case 2: // Pages
447
+ delta = e.deltaY * 100;
448
+ break;
449
+ case 1: // Lines
450
+ delta = e.deltaY * 16;
451
+ break;
452
+ case 0: // Pixels
453
+ delta = e.deltaY;
454
+ break;
455
+
456
+ }
457
+
458
+ // use LOG to scale the scroll delta and hopefully normalize them across platforms
459
+ const deltaSign = Math.sign( delta );
460
+ const normalizedDelta = Math.log( Math.abs( delta ) + 1 );
461
+ this.zoomDelta -= 3 * deltaSign * normalizedDelta;
462
+ this.needsUpdate = true;
463
+
464
+ this._lastUsedState = ZOOM;
465
+ this.dispatchEvent( _endEvent );
466
+
467
+ };
468
+
469
+ const pointerenterCallback = e => {
470
+
471
+ const { pointerTracker } = this;
472
+
473
+ shiftClicked = false;
474
+
475
+ if ( e.buttons !== pointerTracker.getPointerButtons() ) {
476
+
477
+ pointerTracker.deletePointer( e );
478
+ this.resetState();
479
+
480
+ }
481
+
482
+ };
483
+
484
+ domElement.addEventListener( 'contextmenu', contextMenuCallback );
485
+ domElement.addEventListener( 'keydown', keydownCallback );
486
+ domElement.addEventListener( 'keyup', keyupCallback );
487
+ domElement.addEventListener( 'pointerdown', pointerdownCallback );
488
+ domElement.addEventListener( 'pointermove', pointermoveCallback );
489
+ domElement.addEventListener( 'pointerup', pointerupCallback );
490
+ domElement.addEventListener( 'wheel', wheelCallback, { passive: false } );
491
+ domElement.addEventListener( 'pointerenter', pointerenterCallback );
492
+
493
+ this._detachCallback = () => {
494
+
495
+ domElement.removeEventListener( 'contextmenu', contextMenuCallback );
496
+ domElement.removeEventListener( 'keydown', keydownCallback );
497
+ domElement.removeEventListener( 'keyup', keyupCallback );
498
+ domElement.removeEventListener( 'pointerdown', pointerdownCallback );
499
+ domElement.removeEventListener( 'pointermove', pointermoveCallback );
500
+ domElement.removeEventListener( 'pointerup', pointerupCallback );
501
+ domElement.removeEventListener( 'wheel', wheelCallback );
502
+ domElement.removeEventListener( 'pointerenter', pointerenterCallback );
503
+
504
+ };
505
+
506
+ }
507
+
508
+ // override-able functions for retrieving the up direction at a point
509
+ getUpDirection( point, target ) {
510
+
511
+ target.copy( this.up );
512
+
513
+ }
514
+
515
+ getCameraUpDirection( target ) {
516
+
517
+ this.getUpDirection( this.camera.position, target );
518
+
519
+ }
520
+
521
+ // returns the active / last used pivot point for the scene
522
+ getPivotPoint( target ) {
523
+
524
+ if ( this._lastUsedState === ZOOM ) {
525
+
526
+ if ( this._zoomPointWasSet ) {
527
+
528
+ target.copy( this.zoomPoint );
529
+ return target;
530
+
531
+ } else {
532
+
533
+ return null;
534
+
535
+ }
536
+
537
+ } else if ( this._lastUsedState === ROTATE || this._lastUsedState === DRAG ) {
538
+
539
+ target.copy( this.pivotPoint );
540
+ return target;
541
+
542
+ } else {
543
+
544
+ return null;
545
+
546
+ }
547
+
548
+ }
549
+
550
+ detach() {
551
+
552
+ this.domElement = null;
553
+
554
+ if ( this._detachCallback ) {
555
+
556
+ this._detachCallback();
557
+ this._detachCallback = null;
558
+ this.pointerTracker.reset();
559
+
560
+ }
561
+
562
+ }
563
+
564
+ resetState() {
565
+
566
+ if ( this.state !== NONE ) {
567
+
568
+ this.dispatchEvent( _endEvent );
569
+
570
+ }
571
+
572
+ this.state = NONE;
573
+ this.pivotMesh.removeFromParent();
574
+ this.pivotMesh.visible = this.enabled;
575
+ this.actionHeightOffset = 0;
576
+
577
+ }
578
+
579
+ setState( state = this.state, fireEvent = true ) {
580
+
581
+ if ( this.state === state ) {
582
+
583
+ return;
584
+
585
+ }
586
+
587
+ if ( this.state === NONE && fireEvent ) {
588
+
589
+ this.dispatchEvent( _startEvent );
590
+
591
+ }
592
+
593
+ this.pivotMesh.visible = this.enabled;
594
+ this.dragInertia.set( 0, 0, 0 );
595
+ this.rotationInertia.set( 0, 0 );
596
+ this.state = state;
597
+
598
+ if ( state !== NONE && state !== WAITING ) {
599
+
600
+ this._lastUsedState = state;
601
+
602
+ }
603
+
604
+ }
605
+
606
+ update( deltaTime = Math.min( this.clock.getDelta(), 64 / 1000 ) ) {
607
+
608
+ if ( ! this.enabled || ! this.camera || deltaTime === 0 ) {
609
+
610
+ return;
611
+
612
+ }
613
+
614
+ const {
615
+ camera,
616
+ cameraRadius,
617
+ pivotPoint,
618
+ up,
619
+ state,
620
+ adjustHeight,
621
+ } = this;
622
+
623
+ camera.updateMatrixWorld();
624
+
625
+ // set the "up" vector immediately so it's available in the following functions
626
+ this.getCameraUpDirection( _localUp );
627
+ if ( ! this._upInitialized ) {
628
+
629
+ this._upInitialized = true;
630
+ this.up.copy( _localUp );
631
+
632
+ }
633
+
634
+ // update the actions
635
+ const inertiaNeedsUpdate = this._inertiaNeedsUpdate();
636
+ if ( this.needsUpdate || inertiaNeedsUpdate ) {
637
+
638
+ const zoomDelta = this.zoomDelta;
639
+ if ( state === ZOOM || zoomDelta !== 0 ) {
640
+
641
+ this._updateZoom();
642
+
643
+ this.rotationInertia.set( 0, 0 );
644
+ this.dragInertia.set( 0, 0, 0 );
645
+
646
+ }
647
+
648
+ this._updatePosition( deltaTime );
649
+ this._updateRotation( deltaTime );
650
+
651
+ if ( state !== NONE || zoomDelta !== 0 || inertiaNeedsUpdate ) {
652
+
653
+ this.dispatchEvent( _changeEvent );
654
+
655
+ }
656
+
657
+ this.needsUpdate = false;
658
+
659
+ }
660
+
661
+ if ( inertiaNeedsUpdate ) {
662
+
663
+ this._updateInertiaDamping( deltaTime );
664
+
665
+ }
666
+
667
+ // update the up direction based on where the camera moved to
668
+ // if using an orthographic camera then rotate around drag pivot
669
+ // reuse the "hit" information since it can be slow to perform multiple hits
670
+ const hit = camera.isOrthographicCamera ? null : adjustHeight && this._getPointBelowCamera() || null;
671
+ const rotationPoint = camera.isOrthographicCamera ? pivotPoint : hit && hit.point || null;
672
+ this.getCameraUpDirection( _localUp );
673
+ this._setFrame( _localUp, rotationPoint );
674
+
675
+ // when dragging the camera and drag point may be moved
676
+ // to accommodate terrain so we try to move it back down
677
+ // to the original point.
678
+ if ( ( this.state === DRAG || this.state === ROTATE ) && this.actionHeightOffset !== 0 ) {
679
+
680
+ const { actionHeightOffset } = this;
681
+ camera.position.addScaledVector( up, - actionHeightOffset );
682
+ pivotPoint.addScaledVector( up, - actionHeightOffset );
683
+
684
+ // adjust the height
685
+ if ( hit ) {
686
+
687
+ hit.distance -= actionHeightOffset;
688
+
689
+ }
690
+
691
+ }
692
+
693
+ this.actionHeightOffset = 0;
694
+
695
+ if ( hit ) {
696
+
697
+ const dist = hit.distance;
698
+ if ( dist < cameraRadius ) {
699
+
700
+ const delta = cameraRadius - dist;
701
+ camera.position.addScaledVector( up, delta );
702
+ pivotPoint.addScaledVector( up, delta );
703
+ this.actionHeightOffset = delta;
704
+
705
+ }
706
+
707
+ }
708
+
709
+ this.pointerTracker.updateFrame();
710
+
711
+ }
712
+
713
+ // updates the camera to position it based on the constraints of the controls
714
+ adjustCamera( camera ) {
715
+
716
+ const { adjustHeight, cameraRadius } = this;
717
+ if ( camera.isPerspectiveCamera ) {
718
+
719
+ // adjust the camera height
720
+ this.getUpDirection( camera.position, _localUp );
721
+ const hit = adjustHeight && this._getPointBelowCamera( camera.position, _localUp ) || null;
722
+ if ( hit ) {
723
+
724
+ const dist = hit.distance;
725
+ if ( dist < cameraRadius ) {
726
+
727
+ camera.position.addScaledVector( _localUp, cameraRadius - dist );
728
+
729
+ }
730
+
731
+ }
732
+
733
+ }
734
+
735
+ }
736
+
737
+ dispose() {
738
+
739
+ this.detach();
740
+
741
+ }
742
+
743
+ // private
744
+ _updateInertiaDamping( deltaTime ) {
745
+
746
+ // update the damping of momentum variables
747
+ const {
748
+ rotationInertia,
749
+ dragInertia,
750
+ enableDamping,
751
+ dampingFactor,
752
+ } = this;
753
+
754
+ // Based on Freya Holmer's frame-rate independent lerp function
755
+ const factor = Math.pow( 2, - deltaTime / dampingFactor );
756
+
757
+ // scale the residual motion
758
+ rotationInertia.multiplyScalar( factor );
759
+ if ( rotationInertia.lengthSq() < ROT_MOMENTUM_THRESHOLD || ! enableDamping ) {
760
+
761
+ rotationInertia.set( 0, 0 );
762
+
763
+ }
764
+
765
+ dragInertia.multiplyScalar( factor );
766
+ if ( dragInertia.lengthSq() < POS_MOMENTUM_THRESHOLD || ! enableDamping ) {
767
+
768
+ dragInertia.set( 0, 0, 0 );
769
+
770
+ }
771
+
772
+ }
773
+
774
+ _inertiaNeedsUpdate() {
775
+
776
+ const { rotationInertia, dragInertia } = this;
777
+ return rotationInertia.lengthSq() !== 0 || dragInertia.lengthSq() !== 0;
778
+
779
+ }
780
+
781
+ _updateZoom() {
782
+
783
+ const {
784
+ zoomPoint,
785
+ zoomDirection,
786
+ camera,
787
+ minDistance,
788
+ maxDistance,
789
+ pointerTracker,
790
+ domElement,
791
+ minZoom,
792
+ maxZoom,
793
+ zoomSpeed,
794
+ } = this;
795
+
796
+ let scale = this.zoomDelta;
797
+ this.zoomDelta = 0;
798
+
799
+ // get the latest hover / touch point
800
+ if ( ! pointerTracker.getLatestPoint( _pointer ) ) {
801
+
802
+ return;
803
+
804
+ }
805
+
806
+ if ( camera.isOrthographicCamera ) {
807
+
808
+ // update the zoom direction
809
+ this._updateZoomDirection();
810
+
811
+ // zoom straight into the globe if we haven't hit anything
812
+ const zoomIntoPoint = this.zoomPointSet || this._updateZoomPoint();
813
+
814
+ // get the mouse position before zoom
815
+ _mouseBefore.unproject( camera );
816
+
817
+ // zoom the camera
818
+ const normalizedDelta = Math.pow( 0.95, Math.abs( scale * 0.05 ) );
819
+ let scaleFactor = scale > 0 ? 1 / Math.abs( normalizedDelta ) : normalizedDelta;
820
+ scaleFactor *= zoomSpeed;
821
+
822
+ if ( scaleFactor > 1 ) {
823
+
824
+ if ( maxZoom < camera.zoom * scaleFactor ) {
825
+
826
+ scaleFactor = 1;
827
+
828
+ }
829
+
830
+ } else {
831
+
832
+ if ( minZoom > camera.zoom * scaleFactor ) {
833
+
834
+ scaleFactor = 1;
835
+
836
+ }
837
+
838
+ }
839
+
840
+ camera.zoom *= scaleFactor;
841
+ camera.updateProjectionMatrix();
842
+
843
+ // adjust the surface point to be in the same position if the globe is hovered over
844
+ if ( zoomIntoPoint ) {
845
+
846
+ // get the mouse position after zoom
847
+ mouseToCoords( _pointer.x, _pointer.y, domElement, _mouseAfter );
848
+ _mouseAfter.unproject( camera );
849
+
850
+ // shift the camera on the near plane so the mouse is in the same spot
851
+ camera.position.sub( _mouseAfter ).add( _mouseBefore );
852
+ camera.updateMatrixWorld();
853
+
854
+ }
855
+
856
+ // TODO: the user can currently zoom into the sky and hide the globe.
857
+ // Consider forcing the camera to zoom into the closest horizon point
858
+
859
+ } else {
860
+
861
+ // initialize the zoom direction
862
+ this._updateZoomDirection();
863
+
864
+ // track the zoom direction we're going to use
865
+ const finalZoomDirection = _vec.copy( zoomDirection );
866
+
867
+ if ( this.zoomPointSet || this._updateZoomPoint() ) {
868
+
869
+ const dist = zoomPoint.distanceTo( camera.position );
870
+
871
+ // scale the distance based on how far there is to move
872
+ if ( scale < 0 ) {
873
+
874
+ const remainingDistance = Math.min( 0, dist - maxDistance );
875
+ scale = scale * dist * zoomSpeed * 0.0025;
876
+ scale = Math.max( scale, remainingDistance );
877
+
878
+ } else {
879
+
880
+ const remainingDistance = Math.max( 0, dist - minDistance );
881
+ scale = scale * Math.max( dist - minDistance, 0 ) * zoomSpeed * 0.0025;
882
+ scale = Math.min( scale, remainingDistance );
883
+
884
+ }
885
+
886
+ camera.position.addScaledVector( zoomDirection, scale );
887
+ camera.updateMatrixWorld();
888
+
889
+ } else {
890
+
891
+ // if we're zooming into nothing then use the distance from the ground to scale movement
892
+ const hit = this._getPointBelowCamera();
893
+ if ( hit ) {
894
+
895
+ const dist = hit.distance;
896
+ finalZoomDirection.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
897
+ camera.position.addScaledVector( finalZoomDirection, scale * dist * 0.01 );
898
+ camera.updateMatrixWorld();
899
+
900
+ }
901
+
902
+ }
903
+
904
+ }
905
+
906
+ }
907
+
908
+ _updateZoomDirection() {
909
+
910
+ if ( this.zoomDirectionSet ) {
911
+
912
+ return;
913
+
914
+ }
915
+
916
+ const { domElement, raycaster, camera, zoomDirection, pointerTracker } = this;
917
+ pointerTracker.getLatestPoint( _pointer );
918
+ mouseToCoords( _pointer.x, _pointer.y, domElement, _mouseBefore );
919
+ setRaycasterFromCamera( raycaster, _mouseBefore, camera );
920
+ zoomDirection.copy( raycaster.ray.direction ).normalize();
921
+ this.zoomDirectionSet = true;
922
+
923
+ }
924
+
925
+ // update the point being zoomed in to based on the zoom direction
926
+ _updateZoomPoint() {
927
+
928
+ const {
929
+ camera,
930
+ zoomDirectionSet,
931
+ zoomDirection,
932
+ raycaster,
933
+ zoomPoint,
934
+ pointerTracker,
935
+ domElement,
936
+ } = this;
937
+
938
+ this._zoomPointWasSet = false;
939
+
940
+ if ( ! zoomDirectionSet ) {
941
+
942
+ return false;
943
+
944
+ }
945
+
946
+ // If using an orthographic camera we have to account for the mouse position when picking the point
947
+ if ( camera.isOrthographicCamera && pointerTracker.getLatestPoint( _zoomPointPointer ) ) {
948
+
949
+ mouseToCoords( _zoomPointPointer.x, _zoomPointPointer.y, domElement, _zoomPointPointer );
950
+ setRaycasterFromCamera( raycaster, _zoomPointPointer, camera );
951
+
952
+ } else {
953
+
954
+ raycaster.ray.origin.copy( camera.position );
955
+ raycaster.ray.direction.copy( zoomDirection );
956
+ raycaster.near = 0;
957
+ raycaster.far = Infinity;
958
+
959
+ }
960
+
961
+ // get the hit point
962
+ const hit = this._raycast( raycaster );
963
+ if ( hit ) {
964
+
965
+ zoomPoint.copy( hit.point );
966
+ this.zoomPointSet = true;
967
+ this._zoomPointWasSet = true;
968
+ return true;
969
+
970
+ }
971
+
972
+ return false;
973
+
974
+ }
975
+
976
+ // returns the point below the camera
977
+ _getPointBelowCamera( point = this.camera.position, up = this.up ) {
978
+
979
+ const { raycaster } = this;
980
+ raycaster.ray.direction.copy( up ).multiplyScalar( - 1 );
981
+ raycaster.ray.origin.copy( point ).addScaledVector( up, 1e5 );
982
+ raycaster.near = 0;
983
+ raycaster.far = Infinity;
984
+
985
+ const hit = this._raycast( raycaster );
986
+ if ( hit ) {
987
+
988
+ hit.distance -= 1e5;
989
+
990
+ }
991
+
992
+ return hit;
993
+
994
+ }
995
+
996
+ // update the drag action
997
+ _updatePosition( deltaTime ) {
998
+
999
+ const {
1000
+ raycaster,
1001
+ camera,
1002
+ pivotPoint,
1003
+ up,
1004
+ pointerTracker,
1005
+ domElement,
1006
+ state,
1007
+ dragInertia,
1008
+ enableDamping,
1009
+ } = this;
1010
+
1011
+ if ( state === DRAG ) {
1012
+
1013
+ // get the pointer and plane
1014
+ pointerTracker.getCenterPoint( _pointer );
1015
+ mouseToCoords( _pointer.x, _pointer.y, domElement, _pointer );
1016
+
1017
+ _plane.setFromNormalAndCoplanarPoint( up, pivotPoint );
1018
+ setRaycasterFromCamera( raycaster, _pointer, camera );
1019
+
1020
+ // prevent the drag distance from getting too severe by limiting the drag point
1021
+ // to a reasonable angle with the drag plane
1022
+ if ( Math.abs( raycaster.ray.direction.dot( up ) ) < DRAG_PLANE_THRESHOLD ) {
1023
+
1024
+ // rotate the pointer direction down to the correct angle for horizontal dragging
1025
+ const angle = Math.acos( DRAG_PLANE_THRESHOLD );
1026
+
1027
+ _rotationAxis
1028
+ .crossVectors( raycaster.ray.direction, up )
1029
+ .normalize();
1030
+
1031
+ raycaster.ray.direction
1032
+ .copy( up )
1033
+ .applyAxisAngle( _rotationAxis, angle )
1034
+ .multiplyScalar( - 1 );
1035
+
1036
+ }
1037
+
1038
+ // TODO: dragging causes the camera to rise because we're getting "pushed" up by lower resolution tiles and
1039
+ // don't lower back down. We should maintain a target height above tiles where possible
1040
+ // prevent the drag from inverting
1041
+
1042
+ // if we drag to a point that's near the edge of the earth then we want to prevent it
1043
+ // from wrapping around and causing unexpected rotations
1044
+ this.getUpDirection( pivotPoint, _localUp );
1045
+ if ( Math.abs( raycaster.ray.direction.dot( _localUp ) ) < DRAG_UP_THRESHOLD ) {
1046
+
1047
+ const angle = Math.acos( DRAG_UP_THRESHOLD );
1048
+
1049
+ _rotationAxis
1050
+ .crossVectors( raycaster.ray.direction, _localUp )
1051
+ .normalize();
1052
+
1053
+ raycaster.ray.direction
1054
+ .copy( _localUp )
1055
+ .applyAxisAngle( _rotationAxis, angle )
1056
+ .multiplyScalar( - 1 );
1057
+
1058
+ }
1059
+
1060
+ // find the point on the plane that we should drag to
1061
+ if ( raycaster.ray.intersectPlane( _plane, _vec ) ) {
1062
+
1063
+ _delta.subVectors( pivotPoint, _vec );
1064
+ camera.position.add( _delta );
1065
+ camera.updateMatrixWorld();
1066
+
1067
+ // update the drag inertia
1068
+ _delta.multiplyScalar( 1 / deltaTime );
1069
+ if ( pointerTracker.getMoveDistance() / deltaTime < 2 * window.devicePixelRatio ) {
1070
+
1071
+ dragInertia.lerp( _delta, 0.5 );
1072
+
1073
+ } else {
1074
+
1075
+ dragInertia.copy( _delta );
1076
+
1077
+ }
1078
+
1079
+ }
1080
+
1081
+ } else if ( enableDamping ) {
1082
+
1083
+ camera.position.addScaledVector( dragInertia, deltaTime );
1084
+ camera.updateMatrixWorld();
1085
+
1086
+ }
1087
+
1088
+ }
1089
+
1090
+ _updateRotation( deltaTime ) {
1091
+
1092
+ const {
1093
+ pivotPoint,
1094
+ pointerTracker,
1095
+ domElement,
1096
+ state,
1097
+ rotationInertia,
1098
+ enableDamping,
1099
+ } = this;
1100
+
1101
+ if ( state === ROTATE ) {
1102
+
1103
+ // get the rotation motion and divide out the container height to normalize for element size
1104
+ pointerTracker.getCenterPoint( _pointer );
1105
+ pointerTracker.getPreviousCenterPoint( _prevPointer );
1106
+ _deltaPointer.subVectors( _pointer, _prevPointer ).multiplyScalar( 2 * Math.PI / domElement.clientHeight );
1107
+
1108
+ this._applyRotation( _deltaPointer.x, _deltaPointer.y, pivotPoint );
1109
+
1110
+ // update rotation inertia
1111
+ _deltaPointer.multiplyScalar( 1 / deltaTime );
1112
+ if ( pointerTracker.getMoveDistance() / deltaTime < 2 * window.devicePixelRatio ) {
1113
+
1114
+ rotationInertia.lerp( _deltaPointer, 0.5 );
1115
+
1116
+ } else {
1117
+
1118
+ rotationInertia.copy( _deltaPointer );
1119
+
1120
+ }
1121
+
1122
+ } else if ( enableDamping ) {
1123
+
1124
+ this._applyRotation( rotationInertia.x * deltaTime, rotationInertia.y * deltaTime, pivotPoint );
1125
+
1126
+ }
1127
+
1128
+ }
1129
+
1130
+ _applyRotation( x, y, pivotPoint ) {
1131
+
1132
+ if ( x === 0 && y === 0 ) {
1133
+
1134
+ return;
1135
+
1136
+ }
1137
+
1138
+ const {
1139
+ camera,
1140
+ minAltitude,
1141
+ maxAltitude,
1142
+ rotationSpeed,
1143
+ } = this;
1144
+
1145
+ const azimuth = - x * rotationSpeed;
1146
+ let altitude = y * rotationSpeed;
1147
+
1148
+ // calculate current angles and clamp
1149
+ _forward.set( 0, 0, 1 ).transformDirection( camera.matrixWorld );
1150
+
1151
+ this.getUpDirection( pivotPoint, _localUp );
1152
+
1153
+ // get the signed angle relative to the top down view
1154
+ _vec.crossVectors( _localUp, _forward ).normalize();
1155
+ _right.set( 1, 0, 0 ).transformDirection( camera.matrixWorld ).normalize();
1156
+ const sign = Math.sign( _vec.dot( _right ) );
1157
+ const angle = sign * _localUp.angleTo( _forward );
1158
+
1159
+ // clamp the rotation to be within the provided limits
1160
+ // clamp to 0 here, as well, so we don't "pop" to the the value range
1161
+ if ( altitude > 0 ) {
1162
+
1163
+ altitude = Math.min( angle - minAltitude - 1e-2, altitude );
1164
+ altitude = Math.max( 0, altitude );
1165
+
1166
+ } else {
1167
+
1168
+ altitude = Math.max( angle - maxAltitude, altitude );
1169
+ altitude = Math.min( 0, altitude );
1170
+
1171
+ }
1172
+
1173
+ // rotate around the up axis
1174
+ _quaternion.setFromAxisAngle( _localUp, azimuth );
1175
+ makeRotateAroundPoint( pivotPoint, _quaternion, _rotMatrix );
1176
+ camera.matrixWorld.premultiply( _rotMatrix );
1177
+
1178
+ // get a rotation axis for altitude and rotate
1179
+ _rotationAxis.set( - 1, 0, 0 ).transformDirection( camera.matrixWorld );
1180
+
1181
+ _quaternion.setFromAxisAngle( _rotationAxis, altitude );
1182
+ makeRotateAroundPoint( pivotPoint, _quaternion, _rotMatrix );
1183
+ camera.matrixWorld.premultiply( _rotMatrix );
1184
+
1185
+ // update the transform members
1186
+ camera.matrixWorld.decompose( camera.position, camera.quaternion, _vec );
1187
+
1188
+ }
1189
+
1190
+ // sets the "up" axis for the current surface of the tile set
1191
+ _setFrame( newUp, pivot ) {
1192
+
1193
+ const {
1194
+ up,
1195
+ camera,
1196
+ state,
1197
+ zoomPoint,
1198
+ zoomDirectionSet,
1199
+ zoomPointSet,
1200
+ reorientOnDrag,
1201
+ scaleZoomOrientationAtEdges,
1202
+ } = this;
1203
+
1204
+ camera.updateMatrixWorld();
1205
+
1206
+ // get the amount needed to rotate
1207
+ _quaternion.setFromUnitVectors( up, newUp );
1208
+
1209
+ // If we're zooming then reorient around the zoom point
1210
+ const action = state;
1211
+ if ( zoomDirectionSet && ( zoomPointSet || this._updateZoomPoint() ) ) {
1212
+
1213
+ this.getUpDirection( zoomPoint, _vec );
1214
+
1215
+ if ( scaleZoomOrientationAtEdges ) {
1216
+
1217
+ let amt = Math.max( _vec.dot( up ) - 0.6, 0 ) / 0.4;
1218
+ amt = MathUtils.mapLinear( amt, 0, 0.5, 0, 1 );
1219
+ amt = Math.min( amt, 1 );
1220
+
1221
+ // scale the value if we're using an orthographic camera so
1222
+ // GlobeControls works correctly
1223
+ if ( camera.isOrthographicCamera ) {
1224
+
1225
+ amt *= 0.1;
1226
+
1227
+ }
1228
+
1229
+ _quaternion.slerp( _identityQuat, 1.0 - amt );
1230
+
1231
+ }
1232
+
1233
+ // rotates the camera position around the point being zoomed in to
1234
+ makeRotateAroundPoint( zoomPoint, _quaternion, _rotMatrix );
1235
+ camera.matrixWorld.premultiply( _rotMatrix );
1236
+ camera.matrixWorld.decompose( camera.position, camera.quaternion, _vec );
1237
+
1238
+ // recompute the zoom direction after updating rotation to align with frame
1239
+ this.zoomDirectionSet = false;
1240
+ this._updateZoomDirection();
1241
+
1242
+ } else if ( action === DRAG && reorientOnDrag ) {
1243
+
1244
+ // If we're dragging then reorient around the drag point
1245
+
1246
+ // NOTE: We used to derive the pivot point here by getting the point below the camera
1247
+ // but decided to pass it in via "update" to avoid multiple ray casts
1248
+
1249
+ if ( pivot ) {
1250
+
1251
+ // perform a simple realignment by rotating the camera around the pivot
1252
+ makeRotateAroundPoint( pivot, _quaternion, _rotMatrix );
1253
+ camera.matrixWorld.premultiply( _rotMatrix );
1254
+ camera.matrixWorld.decompose( camera.position, camera.quaternion, _vec );
1255
+
1256
+ }
1257
+
1258
+ }
1259
+
1260
+ up.copy( newUp );
1261
+ camera.updateMatrixWorld();
1262
+
1263
+ }
1264
+
1265
+ _raycast( raycaster ) {
1266
+
1267
+ const { scene, useFallbackPlane, fallbackPlane } = this;
1268
+ const result = raycaster.intersectObject( scene )[ 0 ] || null;
1269
+ if ( result ) {
1270
+
1271
+ return result;
1272
+
1273
+ } else if ( useFallbackPlane ) {
1274
+
1275
+ // if we don't hit any geometry then try to intersect the fallback
1276
+ // plane so the camera can still be manipulated
1277
+ const plane = fallbackPlane;
1278
+ if ( raycaster.ray.intersectPlane( plane, _vec ) ) {
1279
+
1280
+ const planeHit = {
1281
+ point: _vec.clone(),
1282
+ distance: raycaster.ray.origin.distanceTo( _vec ),
1283
+ };
1284
+
1285
+ return planeHit;
1286
+
1287
+ }
1288
+
1289
+ }
1290
+
1291
+ return null;
1292
+
1293
+ }
1294
+
1295
+ }