@xeokit/xeokit-sdk 2.6.0-beta-11 → 2.6.0-beta-13

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.
@@ -1,3 +1,4 @@
1
1
  export * from "./AngleMeasurementsPlugin.js";
2
2
  export * from "./AngleMeasurementsControl.js";
3
- export * from "./AngleMeasurementsMouseControl.js";
3
+ export * from "./AngleMeasurementsMouseControl.js";
4
+ export * from "./AngleMeasurementsTouchControl.js";
@@ -0,0 +1,472 @@
1
+ import {Dot} from "../lib/html/Dot.js";
2
+ import {Component} from "../../viewer/scene/Component.js";
3
+ import {math} from "../../viewer/scene/math/math.js";
4
+ import {Marker} from "../../viewer/index.js";
5
+ import {DistanceMeasurementsControl} from "./DistanceMeasurementsControl";
6
+
7
+ /**
8
+ * Creates {@link DistanceMeasurement}s from mouse and touch input.
9
+ *
10
+ * Belongs to a {@link DistanceMeasurementsPlugin}. Located at {@link DistanceMeasurementsPlugin#control}.
11
+ *
12
+ * Once the DistanceMeasurementControl is activated, the first click on any {@link Entity} begins constructing a {@link DistanceMeasurement}, fixing its origin to that Entity. The next click on any Entity will complete the DistanceMeasurement, fixing its target to that second Entity. The DistanceMeasurementControl will then wait for the next click on any Entity, to begin constructing another DistanceMeasurement, and so on, until deactivated.
13
+ *
14
+ * See {@link DistanceMeasurementsPlugin} for more info.
15
+ */
16
+ export class DistanceMeasurementsControlLegacy extends DistanceMeasurementsControl {
17
+
18
+ /**
19
+ * @private
20
+ */
21
+ constructor(plugin, cfg={}) {
22
+
23
+ super(plugin.viewer.scene);
24
+
25
+ /**
26
+ * The {@link DistanceMeasurementsPlugin} that owns this DistanceMeasurementsControlLegacy.
27
+ * @type {DistanceMeasurementsPlugin}
28
+ */
29
+ this.plugin = plugin;
30
+
31
+ this._active = false;
32
+ this._snapMode = "off";
33
+ this._snapToVertex = false;
34
+
35
+ // Add a marker to the canvas
36
+ const markerDiv = document.createElement('div');
37
+ const canvas = this.scene.canvas.canvas;
38
+ canvas.parentNode.insertBefore(markerDiv, canvas);
39
+
40
+ markerDiv.style.background = "black";
41
+ markerDiv.style.border = "2px solid blue";
42
+ markerDiv.style.borderRadius = "10px";
43
+ markerDiv.style.width = "5px";
44
+ markerDiv.style.height = "5px";
45
+ markerDiv.style.margin = "-200px -200px";
46
+ markerDiv.style.zIndex = "100";
47
+ markerDiv.style.position = "absolute";
48
+ markerDiv.style.pointerEvents = "none";
49
+
50
+ this.markerDiv = markerDiv;
51
+
52
+ // Mouse input uses a combo of events that requires us to track
53
+ // the current DistanceMeasurement under construction. This is not used for touch input, which
54
+ // just uses touch-move-release to make a measurement.
55
+ this._currentDistanceMeasurementByMouse = null;
56
+
57
+ this._currentDistanceMeasurementByMouseInittouchState = {
58
+ wireVisible: null,
59
+ axisVisible: null,
60
+ xAxisVisible: null,
61
+ yaxisVisible: null,
62
+ zAxisVisible: null,
63
+ targetVisible: null,
64
+ }
65
+
66
+ // Shows 2D canvas pos of touch start
67
+ this._touchStartDot = new Dot(plugin._container, {
68
+ fillColor: plugin.defaultColor,
69
+ zIndex: plugin.zIndex + 1,
70
+ visible: false
71
+ });
72
+
73
+ // Tracks 3D world pos of touch start, dynamically calculates 2D canvas pos
74
+ this._touchStartMarker = new Marker(this, {
75
+ id: "distanceMeasurementMarker"
76
+ });
77
+
78
+ // Routes 2D canvas pos from Marker to Dot
79
+ this._touchStartMarker.on("canvasPos", (canvasPos) => {
80
+ this._touchStartDot.setPos(canvasPos[0], canvasPos[1]);
81
+ });
82
+
83
+ // Event handles from CameraControl
84
+ this._onMouseHoverSurface = null;
85
+ this._onMouseHoverOff = null;
86
+ this._onPickedNothing = null;
87
+
88
+ // Event handles from Scene.input
89
+ this._onInputMouseDown = null;
90
+ this._onInputMouseUp = null;
91
+
92
+ // Event handles from Canvas element
93
+ this._onCanvasTouchStart = null;
94
+ this._onCanvasTouchEnd = null;
95
+
96
+ this.snapMode = cfg.snapMode;
97
+ }
98
+
99
+ /**
100
+ * Sets the pointer snapping behaviour.
101
+ *
102
+ * Accepted values are "off" and "vertex".
103
+ *
104
+ * If set to "vertex", the DistanceMeasurementsPlugin will continuously snap the pointer to the nearest vertex as the user hovers over the model.
105
+ *
106
+ * @param snapMode {String}
107
+ */
108
+ set snapMode(snapMode) {
109
+ if (snapMode === undefined || snapMode === null) {
110
+ snapMode = "vertex";
111
+ } else if (snapMode !== "vertex") {
112
+ return;
113
+ }
114
+ this._snapMode = snapMode;
115
+ this._snapToVertex = (snapMode === "vertex");
116
+ }
117
+
118
+ /**
119
+ * Gets the pointer snapping behaviour.
120
+ *
121
+ * Accepted values are "off" and "vertex".
122
+ *
123
+ * If set to "vertex", the DistanceMeasurementsPlugin will continuously snap the pointer to the nearest vertex as the user hovers over the model.
124
+ *
125
+ * @returns {String}
126
+ */
127
+ get snapMode() {
128
+ return this._snapMode;
129
+ }
130
+
131
+ /** Gets if this DistanceMeasurementsControlLegacy is currently active, where it is responding to input.
132
+ *
133
+ * @returns {Boolean}
134
+ */
135
+ get active() {
136
+ return this._active;
137
+ }
138
+
139
+ /**
140
+ * Activates this DistanceMeasurementsControlLegacy, ready to respond to input.
141
+ *
142
+ */
143
+ activate() {
144
+
145
+ if (this._active) {
146
+ return;
147
+ }
148
+
149
+ const plugin = this.plugin;
150
+ const scene = this.scene;
151
+ const cameraControl = plugin.viewer.cameraControl;
152
+ const canvas = scene.canvas.canvas;
153
+ const input = scene.input;
154
+ const startDot = this._touchStartDot;
155
+
156
+ const pickSurfacePrecisionEnabled = scene.pickSurfacePrecisionEnabled;
157
+
158
+ let mouseHoverEntity = null;
159
+ const mouseWorldPos = math.vec3();
160
+ const mouseCanvasPos = math.vec2();
161
+
162
+ let lastMouseCanvasX;
163
+ let lastMouseCanvasY;
164
+ const mouseCanvasClickTolerance = 5;
165
+
166
+ const FIRST_TOUCH_EXPECTED = 0;
167
+ const SECOND_TOUCH_EXPECTED = 1;
168
+ let touchState = FIRST_TOUCH_EXPECTED;
169
+ const touchCanvasClickTolerance = 5;
170
+
171
+ const touchStartCanvasPos = math.vec2();
172
+ const touchEndCanvasPos = math.vec2();
173
+ const touchStartWorldPos = math.vec3();
174
+
175
+ this._onMouseHoverSurface = cameraControl.on("hoverSurface", event => {
176
+
177
+ // This gets fired for both mouse and touch input, but we don't care when handling touch
178
+ mouseHoverEntity = event.entity;
179
+
180
+ let useSnapToVertex = false;
181
+
182
+ if (this._snapToVertex) {
183
+ useSnapToVertex = !!event.snappedWorldPos && !!event.snappedCanvasPos;
184
+ }
185
+
186
+ if (useSnapToVertex) {
187
+ mouseWorldPos.set(event.snappedWorldPos);
188
+ mouseCanvasPos.set(event.snappedCanvasPos);
189
+
190
+ if (touchState === FIRST_TOUCH_EXPECTED) {
191
+ this.markerDiv.style.marginLeft = `${event.snappedCanvasPos[0]-5}px`;
192
+ this.markerDiv.style.marginTop = `${event.snappedCanvasPos[1]-5}px`;
193
+
194
+ this.markerDiv.style.background = "greenyellow";
195
+ this.markerDiv.style.border = "2px solid green";
196
+ }
197
+ } else {
198
+ if (event.worldPos !== null && event.canvasPos !== null) {
199
+ mouseWorldPos.set(event.worldPos);
200
+ mouseCanvasPos.set(event.canvasPos);
201
+
202
+ if (touchState === FIRST_TOUCH_EXPECTED) {
203
+ this.markerDiv.style.marginLeft = `${event.canvasPos[0]-5}px`;
204
+ this.markerDiv.style.marginTop = `${event.canvasPos[1]-5}px`;
205
+
206
+ this.markerDiv.style.background = "pink";
207
+ this.markerDiv.style.border = "2px solid red";
208
+ }
209
+ }
210
+ }
211
+
212
+ if (touchState !== FIRST_TOUCH_EXPECTED || !this.active) {
213
+ this.markerDiv.style.marginLeft = `-10000px`;
214
+ this.markerDiv.style.marginTop = `-10000px`;
215
+ }
216
+
217
+ canvas.style.cursor = "pointer";
218
+
219
+ if (this._currentDistanceMeasurementByMouse) {
220
+ this._currentDistanceMeasurementByMouse.wireVisible = this._currentDistanceMeasurementByMouseInittouchState.wireVisible;
221
+ this._currentDistanceMeasurementByMouse.axisVisible = this._currentDistanceMeasurementByMouseInittouchState.axisVisible && this.plugin.defaultAxisVisible;
222
+ this._currentDistanceMeasurementByMouse.xAxisVisible = this._currentDistanceMeasurementByMouseInittouchState.xAxisVisible && this.plugin.defaultXAxisVisible;
223
+ this._currentDistanceMeasurementByMouse.yAxisVisible = this._currentDistanceMeasurementByMouseInittouchState.yAxisVisible && this.plugin.defaultYAxisVisible;
224
+ this._currentDistanceMeasurementByMouse.zAxisVisible = this._currentDistanceMeasurementByMouseInittouchState.zAxisVisible && this.plugin.defaultZAxisVisible;
225
+ this._currentDistanceMeasurementByMouse.targetVisible = this._currentDistanceMeasurementByMouseInittouchState.targetVisible;
226
+ this._currentDistanceMeasurementByMouse.target.entity = mouseHoverEntity;
227
+ this._currentDistanceMeasurementByMouse.target.worldPos = mouseWorldPos;
228
+ }
229
+ });
230
+
231
+ this._onInputMouseDown = input.on("mousedown", (coords) => {
232
+ lastMouseCanvasX = coords[0];
233
+ lastMouseCanvasY = coords[1];
234
+ });
235
+
236
+ this._onInputMouseUp = input.on("mouseup", (coords) => {
237
+ if (coords[0] > lastMouseCanvasX + mouseCanvasClickTolerance ||
238
+ coords[0] < lastMouseCanvasX - mouseCanvasClickTolerance ||
239
+ coords[1] > lastMouseCanvasY + mouseCanvasClickTolerance ||
240
+ coords[1] < lastMouseCanvasY - mouseCanvasClickTolerance) {
241
+ return;
242
+ }
243
+ if (this._currentDistanceMeasurementByMouse) {
244
+ if (mouseHoverEntity) {
245
+ if (pickSurfacePrecisionEnabled) {
246
+ const pickResult = scene.pick({
247
+ canvasPos: mouseCanvasPos,
248
+ pickSurface: true,
249
+ pickSurfacePrecision: true
250
+ });
251
+ if (pickResult && pickResult.worldPos) {
252
+ this._currentDistanceMeasurementByMouse.target.worldPos = pickResult.worldPos;
253
+ }
254
+ this._currentDistanceMeasurementByMouse.approximate = false;
255
+ }
256
+ this._currentDistanceMeasurementByMouse.clickable = true;
257
+ this.fire("measurementEnd", this._currentDistanceMeasurementByMouse);
258
+ this._currentDistanceMeasurementByMouse = null;
259
+ } else {
260
+ this._currentDistanceMeasurementByMouse.destroy();
261
+ this.fire("measurementCancel", this._currentDistanceMeasurementByMouse);
262
+ this._currentDistanceMeasurementByMouse = null;
263
+ }
264
+ } else {
265
+ if (mouseHoverEntity) {
266
+ if (pickSurfacePrecisionEnabled) {
267
+ const pickResult = scene.pick({
268
+ canvasPos: mouseCanvasPos,
269
+ pickSurface: true,
270
+ pickSurfacePrecision: true
271
+ });
272
+ if (pickResult && pickResult.worldPos) {
273
+ mouseWorldPos.set(pickResult.worldPos);
274
+ }
275
+ }
276
+ this._currentDistanceMeasurementByMouse = plugin.createMeasurement({
277
+ id: math.createUUID(),
278
+ origin: {
279
+ entity: mouseHoverEntity,
280
+ worldPos: mouseWorldPos
281
+ },
282
+ target: {
283
+ entity: mouseHoverEntity,
284
+ worldPos: mouseWorldPos
285
+ },
286
+ approximate: true
287
+ });
288
+ this._currentDistanceMeasurementByMouseInittouchState.axisVisible = this._currentDistanceMeasurementByMouse.axisVisible && this.plugin.defaultAxisVisible;
289
+
290
+ this._currentDistanceMeasurementByMouseInittouchState.xAxisVisible = this._currentDistanceMeasurementByMouse.xAxisVisible && this.plugin.defaultXAxisVisible;
291
+ this._currentDistanceMeasurementByMouseInittouchState.yAxisVisible = this._currentDistanceMeasurementByMouse.yAxisVisible && this.plugin.defaultYAxisVisible;
292
+ this._currentDistanceMeasurementByMouseInittouchState.zAxisVisible = this._currentDistanceMeasurementByMouse.zAxisVisible && this.plugin.defaultZAxisVisible;
293
+
294
+ this._currentDistanceMeasurementByMouseInittouchState.wireVisible = this._currentDistanceMeasurementByMouse.wireVisible;
295
+ this._currentDistanceMeasurementByMouseInittouchState.targetVisible = this._currentDistanceMeasurementByMouse.targetVisible;
296
+ this._currentDistanceMeasurementByMouse.clickable = false;
297
+ this.fire("measurementStart", this._currentDistanceMeasurementByMouse);
298
+ }
299
+ }
300
+ });
301
+
302
+ this._onMouseHoverOff = cameraControl.on("hoverOff", event => {
303
+ mouseHoverEntity = null;
304
+
305
+ this.markerDiv.style.marginLeft = `-100px`;
306
+ this.markerDiv.style.marginTop = `-100px`;
307
+
308
+ if (this._currentDistanceMeasurementByMouse) {
309
+ this._currentDistanceMeasurementByMouse.wireVisible = false;
310
+ this._currentDistanceMeasurementByMouse.targetVisible = false;
311
+ this._currentDistanceMeasurementByMouse.axisVisible = false;
312
+ }
313
+ canvas.style.cursor = "default";
314
+ });
315
+
316
+ this._onPickedNothing = cameraControl.on("pickedNothing", event => {
317
+ if (this._currentDistanceMeasurementByMouse) {
318
+ this.fire("measurementCancel", this._currentDistanceMeasurementByMouse);
319
+ this._currentDistanceMeasurementByMouse.destroy();
320
+ this._currentDistanceMeasurementByMouse = null;
321
+ }
322
+ startDot.setVisible(false);
323
+ touchState = FIRST_TOUCH_EXPECTED;
324
+ });
325
+
326
+ canvas.addEventListener("touchstart", this._onCanvasTouchStart = (event) => {
327
+ const touches = event.touches;
328
+ const changedTouches = event.changedTouches;
329
+ if (touches.length === 1 && changedTouches.length === 1) {
330
+ getCanvasPosFromEvent(touches[0], touchStartCanvasPos);
331
+ }
332
+ }, {passive: true});
333
+
334
+ canvas.addEventListener("touchend", this._onCanvasTouchEnd = (event) => {
335
+ const touches = event.touches;
336
+ const changedTouches = event.changedTouches;
337
+ if (touches.length === 0 && changedTouches.length === 1) {
338
+ getCanvasPosFromEvent(changedTouches[0], touchEndCanvasPos);
339
+ if (touchEndCanvasPos[0] > touchStartCanvasPos[0] + touchCanvasClickTolerance ||
340
+ touchEndCanvasPos[0] < touchStartCanvasPos[0] - touchCanvasClickTolerance ||
341
+ touchEndCanvasPos[1] > touchStartCanvasPos[1] + touchCanvasClickTolerance ||
342
+ touchEndCanvasPos[1] < touchStartCanvasPos[1] - touchCanvasClickTolerance) {
343
+ return; // User is repositioning the camera or model
344
+ }
345
+ const pickResult = scene.pick({
346
+ canvasPos: touchEndCanvasPos,
347
+ pickSurface: true,
348
+ pickSurfacePrecision: pickSurfacePrecisionEnabled
349
+ });
350
+ if (pickResult && pickResult.worldPos) {
351
+ switch (touchState) {
352
+ case FIRST_TOUCH_EXPECTED:
353
+ startDot.setVisible(true);
354
+ this._touchStartMarker.worldPos = pickResult.worldPos;
355
+ touchStartWorldPos.set(pickResult.worldPos);
356
+ touchState = SECOND_TOUCH_EXPECTED;
357
+ break;
358
+ case SECOND_TOUCH_EXPECTED:
359
+ startDot.setVisible(false);
360
+ this._touchStartMarker.worldPos = pickResult.worldPos;
361
+ const measurement = plugin.createMeasurement({
362
+ id: math.createUUID(),
363
+ origin: {
364
+ entity: mouseHoverEntity,
365
+ worldPos: touchStartWorldPos
366
+ },
367
+ target: {
368
+ entity: mouseHoverEntity,
369
+ worldPos: pickResult.worldPos
370
+ },
371
+ approximate: (!pickSurfacePrecisionEnabled)
372
+ });
373
+ measurement.clickable = true;
374
+ touchState = FIRST_TOUCH_EXPECTED;
375
+ this.fire("measurementEnd", measurement);
376
+ break;
377
+ }
378
+ } else {
379
+ startDot.setVisible(false);
380
+ touchState = FIRST_TOUCH_EXPECTED;
381
+ }
382
+ }
383
+ // event.stopPropagation();
384
+ }, {passive: true});
385
+
386
+ this._active = true;
387
+ }
388
+
389
+ /**
390
+ * Deactivates this DistanceMeasurementsControlLegacy, making it unresponsive to input.
391
+ *
392
+ * Destroys any {@link DistanceMeasurement} under construction.
393
+ */
394
+ deactivate() {
395
+
396
+ if (!this._active) {
397
+ return;
398
+ }
399
+
400
+ this._touchStartDot.setVisible(false);
401
+
402
+ this.reset();
403
+
404
+ const input = this.plugin.viewer.scene.input;
405
+ input.off(this._onInputMouseDown);
406
+ input.off(this._onInputMouseUp);
407
+
408
+ const cameraControl = this.plugin.viewer.cameraControl;
409
+ cameraControl.off(this._onMouseHoverSurface);
410
+ cameraControl.off(this._onMouseHoverOff);
411
+ cameraControl.off(this._onPickedNothing);
412
+
413
+ const canvas = this.plugin.viewer.scene.canvas.canvas;
414
+ canvas.removeEventListener("touchstart", this._onCanvasTouchStart);
415
+ canvas.removeEventListener("touchend", this._onCanvasTouchEnd);
416
+
417
+ if (this._currentDistanceMeasurementByMouse) {
418
+ this.fire("measurementCancel", this._currentDistanceMeasurementByMouse);
419
+ this._currentDistanceMeasurementByMouse.destroy();
420
+ this._currentDistanceMeasurementByMouse = null;
421
+ }
422
+
423
+ this._active = false;
424
+ }
425
+
426
+ /**
427
+ * Resets this DistanceMeasurementsControlLegacy.
428
+ *
429
+ * Destroys any {@link DistanceMeasurement} under construction.
430
+ *
431
+ * Does nothing if the DistanceMeasurementsControlLegacy is not active.
432
+ */
433
+ reset() {
434
+ if (!this._active) {
435
+ return;
436
+ }
437
+ if (this._currentDistanceMeasurementByMouse) {
438
+ this.fire("measurementCancel", this._currentDistanceMeasurementByMouse);
439
+ this._currentDistanceMeasurementByMouse.destroy();
440
+ this._currentDistanceMeasurementByMouse = null;
441
+ }
442
+ }
443
+
444
+ /**
445
+ * @private
446
+ */
447
+ destroy() {
448
+ this._touchStartDot.destroy();
449
+ this.deactivate();
450
+ super.destroy();
451
+ }
452
+ }
453
+
454
+ const getCanvasPosFromEvent = function (event, canvasPos) {
455
+ if (!event) {
456
+ event = window.event;
457
+ canvasPos[0] = event.x;
458
+ canvasPos[1] = event.y;
459
+ } else {
460
+ let element = event.target;
461
+ let totalOffsetLeft = 0;
462
+ let totalOffsetTop = 0;
463
+ while (element.offsetParent) {
464
+ totalOffsetLeft += element.offsetLeft;
465
+ totalOffsetTop += element.offsetTop;
466
+ element = element.offsetParent;
467
+ }
468
+ canvasPos[0] = event.pageX - totalOffsetLeft;
469
+ canvasPos[1] = event.pageY - totalOffsetTop;
470
+ }
471
+ return canvasPos;
472
+ };
@@ -221,6 +221,46 @@ import {DistanceMeasurementsMouseControl} from "./DistanceMeasurementsMouseContr
221
221
  * });
222
222
  * });
223
223
  * ````
224
+ *
225
+ * ## Example 5: Creating DistanceMeasurements with Touch Input
226
+ *
227
+ * In our fifth example, we'll show how to create distance measurements with touch input, with snapping
228
+ * to the nearest vertex or edge. While creating the measurements, a long-touch when setting the
229
+ * start or end point will cause the point to snap to the nearest vertex or edge. A quick
230
+ * touch-release will immediately set the point at the tapped position on the object surface.
231
+ *
232
+ * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/measurement/#distance_createWithTouch_snapping)]
233
+ *
234
+ * ````javascript
235
+ * import {Viewer, XKTLoaderPlugin, DistanceMeasurementsPlugin, DistanceMeasurementsTouchControl} from "xeokit-sdk.es.js";
236
+ *
237
+ * const viewer = new Viewer({
238
+ * canvasId: "myCanvas",
239
+ * transparent: true
240
+ * });
241
+ *
242
+ * viewer.scene.camera.eye = [-2.37, 18.97, -26.12];
243
+ * viewer.scene.camera.look = [10.97, 5.82, -11.22];
244
+ * viewer.scene.camera.up = [0.36, 0.83, 0.40];
245
+ *
246
+ * const xktLoader = new XKTLoaderPlugin(viewer);
247
+ *
248
+ * const distanceMeasurements = new DistanceMeasurementsPlugin(viewer);
249
+ *
250
+ * const model = xktLoader.load({
251
+ * src: "./models/xkt/duplex/duplex.xkt"
252
+ * });
253
+ *
254
+ * const distanceMeasurements = new DistanceMeasurementsPlugin(viewer);
255
+ *
256
+ * const distanceMeasurementsTouchControl = new DistanceMeasurementsTouchControl(distanceMeasurements, {
257
+ * pointerLens : new PointerLens(viewer),
258
+ * snapToVertex: true,
259
+ * snapToEdge: true
260
+ * })
261
+ *
262
+ * distanceMeasurementsTouchControl.activate();
263
+ * ````
224
264
  */
225
265
  class DistanceMeasurementsPlugin extends Plugin {
226
266
 
@@ -454,7 +494,7 @@ class DistanceMeasurementsPlugin extends Plugin {
454
494
  }
455
495
 
456
496
  /**
457
- * Shows all or hides the angle label of each {@link DistanceMeasurement}.
497
+ * Shows all or hides the distance label of each {@link DistanceMeasurement}.
458
498
  *
459
499
  * @param {Boolean} labelsShown Whether or not to show the labels.
460
500
  */