@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.
@@ -6653,6 +6653,8 @@ const kdTreeDimLength$1 = new Float32Array(3);
6653
6653
  * Automatically indexes a {@link Viewer}'s {@link Entity}s in a 3D k-d tree
6654
6654
  * to support fast collision detection with 3D World-space axis-aligned boundaries (AABBs) and frustums.
6655
6655
  *
6656
+ * See {@link MarqueePicker} for usage example.
6657
+ *
6656
6658
  * An ObjectsKdTree3 is configured with a Viewer, and will then automatically
6657
6659
  * keep itself populated with k-d nodes that contain the Viewer's Entitys.
6658
6660
  *
@@ -8581,23 +8583,90 @@ function frustumIntersectsAABB3(frustum, aabb) {
8581
8583
 
8582
8584
  /**
8583
8585
  * Picks a {@link Viewer}'s {@link Entity}s with a canvas-space 2D marquee box.
8586
+ *
8587
+ * [<img src="https://xeokit.github.io/xeokit-sdk/assets/images/MarqueeSelect.gif">](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_select)
8588
+ *
8589
+ * * [[Example 1: Select Objects with Marquee](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_select)]
8590
+ * * [[Example 2: View-Fit Objects with Marquee](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_viewFit)]
8591
+ *
8592
+ * # Usage
8593
+ *
8594
+ * In the example below, we
8595
+ *
8596
+ * 1. Create a {@link Viewer}, arrange the {@link Camera}
8597
+ * 2. Use an {@link XKTLoaderPlugin} to load a BIM model,
8598
+ * 3. Create a {@link ObjectsKdTree3} to automatically index the `Viewer's` {@link Entity}s for fast spatial lookup,
8599
+ * 4. Create a `MarqueePicker` to pick {@link Entity}s in the {@link Viewer}, using the {@link ObjectsKdTree3} to accelerate picking
8600
+ * 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.
8601
+ *
8602
+ * When the {@link MarqueePickerMouseControl} is active:
8603
+ *
8604
+ * * Long-click, drag and release on the canvas to define a marque box that picks {@link Entity}s.
8605
+ * * Drag left-to-right to pick {@link Entity}s that intersect the box.
8606
+ * * Drag right-to-left to pick {@link Entity}s that are fully inside the box.
8607
+ * * On release, the `MarqueePicker` will fire a "picked" event with IDs of the picked {@link Entity}s, if any.
8608
+ * * Handling that event, we mark the {@link Entity}s as selected.
8609
+ * * Hold down CTRL to multi-pick.
8610
+ *
8611
+ * ````javascript
8612
+ * import {
8613
+ * Viewer,
8614
+ * XKTLoaderPlugin,
8615
+ * ObjectsKdTree3,
8616
+ * MarqueePicker,
8617
+ * MarqueePickerMouseControl
8618
+ * } from "xeokit-sdk.es.js";
8619
+ *
8620
+ * // 1
8621
+ *
8622
+ * const viewer = new Viewer({
8623
+ * canvasId: "myCanvas"
8624
+ * });
8625
+ *
8626
+ * viewer.scene.camera.eye = [14.9, 14.3, 5.4];
8627
+ * viewer.scene.camera.look = [6.5, 8.3, -4.1];
8628
+ * viewer.scene.camera.up = [-0.28, 0.9, -0.3];
8629
+ *
8630
+ * // 2
8631
+ *
8632
+ * const xktLoader = new XKTLoaderPlugin(viewer);
8633
+ *
8634
+ * const sceneModel = xktLoader.load({
8635
+ * id: "myModel",
8636
+ * src: "../../assets/models/xkt/v8/ifc/HolterTower.ifc.xkt"
8637
+ * });
8638
+ *
8639
+ * // 3
8640
+ *
8641
+ * const objectsKdTree3 = new ObjectsKdTree3({viewer});
8642
+ *
8643
+ * // 4
8644
+ *
8645
+ * const marqueePicker = new MarqueePicker({viewer, objectsKdTree3});
8646
+ *
8647
+ * // 5
8648
+ *
8649
+ * const marqueePickerMouseControl = new MarqueePickerMouseControl({marqueePicker});
8650
+ *
8651
+ * marqueePicker.on("clear", () => {
8652
+ * viewer.scene.setObjectsSelected(viewer.scene.selectedObjectIds, false);
8653
+ * });
8654
+ *
8655
+ * marqueePicker.on("picked", (objectIds) => {
8656
+ * viewer.scene.setObjectsSelected(objectIds, true);
8657
+ * });
8658
+ *
8659
+ * marqueePickerMouseControl.setActive(true);
8660
+ * ````
8661
+ *
8662
+ * # Design Notes
8663
+ *
8664
+ * * The {@link ObjectsKdTree3} can be shared with any other components that want to use it to spatially search for {@link Entity}s.
8665
+ * * The {@link MarqueePickerMouseControl} can be replaced with other types of controllers (i.e. touch), or used alongside them.
8666
+ * * 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*.
8584
8667
  */
8585
8668
  class MarqueePicker extends Component {
8586
8669
 
8587
- /**
8588
- * Pick mode that picks {@link Entity}s that intersect the marquee box.
8589
- *
8590
- * @type {number}
8591
- */
8592
- static PICK_MODE_INTERSECTS = 0;
8593
-
8594
- /**
8595
- * Pick mode that picks {@link Entity}s that are completely inside the marquee box.
8596
- *
8597
- * @type {number}
8598
- */
8599
- static PICK_MODE_INSIDE = 1;
8600
-
8601
8670
  /**
8602
8671
  * Creates a MarqueePicker.
8603
8672
  *
@@ -8847,9 +8916,25 @@ class MarqueePicker extends Component {
8847
8916
  }
8848
8917
  }
8849
8918
 
8919
+ /**
8920
+ * Pick mode that picks {@link Entity}s that intersect the marquee box.
8921
+ *
8922
+ * @type {number}
8923
+ */
8924
+ MarqueePicker.PICK_MODE_INTERSECTS = 0;
8925
+
8926
+ /**
8927
+ * Pick mode that picks {@link Entity}s that are completely inside the marquee box.
8928
+ *
8929
+ * @type {number}
8930
+ */
8931
+ MarqueePicker.PICK_MODE_INSIDE = 1;
8932
+
8850
8933
  /**
8851
8934
  * Controls a {@link MarqueePicker} with mouse input.
8852
8935
  *
8936
+ * See {@link MarqueePicker} for usage example.
8937
+ *
8853
8938
  * When the MarqueePickerMouseControl is active:
8854
8939
  *
8855
8940
  * * Long-click, drag and release on the canvas to define a marque box that picks {@link Entity}s.
@@ -20018,23 +20103,31 @@ class Input extends Component {
20018
20103
  }
20019
20104
  });
20020
20105
 
20106
+ const tickifedMouseMoveFn = this.scene.tickify(
20107
+ () => this.fire("mousemove", this.mouseCanvasPos, true)
20108
+ );
20109
+
20021
20110
  this.element.addEventListener("mousemove", this._mouseMoveListener = (e) => {
20022
20111
  if (!this.enabled) {
20023
20112
  return;
20024
20113
  }
20025
20114
  this._getMouseCanvasPos(e);
20026
- this.fire("mousemove", this.mouseCanvasPos, true);
20115
+ tickifedMouseMoveFn();
20027
20116
  if (this.mouseover) {
20028
20117
  e.preventDefault();
20029
20118
  }
20030
20119
  });
20031
20120
 
20121
+ const tickifiedMouseWheelFn = this.scene.tickify(
20122
+ (delta) => { this.fire("mousewheel", delta, true); }
20123
+ );
20124
+
20032
20125
  this.element.addEventListener("wheel", this._mouseWheelListener = (e, d) => {
20033
20126
  if (!this.enabled) {
20034
20127
  return;
20035
20128
  }
20036
20129
  const delta = Math.max(-1, Math.min(1, -e.deltaY * 40));
20037
- this.fire("mousewheel", delta, true);
20130
+ tickifiedMouseWheelFn(delta);
20038
20131
  }, {passive: true});
20039
20132
 
20040
20133
  // mouseclicked
@@ -27858,6 +27951,11 @@ class Scene extends Component {
27858
27951
  throw "Mandatory config expected: valid canvasId or canvasElement";
27859
27952
  }
27860
27953
 
27954
+ /**
27955
+ * @type {{[key: string]: {wrapperFunc: Function, tickSubId: string}}}
27956
+ */
27957
+ this._tickifiedFunctions = {};
27958
+
27861
27959
  const transparent = (!!cfg.transparent);
27862
27960
  const alphaDepthMask = (!!cfg.alphaDepthMask);
27863
27961
 
@@ -30020,6 +30118,63 @@ class Scene extends Component {
30020
30118
  return changed;
30021
30119
  }
30022
30120
 
30121
+ /**
30122
+ * This method will "tickify" the provided `cb` function.
30123
+ *
30124
+ * This means, the function will be wrapped so:
30125
+ *
30126
+ * - it runs time-aligned to scene ticks
30127
+ * - it runs maximum once per scene-tick
30128
+ *
30129
+ * @param {Function} cb The function to tickify
30130
+ * @returns {Function)}
30131
+ */
30132
+ tickify(cb) {
30133
+ const cbString = cb.toString();
30134
+
30135
+ /**
30136
+ * Check if the function is already tickified, and if so return the cached one.
30137
+ */
30138
+ if (cbString in this._tickifiedFunctions) {
30139
+ return this._tickifiedFunctions[cbString].wrapperFunc;
30140
+ }
30141
+
30142
+ let alreadyRun = 0;
30143
+ let needToRun = 0;
30144
+
30145
+ let lastArgs;
30146
+
30147
+ /**
30148
+ * The provided `cb` function is replaced with a "set-dirty" function
30149
+ *
30150
+ * @type {Function}
30151
+ */
30152
+ const wrapperFunc = function (...args) {
30153
+ lastArgs = args;
30154
+ needToRun++;
30155
+ };
30156
+
30157
+ /**
30158
+ * An each scene tick, if the "dirty-flag" is set, run the `cb` function.
30159
+ *
30160
+ * This will make it run time-aligned to the scene tick.
30161
+ */
30162
+ const tickSubId = this.on("tick", () => {
30163
+ const tmp = needToRun;
30164
+ if (tmp > alreadyRun) {
30165
+ alreadyRun = tmp;
30166
+ cb(...lastArgs);
30167
+ }
30168
+ });
30169
+
30170
+ /**
30171
+ * And, store the list of subscribers.
30172
+ */
30173
+ this._tickifiedFunctions[cbString] = { tickSubId, wrapperFunc };
30174
+
30175
+ return wrapperFunc;
30176
+ }
30177
+
30023
30178
  /**
30024
30179
  * Destroys this Scene.
30025
30180
  */
@@ -87286,14 +87441,13 @@ class PickController {
87286
87441
 
87287
87442
  this._lastPickedEntityId = null;
87288
87443
 
87289
- this._needFireEvents = false;
87444
+ this._needFireEvents = 0;
87290
87445
  }
87291
87446
 
87292
87447
  /**
87293
87448
  * Immediately attempts a pick, if scheduled.
87294
87449
  */
87295
87450
  update() {
87296
-
87297
87451
  if (!this._configs.pointerEnabled) {
87298
87452
  return;
87299
87453
  }
@@ -87307,8 +87461,6 @@ class PickController {
87307
87461
  this.snappedOrPicked = false;
87308
87462
  this.hoveredSnappedOrSurfaceOff = false;
87309
87463
 
87310
- this._needFireEvents = false;
87311
-
87312
87464
  const hasHoverSurfaceSubs = this._cameraControl.hasSubs("hoverSurface");
87313
87465
 
87314
87466
  if (this.scheduleSnapOrPick) {
@@ -87321,7 +87473,7 @@ class PickController {
87321
87473
  if (snapPickResult && snapPickResult.snappedWorldPos) {
87322
87474
  this.snapPickResult = snapPickResult;
87323
87475
  this.snappedOrPicked = true;
87324
- this._needFireEvents = true;
87476
+ this._needFireEvents++;
87325
87477
  } else {
87326
87478
  this.schedulePickSurface = true; // Fallback
87327
87479
  this.snapPickResult = null;
@@ -87334,7 +87486,7 @@ class PickController {
87334
87486
  if (pickResultCanvasPos[0] === this.pickCursorPos[0] && pickResultCanvasPos[1] === this.pickCursorPos[1]) {
87335
87487
  this.picked = true;
87336
87488
  this.pickedSurface = true;
87337
- this._needFireEvents = hasHoverSurfaceSubs;
87489
+ this._needFireEvents += hasHoverSurfaceSubs ? 1 : 0;
87338
87490
  this.schedulePickEntity = false;
87339
87491
  this.schedulePickSurface = false;
87340
87492
  if (this.scheduleSnapOrPick) {
@@ -87354,7 +87506,6 @@ class PickController {
87354
87506
  if (pickResultCanvasPos[0] === this.pickCursorPos[0] && pickResultCanvasPos[1] === this.pickCursorPos[1]) {
87355
87507
  this.picked = true;
87356
87508
  this.pickedSurface = false;
87357
- this._needFireEvents = false;
87358
87509
  this.schedulePickEntity = false;
87359
87510
  this.schedulePickSurface = false;
87360
87511
  return;
@@ -87375,10 +87526,10 @@ class PickController {
87375
87526
  } else {
87376
87527
  this.pickedSurface = true;
87377
87528
  }
87378
- this._needFireEvents = true;
87529
+ this._needFireEvents++;
87379
87530
  } else if (this.scheduleSnapOrPick) {
87380
87531
  this.hoveredSnappedOrSurfaceOff = true;
87381
- this._needFireEvents = true;
87532
+ this._needFireEvents++;
87382
87533
  }
87383
87534
 
87384
87535
  } else { // schedulePickEntity == true
@@ -87390,7 +87541,7 @@ class PickController {
87390
87541
  if (this.pickResult) {
87391
87542
  this.picked = true;
87392
87543
  this.pickedSurface = false;
87393
- this._needFireEvents = true;
87544
+ this._needFireEvents++;
87394
87545
  }
87395
87546
  }
87396
87547
 
@@ -87401,7 +87552,7 @@ class PickController {
87401
87552
 
87402
87553
  fireEvents() {
87403
87554
 
87404
- if (!this._needFireEvents) {
87555
+ if (this._needFireEvents == 0) {
87405
87556
  return;
87406
87557
  }
87407
87558
 
@@ -87469,10 +87620,7 @@ class PickController {
87469
87620
 
87470
87621
  this.pickResult = null;
87471
87622
 
87472
- this._needFireEvents = false;
87473
- }
87474
-
87475
- destroy() {
87623
+ this._needFireEvents = 0;
87476
87624
  }
87477
87625
  }
87478
87626
 
@@ -87968,75 +88116,78 @@ class MousePickHandler {
87968
88116
  }
87969
88117
  };
87970
88118
 
87971
- canvas.addEventListener("mousemove", this._canvasMouseMoveHandler = (e) => {
88119
+ const tickifiedMouseMoveFn = scene.tickify (
88120
+ this._canvasMouseMoveHandler = (e) => {
88121
+ if (!(configs.active && configs.pointerEnabled)) {
88122
+ return;
88123
+ }
87972
88124
 
87973
- if (!(configs.active && configs.pointerEnabled)) {
87974
- return;
87975
- }
88125
+ if (leftDown || rightDown) {
88126
+ return;
88127
+ }
87976
88128
 
87977
- if (leftDown || rightDown) {
87978
- return;
87979
- }
88129
+ const hoverSubs = cameraControl.hasSubs("hover");
88130
+ const hoverEnterSubs = cameraControl.hasSubs("hoverEnter");
88131
+ const hoverOutSubs = cameraControl.hasSubs("hoverOut");
88132
+ const hoverOffSubs = cameraControl.hasSubs("hoverOff");
88133
+ const hoverSurfaceSubs = cameraControl.hasSubs("hoverSurface");
88134
+ const hoverSnapOrSurfaceSubs = cameraControl.hasSubs("hoverSnapOrSurface");
87980
88135
 
87981
- const hoverSubs = cameraControl.hasSubs("hover");
87982
- const hoverEnterSubs = cameraControl.hasSubs("hoverEnter");
87983
- const hoverOutSubs = cameraControl.hasSubs("hoverOut");
87984
- const hoverOffSubs = cameraControl.hasSubs("hoverOff");
87985
- const hoverSurfaceSubs = cameraControl.hasSubs("hoverSurface");
87986
- const hoverSnapOrSurfaceSubs = cameraControl.hasSubs("hoverSnapOrSurface");
88136
+ if (hoverSubs || hoverEnterSubs || hoverOutSubs || hoverOffSubs || hoverSurfaceSubs || hoverSnapOrSurfaceSubs) {
87987
88137
 
87988
- if (hoverSubs || hoverEnterSubs || hoverOutSubs || hoverOffSubs || hoverSurfaceSubs || hoverSnapOrSurfaceSubs) {
88138
+ pickController.pickCursorPos = states.pointerCanvasPos;
88139
+ pickController.schedulePickEntity = true;
88140
+ pickController.schedulePickSurface = hoverSurfaceSubs;
88141
+ pickController.scheduleSnapOrPick = hoverSnapOrSurfaceSubs;
87989
88142
 
87990
- pickController.pickCursorPos = states.pointerCanvasPos;
87991
- pickController.schedulePickEntity = true;
87992
- pickController.schedulePickSurface = hoverSurfaceSubs;
87993
- pickController.scheduleSnapOrPick = hoverSnapOrSurfaceSubs;
88143
+ pickController.update();
87994
88144
 
87995
- pickController.update();
88145
+ if (pickController.pickResult) {
87996
88146
 
87997
- if (pickController.pickResult) {
88147
+ if (pickController.pickResult.entity) {
88148
+ const pickedEntityId = pickController.pickResult.entity.id;
87998
88149
 
87999
- if (pickController.pickResult.entity) {
88000
- const pickedEntityId = pickController.pickResult.entity.id;
88150
+ if (this._lastPickedEntityId !== pickedEntityId) {
88001
88151
 
88002
- if (this._lastPickedEntityId !== pickedEntityId) {
88152
+ if (this._lastPickedEntityId !== undefined) {
88003
88153
 
88004
- if (this._lastPickedEntityId !== undefined) {
88154
+ cameraControl.fire("hoverOut", { // Hovered off an entity
88155
+ entity: scene.objects[this._lastPickedEntityId]
88156
+ }, true);
88157
+ }
88005
88158
 
88006
- cameraControl.fire("hoverOut", { // Hovered off an entity
88007
- entity: scene.objects[this._lastPickedEntityId]
88008
- }, true);
88159
+ cameraControl.fire("hoverEnter", pickController.pickResult, true); // Hovering over a new entity
88160
+
88161
+ this._lastPickedEntityId = pickedEntityId;
88009
88162
  }
88163
+ }
88010
88164
 
88011
- cameraControl.fire("hoverEnter", pickController.pickResult, true); // Hovering over a new entity
88165
+ cameraControl.fire("hover", pickController.pickResult, true);
88012
88166
 
88013
- this._lastPickedEntityId = pickedEntityId;
88167
+ if (pickController.pickResult.worldPos || pickController.pickResult.snappedWorldPos) { // Hovering the surface of an entity
88168
+ cameraControl.fire("hoverSurface", pickController.pickResult, true);
88014
88169
  }
88015
- }
88016
88170
 
88017
- cameraControl.fire("hover", pickController.pickResult, true);
88171
+ } else {
88018
88172
 
88019
- if (pickController.pickResult.worldPos || pickController.pickResult.snappedWorldPos) { // Hovering the surface of an entity
88020
- cameraControl.fire("hoverSurface", pickController.pickResult, true);
88021
- }
88173
+ if (this._lastPickedEntityId !== undefined) {
88022
88174
 
88023
- } else {
88175
+ cameraControl.fire("hoverOut", { // Hovered off an entity
88176
+ entity: scene.objects[this._lastPickedEntityId]
88177
+ }, true);
88024
88178
 
88025
- if (this._lastPickedEntityId !== undefined) {
88179
+ this._lastPickedEntityId = undefined;
88180
+ }
88026
88181
 
88027
- cameraControl.fire("hoverOut", { // Hovered off an entity
88028
- entity: scene.objects[this._lastPickedEntityId]
88182
+ cameraControl.fire("hoverOff", { // Not hovering on any entity
88183
+ canvasPos: pickController.pickCursorPos
88029
88184
  }, true);
88030
-
88031
- this._lastPickedEntityId = undefined;
88032
88185
  }
88033
-
88034
- cameraControl.fire("hoverOff", { // Not hovering on any entity
88035
- canvasPos: pickController.pickCursorPos
88036
- }, true);
88037
88186
  }
88038
88187
  }
88039
- });
88188
+ );
88189
+
88190
+ canvas.addEventListener("mousemove", tickifiedMouseMoveFn);
88040
88191
 
88041
88192
  canvas.addEventListener('mousedown', this._canvasMouseDownHandler = (e) => {
88042
88193