@xeokit/xeokit-sdk 2.4.2-beta-14 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xeokit/xeokit-sdk",
3
- "version": "2.4.2-beta-14",
3
+ "version": "2.4.2-beta-15",
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",
@@ -4,6 +4,87 @@ import {Frustum, frustumIntersectsAABB3, setFrustum} from "../../viewer/scene/ma
4
4
 
5
5
  /**
6
6
  * Picks a {@link Viewer}'s {@link Entity}s with a canvas-space 2D marquee box.
7
+ *
8
+ * [<img src="https://xeokit.github.io/xeokit-sdk/assets/images/MarqueeSelect.gif">](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_select)
9
+ *
10
+ * * [[Example 1: Select Objects with Marquee](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_select)]
11
+ * * [[Example 2: View-Fit Objects with Marquee](https://xeokit.github.io/xeokit-sdk/examples/picking/#marqueePick_viewFit)]
12
+ *
13
+ * # Usage
14
+ *
15
+ * In the example below, we
16
+ *
17
+ * 1. Create a {@link Viewer}, arrange the {@link Camera}
18
+ * 2. Use an {@link XKTLoaderPlugin} to load a BIM model,
19
+ * 3. Create a {@link ObjectsKdTree3} to automatically index the `Viewer's` {@link Entity}s for fast spatial lookup,
20
+ * 4. Create a `MarqueePicker` to pick {@link Entity}s in the {@link Viewer}, using the {@link ObjectsKdTree3} to accelerate picking
21
+ * 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.
22
+ *
23
+ * When the {@link MarqueePickerMouseControl} is active:
24
+ *
25
+ * * Long-click, drag and release on the canvas to define a marque box that picks {@link Entity}s.
26
+ * * Drag left-to-right to pick {@link Entity}s that intersect the box.
27
+ * * Drag right-to-left to pick {@link Entity}s that are fully inside the box.
28
+ * * On release, the `MarqueePicker` will fire a "picked" event with IDs of the picked {@link Entity}s, if any.
29
+ * * Handling that event, we mark the {@link Entity}s as selected.
30
+ * * Hold down CTRL to multi-pick.
31
+ *
32
+ * ````javascript
33
+ * import {
34
+ * Viewer,
35
+ * XKTLoaderPlugin,
36
+ * ObjectsKdTree3,
37
+ * MarqueePicker,
38
+ * MarqueePickerMouseControl
39
+ * } from "xeokit-sdk.es.js";
40
+ *
41
+ * // 1
42
+ *
43
+ * const viewer = new Viewer({
44
+ * canvasId: "myCanvas"
45
+ * });
46
+ *
47
+ * viewer.scene.camera.eye = [14.9, 14.3, 5.4];
48
+ * viewer.scene.camera.look = [6.5, 8.3, -4.1];
49
+ * viewer.scene.camera.up = [-0.28, 0.9, -0.3];
50
+ *
51
+ * // 2
52
+ *
53
+ * const xktLoader = new XKTLoaderPlugin(viewer);
54
+ *
55
+ * const sceneModel = xktLoader.load({
56
+ * id: "myModel",
57
+ * src: "../../assets/models/xkt/v8/ifc/HolterTower.ifc.xkt"
58
+ * });
59
+ *
60
+ * // 3
61
+ *
62
+ * const objectsKdTree3 = new ObjectsKdTree3({viewer});
63
+ *
64
+ * // 4
65
+ *
66
+ * const marqueePicker = new MarqueePicker({viewer, objectsKdTree3});
67
+ *
68
+ * // 5
69
+ *
70
+ * const marqueePickerMouseControl = new MarqueePickerMouseControl({marqueePicker});
71
+ *
72
+ * marqueePicker.on("clear", () => {
73
+ * viewer.scene.setObjectsSelected(viewer.scene.selectedObjectIds, false);
74
+ * });
75
+ *
76
+ * marqueePicker.on("picked", (objectIds) => {
77
+ * viewer.scene.setObjectsSelected(objectIds, true);
78
+ * });
79
+ *
80
+ * marqueePickerMouseControl.setActive(true);
81
+ * ````
82
+ *
83
+ * # Design Notes
84
+ *
85
+ * * The {@link ObjectsKdTree3} can be shared with any other components that want to use it to spatially search for {@link Entity}s.
86
+ * * The {@link MarqueePickerMouseControl} can be replaced with other types of controllers (i.e. touch), or used alongside them.
87
+ * * 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*.
7
88
  */
8
89
  export class MarqueePicker extends Component {
9
90
 
@@ -4,6 +4,8 @@ import {MarqueePicker} from "./MarqueePicker";
4
4
  /**
5
5
  * Controls a {@link MarqueePicker} with mouse input.
6
6
  *
7
+ * See {@link MarqueePicker} for usage example.
8
+ *
7
9
  * When the MarqueePickerMouseControl is active:
8
10
  *
9
11
  * * Long-click, drag and release on the canvas to define a marque box that picks {@link Entity}s.
@@ -7,6 +7,8 @@ const kdTreeDimLength = new Float32Array(3);
7
7
  * Automatically indexes a {@link Viewer}'s {@link Entity}s in a 3D k-d tree
8
8
  * to support fast collision detection with 3D World-space axis-aligned boundaries (AABBs) and frustums.
9
9
  *
10
+ * See {@link MarqueePicker} for usage example.
11
+ *
10
12
  * An ObjectsKdTree3 is configured with a Viewer, and will then automatically
11
13
  * keep itself populated with k-d nodes that contain the Viewer's Entitys.
12
14
  *
@@ -70,29 +70,12 @@ class PickController {
70
70
  this._lastPickedEntityId = null;
71
71
 
72
72
  this._needFireEvents = 0;
73
-
74
- this._needToUpdate = false;
75
-
76
- this._tickSub = this._scene.on("tick", () => {
77
- this.runUpdate();
78
- });
79
- }
80
-
81
- update() {
82
- this._needToUpdate = true;
83
73
  }
84
74
 
85
75
  /**
86
76
  * Immediately attempts a pick, if scheduled.
87
77
  */
88
- runUpdate() {
89
-
90
- if (!this._needToUpdate) {
91
- return;
92
- }
93
-
94
- this._needToUpdate = false;
95
-
78
+ update() {
96
79
  if (!this._configs.pointerEnabled) {
97
80
  return;
98
81
  }
@@ -269,10 +252,6 @@ class PickController {
269
252
 
270
253
  this._needFireEvents = 0;
271
254
  }
272
-
273
- destroy() {
274
- this._scene.off(this._tickSub);
275
- }
276
255
  }
277
256
 
278
257
  export {PickController};
@@ -45,75 +45,78 @@ class MousePickHandler {
45
45
  }
46
46
  };
47
47
 
48
- canvas.addEventListener("mousemove", this._canvasMouseMoveHandler = (e) => {
48
+ const tickifiedMouseMoveFn = scene.tickify (
49
+ this._canvasMouseMoveHandler = (e) => {
50
+ if (!(configs.active && configs.pointerEnabled)) {
51
+ return;
52
+ }
49
53
 
50
- if (!(configs.active && configs.pointerEnabled)) {
51
- return;
52
- }
54
+ if (leftDown || rightDown) {
55
+ return;
56
+ }
53
57
 
54
- if (leftDown || rightDown) {
55
- return;
56
- }
58
+ const hoverSubs = cameraControl.hasSubs("hover");
59
+ const hoverEnterSubs = cameraControl.hasSubs("hoverEnter");
60
+ const hoverOutSubs = cameraControl.hasSubs("hoverOut");
61
+ const hoverOffSubs = cameraControl.hasSubs("hoverOff");
62
+ const hoverSurfaceSubs = cameraControl.hasSubs("hoverSurface");
63
+ const hoverSnapOrSurfaceSubs = cameraControl.hasSubs("hoverSnapOrSurface");
57
64
 
58
- const hoverSubs = cameraControl.hasSubs("hover");
59
- const hoverEnterSubs = cameraControl.hasSubs("hoverEnter");
60
- const hoverOutSubs = cameraControl.hasSubs("hoverOut");
61
- const hoverOffSubs = cameraControl.hasSubs("hoverOff");
62
- const hoverSurfaceSubs = cameraControl.hasSubs("hoverSurface");
63
- const hoverSnapOrSurfaceSubs = cameraControl.hasSubs("hoverSnapOrSurface");
65
+ if (hoverSubs || hoverEnterSubs || hoverOutSubs || hoverOffSubs || hoverSurfaceSubs || hoverSnapOrSurfaceSubs) {
64
66
 
65
- if (hoverSubs || hoverEnterSubs || hoverOutSubs || hoverOffSubs || hoverSurfaceSubs || hoverSnapOrSurfaceSubs) {
67
+ pickController.pickCursorPos = states.pointerCanvasPos;
68
+ pickController.schedulePickEntity = true;
69
+ pickController.schedulePickSurface = hoverSurfaceSubs;
70
+ pickController.scheduleSnapOrPick = hoverSnapOrSurfaceSubs
66
71
 
67
- pickController.pickCursorPos = states.pointerCanvasPos;
68
- pickController.schedulePickEntity = true;
69
- pickController.schedulePickSurface = hoverSurfaceSubs;
70
- pickController.scheduleSnapOrPick = hoverSnapOrSurfaceSubs
72
+ pickController.update();
71
73
 
72
- pickController.update();
74
+ if (pickController.pickResult) {
73
75
 
74
- if (pickController.pickResult) {
76
+ if (pickController.pickResult.entity) {
77
+ const pickedEntityId = pickController.pickResult.entity.id;
75
78
 
76
- if (pickController.pickResult.entity) {
77
- const pickedEntityId = pickController.pickResult.entity.id;
79
+ if (this._lastPickedEntityId !== pickedEntityId) {
78
80
 
79
- if (this._lastPickedEntityId !== pickedEntityId) {
81
+ if (this._lastPickedEntityId !== undefined) {
80
82
 
81
- if (this._lastPickedEntityId !== undefined) {
83
+ cameraControl.fire("hoverOut", { // Hovered off an entity
84
+ entity: scene.objects[this._lastPickedEntityId]
85
+ }, true);
86
+ }
87
+
88
+ cameraControl.fire("hoverEnter", pickController.pickResult, true); // Hovering over a new entity
82
89
 
83
- cameraControl.fire("hoverOut", { // Hovered off an entity
84
- entity: scene.objects[this._lastPickedEntityId]
85
- }, true);
90
+ this._lastPickedEntityId = pickedEntityId;
86
91
  }
92
+ }
87
93
 
88
- cameraControl.fire("hoverEnter", pickController.pickResult, true); // Hovering over a new entity
94
+ cameraControl.fire("hover", pickController.pickResult, true);
89
95
 
90
- this._lastPickedEntityId = pickedEntityId;
96
+ if (pickController.pickResult.worldPos || pickController.pickResult.snappedWorldPos) { // Hovering the surface of an entity
97
+ cameraControl.fire("hoverSurface", pickController.pickResult, true);
91
98
  }
92
- }
93
99
 
94
- cameraControl.fire("hover", pickController.pickResult, true);
100
+ } else {
95
101
 
96
- if (pickController.pickResult.worldPos || pickController.pickResult.snappedWorldPos) { // Hovering the surface of an entity
97
- cameraControl.fire("hoverSurface", pickController.pickResult, true);
98
- }
102
+ if (this._lastPickedEntityId !== undefined) {
99
103
 
100
- } else {
104
+ cameraControl.fire("hoverOut", { // Hovered off an entity
105
+ entity: scene.objects[this._lastPickedEntityId]
106
+ }, true);
101
107
 
102
- if (this._lastPickedEntityId !== undefined) {
108
+ this._lastPickedEntityId = undefined;
109
+ }
103
110
 
104
- cameraControl.fire("hoverOut", { // Hovered off an entity
105
- entity: scene.objects[this._lastPickedEntityId]
111
+ cameraControl.fire("hoverOff", { // Not hovering on any entity
112
+ canvasPos: pickController.pickCursorPos
106
113
  }, true);
107
-
108
- this._lastPickedEntityId = undefined;
109
114
  }
110
-
111
- cameraControl.fire("hoverOff", { // Not hovering on any entity
112
- canvasPos: pickController.pickCursorPos
113
- }, true);
114
115
  }
115
116
  }
116
- });
117
+ );
118
+
119
+ canvas.addEventListener("mousemove", tickifiedMouseMoveFn);
117
120
 
118
121
  canvas.addEventListener('mousedown', this._canvasMouseDownHandler = (e) => {
119
122
 
@@ -1175,23 +1175,31 @@ class Input extends Component {
1175
1175
  }
1176
1176
  });
1177
1177
 
1178
+ const tickifedMouseMoveFn = this.scene.tickify(
1179
+ () => this.fire("mousemove", this.mouseCanvasPos, true)
1180
+ );
1181
+
1178
1182
  this.element.addEventListener("mousemove", this._mouseMoveListener = (e) => {
1179
1183
  if (!this.enabled) {
1180
1184
  return;
1181
1185
  }
1182
1186
  this._getMouseCanvasPos(e);
1183
- this.fire("mousemove", this.mouseCanvasPos, true);
1187
+ tickifedMouseMoveFn();
1184
1188
  if (this.mouseover) {
1185
1189
  e.preventDefault();
1186
1190
  }
1187
1191
  });
1188
1192
 
1193
+ const tickifiedMouseWheelFn = this.scene.tickify(
1194
+ (delta) => { this.fire("mousewheel", delta, true); }
1195
+ );
1196
+
1189
1197
  this.element.addEventListener("wheel", this._mouseWheelListener = (e, d) => {
1190
1198
  if (!this.enabled) {
1191
1199
  return;
1192
1200
  }
1193
1201
  const delta = Math.max(-1, Math.min(1, -e.deltaY * 40));
1194
- this.fire("mousewheel", delta, true);
1202
+ tickifiedMouseWheelFn(delta);
1195
1203
  }, {passive: true});
1196
1204
 
1197
1205
  // mouseclicked
@@ -352,6 +352,11 @@ class Scene extends Component {
352
352
  throw "Mandatory config expected: valid canvasId or canvasElement";
353
353
  }
354
354
 
355
+ /**
356
+ * @type {{[key: string]: {wrapperFunc: Function, tickSubId: string}}}
357
+ */
358
+ this._tickifiedFunctions = {};
359
+
355
360
  const transparent = (!!cfg.transparent);
356
361
  const alphaDepthMask = (!!cfg.alphaDepthMask);
357
362
 
@@ -2562,6 +2567,63 @@ class Scene extends Component {
2562
2567
  return changed;
2563
2568
  }
2564
2569
 
2570
+ /**
2571
+ * This method will "tickify" the provided `cb` function.
2572
+ *
2573
+ * This means, the function will be wrapped so:
2574
+ *
2575
+ * - it runs time-aligned to scene ticks
2576
+ * - it runs maximum once per scene-tick
2577
+ *
2578
+ * @param {Function} cb The function to tickify
2579
+ * @returns {Function)}
2580
+ */
2581
+ tickify(cb) {
2582
+ const cbString = cb.toString();
2583
+
2584
+ /**
2585
+ * Check if the function is already tickified, and if so return the cached one.
2586
+ */
2587
+ if (cbString in this._tickifiedFunctions) {
2588
+ return this._tickifiedFunctions[cbString].wrapperFunc;
2589
+ }
2590
+
2591
+ let alreadyRun = 0;
2592
+ let needToRun = 0;
2593
+
2594
+ let lastArgs;
2595
+
2596
+ /**
2597
+ * The provided `cb` function is replaced with a "set-dirty" function
2598
+ *
2599
+ * @type {Function}
2600
+ */
2601
+ const wrapperFunc = function (...args) {
2602
+ lastArgs = args;
2603
+ needToRun++;
2604
+ };
2605
+
2606
+ /**
2607
+ * An each scene tick, if the "dirty-flag" is set, run the `cb` function.
2608
+ *
2609
+ * This will make it run time-aligned to the scene tick.
2610
+ */
2611
+ const tickSubId = this.on("tick", () => {
2612
+ const tmp = needToRun;
2613
+ if (tmp > alreadyRun) {
2614
+ alreadyRun = tmp;
2615
+ cb(...lastArgs);
2616
+ }
2617
+ });
2618
+
2619
+ /**
2620
+ * And, store the list of subscribers.
2621
+ */
2622
+ this._tickifiedFunctions[cbString] = { tickSubId, wrapperFunc };
2623
+
2624
+ return wrapperFunc;
2625
+ }
2626
+
2565
2627
  /**
2566
2628
  * Destroys this Scene.
2567
2629
  */