@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/dist/xeokit-sdk.cjs.js +206 -69
- package/dist/xeokit-sdk.es.js +206 -69
- package/dist/xeokit-sdk.es5.js +318 -209
- package/dist/xeokit-sdk.min.cjs.js +2 -2
- package/dist/xeokit-sdk.min.es.js +2 -2
- package/dist/xeokit-sdk.min.es5.js +2 -2
- package/package.json +1 -1
- package/src/extras/MarqueePicker/MarqueePicker.js +81 -0
- package/src/extras/MarqueePicker/MarqueePickerMouseControl.js +2 -0
- package/src/extras/collision/ObjectsKdTree3.js +2 -0
- package/src/viewer/scene/CameraControl/lib/controllers/PickController.js +1 -22
- package/src/viewer/scene/CameraControl/lib/handlers/MousePickHandler.js +48 -45
- package/src/viewer/scene/input/Input.js +10 -2
- package/src/viewer/scene/scene/Scene.js +62 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
48
|
+
const tickifiedMouseMoveFn = scene.tickify (
|
|
49
|
+
this._canvasMouseMoveHandler = (e) => {
|
|
50
|
+
if (!(configs.active && configs.pointerEnabled)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
if (leftDown || rightDown) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
+
pickController.pickCursorPos = states.pointerCanvasPos;
|
|
68
|
+
pickController.schedulePickEntity = true;
|
|
69
|
+
pickController.schedulePickSurface = hoverSurfaceSubs;
|
|
70
|
+
pickController.scheduleSnapOrPick = hoverSnapOrSurfaceSubs
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
pickController.schedulePickEntity = true;
|
|
69
|
-
pickController.schedulePickSurface = hoverSurfaceSubs;
|
|
70
|
-
pickController.scheduleSnapOrPick = hoverSnapOrSurfaceSubs
|
|
72
|
+
pickController.update();
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
if (pickController.pickResult) {
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
if (pickController.pickResult.entity) {
|
|
77
|
+
const pickedEntityId = pickController.pickResult.entity.id;
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
const pickedEntityId = pickController.pickResult.entity.id;
|
|
79
|
+
if (this._lastPickedEntityId !== pickedEntityId) {
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
if (this._lastPickedEntityId !== undefined) {
|
|
80
82
|
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
entity: scene.objects[this._lastPickedEntityId]
|
|
85
|
-
}, true);
|
|
90
|
+
this._lastPickedEntityId = pickedEntityId;
|
|
86
91
|
}
|
|
92
|
+
}
|
|
87
93
|
|
|
88
|
-
|
|
94
|
+
cameraControl.fire("hover", pickController.pickResult, true);
|
|
89
95
|
|
|
90
|
-
|
|
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
|
-
|
|
100
|
+
} else {
|
|
95
101
|
|
|
96
|
-
|
|
97
|
-
cameraControl.fire("hoverSurface", pickController.pickResult, true);
|
|
98
|
-
}
|
|
102
|
+
if (this._lastPickedEntityId !== undefined) {
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
cameraControl.fire("hoverOut", { // Hovered off an entity
|
|
105
|
+
entity: scene.objects[this._lastPickedEntityId]
|
|
106
|
+
}, true);
|
|
101
107
|
|
|
102
|
-
|
|
108
|
+
this._lastPickedEntityId = undefined;
|
|
109
|
+
}
|
|
103
110
|
|
|
104
|
-
cameraControl.fire("
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
*/
|