@sapui5/sap.ui.vbm 1.93.1 → 1.96.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 (54) hide show
  1. package/package.json +1 -1
  2. package/src/sap/ui/vbm/.library +1 -1
  3. package/src/sap/ui/vbm/Adapter.js +17 -13
  4. package/src/sap/ui/vbm/Adapter3D.js +1 -1
  5. package/src/sap/ui/vbm/Areas.js +7 -8
  6. package/src/sap/ui/vbm/Cluster.js +4 -3
  7. package/src/sap/ui/vbm/ClusterBase.js +4 -7
  8. package/src/sap/ui/vbm/ClusterRenderer.js +0 -1
  9. package/src/sap/ui/vbm/Containers.js +3 -5
  10. package/src/sap/ui/vbm/GeoJsonLayer.js +9 -10
  11. package/src/sap/ui/vbm/GeoMap.js +3 -2
  12. package/src/sap/ui/vbm/VBI.js +40 -8
  13. package/src/sap/ui/vbm/Viewport.js +6 -5
  14. package/src/sap/ui/vbm/VoAggregation.js +3 -5
  15. package/src/sap/ui/vbm/adapter3d/ModelHandler.js +1 -1
  16. package/src/sap/ui/vbm/adapter3d/ObjectFactory.js +1 -1
  17. package/src/sap/ui/vbm/adapter3d/PolygonHandler.js +1 -1
  18. package/src/sap/ui/vbm/adapter3d/SceneBuilder.js +270 -182
  19. package/src/sap/ui/vbm/adapter3d/VBIJSONParser.js +1 -1
  20. package/src/sap/ui/vbm/adapter3d/thirdparty/ColladaLoader.js +2267 -2509
  21. package/src/sap/ui/vbm/adapter3d/thirdparty/DecalGeometry.js +194 -266
  22. package/src/sap/ui/vbm/adapter3d/thirdparty/OrbitControls.js +1004 -700
  23. package/src/sap/ui/vbm/lib/sapactions.js +12 -9
  24. package/src/sap/ui/vbm/lib/sapautomations.js +12 -13
  25. package/src/sap/ui/vbm/lib/sapconfig.js +11 -3
  26. package/src/sap/ui/vbm/lib/sapdataprovider.js +11 -21
  27. package/src/sap/ui/vbm/lib/sapevents.js +11 -11
  28. package/src/sap/ui/vbm/lib/sapgeolocation.js +10 -3
  29. package/src/sap/ui/vbm/lib/sapgeomath.js +11 -4
  30. package/src/sap/ui/vbm/lib/sapgeotool.js +12 -3
  31. package/src/sap/ui/vbm/lib/sapheatmap.js +28 -25
  32. package/src/sap/ui/vbm/lib/saplabels.js +17 -11
  33. package/src/sap/ui/vbm/lib/saplassotrack.js +11 -5
  34. package/src/sap/ui/vbm/lib/sapmaplayer.js +13 -10
  35. package/src/sap/ui/vbm/lib/sapmapmanager.js +14 -4
  36. package/src/sap/ui/vbm/lib/sapmapprovider.js +11 -5
  37. package/src/sap/ui/vbm/lib/sapnavigation.js +11 -3
  38. package/src/sap/ui/vbm/lib/sapparsing.js +11 -1
  39. package/src/sap/ui/vbm/lib/sappositioning.js +12 -5
  40. package/src/sap/ui/vbm/lib/sapprojection.js +12 -5
  41. package/src/sap/ui/vbm/lib/saprecttrack.js +11 -5
  42. package/src/sap/ui/vbm/lib/sapresources.js +12 -6
  43. package/src/sap/ui/vbm/lib/sapscale.js +15 -5
  44. package/src/sap/ui/vbm/lib/sapscene.js +15 -13
  45. package/src/sap/ui/vbm/lib/saputilities.js +19 -91
  46. package/src/sap/ui/vbm/lib/sapvbcluster.js +14 -6
  47. package/src/sap/ui/vbm/lib/sapvbi.js +11 -42
  48. package/src/sap/ui/vbm/lib/sapvbicontext.js +11 -5
  49. package/src/sap/ui/vbm/lib/sapvbmenu.js +19 -13
  50. package/src/sap/ui/vbm/lib/sapvobase.js +17 -16
  51. package/src/sap/ui/vbm/lib/sapvoutils.js +14 -24
  52. package/src/sap/ui/vbm/lib/sapwindow.js +12 -12
  53. package/src/sap/ui/vbm/library.js +28 -31
  54. package/src/sap/ui/vbm/adapter3d/thirdparty/html2canvas.js +0 -6
@@ -1,860 +1,1164 @@
1
- /**
2
- * @author qiao / https://github.com/qiao
3
- * @author mrdoob / http://mrdoob.com
4
- * @author alteredq / http://alteredqualia.com/
5
- * @author WestLangley / http://github.com/WestLangley
6
- * @author erich666 / http://erichaines.com
7
- */
8
-
9
- // This set of controls performs orbiting, dollying (zooming), and panning.
10
- // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
11
- //
12
- // Orbit - left mouse / touch: one finger move
13
- // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
14
- // Pan - right mouse, or arrow keys / touch: three finger swipe
15
-
16
- THREE.OrbitControls = function ( object, domElement ) {
17
- this.object = object;
18
- this.domElement = ( domElement !== undefined ) ? domElement : document;
19
-
20
- // Set to false to disable this control
21
- this.enabled = true;
22
-
23
- // "target" sets the location of focus, where the object orbits around
24
- this.target = new THREE.Vector3();
25
-
26
- // How far you can dolly in and out ( PerspectiveCamera only )
27
- this.minDistance = 0;
28
- this.maxDistance = Infinity;
29
-
30
- // How far you can zoom in and out ( OrthographicCamera only )
31
- this.minZoom = 0;
32
- this.maxZoom = Infinity;
33
-
34
- // How far you can orbit vertically, upper and lower limits.
35
- // Range is 0 to Math.PI radians.
36
- this.minPolarAngle = 0; // radians
37
- this.maxPolarAngle = Math.PI; // radians
38
-
39
- // How far you can orbit horizontally, upper and lower limits.
40
- // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
41
- this.minAzimuthAngle = - Infinity; // radians
42
- this.maxAzimuthAngle = Infinity; // radians
43
-
44
- // Set to true to enable damping (inertia)
45
- // If damping is enabled, you must call controls.update() in your animation loop
46
- this.enableDamping = false;
47
- this.dampingFactor = 0.25;
48
-
49
- // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
50
- // Set to false to disable zooming
51
- this.enableZoom = true;
52
- this.zoomSpeed = 1.0;
53
-
54
- // Set to false to disable rotating
55
- this.enableRotate = true;
56
- this.rotateSpeed = 1.0;
57
-
58
- // Set to false to disable panning
59
- this.enablePan = true;
60
- this.keyPanSpeed = 7.0; // pixels moved per arrow key push
61
- this.keyRotateSpeed = 9.0; // pixels moved per arrow key push
62
-
63
- // Set to true to automatically rotate around the target
64
- // If auto-rotate is enabled, you must call controls.update() in your animation loop
65
- this.autoRotate = false;
66
- this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
67
-
68
- // Set to false to disable use of the keys
69
- this.enableKeys = true;
70
-
71
- // The four arrow keys
72
- this.keys = {LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, PLUS: 187, MINUS: 189, NUMPLUS: 107, NUMMINUS: 109};
73
-
74
- // Mouse buttons
75
- this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
76
-
77
- // for reset
78
- this.target0 = this.target.clone();
79
- this.position0 = this.object.position.clone();
80
- this.zoom0 = this.object.zoom;
81
-
82
- // public methods
83
-
84
- this.getPolarAngle = function () {
85
- return spherical.phi;
86
- };
1
+ ( function () {
2
+
3
+ // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
4
+ //
5
+ // Orbit - left mouse / touch: one-finger move
6
+ // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
7
+ // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
87
8
 
88
- this.getAzimuthalAngle = function () {
89
- return spherical.theta;
9
+ const _changeEvent = {
10
+ type: 'change'
11
+ };
12
+ const _startEvent = {
13
+ type: 'start'
14
+ };
15
+ const _endEvent = {
16
+ type: 'end'
90
17
  };
91
18
 
92
- this.saveState = function () {
93
- var state = {};
19
+ class OrbitControls extends THREE.EventDispatcher {
94
20
 
95
- state.target = scope.target.clone();
96
- state.position = scope.object.position.clone();
97
- state.zoom = scope.object.zoom;
21
+ constructor( object, domElement ) {
98
22
 
99
- return state;
100
- };
23
+ super();
24
+ if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
25
+ if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
26
+ this.object = object;
27
+ this.domElement = domElement;
28
+ this.domElement.style.touchAction = 'none'; // disable touch scroll
29
+ // Set to false to disable this control
101
30
 
102
- this.reset = function (state) {
103
- if (state) {
104
- scope.target.copy(state.target);
105
- scope.object.position.copy(state.position);
106
- scope.object.zoom = state.zoom;
31
+ this.enabled = true; // "target" sets the location of focus, where the object orbits around
107
32
 
108
- scope.object.updateProjectionMatrix();
109
- scope.dispatchEvent(changeEvent);
33
+ this.target = new THREE.Vector3(); // How far you can dolly in and out ( PerspectiveCamera only )
110
34
 
111
- scope.update();
112
- state = STATE.NONE;
113
- }
114
- };
35
+ this.minDistance = 0;
36
+ this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only )
115
37
 
116
- // this method is exposed, but perhaps it would be better if we can make it private...
117
- this.update = function (tag) {
38
+ this.minZoom = 0;
39
+ this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits.
40
+ // Range is 0 to Math.PI radians.
118
41
 
119
- var offset = new THREE.Vector3();
42
+ this.minPolarAngle = 0; // radians
120
43
 
121
- // so camera.up is the orbit axis
122
- var quat = new THREE.Quaternion().setFromUnitVectors(object.up, new THREE.Vector3(0, 1, 0));
123
- var quatInverse = quat.clone().inverse();
44
+ this.maxPolarAngle = Math.PI; // radians
45
+ // How far you can orbit horizontally, upper and lower limits.
46
+ // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
124
47
 
125
- var lastPosition = new THREE.Vector3();
126
- var lastQuaternion = new THREE.Quaternion();
48
+ this.minAzimuthAngle = - Infinity; // radians
127
49
 
128
- return function update(tag) {
129
- var position = scope.object.position;
130
- offset.copy(position).sub(scope.target);
50
+ this.maxAzimuthAngle = Infinity; // radians
51
+ // Set to true to enable damping (inertia)
52
+ // If damping is enabled, you must call controls.update() in your animation loop
131
53
 
132
- // rotate offset to "y-axis-is-up" space
133
- offset.applyQuaternion(quat);
54
+ this.enableDamping = false;
55
+ this.dampingFactor = 0.05; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
56
+ // Set to false to disable zooming
134
57
 
135
- // angle from z-axis around y-axis
136
- spherical.setFromVector3(offset);
58
+ this.enableZoom = true;
59
+ this.zoomSpeed = 1.0; // Set to false to disable rotating
137
60
 
138
- if (scope.autoRotate && state === STATE.NONE) {
139
- rotateLeft(getAutoRotationAngle());
140
- }
61
+ this.enableRotate = true;
62
+ this.rotateSpeed = 1.0; // Set to false to disable panning
141
63
 
142
- spherical.theta += sphericalDelta.theta;
143
- spherical.phi += sphericalDelta.phi;
64
+ this.enablePan = true;
65
+ this.panSpeed = 1.0;
66
+ this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
144
67
 
145
- // restrict theta to be between desired limits
146
- spherical.theta = Math.max(scope.minAzimuthAngle, Math.min(scope.maxAzimuthAngle, spherical.theta));
68
+ this.keyPanSpeed = 7.0; // pixels moved per arrow key push
69
+ this.keyRotateSpeed = 9.0; // pixels moved per arrow key push
147
70
 
148
- // restrict phi to be between desired limits
149
- spherical.phi = Math.max(scope.minPolarAngle, Math.min(scope.maxPolarAngle, spherical.phi));
71
+ // Set to true to automatically rotate around the target
72
+ // If auto-rotate is enabled, you must call controls.update() in your animation loop
73
+ this.autoRotate = false;
74
+ this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
150
75
 
151
- spherical.makeSafe();
152
- spherical.radius *= scale;
76
+ // The keyboard keys
77
+ this.keys = {
78
+ LEFT: 37,
79
+ UP: 38,
80
+ RIGHT: 39,
81
+ DOWN: 40,
82
+ PLUS: 187,
83
+ MINUS: 189,
84
+ NUMPLUS: 107,
85
+ NUMMINUS: 109
86
+ };
153
87
 
154
- // restrict radius to be between desired limits
155
- spherical.radius = Math.max(scope.minDistance, Math.min(scope.maxDistance, spherical.radius));
88
+ // Mouse buttons
89
+ this.mouseButtons = {
90
+ LEFT: THREE.MOUSE.ROTATE,
91
+ MIDDLE: THREE.MOUSE.DOLLY,
92
+ RIGHT: THREE.MOUSE.PAN
93
+ };
156
94
 
157
- // move target to panned location
158
- scope.target.add(panOffset);
95
+ // Touch fingers
96
+ this.touches = {
97
+ ONE: THREE.TOUCH.ROTATE,
98
+ TWO: THREE.TOUCH.DOLLY_PAN
99
+ };
159
100
 
160
- offset.setFromSpherical(spherical);
101
+ // for reset
102
+ this.target0 = this.target.clone();
103
+ this.position0 = this.object.position.clone();
104
+ this.zoom0 = this.object.zoom; // the target DOM element for key events
161
105
 
162
- // rotate offset back to "camera-up-vector-is-up" space
163
- offset.applyQuaternion(quatInverse);
106
+ this._domElementKeyEvents = null; //
107
+ // public methods
108
+ //
164
109
 
165
- position.copy(scope.target).add(offset);
166
- scope.object.lookAt(scope.target);
110
+ this.getPolarAngle = function () {
167
111
 
168
- if (scope.enableDamping === true) {
169
- sphericalDelta.theta *= (1 - scope.dampingFactor);
170
- sphericalDelta.phi *= (1 - scope.dampingFactor);
171
- } else {
172
- sphericalDelta.set(0, 0, 0);
173
- }
112
+ return spherical.phi;
174
113
 
175
- scale = 1;
176
- panOffset.set(0, 0, 0);
114
+ };
177
115
 
178
- // update condition is:
179
- // min(camera displacement, camera rotation in radians)^2 > EPS
180
- // using small-angle approximation cos(x/2) = 1 - x^2 / 8
116
+ this.getAzimuthalAngle = function () {
181
117
 
182
- if (zoomChanged ||
183
- lastPosition.distanceToSquared(scope.object.position) > EPS ||
184
- 8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS) {
118
+ return spherical.theta;
185
119
 
186
- changeEvent.tag = tag;
187
- scope.dispatchEvent(changeEvent);
188
- delete changeEvent.tag;
120
+ };
189
121
 
190
- lastPosition.copy(scope.object.position);
191
- lastQuaternion.copy(scope.object.quaternion);
192
- zoomChanged = false;
122
+ this.getDistance = function () {
193
123
 
194
- return true;
195
- }
196
- return false;
197
- };
198
- }();
124
+ return this.object.position.distanceTo( this.target );
199
125
 
200
- this.dispose = function () {
201
- scope.domElement.removeEventListener('contextmenu', onContextMenu, false);
202
- scope.domElement.removeEventListener('mousedown', onMouseDown, false);
203
- scope.domElement.removeEventListener('wheel', onMouseWheel, false);
126
+ };
204
127
 
205
- scope.domElement.removeEventListener('touchstart', onTouchStart, false);
206
- scope.domElement.removeEventListener('touchend', onTouchEnd, false);
207
- scope.domElement.removeEventListener('touchmove', onTouchMove, false);
128
+ this.listenToKeyEvents = function ( domElement ) {
208
129
 
209
- document.removeEventListener('mousemove', onMouseMove, false);
210
- document.removeEventListener('mouseup', onMouseUp, false);
130
+ domElement.addEventListener( 'keydown', onKeyDown );
131
+ this._domElementKeyEvents = domElement;
211
132
 
212
- window.removeEventListener('keydown', onKeyDown, false);
133
+ };
213
134
 
214
- // scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
215
- };
216
- // internals
217
- var scope = this;
135
+ this.saveState = function () {
136
+ const state = {};
218
137
 
219
- var changeEvent = { type: 'change' };
220
- var startEvent = { type: 'start' };
221
- var endEvent = { type: 'end' };
138
+ state.target = scope.target.clone();
139
+ state.position = scope.object.position.clone();
140
+ state.zoom = scope.object.zoom;
222
141
 
223
- var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 };
142
+ return state;
143
+ };
224
144
 
225
- var state = STATE.NONE;
145
+ this.reset = function (state) {
146
+ scope.target.copy(state ? state.target : scope.target0);
147
+ scope.object.position.copy(state ? state.position: scope.position0);
148
+ scope.object.zoom = state ? state.zoom : scope.zoom0;
226
149
 
227
- var EPS = 0.000001;
150
+ scope.object.updateProjectionMatrix();
151
+ scope.dispatchEvent(_changeEvent);
152
+ scope.update();
228
153
 
229
- // current position in spherical coordinates
230
- var spherical = new THREE.Spherical();
231
- var sphericalDelta = new THREE.Spherical();
154
+ state = STATE.NONE;
155
+ };
232
156
 
233
- var scale = 1;
234
- var panOffset = new THREE.Vector3();
235
- var zoomChanged = false;
236
157
 
237
- var rotateStart = new THREE.Vector2();
238
- var rotateEnd = new THREE.Vector2();
239
- var rotateDelta = new THREE.Vector2();
158
+ this.update = function (tag) {
240
159
 
241
- var panStart = new THREE.Vector2();
242
- var panEnd = new THREE.Vector2();
243
- var panDelta = new THREE.Vector2();
160
+ const offset = new THREE.Vector3(); // so camera.up is the orbit axis
244
161
 
245
- var dollyStart = new THREE.Vector2();
246
- var dollyEnd = new THREE.Vector2();
247
- var dollyDelta = new THREE.Vector2();
162
+ const quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
163
+ const quatInverse = quat.clone().invert();
164
+ const lastPosition = new THREE.Vector3();
165
+ const lastQuaternion = new THREE.Quaternion();
166
+ const twoPI = 2 * Math.PI;
167
+ return function update(tag) {
248
168
 
249
- function getAutoRotationAngle() {
250
- return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
251
- }
169
+ const position = scope.object.position;
170
+ offset.copy( position ).sub( scope.target ); // rotate offset to "y-axis-is-up" space
252
171
 
253
- function getZoomScale() {
254
- return Math.pow(0.95, scope.zoomSpeed);
255
- }
172
+ offset.applyQuaternion( quat ); // angle from z-axis around y-axis
256
173
 
257
- function rotateLeft(angle) {
258
- sphericalDelta.theta -= angle;
259
- }
174
+ spherical.setFromVector3( offset );
260
175
 
261
- function rotateUp(angle) {
262
- sphericalDelta.phi -= angle;
263
- }
176
+ if ( scope.autoRotate && state === STATE.NONE ) {
264
177
 
265
- var panLeft = function () {
266
- var v = new THREE.Vector3();
267
-
268
- return function panLeft(distance, objectMatrix) {
269
- v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
270
- v.multiplyScalar(- distance);
271
-
272
- panOffset.add(v);
273
- };
274
- }();
275
-
276
- var panUp = function () {
277
- var v = new THREE.Vector3();
278
-
279
- return function panUp(distance, objectMatrix) {
280
- v.setFromMatrixColumn(objectMatrix, 1); // get Y column of objectMatrix
281
- v.multiplyScalar(distance);
282
-
283
- panOffset.add(v);
284
- };
285
- }();
286
-
287
- // deltaX and deltaY are in pixels; right and down are positive
288
- var pan = function () {
289
- var offset = new THREE.Vector3();
290
- var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
291
-
292
- return function pan(deltaX, deltaY) {
293
- if (scope.object.isPerspectiveCamera) {
294
- // perspective
295
- var position = scope.object.position;
296
- offset.copy(position).sub(scope.target);
297
- var targetDistance = offset.length();
298
-
299
- // half of the fov is center to top of screen
300
- targetDistance *= Math.tan((scope.object.fov / 2) * Math.PI / 180.0);
301
-
302
- // we actually don't use screenWidth, since perspective camera is fixed to screen height
303
- panLeft(2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix);
304
- panUp(2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix);
305
- } else if (scope.object.isOrthographicCamera) {
306
- // orthographic
307
- panLeft(deltaX * (scope.object.right - scope.object.left) / scope.object.zoom / element.clientWidth, scope.object.matrix);
308
- panUp(deltaY * (scope.object.top - scope.object.bottom) / scope.object.zoom / element.clientHeight, scope.object.matrix);
309
- } else {
310
- // camera neither orthographic nor perspective
311
- console.warn('WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.');
312
- scope.enablePan = false;
313
- }
314
- };
315
- }();
316
-
317
- // deltaX and deltaY are in pixels; right and down are positive
318
- var rotate = function () {
319
- var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
320
-
321
- return function rotate(deltaX, deltaY) {
322
- // rotating across whole screen goes 360 degrees around
323
- rotateLeft(2 * Math.PI * deltaX / element.clientWidth * scope.rotateSpeed);
324
-
325
- // rotating up and down along whole screen attempts to go 360, but limited to 180
326
- rotateUp(2 * Math.PI * deltaY / element.clientHeight * scope.rotateSpeed);
327
- };
328
- }();
329
-
330
- function dollyIn(dollyScale) {
331
- if (scope.object.isPerspectiveCamera) {
332
- scale /= dollyScale;
333
- } else if (scope.object.isOrthographicCamera) {
334
- scope.object.zoom = Math.max(scope.minZoom, Math.min(scope.maxZoom, scope.object.zoom * dollyScale));
335
- scope.object.updateProjectionMatrix();
336
- zoomChanged = true;
337
- } else {
338
- console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.');
339
- scope.enableZoom = false;
340
- }
341
- }
178
+ rotateLeft( getAutoRotationAngle() );
342
179
 
343
- function dollyOut(dollyScale) {
344
- if (scope.object.isPerspectiveCamera) {
345
- scale *= dollyScale;
346
- } else if (scope.object.isOrthographicCamera) {
347
- scope.object.zoom = Math.max(scope.minZoom, Math.min(scope.maxZoom, scope.object.zoom / dollyScale));
348
- scope.object.updateProjectionMatrix();
349
- zoomChanged = true;
350
- } else {
351
- console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.');
352
- scope.enableZoom = false;
353
- }
354
- }
355
- // event callbacks - update the object state
180
+ }
356
181
 
357
- function handleMouseDownRotate(event) {
358
- // console.log( 'handleMouseDownRotate' );
359
- rotateStart.set(event.clientX, event.clientY);
360
- }
182
+ if ( scope.enableDamping ) {
361
183
 
362
- function handleMouseDownDolly(event) {
363
- // console.log( 'handleMouseDownDolly' );
364
- dollyStart.set(event.clientX, event.clientY);
365
- }
184
+ spherical.theta += sphericalDelta.theta * scope.dampingFactor;
185
+ spherical.phi += sphericalDelta.phi * scope.dampingFactor;
366
186
 
367
- function handleMouseDownPan(event) {
368
- // console.log( 'handleMouseDownPan' );
369
- panStart.set(event.clientX, event.clientY);
370
- }
187
+ } else {
371
188
 
372
- function handleMouseMoveRotate(event) {
373
- // console.log( 'handleMouseMoveRotate' );
374
- rotateEnd.set(event.clientX, event.clientY);
375
- rotateDelta.subVectors(rotateEnd, rotateStart);
189
+ spherical.theta += sphericalDelta.theta;
190
+ spherical.phi += sphericalDelta.phi;
376
191
 
377
- var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
192
+ } // restrict theta to be between desired limits
378
193
 
379
- // rotating across whole screen goes 360 degrees around
380
- rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed);
381
194
 
382
- // rotating up and down along whole screen attempts to go 360, but limited to 180
383
- rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed);
195
+ let min = scope.minAzimuthAngle;
196
+ let max = scope.maxAzimuthAngle;
384
197
 
385
- rotateStart.copy(rotateEnd);
198
+ if ( isFinite( min ) && isFinite( max ) ) {
386
199
 
387
- scope.update();
388
- }
200
+ if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
201
+ if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
389
202
 
390
- function handleMouseMoveDolly(event) {
391
- // console.log( 'handleMouseMoveDolly' );
392
- dollyEnd.set(event.clientX, event.clientY);
203
+ if ( min <= max ) {
393
204
 
394
- dollyDelta.subVectors(dollyEnd, dollyStart);
205
+ spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
395
206
 
396
- if (dollyDelta.y > 0) {
397
- dollyIn(getZoomScale());
398
- } else if (dollyDelta.y < 0) {
399
- dollyOut(getZoomScale());
400
- }
401
- dollyStart.copy(dollyEnd);
207
+ } else {
402
208
 
403
- scope.update();
404
- }
209
+ spherical.theta = spherical.theta > ( min + max ) / 2 ? Math.max( min, spherical.theta ) : Math.min( max, spherical.theta );
405
210
 
406
- function handleMouseMovePan(event) {
407
- // console.log( 'handleMouseMovePan' );
408
- panEnd.set(event.clientX, event.clientY);
409
- panDelta.subVectors(panEnd, panStart);
410
- pan(panDelta.x, panDelta.y);
411
- panStart.copy(panEnd);
412
- scope.update();
413
- }
211
+ }
414
212
 
415
- function handleMouseUp(event) {
416
- // console.log( 'handleMouseUp' );
417
- }
213
+ } // restrict phi to be between desired limits
418
214
 
419
- function handleMouseWheel(event) {
420
- // console.log( 'handleMouseWheel' );
421
- if (event.deltaY < 0) {
422
- dollyOut(getZoomScale());
423
- scope.update(scope.keys.PLUS);
424
- } else if (event.deltaY > 0) {
425
- dollyIn(getZoomScale());
426
- scope.update(scope.keys.MINUS);
427
- }
428
- }
429
215
 
430
- function handleKeyDown(event) {
431
- // console.log( 'handleKeyDown' );
216
+ spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
217
+ spherical.makeSafe();
218
+ spherical.radius *= scale; // restrict radius to be between desired limits
219
+
220
+ spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); // move target to panned location
221
+
222
+ if ( scope.enableDamping === true ) {
223
+
224
+ scope.target.addScaledVector( panOffset, scope.dampingFactor );
225
+
226
+ } else {
227
+
228
+ scope.target.add( panOffset );
432
229
 
433
- switch (event.keyCode) {
434
- case scope.keys.UP:
435
- if (event.shiftKey) {
436
- if (scope.enableRotate) {
437
- rotate(0, scope.keyRotateSpeed);
438
230
  }
439
- } else if (scope.enablePan) {
440
- pan(0, scope.keyPanSpeed);
441
- }
442
- scope.update(scope.keys.UP);
443
- break;
444
- case scope.keys.DOWN:
445
- if (event.shiftKey) {
446
- if (scope.enableRotate) {
447
- rotate(0, - scope.keyRotateSpeed);
231
+
232
+ offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space
233
+
234
+ offset.applyQuaternion( quatInverse );
235
+ position.copy( scope.target ).add( offset );
236
+ scope.object.lookAt( scope.target );
237
+
238
+ if ( scope.enableDamping === true ) {
239
+
240
+ sphericalDelta.theta *= 1 - scope.dampingFactor;
241
+ sphericalDelta.phi *= 1 - scope.dampingFactor;
242
+ panOffset.multiplyScalar( 1 - scope.dampingFactor );
243
+
244
+ } else {
245
+
246
+ sphericalDelta.set( 0, 0, 0 );
247
+ panOffset.set( 0, 0, 0 );
248
+
448
249
  }
449
- } else if (scope.enablePan) {
450
- pan(0, - scope.keyPanSpeed);
451
- }
452
- scope.update(scope.keys.DOWN);
453
- break;
454
- case scope.keys.LEFT:
455
- if (event.shiftKey) {
456
- if (scope.enableRotate) {
457
- rotate(scope.keyRotateSpeed, 0);
250
+
251
+ scale = 1; // update condition is:
252
+ // min(camera displacement, camera rotation in radians)^2 > EPS
253
+ // using small-angle approximation cos(x/2) = 1 - x^2 / 8
254
+
255
+ if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
256
+ _changeEvent.tag = tag;
257
+ scope.dispatchEvent( _changeEvent );
258
+ delete _changeEvent.tag;
259
+
260
+ lastPosition.copy( scope.object.position );
261
+ lastQuaternion.copy( scope.object.quaternion );
262
+ zoomChanged = false;
263
+
264
+ return true;
458
265
  }
459
- } else if (scope.enablePan) {
460
- pan(scope.keyPanSpeed, 0);
461
- }
462
- scope.update(scope.keys.LEFT);
463
- break;
464
- case scope.keys.RIGHT:
465
- if (event.shiftKey) {
466
- if (scope.enableRotate) {
467
- rotate(-scope.keyRotateSpeed, 0);
266
+
267
+ return false;
268
+
269
+ };
270
+
271
+ }();
272
+
273
+ this.dispose = function () {
274
+
275
+ scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
276
+ scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
277
+ scope.domElement.removeEventListener( 'pointercancel', onPointerCancel );
278
+ scope.domElement.removeEventListener( 'wheel', onMouseWheel );
279
+ scope.domElement.removeEventListener( 'pointermove', onPointerMove );
280
+ scope.domElement.removeEventListener( 'pointerup', onPointerUp );
281
+
282
+ if ( scope._domElementKeyEvents !== null ) {
283
+
284
+ scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
285
+
286
+ } //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
287
+
288
+ }; //
289
+ // internals
290
+ //
291
+
292
+
293
+ const scope = this;
294
+ const STATE = {
295
+ NONE: - 1,
296
+ ROTATE: 0,
297
+ DOLLY: 1,
298
+ PAN: 2,
299
+ TOUCH_ROTATE: 3,
300
+ TOUCH_PAN: 4,
301
+ TOUCH_DOLLY_PAN: 5,
302
+ TOUCH_DOLLY_ROTATE: 6
303
+ };
304
+ let state = STATE.NONE;
305
+ const EPS = 0.000001; // current position in spherical coordinates
306
+
307
+ const spherical = new THREE.Spherical();
308
+ const sphericalDelta = new THREE.Spherical();
309
+ let scale = 1;
310
+ const panOffset = new THREE.Vector3();
311
+ let zoomChanged = false;
312
+ const rotateStart = new THREE.Vector2();
313
+ const rotateEnd = new THREE.Vector2();
314
+ const rotateDelta = new THREE.Vector2();
315
+ const panStart = new THREE.Vector2();
316
+ const panEnd = new THREE.Vector2();
317
+ const panDelta = new THREE.Vector2();
318
+ const dollyStart = new THREE.Vector2();
319
+ const dollyEnd = new THREE.Vector2();
320
+ const dollyDelta = new THREE.Vector2();
321
+ const pointers = [];
322
+ const pointerPositions = {};
323
+
324
+ function getAutoRotationAngle() {
325
+
326
+ return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
327
+
328
+ }
329
+
330
+ function getZoomScale() {
331
+
332
+ return Math.pow( 0.95, scope.zoomSpeed );
333
+
334
+ }
335
+
336
+ function rotateLeft( angle ) {
337
+
338
+ sphericalDelta.theta -= angle;
339
+
340
+ }
341
+
342
+ function rotateUp( angle ) {
343
+
344
+ sphericalDelta.phi -= angle;
345
+
346
+ }
347
+
348
+ const panLeft = function () {
349
+
350
+ const v = new THREE.Vector3();
351
+ return function panLeft( distance, objectMatrix ) {
352
+
353
+ v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
354
+
355
+ v.multiplyScalar( - distance );
356
+ panOffset.add( v );
357
+
358
+ };
359
+
360
+ }();
361
+
362
+ const panUp = function () {
363
+
364
+ const v = new THREE.Vector3();
365
+ return function panUp( distance, objectMatrix ) {
366
+
367
+ if ( scope.screenSpacePanning === true ) {
368
+
369
+ v.setFromMatrixColumn( objectMatrix, 1 );
370
+
371
+ } else {
372
+
373
+ v.setFromMatrixColumn( objectMatrix, 0 );
374
+ v.crossVectors( scope.object.up, v );
375
+
468
376
  }
469
- } else if (scope.enablePan) {
470
- pan(- scope.keyPanSpeed, 0);
471
- }
472
- scope.update(scope.keys.RIGHT);
473
- break;
474
- case scope.keys.MINUS:
475
- case scope.keys.NUMMINUS:
476
- if (scope.enableZoom) {
477
- dollyIn(getZoomScale());
377
+
378
+ v.multiplyScalar( distance );
379
+ panOffset.add( v );
380
+
381
+ };
382
+
383
+ }(); // deltaX and deltaY are in pixels; right and down are positive
384
+
385
+
386
+ const pan = function () {
387
+
388
+ const offset = new THREE.Vector3();
389
+ return function pan( deltaX, deltaY ) {
390
+
391
+ const element = scope.domElement;
392
+
393
+ if ( scope.object.isPerspectiveCamera ) {
394
+
395
+ // perspective
396
+ const position = scope.object.position;
397
+ offset.copy( position ).sub( scope.target );
398
+ let targetDistance = offset.length(); // half of the fov is center to top of screen
399
+
400
+ targetDistance *= Math.tan( scope.object.fov / 2 * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed
401
+
402
+ panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
403
+ panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
404
+
405
+ } else if ( scope.object.isOrthographicCamera ) {
406
+
407
+ // orthographic
408
+ panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
409
+ panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
410
+
411
+ } else {
412
+
413
+ // camera neither orthographic nor perspective
414
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
415
+ scope.enablePan = false;
416
+
417
+ }
418
+
419
+ };
420
+
421
+ }();
422
+
423
+ // deltaX and deltaY are in pixels; right and down are positive
424
+ const rotate = function () {
425
+ const element = scope.domElement;
426
+
427
+ return function rotate(deltaX, deltaY) {
428
+ // rotating across whole screen goes 360 degrees around
429
+ rotateLeft(2 * Math.PI * deltaX / element.clientWidth * scope.rotateSpeed);
430
+
431
+ // rotating up and down along whole screen attempts to go 360, but limited to 180
432
+ rotateUp(2 * Math.PI * deltaY / element.clientHeight * scope.rotateSpeed);
433
+ };
434
+ }();
435
+
436
+ function dollyOut( dollyScale ) {
437
+
438
+ if ( scope.object.isPerspectiveCamera ) {
439
+
440
+ scale /= dollyScale;
441
+
442
+ } else if ( scope.object.isOrthographicCamera ) {
443
+
444
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
445
+ scope.object.updateProjectionMatrix();
446
+ zoomChanged = true;
447
+
448
+ } else {
449
+
450
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
451
+ scope.enableZoom = false;
452
+
478
453
  }
479
- scope.update(scope.keys.MINUS);
480
- break;
481
- case scope.keys.PLUS:
482
- case scope.keys.NUMPLUS:
483
- if (scope.enableZoom) {
484
- dollyOut(getZoomScale());
454
+
455
+ }
456
+
457
+ function dollyIn( dollyScale ) {
458
+
459
+ if ( scope.object.isPerspectiveCamera ) {
460
+
461
+ scale *= dollyScale;
462
+
463
+ } else if ( scope.object.isOrthographicCamera ) {
464
+
465
+ scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
466
+ scope.object.updateProjectionMatrix();
467
+ zoomChanged = true;
468
+
469
+ } else {
470
+
471
+ console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
472
+ scope.enableZoom = false;
473
+
485
474
  }
486
- scope.update(scope.keys.PLUS);
487
- break;
475
+
476
+ } //
477
+ // event callbacks - update the object state
478
+ //
479
+
480
+
481
+ function handleMouseDownRotate( event ) {
482
+
483
+ rotateStart.set( event.clientX, event.clientY );
484
+
488
485
  }
489
- }
490
486
 
491
- function handleTouchStartRotate(event) {
492
- // console.log( 'handleTouchStartRotate' );
493
- rotateStart.set(event.touches[0].pageX, event.touches[0].pageY);
494
- }
487
+ function handleMouseDownDolly( event ) {
495
488
 
496
- function handleTouchStartDolly(event) {
497
- // console.log( 'handleTouchStartDolly' );
498
- var dx = event.touches[0].pageX - event.touches[1].pageX;
499
- var dy = event.touches[0].pageY - event.touches[1].pageY;
489
+ dollyStart.set( event.clientX, event.clientY );
500
490
 
501
- var distance = Math.sqrt(dx * dx + dy * dy);
491
+ }
502
492
 
503
- dollyStart.set(0, distance);
504
- }
493
+ function handleMouseDownPan( event ) {
505
494
 
506
- function handleTouchStartPan(event) {
507
- // console.log( 'handleTouchStartPan' );
508
- panStart.set(event.touches[0].pageX, event.touches[0].pageY);
509
- }
495
+ panStart.set( event.clientX, event.clientY );
510
496
 
511
- function handleTouchMoveRotate(event) {
512
- // console.log( 'handleTouchMoveRotate' );
513
- rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY);
514
- rotateDelta.subVectors(rotateEnd, rotateStart);
497
+ }
515
498
 
516
- var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
499
+ function handleMouseMoveRotate( event ) {
517
500
 
518
- // rotating across whole screen goes 360 degrees around
519
- rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed);
501
+ rotateEnd.set( event.clientX, event.clientY );
502
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
503
+ const element = scope.domElement;
504
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
520
505
 
521
- // rotating up and down along whole screen attempts to go 360, but limited to 180
522
- rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed);
506
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
507
+ rotateStart.copy( rotateEnd );
508
+ scope.update();
523
509
 
524
- rotateStart.copy(rotateEnd);
510
+ }
525
511
 
526
- scope.update();
527
- }
512
+ function handleMouseMoveDolly( event ) {
528
513
 
529
- function handleTouchMoveDolly(event) {
530
- // console.log( 'handleTouchMoveDolly' );
531
- var dx = event.touches[0].pageX - event.touches[1].pageX;
532
- var dy = event.touches[0].pageY - event.touches[1].pageY;
514
+ dollyEnd.set( event.clientX, event.clientY );
515
+ dollyDelta.subVectors( dollyEnd, dollyStart );
533
516
 
534
- var distance = Math.sqrt(dx * dx + dy * dy);
517
+ if ( dollyDelta.y > 0 ) {
535
518
 
536
- dollyEnd.set(0, distance);
519
+ dollyOut( getZoomScale() );
537
520
 
538
- dollyDelta.subVectors(dollyEnd, dollyStart);
521
+ } else if ( dollyDelta.y < 0 ) {
539
522
 
540
- if (dollyDelta.y > 0) {
541
- dollyOut(getZoomScale());
542
- } else if (dollyDelta.y < 0) {
543
- dollyIn(getZoomScale());
544
- }
523
+ dollyIn( getZoomScale() );
545
524
 
546
- dollyStart.copy(dollyEnd);
525
+ }
547
526
 
548
- scope.update();
549
- }
527
+ dollyStart.copy( dollyEnd );
528
+ scope.update();
550
529
 
551
- function handleTouchMovePan(event) {
552
- // console.log( 'handleTouchMovePan' );
553
- panEnd.set(event.touches[0].pageX, event.touches[0].pageY);
530
+ }
554
531
 
555
- panDelta.subVectors(panEnd, panStart);
532
+ function handleMouseMovePan( event ) {
556
533
 
557
- pan(panDelta.x, panDelta.y);
534
+ panEnd.set( event.clientX, event.clientY );
535
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
536
+ pan( panDelta.x, panDelta.y );
537
+ panStart.copy( panEnd );
538
+ scope.update();
558
539
 
559
- panStart.copy(panEnd);
540
+ }
560
541
 
561
- scope.update();
562
- }
542
+ function handleMouseUp() { // no-op
543
+ }
563
544
 
564
- function handleTouchEnd(event) {
565
- // console.log( 'handleTouchEnd' );
566
- }
545
+ function handleMouseWheel( event ) {
546
+
547
+ if ( event.deltaY < 0 ) {
548
+ dollyIn( getZoomScale() );
549
+ scope.update(scope.keys.PLUS);
550
+ } else if ( event.deltaY > 0 ) {
551
+ dollyOut( getZoomScale() );
552
+ scope.update(scope.keys.MINUS);
553
+ }
567
554
 
568
- // event handlers - FSM: listen for events and reset state
555
+ }
569
556
 
570
- function onMouseDown(event) {
571
- if (scope.enabled === false) {
572
- return;
573
- }
574
- switch (event.button) {
575
- case scope.mouseButtons.ORBIT:
576
- if (scope.enableRotate === false) {
577
- return;
557
+ function handleKeyDown( event ) {
558
+
559
+ let needsUpdate = true;
560
+
561
+ switch ( event.keyCode ) {
562
+
563
+ case scope.keys.UP:
564
+ if (event.shiftKey) {
565
+ if (scope.enableRotate) {
566
+ rotate(0, scope.keyRotateSpeed);
567
+ }
568
+ } else if (scope.enablePan) {
569
+ pan( 0, scope.keyPanSpeed );
570
+ }
571
+ break;
572
+
573
+ case scope.keys.DOWN:
574
+ if (event.shiftKey) {
575
+ if (scope.enableRotate) {
576
+ rotate(0, - scope.keyRotateSpeed);
577
+ }
578
+ } else if (scope.enablePan) {
579
+ pan( 0, - scope.keyPanSpeed );
580
+ }
581
+ break;
582
+
583
+ case scope.keys.LEFT:
584
+ if (event.shiftKey) {
585
+ if (scope.enableRotate) {
586
+ rotate(scope.keyRotateSpeed, 0);
587
+ }
588
+ } else if (scope.enablePan) {
589
+ pan( scope.keyPanSpeed, 0 );
590
+ }
591
+ break;
592
+
593
+ case scope.keys.RIGHT:
594
+ if (event.shiftKey) {
595
+ if (scope.enableRotate) {
596
+ rotate(-scope.keyRotateSpeed, 0);
597
+ }
598
+ } else if (scope.enablePan) {
599
+ pan( - scope.keyPanSpeed, 0 );
600
+ }
601
+ break;
602
+
603
+ case scope.keys.MINUS:
604
+ case scope.keys.NUMMINUS:
605
+ dollyOut(getZoomScale());
606
+ break;
607
+
608
+ case scope.keys.PLUS:
609
+ case scope.keys.NUMPLUS:
610
+ dollyIn(getZoomScale());
611
+ break;
612
+
613
+ default:
614
+ needsUpdate = false;
578
615
  }
579
- handleMouseDownRotate(event);
580
- state = STATE.ROTATE;
581
- break;
582
- case scope.mouseButtons.ZOOM:
583
- if (scope.enableZoom === false) {
584
- return;
616
+
617
+ if ( needsUpdate ) {
618
+
619
+ // prevent the browser from scrolling on cursor keys
620
+ event.preventDefault();
621
+ scope.update(event.keyCode);
622
+
585
623
  }
586
- handleMouseDownDolly(event);
587
- state = STATE.DOLLY;
588
- break;
589
- case scope.mouseButtons.PAN:
590
- if (scope.enablePan === false) {
591
- return;
624
+
625
+ }
626
+
627
+ function handleTouchStartRotate() {
628
+
629
+ if ( pointers.length === 1 ) {
630
+
631
+ rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
632
+
633
+ } else {
634
+
635
+ const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
636
+ const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
637
+ rotateStart.set( x, y );
638
+
592
639
  }
593
- handleMouseDownPan(event);
594
- state = STATE.PAN;
595
- break;
596
- }
597
640
 
598
- if (state !== STATE.NONE) {
599
- document.addEventListener('mousemove', onMouseMove, false);
600
- document.addEventListener('mouseup', onMouseUp, false);
601
- scope.dispatchEvent(startEvent);
602
- }
603
- }
641
+ }
642
+
643
+ function handleTouchStartPan() {
644
+
645
+ if ( pointers.length === 1 ) {
646
+
647
+ panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
648
+
649
+ } else {
650
+
651
+ const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
652
+ const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
653
+ panStart.set( x, y );
604
654
 
605
- function onMouseMove(event) {
606
- if (scope.enabled === false) {
607
- return;
608
- }
609
- switch (state) {
610
- case STATE.ROTATE:
611
- if (scope.enableRotate === false) {
612
- return;
613
655
  }
614
- handleMouseMoveRotate(event);
615
- event.preventDefault();
616
- break;
617
- case STATE.DOLLY:
618
- if (scope.enableZoom === false) {
619
- return;
656
+
657
+ }
658
+
659
+ function handleTouchStartDolly() {
660
+
661
+ const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX;
662
+ const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY;
663
+ const distance = Math.sqrt( dx * dx + dy * dy );
664
+ dollyStart.set( 0, distance );
665
+
666
+ }
667
+
668
+ function handleTouchStartDollyPan() {
669
+
670
+ if ( scope.enableZoom ) handleTouchStartDolly();
671
+ if ( scope.enablePan ) handleTouchStartPan();
672
+
673
+ }
674
+
675
+ function handleTouchStartDollyRotate() {
676
+
677
+ if ( scope.enableZoom ) handleTouchStartDolly();
678
+ if ( scope.enableRotate ) handleTouchStartRotate();
679
+
680
+ }
681
+
682
+ function handleTouchMoveRotate( event ) {
683
+
684
+ if ( pointers.length == 1 ) {
685
+
686
+ rotateEnd.set( event.pageX, event.pageY );
687
+
688
+ } else {
689
+
690
+ const position = getSecondPointerPosition( event );
691
+ const x = 0.5 * ( event.pageX + position.x );
692
+ const y = 0.5 * ( event.pageY + position.y );
693
+ rotateEnd.set( x, y );
694
+
620
695
  }
621
- handleMouseMoveDolly(event);
622
- event.preventDefault();
623
- break;
624
- case STATE.PAN:
625
- if (scope.enablePan === false) {
626
- return;
696
+
697
+ rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
698
+ const element = scope.domElement;
699
+ rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
700
+
701
+ rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
702
+ rotateStart.copy( rotateEnd );
703
+
704
+ }
705
+
706
+ function handleTouchMovePan( event ) {
707
+
708
+ if ( pointers.length === 1 ) {
709
+
710
+ panEnd.set( event.pageX, event.pageY );
711
+
712
+ } else {
713
+
714
+ const position = getSecondPointerPosition( event );
715
+ const x = 0.5 * ( event.pageX + position.x );
716
+ const y = 0.5 * ( event.pageY + position.y );
717
+ panEnd.set( x, y );
718
+
627
719
  }
628
- handleMouseMovePan(event);
629
- event.preventDefault();
630
- break;
631
- }
632
- }
633
720
 
634
- function onMouseUp(event) {
635
- if (scope.enabled === false) {
636
- return;
637
- }
638
- handleMouseUp(event);
721
+ panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
722
+ pan( panDelta.x, panDelta.y );
723
+ panStart.copy( panEnd );
639
724
 
640
- document.removeEventListener('mousemove', onMouseMove, false);
641
- document.removeEventListener('mouseup', onMouseUp, false);
725
+ }
642
726
 
643
- scope.dispatchEvent(endEvent);
727
+ function handleTouchMoveDolly( event ) {
644
728
 
645
- state = STATE.NONE;
646
- }
729
+ const position = getSecondPointerPosition( event );
730
+ const dx = event.pageX - position.x;
731
+ const dy = event.pageY - position.y;
732
+ const distance = Math.sqrt( dx * dx + dy * dy );
733
+ dollyEnd.set( 0, distance );
734
+ dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
735
+ dollyOut( dollyDelta.y );
736
+ dollyStart.copy( dollyEnd );
647
737
 
648
- function onMouseWheel(event) {
649
- if (scope.enabled === false || scope.enableZoom === false || (state !== STATE.NONE && state !== STATE.ROTATE)) {
650
- return;
651
- }
652
- event.preventDefault();
653
- event.stopPropagation();
738
+ }
654
739
 
655
- handleMouseWheel(event);
740
+ function handleTouchMoveDollyPan( event ) {
656
741
 
657
- scope.dispatchEvent(startEvent); // not sure why these are here...
658
- scope.dispatchEvent(endEvent);
659
- }
742
+ if ( scope.enableZoom ) handleTouchMoveDolly( event );
743
+ if ( scope.enablePan ) handleTouchMovePan( event );
660
744
 
661
- function onKeyDown(event) {
662
- if (scope.enabled === false || scope.enableKeys === false || scope.enablePan === false) {
663
- return;
664
- }
665
- handleKeyDown(event);
666
- }
745
+ }
746
+
747
+ function handleTouchMoveDollyRotate( event ) {
748
+
749
+ if ( scope.enableZoom ) handleTouchMoveDolly( event );
750
+ if ( scope.enableRotate ) handleTouchMoveRotate( event );
751
+
752
+ }
753
+
754
+ function handleTouchEnd() { // no-op
755
+ } //
756
+ // event handlers - FSM: listen for events and reset state
757
+ //
758
+
759
+
760
+ function onPointerDown( event ) {
761
+
762
+ if ( scope.enabled === false ) return;
763
+
764
+ if ( pointers.length === 0 ) {
765
+
766
+ scope.domElement.setPointerCapture( event.pointerId );
767
+ scope.domElement.addEventListener( 'pointermove', onPointerMove );
768
+ scope.domElement.addEventListener( 'pointerup', onPointerUp );
769
+
770
+ } //
771
+
772
+
773
+ addPointer( event );
774
+
775
+ if ( event.pointerType === 'touch' ) {
776
+
777
+ onTouchStart( event );
778
+
779
+ } else {
780
+
781
+ onMouseDown( event );
667
782
 
668
- function onTouchStart(event) {
669
- if (scope.enabled === false) {
670
- return;
671
- }
672
- switch (event.touches.length) {
673
- case 1: // one-fingered touch: rotate
674
- if (scope.enableRotate === false) {
675
- return;
676
783
  }
677
- handleTouchStartRotate(event);
678
- state = STATE.TOUCH_ROTATE;
679
- break;
680
- case 2: // two-fingered touch: dolly
681
- if (scope.enableZoom === false) {
682
- return;
784
+
785
+ }
786
+
787
+ function onPointerMove( event ) {
788
+
789
+ if ( scope.enabled === false ) return;
790
+
791
+ if ( event.pointerType === 'touch' ) {
792
+
793
+ onTouchMove( event );
794
+
795
+ } else {
796
+
797
+ onMouseMove( event );
798
+
683
799
  }
684
- handleTouchStartDolly(event);
685
- state = STATE.TOUCH_DOLLY;
686
- break;
687
- case 3: // three-fingered touch: pan
688
- if (scope.enablePan === false) {
689
- return;
800
+
801
+ }
802
+
803
+ function onPointerUp( event ) {
804
+
805
+ if ( scope.enabled === false ) return;
806
+
807
+ if ( event.pointerType === 'touch' ) {
808
+
809
+ onTouchEnd();
810
+
811
+ } else {
812
+
813
+ onMouseUp( event );
814
+
690
815
  }
691
- handleTouchStartPan(event);
692
- state = STATE.TOUCH_PAN;
693
- break;
694
- default:
695
- state = STATE.NONE;
696
- }
697
816
 
698
- if (state !== STATE.NONE) {
699
- scope.dispatchEvent(startEvent);
700
- }
701
- }
817
+ removePointer( event ); //
702
818
 
703
- function onTouchMove(event) {
819
+ if ( pointers.length === 0 ) {
704
820
 
705
- if (scope.enabled === false) {
706
- return;
707
- }
708
- event.preventDefault();
709
- event.stopPropagation();
821
+ scope.domElement.releasePointerCapture( event.pointerId );
822
+ scope.domElement.removeEventListener( 'pointermove', onPointerMove );
823
+ scope.domElement.removeEventListener( 'pointerup', onPointerUp );
710
824
 
711
- switch (event.touches.length) {
712
- case 1: // one-fingered touch: rotate
713
- if (scope.enableRotate === false) {
714
- return;
715
825
  }
716
- if (state !== STATE.TOUCH_ROTATE) {
717
- return; // is this needed?...
826
+
827
+ }
828
+
829
+ function onPointerCancel( event ) {
830
+
831
+ removePointer( event );
832
+
833
+ }
834
+
835
+ function onMouseDown( event ) {
836
+
837
+ let mouseAction;
838
+
839
+ switch ( event.button ) {
840
+
841
+ case 0:
842
+ mouseAction = scope.mouseButtons.LEFT;
843
+ break;
844
+
845
+ case 1:
846
+ mouseAction = scope.mouseButtons.MIDDLE;
847
+ break;
848
+
849
+ case 2:
850
+ mouseAction = scope.mouseButtons.RIGHT;
851
+ break;
852
+
853
+ default:
854
+ mouseAction = - 1;
855
+
718
856
  }
719
- handleTouchMoveRotate(event);
720
- break;
721
- case 2: // two-fingered touch: dolly
722
- if (scope.enableZoom === false) {
723
- return;
857
+
858
+ switch ( mouseAction ) {
859
+
860
+ case THREE.MOUSE.DOLLY:
861
+ if ( scope.enableZoom === false ) return;
862
+ handleMouseDownDolly( event );
863
+ state = STATE.DOLLY;
864
+ break;
865
+
866
+ case THREE.MOUSE.ROTATE:
867
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
868
+
869
+ if ( scope.enablePan === false ) return;
870
+ handleMouseDownPan( event );
871
+ state = STATE.PAN;
872
+
873
+ } else {
874
+
875
+ if ( scope.enableRotate === false ) return;
876
+ handleMouseDownRotate( event );
877
+ state = STATE.ROTATE;
878
+
879
+ }
880
+
881
+ break;
882
+
883
+ case THREE.MOUSE.PAN:
884
+ if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
885
+
886
+ if ( scope.enableRotate === false ) return;
887
+ handleMouseDownRotate( event );
888
+ state = STATE.ROTATE;
889
+
890
+ } else {
891
+
892
+ if ( scope.enablePan === false ) return;
893
+ handleMouseDownPan( event );
894
+ state = STATE.PAN;
895
+
896
+ }
897
+
898
+ break;
899
+
900
+ default:
901
+ state = STATE.NONE;
902
+
724
903
  }
725
- if (state !== STATE.TOUCH_DOLLY) {
726
- return; // is this needed?...
904
+
905
+ if ( state !== STATE.NONE ) {
906
+
907
+ scope.dispatchEvent( _startEvent );
908
+
909
+ }
910
+
911
+ }
912
+
913
+ function onMouseMove( event ) {
914
+
915
+ if ( scope.enabled === false ) return;
916
+
917
+ switch ( state ) {
918
+
919
+ case STATE.ROTATE:
920
+ if ( scope.enableRotate === false ) return;
921
+ handleMouseMoveRotate( event );
922
+ break;
923
+
924
+ case STATE.DOLLY:
925
+ if ( scope.enableZoom === false ) return;
926
+ handleMouseMoveDolly( event );
927
+ break;
928
+
929
+ case STATE.PAN:
930
+ if ( scope.enablePan === false ) return;
931
+ handleMouseMovePan( event );
932
+ break;
933
+
934
+ }
935
+
936
+ }
937
+
938
+ function onMouseUp( event ) {
939
+
940
+ handleMouseUp( event );
941
+ scope.dispatchEvent( _endEvent );
942
+ state = STATE.NONE;
943
+
944
+ }
945
+
946
+ function onMouseWheel( event ) {
947
+
948
+ if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE && state !== STATE.ROTATE ) return;
949
+ event.preventDefault();
950
+ scope.dispatchEvent( _startEvent );
951
+ handleMouseWheel( event );
952
+ scope.dispatchEvent( _endEvent );
953
+
954
+ }
955
+
956
+ function onKeyDown( event ) {
957
+
958
+ if ( scope.enabled === false || scope.enablePan === false ) return;
959
+ handleKeyDown( event );
960
+
961
+ }
962
+
963
+ function onTouchStart( event ) {
964
+
965
+ trackPointer( event );
966
+
967
+ switch ( pointers.length ) {
968
+
969
+ case 1:
970
+ switch ( scope.touches.ONE ) {
971
+
972
+ case THREE.TOUCH.ROTATE:
973
+ if ( scope.enableRotate === false ) return;
974
+ handleTouchStartRotate();
975
+ state = STATE.TOUCH_ROTATE;
976
+ break;
977
+
978
+ case THREE.TOUCH.PAN:
979
+ if ( scope.enablePan === false ) return;
980
+ handleTouchStartPan();
981
+ state = STATE.TOUCH_PAN;
982
+ break;
983
+
984
+ default:
985
+ state = STATE.NONE;
986
+
987
+ }
988
+
989
+ break;
990
+
991
+ case 2:
992
+ switch ( scope.touches.TWO ) {
993
+
994
+ case THREE.TOUCH.DOLLY_PAN:
995
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
996
+ handleTouchStartDollyPan();
997
+ state = STATE.TOUCH_DOLLY_PAN;
998
+ break;
999
+
1000
+ case THREE.TOUCH.DOLLY_ROTATE:
1001
+ if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1002
+ handleTouchStartDollyRotate();
1003
+ state = STATE.TOUCH_DOLLY_ROTATE;
1004
+ break;
1005
+
1006
+ default:
1007
+ state = STATE.NONE;
1008
+
1009
+ }
1010
+
1011
+ break;
1012
+
1013
+ default:
1014
+ state = STATE.NONE;
1015
+
727
1016
  }
728
- handleTouchMoveDolly(event);
729
- break;
730
- case 3: // three-fingered touch: pan
731
- if (scope.enablePan === false) {
732
- return;
1017
+
1018
+ if ( state !== STATE.NONE ) {
1019
+
1020
+ scope.dispatchEvent( _startEvent );
1021
+
733
1022
  }
734
- if (state !== STATE.TOUCH_PAN) {
735
- return; // is this needed?...
1023
+
1024
+ }
1025
+
1026
+ function onTouchMove( event ) {
1027
+
1028
+ trackPointer( event );
1029
+
1030
+ switch ( state ) {
1031
+
1032
+ case STATE.TOUCH_ROTATE:
1033
+ if ( scope.enableRotate === false ) return;
1034
+ handleTouchMoveRotate( event );
1035
+ scope.update();
1036
+ break;
1037
+
1038
+ case STATE.TOUCH_PAN:
1039
+ if ( scope.enablePan === false ) return;
1040
+ handleTouchMovePan( event );
1041
+ scope.update();
1042
+ break;
1043
+
1044
+ case STATE.TOUCH_DOLLY_PAN:
1045
+ if ( scope.enableZoom === false && scope.enablePan === false ) return;
1046
+ handleTouchMoveDollyPan( event );
1047
+ scope.update();
1048
+ break;
1049
+
1050
+ case STATE.TOUCH_DOLLY_ROTATE:
1051
+ if ( scope.enableZoom === false && scope.enableRotate === false ) return;
1052
+ handleTouchMoveDollyRotate( event );
1053
+ scope.update();
1054
+ break;
1055
+
1056
+ default:
1057
+ state = STATE.NONE;
1058
+
736
1059
  }
737
- handleTouchMovePan(event);
738
- break;
739
- default:
1060
+
1061
+ }
1062
+
1063
+ function onTouchEnd( event ) {
1064
+
1065
+ handleTouchEnd( event );
1066
+ scope.dispatchEvent( _endEvent );
740
1067
  state = STATE.NONE;
741
- }
742
- }
743
1068
 
744
- function onTouchEnd(event) {
745
- if (scope.enabled === false) {
746
- return;
747
- }
748
- handleTouchEnd(event);
749
- scope.dispatchEvent(endEvent);
750
- state = STATE.NONE;
751
- }
1069
+ }
752
1070
 
753
- function onContextMenu(event) {
754
- if (scope.enabled === false) {
755
- return;
756
- }
757
- event.preventDefault();
758
- }
1071
+ function onContextMenu( event ) {
759
1072
 
760
- scope.domElement.addEventListener('contextmenu', onContextMenu, false);
1073
+ if ( scope.enabled === false ) return;
1074
+ event.preventDefault();
761
1075
 
762
- scope.domElement.addEventListener('mousedown', onMouseDown, false);
763
- scope.domElement.addEventListener('wheel', onMouseWheel, false);
1076
+ }
764
1077
 
765
- scope.domElement.addEventListener('touchstart', onTouchStart, false);
766
- scope.domElement.addEventListener('touchend', onTouchEnd, false);
767
- scope.domElement.addEventListener('touchmove', onTouchMove, false);
1078
+ function addPointer( event ) {
768
1079
 
769
- scope.domElement.addEventListener('keydown', onKeyDown, false);
1080
+ pointers.push( event );
770
1081
 
771
- // force an update at start
772
- this.update();
773
- };
1082
+ }
774
1083
 
775
- THREE.OrbitControls.prototype = Object.create(THREE.EventDispatcher.prototype);
776
- THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
1084
+ function removePointer( event ) {
777
1085
 
778
- Object.defineProperties(THREE.OrbitControls.prototype, {
779
- center: {
780
- get: function () {
781
- console.warn('THREE.OrbitControls: .center has been renamed to .target');
782
- return this.target;
783
- }
784
- },
1086
+ delete pointerPositions[ event.pointerId ];
785
1087
 
786
- // backward compatibility
1088
+ for ( let i = 0; i < pointers.length; i ++ ) {
787
1089
 
788
- noZoom: {
789
- get: function () {
790
- console.warn('THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.');
791
- return !this.enableZoom;
792
- },
1090
+ if ( pointers[ i ].pointerId == event.pointerId ) {
793
1091
 
794
- set: function (value) {
795
- console.warn('THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.');
796
- this.enableZoom = !value;
797
- }
798
- },
1092
+ pointers.splice( i, 1 );
1093
+ return;
799
1094
 
800
- noRotate: {
801
- get: function () {
802
- console.warn('THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.');
803
- return !this.enableRotate;
804
- },
1095
+ }
805
1096
 
806
- set: function (value) {
807
- console.warn('THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.');
808
- this.enableRotate = !value;
809
- }
810
- },
1097
+ }
811
1098
 
812
- noPan: {
813
- get: function () {
814
- console.warn('THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.');
815
- return !this.enablePan;
816
- },
1099
+ }
817
1100
 
818
- set: function (value) {
819
- console.warn('THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.');
820
- this.enablePan = !value;
821
- }
822
- },
1101
+ function trackPointer( event ) {
823
1102
 
824
- noKeys: {
825
- get: function () {
826
- console.warn('THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.');
827
- return !this.enableKeys;
828
- },
1103
+ let position = pointerPositions[ event.pointerId ];
829
1104
 
830
- set: function (value) {
831
- console.warn('THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.');
832
- this.enableKeys = !value;
833
- }
834
- },
1105
+ if ( position === undefined ) {
1106
+
1107
+ position = new THREE.Vector2();
1108
+ pointerPositions[ event.pointerId ] = position;
1109
+
1110
+ }
1111
+
1112
+ position.set( event.pageX, event.pageY );
1113
+
1114
+ }
1115
+
1116
+ function getSecondPointerPosition( event ) {
835
1117
 
836
- staticMoving: {
837
- get: function () {
838
- console.warn('THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.');
839
- return !this.enableDamping;
840
- },
1118
+ const pointer = event.pointerId === pointers[ 0 ].pointerId ? pointers[ 1 ] : pointers[ 0 ];
1119
+ return pointerPositions[ pointer.pointerId ];
1120
+
1121
+ } //
1122
+
1123
+
1124
+ scope.domElement.addEventListener( 'contextmenu', onContextMenu );
1125
+ scope.domElement.addEventListener( 'pointerdown', onPointerDown );
1126
+ scope.domElement.addEventListener( 'pointercancel', onPointerCancel );
1127
+ scope.domElement.addEventListener( 'wheel', onMouseWheel, {
1128
+ passive: false
1129
+ } ); // force an update at start
1130
+
1131
+ this.update();
841
1132
 
842
- set: function (value) {
843
- console.warn('THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.');
844
- this.enableDamping = !value;
845
1133
  }
846
- },
847
1134
 
848
- dynamicDampingFactor: {
849
- get: function () {
850
- console.warn('THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.');
851
- return this.dampingFactor;
852
- },
1135
+ }
1136
+
1137
+ // This set of controls performs orbiting, dollying (zooming), and panning.
1138
+ // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
1139
+ // This is very similar to OrbitControls, another set of touch behavior
1140
+ //
1141
+ // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
1142
+ // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
1143
+ // Pan - left mouse, or arrow keys / touch: one-finger move
1144
+
1145
+ class MapControls extends OrbitControls {
1146
+
1147
+ constructor( object, domElement ) {
1148
+
1149
+ super( object, domElement );
1150
+ this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
1151
+
1152
+ this.mouseButtons.LEFT = THREE.MOUSE.PAN;
1153
+ this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
1154
+ this.touches.ONE = THREE.TOUCH.PAN;
1155
+ this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
853
1156
 
854
- set: function (value) {
855
- console.warn('THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.');
856
- this.dampingFactor = value;
857
1157
  }
1158
+
858
1159
  }
859
- });
860
1160
 
1161
+ THREE.MapControls = MapControls;
1162
+ THREE.OrbitControls = OrbitControls;
1163
+
1164
+ } )();