@xeokit/xeokit-sdk 2.4.2-beta-13 → 2.4.2-beta-15

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.
@@ -6649,6 +6649,8 @@ const kdTreeDimLength$1 = new Float32Array(3);
6649
6649
  * Automatically indexes a {@link Viewer}'s {@link Entity}s in a 3D k-d tree
6650
6650
  * to support fast collision detection with 3D World-space axis-aligned boundaries (AABBs) and frustums.
6651
6651
  *
6652
+ * See {@link MarqueePicker} for usage example.
6653
+ *
6652
6654
  * An ObjectsKdTree3 is configured with a Viewer, and will then automatically
6653
6655
  * keep itself populated with k-d nodes that contain the Viewer's Entitys.
6654
6656
  *
@@ -8577,23 +8579,90 @@ function frustumIntersectsAABB3(frustum, aabb) {
8577
8579
 
8578
8580
  /**
8579
8581
  * Picks a {@link Viewer}'s {@link Entity}s with a canvas-space 2D marquee box.
8582
+ *
8583
+ * [<img src="https://xeokit.github.io/xeokit-sdk/assets/images/MarqueeSelect.gif">](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_select)
8584
+ *
8585
+ * * [[Example 1: Select Objects with Marquee](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_select)]
8586
+ * * [[Example 2: View-Fit Objects with Marquee](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_viewFit)]
8587
+ *
8588
+ * # Usage
8589
+ *
8590
+ * In the example below, we
8591
+ *
8592
+ * 1. Create a {@link Viewer}, arrange the {@link Camera}
8593
+ * 2. Use an {@link XKTLoaderPlugin} to load a BIM model,
8594
+ * 3. Create a {@link ObjectsKdTree3} to automatically index the `Viewer's` {@link Entity}s for fast spatial lookup,
8595
+ * 4. Create a `MarqueePicker` to pick {@link Entity}s in the {@link Viewer}, using the {@link ObjectsKdTree3} to accelerate picking
8596
+ * 5. Create a {@link MarqueePickerMouseControl} to perform the marquee-picking with the `MarqueePicker`, using mouse input to draw the marquee box on the `Viewer's` canvas.
8597
+ *
8598
+ * When the {@link MarqueePickerMouseControl} is active:
8599
+ *
8600
+ * * Long-click, drag and release on the canvas to define a marque box that picks {@link Entity}s.
8601
+ * * Drag left-to-right to pick {@link Entity}s that intersect the box.
8602
+ * * Drag right-to-left to pick {@link Entity}s that are fully inside the box.
8603
+ * * On release, the `MarqueePicker` will fire a "picked" event with IDs of the picked {@link Entity}s, if any.
8604
+ * * Handling that event, we mark the {@link Entity}s as selected.
8605
+ * * Hold down CTRL to multi-pick.
8606
+ *
8607
+ * ````javascript
8608
+ * import {
8609
+ * Viewer,
8610
+ * XKTLoaderPlugin,
8611
+ * ObjectsKdTree3,
8612
+ * MarqueePicker,
8613
+ * MarqueePickerMouseControl
8614
+ * } from "xeokit-sdk.es.js";
8615
+ *
8616
+ * // 1
8617
+ *
8618
+ * const viewer = new Viewer({
8619
+ * canvasId: "myCanvas"
8620
+ * });
8621
+ *
8622
+ * viewer.scene.camera.eye = [14.9, 14.3, 5.4];
8623
+ * viewer.scene.camera.look = [6.5, 8.3, -4.1];
8624
+ * viewer.scene.camera.up = [-0.28, 0.9, -0.3];
8625
+ *
8626
+ * // 2
8627
+ *
8628
+ * const xktLoader = new XKTLoaderPlugin(viewer);
8629
+ *
8630
+ * const sceneModel = xktLoader.load({
8631
+ * id: "myModel",
8632
+ * src: "../../assets/models/xkt/v8/ifc/HolterTower.ifc.xkt"
8633
+ * });
8634
+ *
8635
+ * // 3
8636
+ *
8637
+ * const objectsKdTree3 = new ObjectsKdTree3({viewer});
8638
+ *
8639
+ * // 4
8640
+ *
8641
+ * const marqueePicker = new MarqueePicker({viewer, objectsKdTree3});
8642
+ *
8643
+ * // 5
8644
+ *
8645
+ * const marqueePickerMouseControl = new MarqueePickerMouseControl({marqueePicker});
8646
+ *
8647
+ * marqueePicker.on("clear", () => {
8648
+ * viewer.scene.setObjectsSelected(viewer.scene.selectedObjectIds, false);
8649
+ * });
8650
+ *
8651
+ * marqueePicker.on("picked", (objectIds) => {
8652
+ * viewer.scene.setObjectsSelected(objectIds, true);
8653
+ * });
8654
+ *
8655
+ * marqueePickerMouseControl.setActive(true);
8656
+ * ````
8657
+ *
8658
+ * # Design Notes
8659
+ *
8660
+ * * The {@link ObjectsKdTree3} can be shared with any other components that want to use it to spatially search for {@link Entity}s.
8661
+ * * The {@link MarqueePickerMouseControl} can be replaced with other types of controllers (i.e. touch), or used alongside them.
8662
+ * * The `MarqueePicker` has no input handlers of its own, and provides an API through which to programmatically control marquee picking. By firing the "picked" events, `MarqueePicker` implements the *Blackboard Pattern*.
8580
8663
  */
8581
8664
  class MarqueePicker extends Component {
8582
8665
 
8583
- /**
8584
- * Pick mode that picks {@link Entity}s that intersect the marquee box.
8585
- *
8586
- * @type {number}
8587
- */
8588
- static PICK_MODE_INTERSECTS = 0;
8589
-
8590
- /**
8591
- * Pick mode that picks {@link Entity}s that are completely inside the marquee box.
8592
- *
8593
- * @type {number}
8594
- */
8595
- static PICK_MODE_INSIDE = 1;
8596
-
8597
8666
  /**
8598
8667
  * Creates a MarqueePicker.
8599
8668
  *
@@ -8843,9 +8912,25 @@ class MarqueePicker extends Component {
8843
8912
  }
8844
8913
  }
8845
8914
 
8915
+ /**
8916
+ * Pick mode that picks {@link Entity}s that intersect the marquee box.
8917
+ *
8918
+ * @type {number}
8919
+ */
8920
+ MarqueePicker.PICK_MODE_INTERSECTS = 0;
8921
+
8922
+ /**
8923
+ * Pick mode that picks {@link Entity}s that are completely inside the marquee box.
8924
+ *
8925
+ * @type {number}
8926
+ */
8927
+ MarqueePicker.PICK_MODE_INSIDE = 1;
8928
+
8846
8929
  /**
8847
8930
  * Controls a {@link MarqueePicker} with mouse input.
8848
8931
  *
8932
+ * See {@link MarqueePicker} for usage example.
8933
+ *
8849
8934
  * When the MarqueePickerMouseControl is active:
8850
8935
  *
8851
8936
  * * Long-click, drag and release on the canvas to define a marque box that picks {@link Entity}s.
@@ -20014,23 +20099,31 @@ class Input extends Component {
20014
20099
  }
20015
20100
  });
20016
20101
 
20102
+ const tickifedMouseMoveFn = this.scene.tickify(
20103
+ () => this.fire("mousemove", this.mouseCanvasPos, true)
20104
+ );
20105
+
20017
20106
  this.element.addEventListener("mousemove", this._mouseMoveListener = (e) => {
20018
20107
  if (!this.enabled) {
20019
20108
  return;
20020
20109
  }
20021
20110
  this._getMouseCanvasPos(e);
20022
- this.fire("mousemove", this.mouseCanvasPos, true);
20111
+ tickifedMouseMoveFn();
20023
20112
  if (this.mouseover) {
20024
20113
  e.preventDefault();
20025
20114
  }
20026
20115
  });
20027
20116
 
20117
+ const tickifiedMouseWheelFn = this.scene.tickify(
20118
+ (delta) => { this.fire("mousewheel", delta, true); }
20119
+ );
20120
+
20028
20121
  this.element.addEventListener("wheel", this._mouseWheelListener = (e, d) => {
20029
20122
  if (!this.enabled) {
20030
20123
  return;
20031
20124
  }
20032
20125
  const delta = Math.max(-1, Math.min(1, -e.deltaY * 40));
20033
- this.fire("mousewheel", delta, true);
20126
+ tickifiedMouseWheelFn(delta);
20034
20127
  }, {passive: true});
20035
20128
 
20036
20129
  // mouseclicked
@@ -27854,6 +27947,11 @@ class Scene extends Component {
27854
27947
  throw "Mandatory config expected: valid canvasId or canvasElement";
27855
27948
  }
27856
27949
 
27950
+ /**
27951
+ * @type {{[key: string]: {wrapperFunc: Function, tickSubId: string}}}
27952
+ */
27953
+ this._tickifiedFunctions = {};
27954
+
27857
27955
  const transparent = (!!cfg.transparent);
27858
27956
  const alphaDepthMask = (!!cfg.alphaDepthMask);
27859
27957
 
@@ -30016,6 +30114,63 @@ class Scene extends Component {
30016
30114
  return changed;
30017
30115
  }
30018
30116
 
30117
+ /**
30118
+ * This method will "tickify" the provided `cb` function.
30119
+ *
30120
+ * This means, the function will be wrapped so:
30121
+ *
30122
+ * - it runs time-aligned to scene ticks
30123
+ * - it runs maximum once per scene-tick
30124
+ *
30125
+ * @param {Function} cb The function to tickify
30126
+ * @returns {Function)}
30127
+ */
30128
+ tickify(cb) {
30129
+ const cbString = cb.toString();
30130
+
30131
+ /**
30132
+ * Check if the function is already tickified, and if so return the cached one.
30133
+ */
30134
+ if (cbString in this._tickifiedFunctions) {
30135
+ return this._tickifiedFunctions[cbString].wrapperFunc;
30136
+ }
30137
+
30138
+ let alreadyRun = 0;
30139
+ let needToRun = 0;
30140
+
30141
+ let lastArgs;
30142
+
30143
+ /**
30144
+ * The provided `cb` function is replaced with a "set-dirty" function
30145
+ *
30146
+ * @type {Function}
30147
+ */
30148
+ const wrapperFunc = function (...args) {
30149
+ lastArgs = args;
30150
+ needToRun++;
30151
+ };
30152
+
30153
+ /**
30154
+ * An each scene tick, if the "dirty-flag" is set, run the `cb` function.
30155
+ *
30156
+ * This will make it run time-aligned to the scene tick.
30157
+ */
30158
+ const tickSubId = this.on("tick", () => {
30159
+ const tmp = needToRun;
30160
+ if (tmp > alreadyRun) {
30161
+ alreadyRun = tmp;
30162
+ cb(...lastArgs);
30163
+ }
30164
+ });
30165
+
30166
+ /**
30167
+ * And, store the list of subscribers.
30168
+ */
30169
+ this._tickifiedFunctions[cbString] = { tickSubId, wrapperFunc };
30170
+
30171
+ return wrapperFunc;
30172
+ }
30173
+
30019
30174
  /**
30020
30175
  * Destroys this Scene.
30021
30176
  */
@@ -87282,14 +87437,13 @@ class PickController {
87282
87437
 
87283
87438
  this._lastPickedEntityId = null;
87284
87439
 
87285
- this._needFireEvents = false;
87440
+ this._needFireEvents = 0;
87286
87441
  }
87287
87442
 
87288
87443
  /**
87289
87444
  * Immediately attempts a pick, if scheduled.
87290
87445
  */
87291
87446
  update() {
87292
-
87293
87447
  if (!this._configs.pointerEnabled) {
87294
87448
  return;
87295
87449
  }
@@ -87303,8 +87457,6 @@ class PickController {
87303
87457
  this.snappedOrPicked = false;
87304
87458
  this.hoveredSnappedOrSurfaceOff = false;
87305
87459
 
87306
- this._needFireEvents = false;
87307
-
87308
87460
  const hasHoverSurfaceSubs = this._cameraControl.hasSubs("hoverSurface");
87309
87461
 
87310
87462
  if (this.scheduleSnapOrPick) {
@@ -87317,7 +87469,7 @@ class PickController {
87317
87469
  if (snapPickResult && snapPickResult.snappedWorldPos) {
87318
87470
  this.snapPickResult = snapPickResult;
87319
87471
  this.snappedOrPicked = true;
87320
- this._needFireEvents = true;
87472
+ this._needFireEvents++;
87321
87473
  } else {
87322
87474
  this.schedulePickSurface = true; // Fallback
87323
87475
  this.snapPickResult = null;
@@ -87330,7 +87482,7 @@ class PickController {
87330
87482
  if (pickResultCanvasPos[0] === this.pickCursorPos[0] && pickResultCanvasPos[1] === this.pickCursorPos[1]) {
87331
87483
  this.picked = true;
87332
87484
  this.pickedSurface = true;
87333
- this._needFireEvents = hasHoverSurfaceSubs;
87485
+ this._needFireEvents += hasHoverSurfaceSubs ? 1 : 0;
87334
87486
  this.schedulePickEntity = false;
87335
87487
  this.schedulePickSurface = false;
87336
87488
  if (this.scheduleSnapOrPick) {
@@ -87350,7 +87502,6 @@ class PickController {
87350
87502
  if (pickResultCanvasPos[0] === this.pickCursorPos[0] && pickResultCanvasPos[1] === this.pickCursorPos[1]) {
87351
87503
  this.picked = true;
87352
87504
  this.pickedSurface = false;
87353
- this._needFireEvents = false;
87354
87505
  this.schedulePickEntity = false;
87355
87506
  this.schedulePickSurface = false;
87356
87507
  return;
@@ -87371,10 +87522,10 @@ class PickController {
87371
87522
  } else {
87372
87523
  this.pickedSurface = true;
87373
87524
  }
87374
- this._needFireEvents = true;
87525
+ this._needFireEvents++;
87375
87526
  } else if (this.scheduleSnapOrPick) {
87376
87527
  this.hoveredSnappedOrSurfaceOff = true;
87377
- this._needFireEvents = true;
87528
+ this._needFireEvents++;
87378
87529
  }
87379
87530
 
87380
87531
  } else { // schedulePickEntity == true
@@ -87386,7 +87537,7 @@ class PickController {
87386
87537
  if (this.pickResult) {
87387
87538
  this.picked = true;
87388
87539
  this.pickedSurface = false;
87389
- this._needFireEvents = true;
87540
+ this._needFireEvents++;
87390
87541
  }
87391
87542
  }
87392
87543
 
@@ -87397,7 +87548,7 @@ class PickController {
87397
87548
 
87398
87549
  fireEvents() {
87399
87550
 
87400
- if (!this._needFireEvents) {
87551
+ if (this._needFireEvents == 0) {
87401
87552
  return;
87402
87553
  }
87403
87554
 
@@ -87465,10 +87616,7 @@ class PickController {
87465
87616
 
87466
87617
  this.pickResult = null;
87467
87618
 
87468
- this._needFireEvents = false;
87469
- }
87470
-
87471
- destroy() {
87619
+ this._needFireEvents = 0;
87472
87620
  }
87473
87621
  }
87474
87622
 
@@ -87964,75 +88112,78 @@ class MousePickHandler {
87964
88112
  }
87965
88113
  };
87966
88114
 
87967
- canvas.addEventListener("mousemove", this._canvasMouseMoveHandler = (e) => {
88115
+ const tickifiedMouseMoveFn = scene.tickify (
88116
+ this._canvasMouseMoveHandler = (e) => {
88117
+ if (!(configs.active && configs.pointerEnabled)) {
88118
+ return;
88119
+ }
87968
88120
 
87969
- if (!(configs.active && configs.pointerEnabled)) {
87970
- return;
87971
- }
88121
+ if (leftDown || rightDown) {
88122
+ return;
88123
+ }
87972
88124
 
87973
- if (leftDown || rightDown) {
87974
- return;
87975
- }
88125
+ const hoverSubs = cameraControl.hasSubs("hover");
88126
+ const hoverEnterSubs = cameraControl.hasSubs("hoverEnter");
88127
+ const hoverOutSubs = cameraControl.hasSubs("hoverOut");
88128
+ const hoverOffSubs = cameraControl.hasSubs("hoverOff");
88129
+ const hoverSurfaceSubs = cameraControl.hasSubs("hoverSurface");
88130
+ const hoverSnapOrSurfaceSubs = cameraControl.hasSubs("hoverSnapOrSurface");
87976
88131
 
87977
- const hoverSubs = cameraControl.hasSubs("hover");
87978
- const hoverEnterSubs = cameraControl.hasSubs("hoverEnter");
87979
- const hoverOutSubs = cameraControl.hasSubs("hoverOut");
87980
- const hoverOffSubs = cameraControl.hasSubs("hoverOff");
87981
- const hoverSurfaceSubs = cameraControl.hasSubs("hoverSurface");
87982
- const hoverSnapOrSurfaceSubs = cameraControl.hasSubs("hoverSnapOrSurface");
88132
+ if (hoverSubs || hoverEnterSubs || hoverOutSubs || hoverOffSubs || hoverSurfaceSubs || hoverSnapOrSurfaceSubs) {
87983
88133
 
87984
- if (hoverSubs || hoverEnterSubs || hoverOutSubs || hoverOffSubs || hoverSurfaceSubs || hoverSnapOrSurfaceSubs) {
88134
+ pickController.pickCursorPos = states.pointerCanvasPos;
88135
+ pickController.schedulePickEntity = true;
88136
+ pickController.schedulePickSurface = hoverSurfaceSubs;
88137
+ pickController.scheduleSnapOrPick = hoverSnapOrSurfaceSubs;
87985
88138
 
87986
- pickController.pickCursorPos = states.pointerCanvasPos;
87987
- pickController.schedulePickEntity = true;
87988
- pickController.schedulePickSurface = hoverSurfaceSubs;
87989
- pickController.scheduleSnapOrPick = hoverSnapOrSurfaceSubs;
88139
+ pickController.update();
87990
88140
 
87991
- pickController.update();
88141
+ if (pickController.pickResult) {
87992
88142
 
87993
- if (pickController.pickResult) {
88143
+ if (pickController.pickResult.entity) {
88144
+ const pickedEntityId = pickController.pickResult.entity.id;
87994
88145
 
87995
- if (pickController.pickResult.entity) {
87996
- const pickedEntityId = pickController.pickResult.entity.id;
88146
+ if (this._lastPickedEntityId !== pickedEntityId) {
87997
88147
 
87998
- if (this._lastPickedEntityId !== pickedEntityId) {
88148
+ if (this._lastPickedEntityId !== undefined) {
87999
88149
 
88000
- if (this._lastPickedEntityId !== undefined) {
88150
+ cameraControl.fire("hoverOut", { // Hovered off an entity
88151
+ entity: scene.objects[this._lastPickedEntityId]
88152
+ }, true);
88153
+ }
88001
88154
 
88002
- cameraControl.fire("hoverOut", { // Hovered off an entity
88003
- entity: scene.objects[this._lastPickedEntityId]
88004
- }, true);
88155
+ cameraControl.fire("hoverEnter", pickController.pickResult, true); // Hovering over a new entity
88156
+
88157
+ this._lastPickedEntityId = pickedEntityId;
88005
88158
  }
88159
+ }
88006
88160
 
88007
- cameraControl.fire("hoverEnter", pickController.pickResult, true); // Hovering over a new entity
88161
+ cameraControl.fire("hover", pickController.pickResult, true);
88008
88162
 
88009
- this._lastPickedEntityId = pickedEntityId;
88163
+ if (pickController.pickResult.worldPos || pickController.pickResult.snappedWorldPos) { // Hovering the surface of an entity
88164
+ cameraControl.fire("hoverSurface", pickController.pickResult, true);
88010
88165
  }
88011
- }
88012
88166
 
88013
- cameraControl.fire("hover", pickController.pickResult, true);
88167
+ } else {
88014
88168
 
88015
- if (pickController.pickResult.worldPos || pickController.pickResult.snappedWorldPos) { // Hovering the surface of an entity
88016
- cameraControl.fire("hoverSurface", pickController.pickResult, true);
88017
- }
88169
+ if (this._lastPickedEntityId !== undefined) {
88018
88170
 
88019
- } else {
88171
+ cameraControl.fire("hoverOut", { // Hovered off an entity
88172
+ entity: scene.objects[this._lastPickedEntityId]
88173
+ }, true);
88020
88174
 
88021
- if (this._lastPickedEntityId !== undefined) {
88175
+ this._lastPickedEntityId = undefined;
88176
+ }
88022
88177
 
88023
- cameraControl.fire("hoverOut", { // Hovered off an entity
88024
- entity: scene.objects[this._lastPickedEntityId]
88178
+ cameraControl.fire("hoverOff", { // Not hovering on any entity
88179
+ canvasPos: pickController.pickCursorPos
88025
88180
  }, true);
88026
-
88027
- this._lastPickedEntityId = undefined;
88028
88181
  }
88029
-
88030
- cameraControl.fire("hoverOff", { // Not hovering on any entity
88031
- canvasPos: pickController.pickCursorPos
88032
- }, true);
88033
88182
  }
88034
88183
  }
88035
- });
88184
+ );
88185
+
88186
+ canvas.addEventListener("mousemove", tickifiedMouseMoveFn);
88036
88187
 
88037
88188
  canvas.addEventListener('mousedown', this._canvasMouseDownHandler = (e) => {
88038
88189