@xeokit/xeokit-sdk 2.6.53 → 2.6.55

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xeokit/xeokit-sdk",
3
- "version": "2.6.53",
3
+ "version": "2.6.55",
4
4
  "description": "Web Programming Toolkit for 3D/2D BIM and AEC Graphics",
5
5
  "module": "./dist/xeokit-sdk.es.js",
6
6
  "main": "./dist/xeokit-sdk.cjs.js",
@@ -680,9 +680,10 @@ class StoreyViewsPlugin extends Plugin {
680
680
  * @param {Number[]} worldPos 3D World-space position.
681
681
  * @returns {String} ID of the storey containing the position, or null if the position falls outside all the storeys.
682
682
  */
683
- getStoreyContainingWorldPos(worldPos) {
684
- for (var storeyId in this.storeys) {
685
- const storey = this.storeys[storeyId];
683
+ getStoreyContainingWorldPos(worldPos, modelId = null) {
684
+ const storeys = modelId ? this._filterStoreys(modelId) : this.storeys;
685
+ for (let storeyId in storeys) {
686
+ const storey = storeys[storeyId];
686
687
  if (math.point3AABB3AbsoluteIntersect(storey.storeyAABB, worldPos)) {
687
688
  return storeyId;
688
689
  }
@@ -696,9 +697,10 @@ class StoreyViewsPlugin extends Plugin {
696
697
  * @param {Number[]} worldPos 3D World-space position.
697
698
  * @returns {String} ID of the storey containing the position, or null if the position falls outside all the storeys.
698
699
  */
699
- getStoreyInVerticalRange(worldPos) {
700
- for (let storeyId in this.storeys) {
701
- const storey = this.storeys[storeyId];
700
+ getStoreyInVerticalRange(worldPos, modelId = null) {
701
+ const storeys = modelId ? this._filterStoreys(modelId) : this.storeys;
702
+ for (let storeyId in storeys) {
703
+ const storey = storeys[storeyId];
702
704
  const aabb = [0, 0, 0, 0, 0, 0], pos = [0, 0, 0];
703
705
  aabb[1] = storey.storeyAABB[1];
704
706
  aabb[4] = storey.storeyAABB[4];
@@ -716,12 +718,14 @@ class StoreyViewsPlugin extends Plugin {
716
718
  * @param {Number[]} worldPos 3D World-space position.
717
719
  * @returns {String} ID of the lowest/highest story or null.
718
720
  */
719
- isPositionAboveOrBelowBuilding(worldPos) {
720
- const keys = Object.keys(this.storeys);
721
+ isPositionAboveOrBelowBuilding(worldPos, modelId = null) {
722
+ const storeys = modelId ? this._filterStoreys(modelId) : this.storeys;
723
+ const keys = Object.keys(storeys);
724
+ if(keys.length <= 0) return null;
721
725
  const ids = [keys[0], keys[keys.length - 1]];
722
- if (worldPos[1] < this.storeys[ids[0]].storeyAABB[1])
726
+ if (worldPos[1] < storeys[ids[0]].storeyAABB[1])
723
727
  return ids[0];
724
- else if (worldPos[1] > this.storeys[ids[1]].storeyAABB[4])
728
+ else if (worldPos[1] > storeys[ids[1]].storeyAABB[4])
725
729
  return ids[1];
726
730
  return null;
727
731
  }
@@ -845,8 +849,8 @@ class StoreyViewsPlugin extends Plugin {
845
849
 
846
850
  if (storey1MetaObject && (storey1MetaObject.attributes && storey1MetaObject.attributes.elevation !== undefined) &&
847
851
  storey2MetaObject && (storey2MetaObject.attributes && storey2MetaObject.attributes.elevation !== undefined)) {
848
- const elevation1 = storey1MetaObject.attributes.elevation;
849
- const elevation2 = storey2MetaObject.attributes.elevation;
852
+ const elevation1 = Number.parseFloat(storey1MetaObject.attributes.elevation);
853
+ const elevation2 = Number.parseFloat(storey2MetaObject.attributes.elevation);
850
854
  if (elevation1 > elevation2) {
851
855
  return -1;
852
856
  }
@@ -865,6 +869,12 @@ class StoreyViewsPlugin extends Plugin {
865
869
  }
866
870
  });
867
871
  }
872
+
873
+ _filterStoreys(modelId) {
874
+ return Object.fromEntries(
875
+ Object.entries(this.storeys).filter(([_, value]) => value.modelId === modelId)
876
+ );
877
+ }
868
878
  }
869
879
 
870
880
  export {StoreyViewsPlugin}
@@ -1235,8 +1235,8 @@ export class TreeViewPlugin extends Plugin {
1235
1235
 
1236
1236
  if (storey1MetaObject && (storey1MetaObject.attributes && storey1MetaObject.attributes.elevation !== undefined) &&
1237
1237
  storey2MetaObject && (storey2MetaObject.attributes && storey2MetaObject.attributes.elevation !== undefined)) {
1238
- const elevation1 = storey1MetaObject.attributes.elevation;
1239
- const elevation2 = storey2MetaObject.attributes.elevation;
1238
+ const elevation1 = Number.parseFloat(storey1MetaObject.attributes.elevation);
1239
+ const elevation2 = Number.parseFloat(storey2MetaObject.attributes.elevation);
1240
1240
  if (elevation1 > elevation2) {
1241
1241
  return -1;
1242
1242
  }
@@ -493,19 +493,15 @@ class Viewer {
493
493
 
494
494
  for (let i = 0, len = pluginContainerElements.length; i < len; i++) {
495
495
  const containerElement = pluginContainerElements[i];
496
- //only calculate the scale for first plugin
497
- //for all others keep the scale 1 otherwise it will keep multiplying the scale with the base scale of canvas
498
- //resulting in increase/decreased size for the the canvas that is being overlapped
499
- const scale = i == 0 ? snapshotCanvas.width / containerElement.clientWidth : 1;
500
- const off = math.vec2([ 0, 0 ]);
501
- transformToNode(canvas, containerElement, off);
502
496
  await html2canvas(containerElement, {
503
497
  canvas: snapshotCanvas,
504
498
  backgroundColor: null,
505
- x: off[0],
506
- y: off[1],
507
- scale
499
+ scale: snapshotCanvas.width / containerElement.clientWidth
508
500
  });
501
+ // Reverts translation and scaling applied to the snapshotCanvas's context
502
+ // by the html2canvas call (inside the ForeignObjectRenderer's constructor)
503
+ // (implemented to compensate XCD-153 issue)
504
+ snapshotCanvas.getContext("2d").resetTransform();
509
505
  }
510
506
  if (!params.includeGizmos) {
511
507
  this.sendToPlugins("snapshotFinished");
@@ -459,6 +459,12 @@ const DEFAULT_SNAP_EDGE = true;
459
459
  * keyMap[cameraControl.AXIS_VIEW_FRONT] = [input.KEY_NUM_4];
460
460
  * keyMap[cameraControl.AXIS_VIEW_TOP] = [input.KEY_NUM_5];
461
461
  * keyMap[cameraControl.AXIS_VIEW_BOTTOM] = [input.KEY_NUM_6];
462
+ * keyMap[cameraControl.MOUSE_PAN] = [[input.KEY_SHIFT, input.MOUSE_LEFT_BUTTON]];
463
+ * keyMap[cameraControl.MOUSE_ROTATE] = [
464
+ * [input.KEY_SHIFT, input.MOUSE_MIDDLE_BUTTON],
465
+ * [input.KEY_SHIFT, input.MOUSE_RIGHT_BUTTON]
466
+ * ]
467
+ * keyMap[cameraControl.MOUSE_DOLLY] = [[input.KEY_CTRL, input.MOUSE_RIGHT_BUTTON]];
462
468
  *
463
469
  * cameraControl.keyMap = keyMap;
464
470
  * ````
@@ -477,6 +483,70 @@ const DEFAULT_SNAP_EDGE = true;
477
483
  *
478
484
  * * ````"qwerty"````
479
485
  * * ````"azerty"````
486
+ *
487
+ * ## Basic Keyboard Mapping
488
+ * * ````"OR" Relation````
489
+ * Set multiple keys to trigger an action if any one is pressed:
490
+ *
491
+ * ````javascript
492
+ * keyMap[cameraControl.DOLLY_BACKWARDS] = [input.KEY_S, input.KEY_SUBTRACT];
493
+ * ````
494
+ *
495
+ * If either ````KEY_S```` or ````KEY_SUBTRACT```` is pressed, the camera will dolly backward.
496
+ *
497
+ * * ````"AND" Relation````
498
+ * To require all keys in a combination to be pressed:
499
+ *
500
+ * ````javascript
501
+ * keyMap[cameraControl.DOLLY_BACKWARDS] = [[input.KEY_S, input.KEY_SUBTRACT]];
502
+ * ````
503
+ *
504
+ * The camera will dolly backward if ````both KEY_S```` and ````KEY_SUBTRACT```` are pressed.
505
+ *
506
+ * * ````Mix "AND" and "OR" Relation````
507
+ * Use a combination of keys and groups for flexibility:
508
+ *
509
+ * ````javascript
510
+ * keyMap[cameraControl.DOLLY_BACKWARDS] = [
511
+ * [input.KEY_S, input.KEY_SUBTRACT], // 'And' group
512
+ * input.KEY_SHIFT // 'Or' with previous
513
+ * ];
514
+ * ````
515
+ *
516
+ * The camera will dolly backward if ````KEY_S```` + ````KEY_SUBTRACT```` are pressed together, or if ````KEY_SHIFT```` is pressed.
517
+ *
518
+ * ## Special Mouse Actions
519
+ * Certain actions support combinations with specific mouse buttons or events:
520
+ *
521
+ * * ````MOUSE_PAN````
522
+ * For panning with a key and mouse movement:
523
+ *
524
+ * ````javascript
525
+ * keyMap[cameraControl.MOUSE_PAN] = [[input.KEY_SHIFT, input.MOUSE_LEFT_BUTTON]];
526
+ * ````
527
+ *
528
+ * Panning is triggered by pressing ````KEY_SHIFT```` + ````MOUSE_LEFT_BUTTON```` while moving the mouse.
529
+ *
530
+ * * ````MOUSE_ROTATE````
531
+ * Similar to panning, rotation can be configured with key and mouse button combinations:
532
+ *
533
+ * ````javascript
534
+ * keyMap[cameraControl.MOUSE_ROTATE] = [[input.KEY_CTRL, input.MOUSE_LEFT_BUTTON]];
535
+ * ````
536
+ *
537
+ * Rotation is triggered by pressing ````KEY_CTRL```` + ````MOUSE_LEFT_BUTTON```` during mouse movement.
538
+ *
539
+ * * ````MOUSE_DOLLY````
540
+ * Dolly with mouse wheel scrolling and optional key combinations:
541
+ *
542
+ * ````javascript
543
+ * keyMap[cameraControl.MOUSE_DOLLY] = [[input.KEY_ALT]];
544
+ * ````
545
+ *
546
+ * Dolly action occurs when scrolling the mouse wheel with ````KEY_ALT```` held (no need to specify MOUSE_WHEEL).
547
+ *
548
+ * ## Special Mouse Actions
549
+ * Use ````input.MOUSE_LEFT_BUTTON````, ````input.MOUSE_MIDDLE_BUTTON````, and ````input.MOUSE_RIGHT_BUTTON```` in combinations only for camera movements involving the mouse.
480
550
  */
481
551
  class CameraControl extends Component {
482
552
 
@@ -614,6 +684,27 @@ class CameraControl extends Component {
614
684
  */
615
685
  this.AXIS_VIEW_BOTTOM = 17;
616
686
 
687
+ /**
688
+ * Identifies the XX action.
689
+ * @final
690
+ * @type {Number}
691
+ */
692
+ this.MOUSE_PAN = 18;
693
+
694
+ /**
695
+ * Identifies the XX action.
696
+ * @final
697
+ * @type {Number}
698
+ */
699
+ this.MOUSE_ROTATE = 19;
700
+
701
+ /**
702
+ * Identifies the XX action.
703
+ * @final
704
+ * @type {Number}
705
+ */
706
+ this.MOUSE_DOLLY = 20;
707
+
617
708
  this._keyMap = {}; // Maps key codes to the above actions
618
709
 
619
710
  this.scene.canvas.canvas.oncontextmenu = (e) => {
@@ -729,6 +820,13 @@ class CameraControl extends Component {
729
820
  new KeyboardPanRotateDollyHandler(this.scene, this._controllers, this._configs, this._states, this._updates)
730
821
  ];
731
822
 
823
+ this._cursors = {
824
+ dollyForward: "zoom-in",
825
+ dollyBackward: "zoom-out",
826
+ rotate: 'grabbing',
827
+ pan: 'move',
828
+ }
829
+
732
830
  // Applies scheduled updates to the Camera on each Scene "tick" event
733
831
 
734
832
  this._cameraUpdater = new CameraUpdater(this.scene, this._controllers, this._configs, this._states, this._updates);
@@ -802,6 +900,12 @@ class CameraControl extends Component {
802
900
  keyMap[this.AXIS_VIEW_FRONT] = [input.KEY_NUM_4];
803
901
  keyMap[this.AXIS_VIEW_TOP] = [input.KEY_NUM_5];
804
902
  keyMap[this.AXIS_VIEW_BOTTOM] = [input.KEY_NUM_6];
903
+ keyMap[this.MOUSE_PAN] = [
904
+ [input.MOUSE_LEFT_BUTTON, input.KEY_SHIFT],
905
+ this._configs.panRightClick ? input.MOUSE_RIGHT_BUTTON : input.MOUSE_MIDDLE_BUTTON
906
+ ]
907
+ keyMap[this.MOUSE_ROTATE] = [input.MOUSE_LEFT_BUTTON];
908
+ keyMap[this.MOUSE_DOLLY] = [];
805
909
  break;
806
910
 
807
911
  case "azerty":
@@ -823,6 +927,12 @@ class CameraControl extends Component {
823
927
  keyMap[this.AXIS_VIEW_FRONT] = [input.KEY_NUM_4];
824
928
  keyMap[this.AXIS_VIEW_TOP] = [input.KEY_NUM_5];
825
929
  keyMap[this.AXIS_VIEW_BOTTOM] = [input.KEY_NUM_6];
930
+ keyMap[this.MOUSE_PAN] = [
931
+ [input.MOUSE_LEFT_BUTTON, input.KEY_SHIFT],
932
+ this._configs.panRightClick ? input.MOUSE_RIGHT_BUTTON : input.MOUSE_MIDDLE_BUTTON
933
+ ]
934
+ keyMap[this.MOUSE_ROTATE] = [input.MOUSE_LEFT_BUTTON];
935
+ keyMap[this.MOUSE_DOLLY] = [];
826
936
  break;
827
937
  }
828
938
 
@@ -842,6 +952,44 @@ class CameraControl extends Component {
842
952
  return this._keyMap;
843
953
  }
844
954
 
955
+ _areAllKeysDown(keyDownMap, keys) {
956
+ if (!keys || keys.length <= 0) {
957
+ return true;
958
+ }
959
+ if (!keyDownMap) {
960
+ return false;
961
+ }
962
+ for (let i = 0, len = keys.length; i < len; i++) {
963
+ const key = keys[i];
964
+ if (!keyDownMap[key])
965
+ return false;
966
+ }
967
+ return true;
968
+ }
969
+
970
+ _isAnyOtherKeyDown(keyDownMap, keyMap) {
971
+ for (let i = 0, len = keyDownMap.length; i < len; i++) {
972
+ if (keyDownMap[i]) {
973
+ if (Array.isArray(keyMap)) {
974
+ if (keyMap.indexOf(i) < 0) return true;
975
+ }
976
+ else if (i !== keyMap) return true;
977
+ }
978
+ }
979
+ return false;
980
+ }
981
+
982
+ _isMouseAction(action) {
983
+ switch (action) {
984
+ case this.MOUSE_ROTATE:
985
+ case this.MOUSE_DOLLY:
986
+ case this.MOUSE_PAN:
987
+ return true;
988
+ default:
989
+ return false;
990
+ }
991
+ }
992
+
845
993
  /**
846
994
  * Returns true if any keys configured for the given action are down.
847
995
  * @param action
@@ -853,14 +1001,21 @@ class CameraControl extends Component {
853
1001
  if (!keys) {
854
1002
  return false;
855
1003
  }
1004
+ if (keys.length === 0 && this._isMouseAction(action)) return true;
856
1005
  if (!keyDownMap) {
857
1006
  keyDownMap = this.scene.input.keyDown;
858
1007
  }
859
1008
  for (let i = 0, len = keys.length; i < len; i++) {
860
1009
  const key = keys[i];
861
- if (keyDownMap[key]) {
862
- return true;
1010
+ if (!Array.isArray(key)) {
1011
+ if (keyDownMap[key] && !this._isAnyOtherKeyDown(keyDownMap, key))
1012
+ return true;
1013
+ }
1014
+ else {
1015
+ if (this._areAllKeysDown(keyDownMap, key) && !this._isAnyOtherKeyDown(keyDownMap, key))
1016
+ return true;
863
1017
  }
1018
+
864
1019
  }
865
1020
  return false;
866
1021
  }
@@ -1033,6 +1188,37 @@ class CameraControl extends Component {
1033
1188
  this._configs.pointerEnabled = !!value;
1034
1189
  }
1035
1190
 
1191
+ /**
1192
+ * Sets the cursor to be used when a particular action is being performed.
1193
+ *
1194
+ * Accepted actions are:
1195
+ *
1196
+ * * "dollyForward" - when the camera is dollying in the forward direction
1197
+ * * "dollyBackward" - when the camera is dollying in the backward direction
1198
+ * * "pan" - when the camera is being panned
1199
+ * * "rotate" - when the camera is being rotated
1200
+ *
1201
+ * @param {String} action
1202
+ * @param {String} style
1203
+ */
1204
+ setCursorStyle(action, style) {
1205
+ if (Object.prototype.hasOwnProperty.call(this._cursors, action)) {
1206
+ this._cursors = { ...this._cursors, [action]: style };
1207
+ }
1208
+ else
1209
+ console.warn(`Action '${action}' is not valid for cursor styles.`);
1210
+ }
1211
+
1212
+ /**
1213
+ * Gets the current style for a particular action.
1214
+ *
1215
+ * @param {String} action To get the style for
1216
+ * @returns {String} style set on the cursor for action
1217
+ */
1218
+ getCursorStyle(action) {
1219
+ return this._cursors[action] || null;
1220
+ }
1221
+
1036
1222
  _reset() {
1037
1223
  for (let i = 0, len = this._handlers.length; i < len; i++) {
1038
1224
  const handler = this._handlers[i];
@@ -1296,6 +1482,16 @@ class CameraControl extends Component {
1296
1482
  */
1297
1483
  set panRightClick(value) {
1298
1484
  this._configs.panRightClick = value !== false;
1485
+ const panKeyMap = this._keyMap[this.MOUSE_PAN];
1486
+ if (panKeyMap && panKeyMap.length > 0) {
1487
+ const input = this.scene.input;
1488
+ for (let i = 0, len = panKeyMap.length; i < len; i++) {
1489
+ if (this._configs.panRightClick && panKeyMap[i] === input.MOUSE_MIDDLE_BUTTON)
1490
+ this._keyMap[this.MOUSE_PAN][i] = input.MOUSE_RIGHT_BUTTON;
1491
+ else if (!this._configs.panRightClick && panKeyMap[i] === input.MOUSE_RIGHT_BUTTON)
1492
+ this._keyMap[this.MOUSE_PAN][i] = input.MOUSE_MIDDLE_BUTTON;
1493
+ }
1494
+ }
1299
1495
  }
1300
1496
 
1301
1497
  /**
@@ -18,6 +18,7 @@ class CameraUpdater {
18
18
  const pickController = controllers.pickController;
19
19
  const pivotController = controllers.pivotController;
20
20
  const panController = controllers.panController;
21
+ const cameraControl = controllers.cameraControl;
21
22
 
22
23
  let countDown = SCALE_DOLLY_EACH_FRAME; // Decrements on each tick
23
24
  let dollyDistFactor = 1.0; // Calculated when countDown is zero
@@ -142,7 +143,7 @@ class CameraUpdater {
142
143
  updates.rotateDeltaX *= configs.rotationInertia;
143
144
  updates.rotateDeltaY *= configs.rotationInertia;
144
145
 
145
- cursorType = "grabbing";
146
+ cursorType = cameraControl._cursors.rotate;
146
147
  }
147
148
 
148
149
  //----------------------------------------------------------------------------------------------------------
@@ -208,7 +209,7 @@ class CameraUpdater {
208
209
  camera.pan(vec);
209
210
  }
210
211
 
211
- cursorType = "grabbing";
212
+ cursorType = cameraControl._cursors.pan;
212
213
  }
213
214
 
214
215
  updates.panDeltaX *= configs.panInertia;
@@ -222,9 +223,9 @@ class CameraUpdater {
222
223
  if (dollyDeltaForDist !== 0) {
223
224
 
224
225
  if (dollyDeltaForDist < 0) {
225
- cursorType = "zoom-in";
226
+ cursorType = cameraControl._cursors.dollyForward;
226
227
  } else {
227
- cursorType = "zoom-out";
228
+ cursorType = cameraControl._cursors.dollyBackward;
228
229
  }
229
230
 
230
231
  if (configs.firstPerson) {
@@ -26,10 +26,6 @@ class KeyboardPanRotateDollyHandler {
26
26
  return;
27
27
  }
28
28
  keyDownMap[keyCode] = true;
29
-
30
- if (keyCode === input.KEY_SHIFT) {
31
- canvas.style.cursor = "move";
32
- }
33
29
  });
34
30
 
35
31
  this._onSceneKeyUp = input.on("keyup", (keyCode) => {
@@ -38,10 +34,6 @@ class KeyboardPanRotateDollyHandler {
38
34
  }
39
35
  keyDownMap[keyCode] = false;
40
36
 
41
- if (keyCode === input.KEY_SHIFT) {
42
- canvas.style.cursor = null;
43
- }
44
-
45
37
  if (controllers.pivotController.getPivoting()) {
46
38
  controllers.pivotController.endPivot()
47
39
  }
@@ -39,6 +39,7 @@ class MousePanRotateDollyHandler {
39
39
  this._scene = scene;
40
40
 
41
41
  const pickController = controllers.pickController;
42
+ const cameraControl = controllers.cameraControl;
42
43
 
43
44
  let lastX = 0;
44
45
  let lastY = 0;
@@ -77,7 +78,6 @@ class MousePanRotateDollyHandler {
77
78
  });
78
79
 
79
80
  function setMousedownState(pick = true) {
80
- canvas.style.cursor = "move";
81
81
  setMousedownPositions();
82
82
  if (pick) {
83
83
  setMousedownPick();
@@ -107,6 +107,15 @@ class MousePanRotateDollyHandler {
107
107
  }
108
108
  }
109
109
 
110
+ function isPanning() {
111
+ return cameraControl._isKeyDownForAction(cameraControl.MOUSE_PAN, keyDown);
112
+ }
113
+
114
+ function isRotating() {
115
+ return cameraControl._isKeyDownForAction(cameraControl.MOUSE_ROTATE, keyDown);
116
+ }
117
+
118
+
110
119
  canvas.addEventListener("mousedown", this._mouseDownHandler = (e) => {
111
120
 
112
121
  if (!(configs.active && configs.pointerEnabled)) {
@@ -120,12 +129,14 @@ class MousePanRotateDollyHandler {
120
129
  if (keyDown[scene.input.KEY_SHIFT] || configs.planView) {
121
130
 
122
131
  mouseDownLeft = true;
132
+ keyDown[scene.input.MOUSE_LEFT_BUTTON] = true;
123
133
 
124
134
  setMousedownState();
125
135
 
126
136
  } else {
127
137
 
128
138
  mouseDownLeft = true;
139
+ keyDown[scene.input.MOUSE_LEFT_BUTTON] = true;
129
140
 
130
141
  setMousedownState(false);
131
142
  }
@@ -135,6 +146,7 @@ class MousePanRotateDollyHandler {
135
146
  case 2: // Middle/both buttons
136
147
 
137
148
  mouseDownMiddle = true;
149
+ keyDown[scene.input.MOUSE_MIDDLE_BUTTON] = true;
138
150
 
139
151
  setMousedownState();
140
152
 
@@ -143,6 +155,7 @@ class MousePanRotateDollyHandler {
143
155
  case 3: // Right button
144
156
 
145
157
  mouseDownRight = true;
158
+ keyDown[scene.input.MOUSE_RIGHT_BUTTON] = true;
146
159
 
147
160
  if (configs.panRightClick) {
148
161
 
@@ -175,7 +188,8 @@ class MousePanRotateDollyHandler {
175
188
  const x = states.pointerCanvasPos[0];
176
189
  const y = states.pointerCanvasPos[1];
177
190
 
178
- const panning = keyDown[scene.input.KEY_SHIFT] || configs.planView || (!configs.panRightClick && mouseDownMiddle) || (configs.panRightClick && mouseDownRight);
191
+ const panning = isPanning();
192
+ const rotating = isRotating();
179
193
 
180
194
  const xDelta = document.pointerLockElement ? e.movementX : (x - lastX);
181
195
  const yDelta = document.pointerLockElement ? e.movementY : (y - lastY);
@@ -200,7 +214,7 @@ class MousePanRotateDollyHandler {
200
214
  updates.panDeltaY += 0.5 * camera.ortho.scale * (yDelta / canvasHeight);
201
215
  }
202
216
 
203
- } else if (mouseDownLeft && !mouseDownMiddle && !mouseDownRight) {
217
+ } else if (rotating) {
204
218
 
205
219
  if (!configs.planView) { // No rotating in plan-view mode
206
220
 
@@ -241,16 +255,25 @@ class MousePanRotateDollyHandler {
241
255
  mouseDownLeft = false;
242
256
  mouseDownMiddle = false;
243
257
  mouseDownRight = false;
258
+ keyDown[scene.input.MOUSE_LEFT_BUTTON] = false;
259
+ keyDown[scene.input.MOUSE_MIDDLE_BUTTON] = false;
260
+ keyDown[scene.input.MOUSE_RIGHT_BUTTON] = false;
244
261
  break;
245
262
  case 2: // Middle/both buttons
246
263
  mouseDownLeft = false;
247
264
  mouseDownMiddle = false;
248
265
  mouseDownRight = false;
266
+ keyDown[scene.input.MOUSE_LEFT_BUTTON] = false;
267
+ keyDown[scene.input.MOUSE_MIDDLE_BUTTON] = false;
268
+ keyDown[scene.input.MOUSE_RIGHT_BUTTON] = false;
249
269
  break;
250
270
  case 3: // Right button
251
271
  mouseDownLeft = false;
252
272
  mouseDownMiddle = false;
253
273
  mouseDownRight = false;
274
+ keyDown[scene.input.MOUSE_LEFT_BUTTON] = false;
275
+ keyDown[scene.input.MOUSE_MIDDLE_BUTTON] = false;
276
+ keyDown[scene.input.MOUSE_RIGHT_BUTTON] = false;
254
277
  break;
255
278
  default:
256
279
  break;
@@ -296,7 +319,7 @@ class MousePanRotateDollyHandler {
296
319
  let secsNowLast = null;
297
320
 
298
321
  canvas.addEventListener("wheel", this._mouseWheelHandler = (e) => {
299
- if (!(configs.active && configs.pointerEnabled && configs.zoomOnMouseWheel)) {
322
+ if (!(configs.active && configs.pointerEnabled && configs.zoomOnMouseWheel && cameraControl._isKeyDownForAction(cameraControl.MOUSE_DOLLY, keyDown))) {
300
323
  return;
301
324
  }
302
325
  const secsNow = performance.now() / 1000.0;
@@ -16,6 +16,7 @@ class MousePickHandler {
16
16
  this._clicks = 0;
17
17
  this._timeout = null;
18
18
  this._lastPickedEntityId = null;
19
+ this._lastClickedWorldPos = null;
19
20
 
20
21
  let leftDown = false;
21
22
  let rightDown = false;
@@ -165,11 +166,16 @@ class MousePickHandler {
165
166
  if (pickResult && pickResult.worldPos) {
166
167
  pivotController.setPivotPos(pickResult.worldPos);
167
168
  pivotController.startPivot();
169
+ this._lastClickedWorldPos = pickResult.worldPos.slice();
168
170
  } else {
169
171
  if (configs.smartPivot) {
170
172
  pivotController.setCanvasPivotPos(states.pointerCanvasPos);
171
173
  } else {
172
- pivotController.setPivotPos(scene.camera.look);
174
+ if (this._lastClickedWorldPos) {
175
+ pivotController.setPivotPos(this._lastClickedWorldPos);
176
+ } else {
177
+ pivotController.setPivotPos(scene.camera.look);
178
+ }
173
179
  }
174
180
  pivotController.startPivot();
175
181
  }
@@ -937,6 +937,30 @@ class Input extends Component {
937
937
  */
938
938
  this.KEY_SPACE = 32;
939
939
 
940
+ /**
941
+ * Code for the left mouse button.
942
+ * @property MOUSE_LEFT_BUTTON
943
+ * @final
944
+ * @type {Number}
945
+ */
946
+ this.MOUSE_LEFT_BUTTON = 260;
947
+
948
+ /**
949
+ * Code for the middle mouse button.
950
+ * @property MOUSE_MIDDLE_BUTTON
951
+ * @final
952
+ * @type {Number}
953
+ */
954
+ this.MOUSE_MIDDLE_BUTTON = 261;
955
+
956
+ /**
957
+ * Code for the right mouse button.
958
+ * @property MOUSE_RIGHT_BUTTON
959
+ * @final
960
+ * @type {Number}
961
+ */
962
+ this.MOUSE_RIGHT_BUTTON = 262;
963
+
940
964
  /**
941
965
  * The canvas element that mouse and keyboards are bound to.
942
966
  *
@@ -334,7 +334,7 @@ export class VBOInstancingTrianglesLayer {
334
334
  state.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(geometry.indices), geometry.indices.length, 1, gl.STATIC_DRAW);
335
335
  state.numIndices = geometry.indices.length;
336
336
  }
337
- if (geometry.primitive === "triangles" || geometry.primitive === "solid" || geometry.primitive === "surface") {
337
+ if (geometry.edgeIndices && geometry.edgeIndices.length > 0) {
338
338
  state.edgeIndicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(geometry.edgeIndices), geometry.edgeIndices.length, 1, gl.STATIC_DRAW);
339
339
  }
340
340
 
@@ -116,7 +116,7 @@ export declare class StoreyViewsPlugin extends Plugin {
116
116
  * @param {Number[]} worldPos 3D World-space position.
117
117
  * @returns {String} ID of the storey containing the position, or null if the position falls outside all the storeys.
118
118
  */
119
- getStoreyContainingWorldPos(worldPos: number[]): string;
119
+ getStoreyContainingWorldPos(worldPos: number[], modelId: null | string): string;
120
120
 
121
121
  /**
122
122
  * Gets the ID of the storey which's bounding box contains the y point of the world position
@@ -124,7 +124,7 @@ export declare class StoreyViewsPlugin extends Plugin {
124
124
  * @param {Number[]} worldPos 3D World-space position.
125
125
  * @returns {String} ID of the storey containing the position, or null if the position falls outside all the storeys.
126
126
  */
127
- getStoreyInVerticalRange(worldPos: number[]): string;
127
+ getStoreyInVerticalRange(worldPos: number[], modelId: null | string): string;
128
128
 
129
129
 
130
130
  /**
@@ -133,7 +133,7 @@ export declare class StoreyViewsPlugin extends Plugin {
133
133
  * @param {Number[]} worldPos 3D World-space position.
134
134
  * @returns {String} ID of the lowest/highest story or null.
135
135
  */
136
- isPositionAboveOrBelowBuilding(worldPos: number[]): string;
136
+ isPositionAboveOrBelowBuilding(worldPos: number[], modelId: null | string): string;
137
137
 
138
138
  /**
139
139
  * Converts a 3D World-space position to a 2D position within a StoreyMap image.