@xeokit/xeokit-sdk 2.6.16 → 2.6.19

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.
Files changed (37) hide show
  1. package/dist/xeokit-sdk.cjs.js +2348 -118
  2. package/dist/xeokit-sdk.es.js +2338 -119
  3. package/dist/xeokit-sdk.es5.js +559 -419
  4. package/dist/xeokit-sdk.min.cjs.js +4 -4
  5. package/dist/xeokit-sdk.min.es.js +5 -5
  6. package/dist/xeokit-sdk.min.es5.js +4 -4
  7. package/package.json +2 -2
  8. package/src/extras/PointerLens/PointerLens.js +2 -2
  9. package/src/plugins/AnnotationsPlugin/Annotation.js +1 -0
  10. package/src/plugins/CityJSONLoaderPlugin/CityJSONDefaultDataSource.js +15 -2
  11. package/src/plugins/DistanceMeasurementsPlugin/DistanceMeasurement.js +1 -1
  12. package/src/plugins/DotBIMLoaderPlugin/DotBIMDefaultDataSource.js +15 -2
  13. package/src/plugins/GLTFLoaderPlugin/GLTFDefaultDataSource.js +18 -5
  14. package/src/plugins/LASLoaderPlugin/LASDefaultDataSource.js +15 -1
  15. package/src/plugins/STLLoaderPlugin/STLDefaultDataSource.js +17 -0
  16. package/src/plugins/WebIFCLoaderPlugin/WebIFCDefaultDataSource.js +16 -1
  17. package/src/plugins/XKTLoaderPlugin/XKTDefaultDataSource.js +18 -4
  18. package/src/plugins/ZonesPlugin/index.js +2046 -0
  19. package/src/plugins/index.js +2 -1
  20. package/src/plugins/lib/html/Dot.js +19 -1
  21. package/src/viewer/Viewer.js +3 -1
  22. package/src/viewer/scene/CameraControl/lib/handlers/MousePickHandler.js +9 -0
  23. package/src/viewer/scene/geometry/builders/buildLineGeometry.js +267 -0
  24. package/src/viewer/scene/input/Input.js +41 -0
  25. package/src/viewer/scene/marker/Marker.js +4 -1
  26. package/src/viewer/scene/mesh/Mesh.js +5 -0
  27. package/src/viewer/scene/model/SceneModel.js +7 -1
  28. package/src/viewer/scene/scene/Scene.js +30 -4
  29. package/src/viewer/scene/sectionPlane/SectionPlane.js +3 -7
  30. package/src/viewer/scene/webgl/Renderer.js +84 -79
  31. package/src/viewer/scene/webgl/occlusion/OcclusionLayer.js +1 -1
  32. package/src/viewer/scene/webgl/occlusion/OcclusionTester.js +8 -5
  33. package/types/viewer/Viewer.d.ts +2 -0
  34. package/types/viewer/scene/geometry/builders/buildLineGeometry.d.ts +22 -0
  35. package/types/viewer/scene/geometry/builders/buildPolylineGeometry.d.ts +2 -2
  36. package/types/viewer/scene/scene/Scene.d.ts +11 -0
  37. package/dist/web-ifc.wasm +0 -0
@@ -0,0 +1,2046 @@
1
+ import {Plugin} from "../../viewer/Plugin.js";
2
+ import {Component} from "../../viewer/scene/Component.js";
3
+ import {buildBoxGeometry} from "../../viewer/scene/geometry/builders/buildBoxGeometry.js";
4
+ import {ReadableGeometry} from "../../viewer/scene/geometry/ReadableGeometry.js";
5
+ import {Marker} from "../../viewer/scene/marker/Marker.js";
6
+ import {PhongMaterial} from "../../viewer/scene/materials/PhongMaterial.js";
7
+ import {math} from "../../viewer/scene/math/math.js";
8
+ import {Mesh} from "../../viewer/scene/mesh/Mesh.js";
9
+ import {Dot} from "../lib/html/Dot.js";
10
+
11
+ const hex2rgb = function(color) {
12
+ const rgb = idx => parseInt(color.substr(idx + 1, 2), 16) / 255;
13
+ return [ rgb(0), rgb(2), rgb(4) ];
14
+ };
15
+
16
+ const transformToNode = function(from, to, vec) {
17
+ const fromRec = from.getBoundingClientRect();
18
+ const toRec = to.getBoundingClientRect();
19
+ vec[0] += fromRec.left - toRec.left;
20
+ vec[1] += fromRec.top - toRec.top;
21
+ };
22
+
23
+ const triangulateEarClipping = function(planeCoords) {
24
+
25
+ const polygonVertices = [ ];
26
+ for (let i = 0; i < planeCoords.length; ++i)
27
+ polygonVertices.push(i);
28
+
29
+ const isCCW = (function() {
30
+ const ba = math.vec2();
31
+ const bc = math.vec2();
32
+
33
+ let anglesSum = 0;
34
+ const angles = [ ];
35
+
36
+ for (let i = 0; i < polygonVertices.length; ++i)
37
+ {
38
+ const a = planeCoords[polygonVertices[i]];
39
+ const b = planeCoords[polygonVertices[(i + 1) % polygonVertices.length]];
40
+ const c = planeCoords[polygonVertices[(i + 2) % polygonVertices.length]];
41
+
42
+ math.subVec2(a, b, ba);
43
+ math.subVec2(c, b, bc);
44
+
45
+ const theta = math.dotVec2(ba, bc) / Math.sqrt(math.sqLenVec2(ba) * math.sqLenVec2(bc));
46
+ const angle = Math.acos(Math.max(-1, Math.min(theta, 1)));
47
+ const convex = (ba[0] * bc[1] - ba[1] * bc[0]) >= 0;
48
+ anglesSum += convex ? angle : (2 * Math.PI - angle);
49
+ }
50
+
51
+ return anglesSum < (polygonVertices.length * Math.PI);
52
+ })();
53
+
54
+ const pointInTriangle = (function() {
55
+ const sign = (p1, p2, p3) => {
56
+ return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]);
57
+ };
58
+
59
+ return (pt, v1, v2, v3) => {
60
+ const d1 = sign(pt, v1, v2);
61
+ const d2 = sign(pt, v2, v3);
62
+ const d3 = sign(pt, v3, v1);
63
+
64
+ const has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
65
+ const has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
66
+
67
+ return !(has_neg && has_pos);
68
+ };
69
+ })();
70
+
71
+ const baseTriangles = [ ];
72
+
73
+ const vertices = (isCCW ? polygonVertices : polygonVertices.slice(0).reverse()).map(i => ({ idx: i }));
74
+ vertices.forEach((v, i) => {
75
+ v.prev = vertices[(i - 1 + vertices.length) % vertices.length];
76
+ v.next = vertices[(i + 1) % vertices.length];
77
+ });
78
+
79
+ const ba = math.vec2();
80
+ const bc = math.vec2();
81
+
82
+ while (vertices.length > 2) {
83
+ let earIdx = 0;
84
+ while (true) {
85
+ if (earIdx >= vertices.length)
86
+ {
87
+ throw `isCCW = ${isCCW}; earIdx = ${earIdx}; len = ${vertices.length}`;
88
+ }
89
+ const v = vertices[earIdx];
90
+
91
+ const a = planeCoords[v.prev.idx];
92
+ const b = planeCoords[v.idx];
93
+ const c = planeCoords[v.next.idx];
94
+
95
+ math.subVec2(a, b, ba);
96
+ math.subVec2(c, b, bc);
97
+
98
+ if (((ba[0] * bc[1] - ba[1] * bc[0]) >= 0) // a convex vertex
99
+ &&
100
+ vertices.every( // no other vertices inside
101
+ vv => ((vv === v)
102
+ ||
103
+ (vv === v.prev)
104
+ ||
105
+ (vv === v.next)
106
+ ||
107
+ !pointInTriangle(planeCoords[vv.idx], a, b, c))))
108
+ break;
109
+ ++earIdx;
110
+ }
111
+
112
+ const ear = vertices[earIdx];
113
+ vertices.splice(earIdx, 1);
114
+
115
+ baseTriangles.push([ ear.idx, ear.next.idx, ear.prev.idx ]);
116
+
117
+ const prev = ear.prev;
118
+ prev.next = ear.next;
119
+ const next = ear.next;
120
+ next.prev = ear.prev;
121
+ }
122
+
123
+ return [ planeCoords, baseTriangles ];
124
+ };
125
+
126
+ const draggableDot3D = function(handleMouseEvents, handleTouchEvents, viewer, worldPos, color, ray2WorldPos, onStart, onMove, onEnd) {
127
+ const scene = viewer.scene;
128
+ const canvas = scene.canvas.canvas;
129
+
130
+ const marker = new Marker(scene, {});
131
+
132
+ const pickWorldPos = canvasPos => {
133
+ const origin = math.vec3();
134
+ const direction = math.vec3();
135
+ math.canvasPosToWorldRay(canvas, scene.camera.viewMatrix, scene.camera.projMatrix, canvasPos, origin, direction);
136
+ return ray2WorldPos(origin, direction);
137
+ };
138
+
139
+ const onChange = event => {
140
+ const canvasPos = math.vec2([ event.clientX, event.clientY ]);
141
+ transformToNode(canvas.ownerDocument.body, canvas, canvasPos);
142
+
143
+ const worldPos = pickWorldPos(canvasPos);
144
+ marker.worldPos = worldPos;
145
+ updateDotPos();
146
+ onMove(canvasPos, worldPos);
147
+ };
148
+
149
+ let currentDrag = null;
150
+
151
+ const onDragMove = function(event) {
152
+ const e = currentDrag.matchesEvent(event);
153
+ if (e)
154
+ {
155
+ onChange(e);
156
+ }
157
+ };
158
+
159
+ const onDragEnd = function(event) {
160
+ const e = currentDrag.matchesEvent(event);
161
+ if (e)
162
+ {
163
+ dot.setOpacity(idleOpacity);
164
+ currentDrag.cleanup();
165
+ onChange(e);
166
+ onEnd();
167
+ }
168
+ };
169
+
170
+ const startDrag = function(matchesEvent, cleanupHandlers) {
171
+ if (currentDrag) {
172
+ currentDrag.cleanup();
173
+ }
174
+
175
+ dot.setOpacity(1.0);
176
+ dot.setClickable(false);
177
+ viewer.cameraControl.active = false;
178
+
179
+ currentDrag = {
180
+ matchesEvent: matchesEvent,
181
+ cleanup: function() {
182
+ currentDrag = null;
183
+ dot.setClickable(true);
184
+ viewer.cameraControl.active = true;
185
+ cleanupHandlers();
186
+ }
187
+ };
188
+
189
+ onStart();
190
+ };
191
+
192
+ const dotCfg = { fillColor: color };
193
+
194
+ if (handleMouseEvents)
195
+ {
196
+ dotCfg.onMouseOver = () => (! currentDrag) && dot.setOpacity(1.0);
197
+ dotCfg.onMouseLeave = () => (! currentDrag) && dot.setOpacity(idleOpacity);
198
+ dotCfg.onMouseDown = event => {
199
+ if (event.which === 1)
200
+ {
201
+ canvas.addEventListener("mousemove", onDragMove);
202
+ canvas.addEventListener("mouseup", onDragEnd);
203
+ startDrag(
204
+ event => (event.which === 1) && event,
205
+ () => {
206
+ canvas.removeEventListener("mousemove", onDragMove);
207
+ canvas.removeEventListener("mouseup", onDragEnd);
208
+ });
209
+ }
210
+ };
211
+ }
212
+
213
+ if (handleTouchEvents)
214
+ {
215
+ let touchStartId;
216
+ dotCfg.onTouchstart = event => {
217
+ event.preventDefault();
218
+ if (event.touches.length === 1)
219
+ {
220
+ touchStartId = event.touches[0].identifier;
221
+ startDrag(
222
+ event => [...event.changedTouches].find(e => e.identifier === touchStartId),
223
+ () => { touchStartId = null; });
224
+ }
225
+ };
226
+ dotCfg.onTouchmove = event => {
227
+ event.preventDefault();
228
+ onDragMove(event);
229
+ };
230
+ dotCfg.onTouchend = event => {
231
+ event.preventDefault();
232
+ onDragEnd(event);
233
+ };
234
+ }
235
+
236
+ const dotParent = canvas.ownerDocument.body;
237
+ const dot = new Dot(dotParent, dotCfg);
238
+
239
+ const idleOpacity = 0.5;
240
+ dot.setOpacity(idleOpacity);
241
+
242
+ const updateDotPos = function() {
243
+ const pos = marker.canvasPos.slice();
244
+ transformToNode(canvas, dotParent, pos);
245
+ dot.setPos(pos[0], pos[1]);
246
+ };
247
+
248
+ marker.worldPos = worldPos;
249
+ updateDotPos();
250
+
251
+ const onViewMatrix = scene.camera.on("viewMatrix", updateDotPos);
252
+ const onProjMatrix = scene.camera.on("projMatrix", updateDotPos);
253
+
254
+ return {
255
+ setActive: value => dot.setClickable(value),
256
+ getWorldPos: () => marker.worldPos,
257
+ setWorldPos: pos => { marker.worldPos = pos; updateDotPos(); },
258
+ destroy: function() {
259
+ currentDrag && currentDrag.cleanup();
260
+ scene.camera.off(onViewMatrix);
261
+ scene.camera.off(onProjMatrix);
262
+ marker.destroy();
263
+ dot.destroy();
264
+ }
265
+ };
266
+ };
267
+
268
+ const marker3D = function(scene, color) {
269
+ const canvas = scene.canvas.canvas;
270
+
271
+ const markerParent = canvas.parentNode;
272
+ const markerDiv = document.createElement("div");
273
+ markerParent.insertBefore(markerDiv, canvas);
274
+
275
+ let size = 5;
276
+ markerDiv.style.background = color;
277
+ markerDiv.style.border = "2px solid white";
278
+ markerDiv.style.margin = "0 0";
279
+ markerDiv.style.zIndex = "100";
280
+ markerDiv.style.position = "absolute";
281
+ markerDiv.style.pointerEvents = "none";
282
+ markerDiv.style.display = "none";
283
+
284
+ const marker = new Marker(scene, {});
285
+
286
+ const px = x => x + "px";
287
+ const update = function() {
288
+ const pos = marker.canvasPos.slice();
289
+ transformToNode(canvas, markerParent, pos);
290
+ markerDiv.style.left = px(pos[0] - 3 - size / 2);
291
+ markerDiv.style.top = px(pos[1] - 3 - size / 2);
292
+ markerDiv.style.borderRadius = px(size * 2);
293
+ markerDiv.style.width = px(size);
294
+ markerDiv.style.height = px(size);
295
+ };
296
+ const onViewMatrix = scene.camera.on("viewMatrix", update);
297
+ const onProjMatrix = scene.camera.on("projMatrix", update);
298
+
299
+ return {
300
+ update: function(worldPos) {
301
+ if (worldPos)
302
+ {
303
+ marker.worldPos = worldPos;
304
+ update();
305
+ }
306
+ markerDiv.style.display = worldPos ? "" : "none";
307
+ },
308
+
309
+ setHighlighted: function(h) {
310
+ size = h ? 10 : 5;
311
+ update();
312
+ },
313
+
314
+ getCanvasPos: () => marker.canvasPos,
315
+
316
+ getWorldPos: () => marker.worldPos,
317
+
318
+ destroy: function() {
319
+ markerDiv.parentNode.removeChild(markerDiv);
320
+ scene.camera.off(onViewMatrix);
321
+ scene.camera.off(onProjMatrix);
322
+ marker.destroy();
323
+ }
324
+ };
325
+ };
326
+
327
+ import {Wire} from "../lib/html/Wire.js";
328
+
329
+ const wire3D = function(scene, color, startWorldPos) {
330
+ const canvas = scene.canvas.canvas;
331
+
332
+ const startMarker = new Marker(scene, {});
333
+ startMarker.worldPos = startWorldPos;
334
+ const endMarker = new Marker(scene, {});
335
+ const wireParent = canvas.ownerDocument.body;
336
+ const wire = new Wire(wireParent, {
337
+ color: color,
338
+ thickness: 1,
339
+ thicknessClickable: 6
340
+ });
341
+ wire.setVisible(false);
342
+
343
+ const updatePos = function() {
344
+ const p0 = startMarker.canvasPos.slice();
345
+ const p1 = endMarker.canvasPos.slice();
346
+ transformToNode(canvas, wireParent, p0);
347
+ transformToNode(canvas, wireParent, p1);
348
+ wire.setStartAndEnd(p0[0], p0[1], p1[0], p1[1]);
349
+ };
350
+ const onViewMatrix = scene.camera.on("viewMatrix", updatePos);
351
+ const onProjMatrix = scene.camera.on("projMatrix", updatePos);
352
+
353
+ return {
354
+ update: function(endWorldPos) {
355
+ if (endWorldPos)
356
+ {
357
+ endMarker.worldPos = endWorldPos;
358
+ updatePos();
359
+ }
360
+ wire.setVisible(!!endWorldPos);
361
+ },
362
+
363
+ destroy: function() {
364
+ scene.camera.off(onViewMatrix);
365
+ scene.camera.off(onProjMatrix);
366
+ startMarker.destroy();
367
+ endMarker.destroy();
368
+ wire.destroy();
369
+ }
370
+ };
371
+ };
372
+
373
+ const basePolygon3D = function(scene, color, alpha) {
374
+ let mesh = null;
375
+
376
+ const updateBase = points => {
377
+ if (points)
378
+ {
379
+ if (mesh)
380
+ {
381
+ mesh.destroy();
382
+ }
383
+
384
+ try {
385
+ const [ baseVertices, baseTriangles ] = triangulateEarClipping(points.map(p => [ p[0], p[2] ]));
386
+
387
+ const positions = [ ].concat(...baseVertices.map(p => [p[0], points[0][1], p[1]])); // To convert from Float64Array into an Array
388
+ const ind = [ ].concat(...baseTriangles);
389
+ mesh = new Mesh(scene, {
390
+ pickable: false, // otherwise there's a WebGL error inside PickMeshRenderer.prototype.drawMesh
391
+ geometry: new ReadableGeometry(
392
+ scene,
393
+ {
394
+ positions: positions,
395
+ indices: ind,
396
+ normals: math.buildNormals(positions, ind)
397
+ }),
398
+ material: new PhongMaterial(scene, {
399
+ alpha: (alpha !== undefined) ? alpha : 0.5,
400
+ backfaces: true,
401
+ diffuse: hex2rgb(color)
402
+ })
403
+ });
404
+ } catch (e) {
405
+ mesh = null;
406
+ }
407
+ }
408
+
409
+ if (mesh)
410
+ {
411
+ mesh.visible = !!points;
412
+ }
413
+ };
414
+ updateBase(null);
415
+
416
+ return {
417
+ updateBase: updateBase,
418
+ destroy: () => mesh && mesh.destroy()
419
+ };
420
+ };
421
+
422
+ const startAAZoneCreateUI = function(scene, zoneAltitude, zoneHeight, zoneColor, zoneAlpha, pointerLens, zonesPlugin, select3dPoint, onZoneCreated) {
423
+ const marker1 = marker3D(scene, zoneColor);
424
+ const marker2 = marker3D(scene, zoneColor);
425
+ const basePolygon = basePolygon3D(scene, zoneColor, zoneAlpha);
426
+
427
+ const updatePointerLens = (pointerLens
428
+ ? function(canvasPos) {
429
+ pointerLens.visible = !! canvasPos;
430
+ if (canvasPos)
431
+ {
432
+ pointerLens.canvasPos = canvasPos;
433
+ }
434
+ }
435
+ : () => { });
436
+
437
+ let deactivatePointSelection = select3dPoint(
438
+ () => {
439
+ updatePointerLens(null);
440
+ marker1.update(null);
441
+ },
442
+ (canvasPos, worldPos) => {
443
+ updatePointerLens(canvasPos);
444
+ marker1.update(worldPos);
445
+ },
446
+ function(point1CanvasPos, point1WorldPos) {
447
+ marker1.update(point1WorldPos);
448
+
449
+ deactivatePointSelection = select3dPoint(
450
+ function() {
451
+ updatePointerLens(null);
452
+ marker2.update(null);
453
+ basePolygon.updateBase(null);
454
+ },
455
+ function(canvasPos, point2WorldPos) {
456
+ updatePointerLens(canvasPos);
457
+ marker2.update(point2WorldPos);
458
+
459
+ if (math.distVec3(point1WorldPos, point2WorldPos) > 0.01)
460
+ {
461
+ const min = (idx) => Math.min(point1WorldPos[idx], point2WorldPos[idx]);
462
+ const max = (idx) => Math.max(point1WorldPos[idx], point2WorldPos[idx]);
463
+
464
+ const xmin = min(0);
465
+ const ymin = min(1);
466
+ const zmin = min(2);
467
+ const xmax = max(0);
468
+ const ymax = max(1);
469
+ const zmax = max(2);
470
+
471
+ basePolygon.updateBase([ [ xmin, ymin, zmax ], [ xmax, ymin, zmax ],
472
+ [ xmax, ymin, zmin ], [ xmin, ymin, zmin ] ]);
473
+ }
474
+ else
475
+ basePolygon.updateBase(null);
476
+ },
477
+ function(point2CanvasPos, point2WorldPos) {
478
+ // `marker2.update' makes sure marker's position has been updated from its default [0,0,0]
479
+ // This works around an unidentified bug somewhere around OcclusionLayer, that causes error
480
+ // [.WebGL-0x13400c47e00] GL_INVALID_OPERATION: Vertex buffer is not big enough for the draw call
481
+ marker2.update(point2WorldPos);
482
+
483
+ marker1.destroy();
484
+ marker2.destroy();
485
+ basePolygon.destroy();
486
+ updatePointerLens(null);
487
+
488
+ const min = (idx) => Math.min(point1WorldPos[idx], point2WorldPos[idx]);
489
+ const max = (idx) => Math.max(point1WorldPos[idx], point2WorldPos[idx]);
490
+
491
+ const xmin = min(0);
492
+ const zmin = min(2);
493
+ const xmax = max(0);
494
+ const zmax = max(2);
495
+
496
+ const zone = zonesPlugin.createZone(
497
+ {
498
+ id: math.createUUID(),
499
+ geometry: {
500
+ planeCoordinates: [
501
+ [ xmin, zmax ],
502
+ [ xmax, zmax ],
503
+ [ xmax, zmin ],
504
+ [ xmin, zmin ]
505
+ ],
506
+ altitude: zoneAltitude,
507
+ height: zoneHeight
508
+ },
509
+ alpha: zoneAlpha,
510
+ color: zoneColor
511
+ });
512
+
513
+ onZoneCreated(zone);
514
+ });
515
+ });
516
+
517
+ return {
518
+ deactivate: function() {
519
+ deactivatePointSelection();
520
+ marker1.destroy();
521
+ marker2.destroy();
522
+ basePolygon.destroy();
523
+ updatePointerLens(null);
524
+ }
525
+ };
526
+ };
527
+
528
+ const mousePointSelector = function(viewer, ray2WorldPos) {
529
+ return function(onCancel, onChange, onCommit) {
530
+ const scene = viewer.scene;
531
+ const canvas = scene.canvas.canvas;
532
+ const moveTolerance = 20;
533
+
534
+ const copyCanvasPos = (event, vec2) => {
535
+ vec2[0] = event.clientX;
536
+ vec2[1] = event.clientY;
537
+ transformToNode(canvas.ownerDocument.body, canvas, vec2);
538
+ return vec2;
539
+ };
540
+
541
+ const pickWorldPos = canvasPos => {
542
+ const origin = math.vec3();
543
+ const direction = math.vec3();
544
+ math.canvasPosToWorldRay(canvas, scene.camera.viewMatrix, scene.camera.projMatrix, canvasPos, origin, direction);
545
+ return ray2WorldPos(origin, direction);
546
+ };
547
+
548
+ let buttonDown = false;
549
+ const resetAction = function() {
550
+ buttonDown = false;
551
+ };
552
+
553
+ const cleanup = function() {
554
+ resetAction();
555
+ canvas.removeEventListener("mousedown", onMouseDown);
556
+ canvas.removeEventListener("mousemove", onMouseMove);
557
+ viewer.cameraControl.off(onCameraControlRayMove);
558
+ canvas.removeEventListener("mouseup", onMouseUp);
559
+ };
560
+
561
+ const startCanvasPos = math.vec2();
562
+ const onMouseDown = function(event) {
563
+ if (event.which === 1)
564
+ {
565
+ copyCanvasPos(event, startCanvasPos);
566
+ buttonDown = true;
567
+ }
568
+ };
569
+ canvas.addEventListener("mousedown", onMouseDown);
570
+
571
+ const onMouseMove = function(event) {
572
+ const canvasPos = copyCanvasPos(event, math.vec2());
573
+ if (buttonDown && math.distVec2(startCanvasPos, canvasPos) > moveTolerance)
574
+ {
575
+ resetAction();
576
+ onCancel();
577
+ }
578
+ };
579
+ canvas.addEventListener("mousemove", onMouseMove);
580
+
581
+ const onCameraControlRayMove = viewer.cameraControl.on(
582
+ "rayMove",
583
+ event => {
584
+ const canvasPos = event.canvasPos;
585
+ onChange(canvasPos, pickWorldPos(canvasPos));
586
+ });
587
+
588
+ const onMouseUp = function(event) {
589
+ if ((event.which === 1) && buttonDown)
590
+ {
591
+ cleanup();
592
+ const canvasPos = copyCanvasPos(event, math.vec2());
593
+ onCommit(canvasPos, pickWorldPos(canvasPos));
594
+ }
595
+ };
596
+ canvas.addEventListener("mouseup", onMouseUp);
597
+
598
+ return cleanup;
599
+ };
600
+ };
601
+
602
+ const touchPointSelector = function(viewer, pointerCircle, ray2WorldPos) {
603
+ return function(onCancel, onChange, onCommit) {
604
+ const scene = viewer.scene;
605
+ const canvas = scene.canvas.canvas;
606
+ const longTouchTimeoutMs = 300;
607
+ const moveTolerance = 20;
608
+
609
+ const copyCanvasPos = (event, vec2) => {
610
+ vec2[0] = event.clientX;
611
+ vec2[1] = event.clientY;
612
+ transformToNode(canvas.ownerDocument.body, canvas, vec2);
613
+ return vec2;
614
+ };
615
+
616
+ const pickWorldPos = canvasPos => {
617
+ const origin = math.vec3();
618
+ const direction = math.vec3();
619
+ math.canvasPosToWorldRay(canvas, scene.camera.viewMatrix, scene.camera.projMatrix, canvasPos, origin, direction);
620
+ return ray2WorldPos(origin, direction);
621
+ };
622
+
623
+ let longTouchTimeout = null;
624
+ const nop = () => { };
625
+ let onSingleTouchMove = nop;
626
+ let startTouchIdentifier;
627
+
628
+ const resetAction = function() {
629
+ pointerCircle.stop();
630
+ clearTimeout(longTouchTimeout);
631
+ viewer.cameraControl.active = true;
632
+ onSingleTouchMove = nop;
633
+ startTouchIdentifier = null;
634
+ };
635
+
636
+ const cleanup = function() {
637
+ resetAction();
638
+ canvas.removeEventListener("touchstart", onCanvasTouchStart);
639
+ canvas.removeEventListener("touchmove", onCanvasTouchMove);
640
+ canvas.removeEventListener("touchend", onCanvasTouchEnd);
641
+ };
642
+
643
+ const onCanvasTouchStart = function(event) {
644
+ const touches = event.touches;
645
+
646
+ if (touches.length !== 1)
647
+ {
648
+ resetAction();
649
+ onCancel();
650
+ }
651
+ else
652
+ {
653
+ const startTouch = touches[0];
654
+ const startCanvasPos = copyCanvasPos(startTouch, math.vec2());
655
+
656
+ const startWorldPos = pickWorldPos(startCanvasPos);
657
+ if (startWorldPos)
658
+ {
659
+ startTouchIdentifier = startTouch.identifier;
660
+
661
+ onSingleTouchMove = canvasPos => {
662
+ if (math.distVec2(startCanvasPos, canvasPos) > moveTolerance)
663
+ {
664
+ resetAction();
665
+ }
666
+ };
667
+
668
+ longTouchTimeout = setTimeout(
669
+ function() {
670
+ pointerCircle.start(startCanvasPos);
671
+
672
+ longTouchTimeout = setTimeout(
673
+ function() {
674
+ pointerCircle.stop();
675
+
676
+ viewer.cameraControl.active = false;
677
+
678
+ onSingleTouchMove = canvasPos => {
679
+ onChange(canvasPos, pickWorldPos(canvasPos));
680
+ };
681
+
682
+ onSingleTouchMove(startCanvasPos);
683
+ },
684
+ longTouchTimeoutMs);
685
+ },
686
+ 250);
687
+ }
688
+ }
689
+ };
690
+ canvas.addEventListener("touchstart", onCanvasTouchStart, {passive: true});
691
+
692
+ // canvas.addEventListener("touchcancel", e => console.log("touchcancel", e), {passive: true});
693
+
694
+ const onCanvasTouchMove = function(event) {
695
+ const touch = [...event.changedTouches].find(e => e.identifier === startTouchIdentifier);
696
+ if (touch)
697
+ {
698
+ onSingleTouchMove(copyCanvasPos(touch, math.vec2()));
699
+ }
700
+ };
701
+ canvas.addEventListener("touchmove", onCanvasTouchMove, {passive: true});
702
+
703
+ const onCanvasTouchEnd = function(event) {
704
+ const touch = [...event.changedTouches].find(e => e.identifier === startTouchIdentifier);
705
+ if (touch)
706
+ {
707
+ cleanup();
708
+ const canvasPos = copyCanvasPos(touch, math.vec2());
709
+ onCommit(canvasPos, pickWorldPos(canvasPos));
710
+ }
711
+ };
712
+ canvas.addEventListener("touchend", onCanvasTouchEnd, {passive: true});
713
+
714
+ return cleanup;
715
+ };
716
+ };
717
+
718
+ const planeIntersect = function(p0, n, origin, direction) {
719
+ const t = - (math.dotVec3(origin, n) - p0) / math.dotVec3(direction, n);
720
+ if (false) // (t < 0)
721
+ {
722
+ return false;
723
+ }
724
+ else
725
+ {
726
+ const worldPos = math.vec3();
727
+ math.mulVec3Scalar(direction, t, worldPos);
728
+ math.addVec3(origin, worldPos, worldPos);
729
+ return worldPos;
730
+ }
731
+ };
732
+
733
+ /**
734
+ * @desc Renders a transparent box between two 3D points.
735
+ *
736
+ * See {@link ZonesPlugin} for more info.
737
+ */
738
+
739
+ class Zone extends Component {
740
+
741
+ /**
742
+ * @private
743
+ */
744
+ constructor(plugin, cfg = {}) {
745
+
746
+ super(plugin.viewer.scene, cfg);
747
+
748
+ /**
749
+ * The {@link ZonesPlugin} that owns this Zone.
750
+ * @type {ZonesPlugin}
751
+ */
752
+ this.plugin = plugin;
753
+
754
+ this._container = cfg.container;
755
+ if (!this._container) {
756
+ throw "config missing: container";
757
+ }
758
+
759
+ this._eventSubs = {};
760
+
761
+ const scene = this.plugin.viewer.scene;
762
+
763
+ this._geometry = cfg.geometry;
764
+
765
+ const onMouseOver = cfg.onMouseOver ? (event) => {
766
+ cfg.onMouseOver(event, this);
767
+ this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mouseover', event));
768
+ } : null;
769
+
770
+ const onMouseLeave = cfg.onMouseLeave ? (event) => {
771
+ cfg.onMouseLeave(event, this);
772
+ this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mouseleave', event));
773
+ } : null;
774
+
775
+ const onMouseDown = (event) => {
776
+ this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mousedown', event));
777
+ } ;
778
+
779
+ const onMouseUp = (event) => {
780
+ this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mouseup', event));
781
+ };
782
+
783
+ const onMouseMove = (event) => {
784
+ this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new MouseEvent('mousemove', event));
785
+ };
786
+
787
+ const onContextMenu = cfg.onContextMenu ? (event) => {
788
+ cfg.onContextMenu(event, this);
789
+ } : null;
790
+
791
+ const onMouseWheel = (event) => {
792
+ this.plugin.viewer.scene.canvas.canvas.dispatchEvent(new WheelEvent('wheel', event));
793
+ };
794
+
795
+ this._alpha = (("alpha" in cfg) && (cfg.alpha !== undefined)) ? cfg.alpha : 0.5;
796
+ this.color = cfg.color;
797
+
798
+ this._visible = true;
799
+
800
+ this._rebuildMesh();
801
+ }
802
+
803
+ _rebuildMesh() {
804
+ const scene = this.plugin.viewer.scene;
805
+ const planeCoords = this._geometry.planeCoordinates.slice();
806
+ const downward = this._geometry.height < 0;
807
+ const altitude = this._geometry.altitude + (downward ? this._geometry.height : 0);
808
+ const height = this._geometry.height * (downward ? -1 : 1);
809
+
810
+ const [ baseVertices, baseTriangles ] = triangulateEarClipping(planeCoords); // TODO: prevent crossing edges
811
+
812
+ const pos = [ ];
813
+ const ind = [ ];
814
+
815
+
816
+ const addPlane = (isCeiling) => {
817
+ const baseIdx = pos.length;
818
+
819
+ for (let c of baseVertices) {
820
+ pos.push([ c[0], altitude + (isCeiling ? height : 0), c[1] ]);
821
+ }
822
+
823
+ for (let t of baseTriangles) {
824
+ ind.push(...(isCeiling ? t : t.slice(0).reverse()).map(i => i + baseIdx));
825
+ }
826
+ };
827
+ addPlane(false); // floor
828
+ addPlane(true); // ceiling
829
+
830
+
831
+ // sides
832
+ for (let i = 0; i < baseVertices.length; ++i) {
833
+ const a = baseVertices[i];
834
+ const b = baseVertices[(i+1) % baseVertices.length];
835
+ const f = altitude;
836
+ const c = altitude + height;
837
+
838
+ const baseIdx = pos.length;
839
+
840
+ pos.push(
841
+ [ a[0], f, a[1] ],
842
+ [ b[0], f, b[1] ],
843
+ [ b[0], c, b[1] ],
844
+ [ a[0], c, a[1] ]
845
+ );
846
+
847
+ ind.push(...[ 0, 1, 2, 0, 2, 3 ].map(i => i + baseIdx));
848
+ }
849
+
850
+
851
+ if (this._zoneMesh) {
852
+ this._zoneMesh.destroy();
853
+ }
854
+
855
+
856
+ const positions = [].concat(...pos);
857
+ this._zoneMesh = new Mesh(scene, {
858
+ edges: this._edges,
859
+ geometry: new ReadableGeometry(
860
+ scene,
861
+ {
862
+ positions: positions,
863
+ indices: ind,
864
+ normals: math.buildNormals(positions, ind)
865
+ }),
866
+ material: new PhongMaterial(scene, {
867
+ alpha: this._alpha,
868
+ backfaces: true,
869
+ diffuse: hex2rgb(this._color)
870
+ }),
871
+ visible: this._visible
872
+ });
873
+ this._zoneMesh.highlighted = this._highlighted;
874
+
875
+ this._zoneMesh.zone = this;
876
+
877
+
878
+ const min = idx => Math.min(...pos.map(p => p[idx]));
879
+ const max = idx => Math.max(...pos.map(p => p[idx]));
880
+
881
+ const xmin = min(0);
882
+ const ymin = min(1);
883
+ const zmin = min(2);
884
+ const xmax = max(0);
885
+ const ymax = max(1);
886
+ const zmax = max(2);
887
+
888
+ this._center = math.vec3([ (xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2 ]);
889
+ }
890
+
891
+ sectionedAverage(sectionPlanes) {
892
+ const planeCoords = this._geometry.planeCoordinates.slice();
893
+
894
+ let faces = [ ];
895
+ {
896
+ const h = this._geometry.height;
897
+ const a = this._geometry.altitude;
898
+ const c = a + Math.max(0, h);
899
+ const f = a + Math.min(0, h);
900
+
901
+ const addPlane = (isCeiling) => {
902
+ const face = planeCoords.map(p => [ p[0], isCeiling ? c : f, p[1] ]);
903
+ faces.push(isCeiling ? face : face.slice(0).reverse());
904
+ };
905
+ addPlane(true); // ceiling
906
+ addPlane(false); // floor
907
+
908
+ // sides
909
+ const p = (idx, y) => [ planeCoords[idx][0], y, planeCoords[idx][1] ];
910
+ for (let i = 0; i < planeCoords.length; ++i)
911
+ {
912
+ const j = (i + 1) % planeCoords.length;
913
+ faces.push([ p(i, f), p(j, f), p(j, c), p(i, c) ]);
914
+ }
915
+ }
916
+
917
+ for (const s of sectionPlanes)
918
+ {
919
+ const dir = s.dir;
920
+ const dist = s.dist;
921
+ const newFaces = [ ];
922
+
923
+ for (const face of faces)
924
+ {
925
+ const EPSILON = 1e-5;
926
+ const COPLANAR = 0;
927
+ const FRONT = 1;
928
+ const BACK = 2;
929
+ const SPANNING = 3;
930
+
931
+ // Classify each point as well as the entire polygon into one of the above four classes.
932
+ let polygonType = 0;
933
+ const types = [ ];
934
+ for (let i = 0; i < face.length; i++) {
935
+ const t = math.dotVec3(dir, face[i]) + dist;
936
+ const type = (t < -EPSILON) ? BACK : (t > EPSILON) ? FRONT : COPLANAR;
937
+ polygonType |= type;
938
+ types.push(type);
939
+ }
940
+
941
+ // Put the polygon in the correct list, splitting it when necessary.
942
+ switch (polygonType) {
943
+ case COPLANAR:
944
+ newFaces.push(face);
945
+ break;
946
+ case FRONT:
947
+ newFaces.push(face);
948
+ break;
949
+ case BACK:
950
+ break;
951
+ case SPANNING:
952
+ const f = [ ];
953
+ for (let i = 0; i < face.length; i++)
954
+ {
955
+ var j = (i + 1) % face.length;
956
+ const ti = types[i];
957
+ const tj = types[j];
958
+ const vi = face[i];
959
+ const vj = face[j];
960
+ if (ti !== BACK)
961
+ {
962
+ f.push(vi);
963
+ }
964
+
965
+ if ((ti | tj) === SPANNING)
966
+ {
967
+ const diff = math.vec3();
968
+ math.subVec3(vj, vi, diff);
969
+ const t = - (dist + math.dotVec3(dir, vi)) / math.dotVec3(dir, diff);
970
+ const v = [0,0,0];
971
+ math.lerpVec3(t, 0, 1, vi, vj, v);
972
+ f.push(v);
973
+ }
974
+ }
975
+ if (f.length >= 3)
976
+ {
977
+ newFaces.push(f);
978
+ }
979
+ break;
980
+ }
981
+ }
982
+
983
+ faces = newFaces;
984
+ }
985
+
986
+ if (faces.length === 0)
987
+ {
988
+ return null;
989
+ }
990
+ else
991
+ {
992
+ const avg = math.vec3([ 0, 0, 0 ]);
993
+ const unique = new Set();
994
+
995
+ for (const f of faces)
996
+ {
997
+ for (const p of f)
998
+ {
999
+ const id = p.map(x => x.toFixed(3)).join(":");
1000
+ if (! (unique.has(id)))
1001
+ {
1002
+ unique.add(id);
1003
+ math.addVec3(avg, p, avg);
1004
+ }
1005
+ }
1006
+ }
1007
+
1008
+ math.mulVec3Scalar(avg, 1 / unique.size, avg);
1009
+
1010
+ return avg;
1011
+ }
1012
+ }
1013
+
1014
+ get center() {
1015
+ return this._center;
1016
+ }
1017
+
1018
+ get altitude() {
1019
+ return this._geometry.altitude;
1020
+ }
1021
+
1022
+ set altitude(value) {
1023
+ this._geometry.altitude = value;
1024
+ this._rebuildMesh();
1025
+ }
1026
+
1027
+ get height() {
1028
+ return this._geometry.height;
1029
+ }
1030
+
1031
+ set height(value) {
1032
+ this._geometry.height = value;
1033
+ this._rebuildMesh();
1034
+ }
1035
+
1036
+ get highlighted() {
1037
+ return this._highlighted;
1038
+ }
1039
+
1040
+ set highlighted(value)
1041
+ {
1042
+ this._highlighted = value;
1043
+ if (this._zoneMesh) {
1044
+ this._zoneMesh.highlighted = value;
1045
+ }
1046
+ }
1047
+
1048
+ set color(value) {
1049
+ this._color = value;
1050
+ if (this._zoneMesh) {
1051
+ this._zoneMesh.material.diffuse = hex2rgb(this._color);
1052
+ }
1053
+ }
1054
+
1055
+ get color() {
1056
+ return this._color;
1057
+ }
1058
+
1059
+ set alpha(value) {
1060
+ this._alpha = value;
1061
+ if (this._zoneMesh) {
1062
+ this._zoneMesh.material.alpha = this._alpha;
1063
+ }
1064
+ }
1065
+
1066
+ get alpha() {
1067
+ return this._alpha;
1068
+ }
1069
+
1070
+ get edges() {
1071
+ return this._edges;
1072
+ }
1073
+
1074
+ set edges(edges) {
1075
+ this._edges = edges;
1076
+ if (this._zoneMesh) {
1077
+ this._zoneMesh.edges = this._edges;
1078
+ }
1079
+ }
1080
+
1081
+ /**
1082
+ * Sets whether this Zone is visible or not.
1083
+ *
1084
+ * @type {Boolean}
1085
+ */
1086
+ set visible(value) {
1087
+ this._visible = !!value;
1088
+ this._zoneMesh.visible = this._visible;
1089
+ this._needUpdate();
1090
+ }
1091
+
1092
+ /**
1093
+ * Gets whether this Zone is visible or not.
1094
+ *
1095
+ * @type {Boolean}
1096
+ */
1097
+ get visible() {
1098
+ return this._visible;
1099
+ }
1100
+
1101
+ /**
1102
+ * Gets this Zone as JSON.
1103
+ *
1104
+ * @returns {JSON}
1105
+ */
1106
+
1107
+ getJSON() {
1108
+ return {
1109
+ id: this.id,
1110
+ geometry: this._geometry,
1111
+ alpha: this._alpha,
1112
+ color: this._color
1113
+ };
1114
+ }
1115
+
1116
+ duplicate() {
1117
+ return this.plugin.createZone(
1118
+ {
1119
+ id: math.createUUID(),
1120
+ geometry: {
1121
+ planeCoordinates: this._geometry.planeCoordinates.map(c => c.slice()),
1122
+ altitude: this._geometry.altitude,
1123
+ height: this._geometry.height
1124
+ },
1125
+ alpha: this._alpha,
1126
+ color: this._color
1127
+ });
1128
+ }
1129
+
1130
+ /**
1131
+ * @private
1132
+ */
1133
+ destroy() {
1134
+ this._zoneMesh.destroy();
1135
+ super.destroy();
1136
+ }
1137
+ }
1138
+
1139
+ /**
1140
+ * Creates {@link Zone}s in a {@link ZonesPlugin} from mouse input.
1141
+ *
1142
+ * ## Usage
1143
+ *
1144
+ * [[Run example](/examples/measurement/#distance_createWithMouse_snapping)]
1145
+ *
1146
+ * ````javascript
1147
+ * import {Viewer, XKTLoaderPlugin, ZonesPlugin, ZonesMouseControl} from "xeokit-sdk.es.js";
1148
+ *
1149
+ * const viewer = new Viewer({
1150
+ * canvasId: "myCanvas",
1151
+ * });
1152
+ *
1153
+ * viewer.camera.eye = [-3.93, 2.85, 27.01];
1154
+ * viewer.camera.look = [4.40, 3.72, 8.89];
1155
+ * viewer.camera.up = [-0.01, 0.99, 0.039];
1156
+ *
1157
+ * const xktLoader = new XKTLoaderPlugin(viewer);
1158
+ *
1159
+ * const sceneModel = xktLoader.load({
1160
+ * id: "myModel",
1161
+ * src: "Duplex.xkt"
1162
+ * });
1163
+ *
1164
+ * const zones = new ZonesPlugin(viewer);
1165
+ *
1166
+ * const zonesControl = new ZonesMouseControl(Zones)
1167
+ * ````
1168
+ */
1169
+ class ZonesMouseControl extends Component {
1170
+
1171
+ /**
1172
+ * Creates a ZonesMouseControl bound to the given ZonesPlugin.
1173
+ *
1174
+ * @param {ZonesPlugin} zonesPlugin The ZonesPlugin to control.
1175
+ * @param [cfg] Configuration
1176
+ * @param {PointerLens} [cfg.pointerLens] A PointerLens to use to provide a magnified view of the cursor when snapping is enabled.
1177
+ */
1178
+ constructor(zonesPlugin, cfg = {}) {
1179
+ super(zonesPlugin.viewer.scene);
1180
+
1181
+ this.zonesPlugin = zonesPlugin;
1182
+ this.pointerLens = cfg.pointerLens;
1183
+ this._deactivate = null;
1184
+ }
1185
+
1186
+ get active() {
1187
+ return !! this._deactivate;
1188
+ }
1189
+
1190
+ activate(zoneAltitude, zoneHeight, zoneColor, zoneAlpha) {
1191
+
1192
+ if (this._deactivate) {
1193
+ return;
1194
+ }
1195
+
1196
+ if (typeof(zoneAltitude) === "object" && (zoneAltitude !== null)) {
1197
+ const params = zoneAltitude;
1198
+ const param = (name, defaultValue) => {
1199
+ if (name in params) {
1200
+ return params[name];
1201
+ } else if (defaultValue !== undefined) {
1202
+ return defaultValue;
1203
+ } else {
1204
+ throw "config missing: " + name;
1205
+ }
1206
+ };
1207
+
1208
+ zoneAltitude = param("altitude");
1209
+ zoneHeight = param("height");
1210
+ zoneColor = param("color", "#008000");
1211
+ zoneAlpha = param("alpha", 0.5);
1212
+ }
1213
+
1214
+ const zonesPlugin = this.zonesPlugin;
1215
+ const viewer = zonesPlugin.viewer;
1216
+ const scene = viewer.scene;
1217
+ const self = this;
1218
+
1219
+ const select3dPoint = mousePointSelector(
1220
+ viewer,
1221
+ function(origin, direction) {
1222
+ return planeIntersect(zoneAltitude, math.vec3([ 0, 1, 0 ]), origin, direction);
1223
+ });
1224
+
1225
+ (function rec() {
1226
+ self._deactivate = startAAZoneCreateUI(
1227
+ scene, zoneAltitude, zoneHeight, zoneColor, zoneAlpha, self.pointerLens, zonesPlugin, select3dPoint,
1228
+ zone => {
1229
+ let reactivate = true;
1230
+ self._deactivate = () => { reactivate = false; };
1231
+ self.fire("zoneEnd", zone);
1232
+ if (reactivate)
1233
+ {
1234
+ rec();
1235
+ }
1236
+ }).deactivate;
1237
+ })();
1238
+ }
1239
+
1240
+ deactivate() {
1241
+ if (this._deactivate)
1242
+ {
1243
+ this._deactivate();
1244
+ this._deactivate = null;
1245
+ }
1246
+ }
1247
+
1248
+ /**
1249
+ * Destroys this ZonesMouseControl.
1250
+ *
1251
+ * Destroys any {@link Zone} under construction by this ZonesMouseControl.
1252
+ */
1253
+ destroy() {
1254
+ this.deactivate();
1255
+ super.destroy();
1256
+ }
1257
+ }
1258
+
1259
+ /**
1260
+ * ZonesPlugin documentation to be added, mostly compatible with DistanceMeasurementsPlugin.
1261
+ */
1262
+ class ZonesPlugin extends Plugin {
1263
+
1264
+ /**
1265
+ * @constructor
1266
+ * @param {Viewer} viewer The Viewer.
1267
+ * @param {Object} [cfg] Plugin configuration.
1268
+ * @param {String} [cfg.id="Zones"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}.
1269
+ * @param {HTMLElement} [cfg.container] Container DOM element for markers and labels. Defaults to ````document.body````.
1270
+ * @param {string} [cfg.defaultColor=#00BBFF] The default color of the length dots, wire and label.
1271
+ * @param {number} [cfg.zIndex] If set, the wires, dots and labels will have this zIndex (+1 for dots and +2 for labels).
1272
+ * @param {PointerCircle} [cfg.pointerLens] A PointerLens to help the user position the pointer. This can be shared with other plugins.
1273
+ */
1274
+ constructor(viewer, cfg = {}) {
1275
+
1276
+ super("Zones", viewer);
1277
+
1278
+ this._pointerLens = cfg.pointerLens;
1279
+
1280
+ this._container = cfg.container || document.body;
1281
+
1282
+ this._zones = [ ];
1283
+
1284
+ this.defaultColor = cfg.defaultColor !== undefined ? cfg.defaultColor : "#00BBFF";
1285
+ this.zIndex = cfg.zIndex || 10000;
1286
+
1287
+ this._onMouseOver = (event, zone) => {
1288
+ this.fire("mouseOver", {
1289
+ plugin: this,
1290
+ zone,
1291
+ event
1292
+ });
1293
+ };
1294
+
1295
+ this._onMouseLeave = (event, zone) => {
1296
+ this.fire("mouseLeave", {
1297
+ plugin: this,
1298
+ zone,
1299
+ event
1300
+ });
1301
+ };
1302
+
1303
+ this._onContextMenu = (event, zone) => {
1304
+ this.fire("contextMenu", {
1305
+ plugin: this,
1306
+ zone,
1307
+ event
1308
+ });
1309
+ };
1310
+ }
1311
+
1312
+ /**
1313
+ * Creates a {@link Zone}.
1314
+ *
1315
+ * The Zone is then registered by {@link Zone#id} in {@link ZonesPlugin#zones}.
1316
+ *
1317
+ * @param {Object} params {@link Zone} configuration.
1318
+ * @param {String} params.id Unique ID to assign to {@link Zone#id}. The Zone will be registered by this in {@link ZonesPlugin#zones} and {@link Scene.components}. Must be unique among all components in the {@link Viewer}.
1319
+ * @param {Number[]} params.origin.worldPos Origin World-space 3D position.
1320
+ * @param {Entity} params.origin.entity Origin Entity.
1321
+ * @param {Number[]} params.target.worldPos Target World-space 3D position.
1322
+ * @param {Entity} params.target.entity Target Entity.
1323
+ * @param {string} [params.color] The color of the length dot, wire and label.
1324
+ * @returns {Zone} The new {@link Zone}.
1325
+ */
1326
+ createZone(params = {}) {
1327
+ if (this.viewer.scene.components[params.id]) {
1328
+ this.error("Viewer scene component with this ID already exists: " + params.id);
1329
+ delete params.id;
1330
+ }
1331
+
1332
+ const zone = new Zone(this, {
1333
+ id: params.id,
1334
+ plugin: this,
1335
+ container: this._container,
1336
+ geometry: params.geometry,
1337
+ alpha: params.alpha,
1338
+ color: params.color,
1339
+ onMouseOver: this._onMouseOver,
1340
+ onMouseLeave: this._onMouseLeave,
1341
+ onContextMenu: this._onContextMenu
1342
+ });
1343
+ this._zones.push(zone);
1344
+ zone.on("destroyed", () => {
1345
+ const idx = this._zones.indexOf(zone);
1346
+ if (idx >= 0) {
1347
+ this._zones.splice(idx, 1);
1348
+ }
1349
+ });
1350
+ this.fire("zoneCreated", zone);
1351
+ return zone;
1352
+ }
1353
+
1354
+ /**
1355
+ * Gets the existing {@link Zone}s, each mapped to its {@link Zone#id}.
1356
+ *
1357
+ * @type {{String:Zone}}
1358
+ */
1359
+ get zones() {
1360
+ return this._zones;
1361
+ }
1362
+
1363
+ /**
1364
+ * Destroys this ZonesPlugin.
1365
+ *
1366
+ * Destroys all {@link Zone}s first.
1367
+ */
1368
+ destroy() {
1369
+ super.destroy();
1370
+ }
1371
+ }
1372
+
1373
+ import {PointerCircle} from "../../extras/PointerCircle/PointerCircle.js";
1374
+
1375
+ export class ZonesTouchControl extends Component {
1376
+
1377
+ constructor(zonesPlugin, cfg = {}) {
1378
+ super(zonesPlugin.viewer.scene);
1379
+
1380
+ this.zonesPlugin = zonesPlugin;
1381
+ this.pointerLens = cfg.pointerLens;
1382
+ this.pointerCircle = new PointerCircle(zonesPlugin.viewer);
1383
+ this._deactivate = null;
1384
+ }
1385
+
1386
+ get active() {
1387
+ return !! this._deactivate;
1388
+ }
1389
+
1390
+ activate(zoneAltitude, zoneHeight, zoneColor, zoneAlpha) {
1391
+
1392
+ if (typeof(zoneAltitude) === "object" && (zoneAltitude !== null)) {
1393
+ const params = zoneAltitude;
1394
+ const param = (name, defaultValue) => {
1395
+ if (name in params) {
1396
+ return params[name];
1397
+ } else if (defaultValue !== undefined) {
1398
+ return defaultValue;
1399
+ } else {
1400
+ throw "config missing: " + name;
1401
+ }
1402
+ };
1403
+
1404
+ zoneAltitude = param("altitude");
1405
+ zoneHeight = param("height");
1406
+ zoneColor = param("color", "#008000");
1407
+ zoneAlpha = param("alpha", 0.5);
1408
+ }
1409
+
1410
+ if (this._deactivate) {
1411
+ return;
1412
+ }
1413
+
1414
+ const zonesPlugin = this.zonesPlugin;
1415
+ const viewer = zonesPlugin.viewer;
1416
+ const scene = viewer.scene;
1417
+ const self = this;
1418
+
1419
+ const select3dPoint = touchPointSelector(
1420
+ viewer,
1421
+ this.pointerCircle,
1422
+ function(origin, direction) {
1423
+ return planeIntersect(zoneAltitude, math.vec3([ 0, 1, 0 ]), origin, direction);
1424
+ });
1425
+
1426
+ (function rec() {
1427
+ self._deactivate = startAAZoneCreateUI(
1428
+ scene, zoneAltitude, zoneHeight, zoneColor, zoneAlpha, self.pointerLens, zonesPlugin, select3dPoint,
1429
+ zone => {
1430
+ let reactivate = true;
1431
+ self._deactivate = () => { reactivate = false; };
1432
+ self.fire("zoneEnd", zone);
1433
+ if (reactivate)
1434
+ {
1435
+ rec();
1436
+ }
1437
+ }).deactivate;
1438
+ })();
1439
+ }
1440
+
1441
+ deactivate() {
1442
+ if (this._deactivate)
1443
+ {
1444
+ this._deactivate();
1445
+ this._deactivate = null;
1446
+ }
1447
+ }
1448
+
1449
+ destroy() {
1450
+ this.deactivate();
1451
+ super.destroy();
1452
+ }
1453
+ }
1454
+
1455
+ const startPolysurfaceZoneCreateUI = function(scene, zoneAltitude, zoneHeight, zoneColor, zoneAlpha, pointerLens, zonesPlugin, select3dPoint, onZoneCreated) {
1456
+ const updatePointerLens = (pointerLens
1457
+ ? function(canvasPos) {
1458
+ pointerLens.visible = !! canvasPos;
1459
+ if (canvasPos)
1460
+ {
1461
+ pointerLens.canvasPos = canvasPos;
1462
+ }
1463
+ }
1464
+ : () => { });
1465
+
1466
+ let deactivatePointSelection;
1467
+ const cleanups = [ () => updatePointerLens(null) ];
1468
+
1469
+ const basePolygon = basePolygon3D(scene, zoneColor, zoneAlpha);
1470
+ cleanups.push(() => basePolygon.destroy());
1471
+
1472
+ (function selectNextPoint(markers) {
1473
+ const marker = marker3D(scene, zoneColor);
1474
+ const wire = (markers.length > 0) && wire3D(scene, zoneColor, markers[markers.length - 1].getWorldPos());
1475
+
1476
+ cleanups.push(() => {
1477
+ marker.destroy();
1478
+ wire && wire.destroy();
1479
+ });
1480
+
1481
+ const firstMarker = (markers.length > 0) && markers[0];
1482
+ const getSnappedFirst = function(canvasPos) {
1483
+ const firstCanvasPos = firstMarker && firstMarker.getCanvasPos();
1484
+ const snapToFirst = firstCanvasPos && (math.distVec2(firstCanvasPos, canvasPos) < 10);
1485
+ return snapToFirst && { canvasPos: firstCanvasPos, worldPos: firstMarker.getWorldPos() };
1486
+ };
1487
+
1488
+ const lastSegmentIntersects = (function() {
1489
+ const onSegment = (p, q, r) => ((q[0] <= Math.max(p[0], r[0])) &&
1490
+ (q[0] >= Math.min(p[0], r[0])) &&
1491
+ (q[1] <= Math.max(p[1], r[1])) &&
1492
+ (q[1] >= Math.min(p[1], r[1])));
1493
+
1494
+ const orient = (p, q, r) => {
1495
+ const val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]);
1496
+ // collinear
1497
+ // clockwise
1498
+ // counterclockwise
1499
+ return ((val === 0) ? 0 : ((val > 0) ? 1 : 2));
1500
+ };
1501
+
1502
+ return function(pos2D, excludeFirstSegment) {
1503
+ const a = pos2D[pos2D.length - 2];
1504
+ const b = pos2D[pos2D.length - 1];
1505
+
1506
+ for (let i = excludeFirstSegment ? 1 : 0; i < pos2D.length - 2 - 1; ++i)
1507
+ {
1508
+ const c = pos2D[i];
1509
+ const d = pos2D[i + 1];
1510
+
1511
+ const o1 = orient(a, b, c);
1512
+ const o2 = orient(a, b, d);
1513
+ const o3 = orient(c, d, a);
1514
+ const o4 = orient(c, d, b);
1515
+
1516
+ if (((o1 !== o2) && (o3 !== o4)) || // General case
1517
+ ((o1 === 0) && onSegment(a, c, b)) || // a, b and c are collinear and c lies on segment ab
1518
+ ((o2 === 0) && onSegment(a, d, b)) || // a, b and d are collinear and d lies on segment ab
1519
+ ((o3 === 0) && onSegment(c, a, d)) || // c, d and a are collinear and a lies on segment cd
1520
+ ((o4 === 0) && onSegment(c, b, d))) // c, d and b are collinear and b lies on segment cd
1521
+ {
1522
+ return true;
1523
+ }
1524
+ }
1525
+
1526
+ return false;
1527
+ };
1528
+ })();
1529
+
1530
+ deactivatePointSelection = select3dPoint(
1531
+ () => {
1532
+ updatePointerLens(null);
1533
+ marker.update(null);
1534
+ wire && wire.update(null);
1535
+ basePolygon.updateBase((markers.length > 2) ? markers.map(m => m.getWorldPos()) : null);
1536
+ },
1537
+ (canvasPos, worldPos) => {
1538
+ const snappedFirst = (markers.length > 2) && getSnappedFirst(canvasPos);
1539
+ firstMarker && firstMarker.setHighlighted(!! snappedFirst);
1540
+ updatePointerLens(snappedFirst ? snappedFirst.canvasPos : canvasPos);
1541
+ marker.update((! snappedFirst) && worldPos);
1542
+ wire && wire.update(snappedFirst ? snappedFirst.worldPos : worldPos);
1543
+ if ((markers.length >= 2))
1544
+ {
1545
+ const pos = markers.map(m => m.getWorldPos()).concat(snappedFirst ? [] : [worldPos]);
1546
+ const inter = lastSegmentIntersects(pos.map(p => [ p[0], p[2] ]), snappedFirst);
1547
+ basePolygon.updateBase(inter ? null : pos);
1548
+ }
1549
+ else
1550
+ basePolygon.updateBase(null);
1551
+ },
1552
+ function(canvasPos, worldPos) {
1553
+ const snappedFirst = (markers.length > 2) && getSnappedFirst(canvasPos);
1554
+ const pos = markers.map(m => m.getWorldPos()).concat(snappedFirst ? [] : [worldPos]);
1555
+ basePolygon.updateBase(pos);
1556
+ const pos2D = pos.map(p => [ p[0], p[2] ]);
1557
+ if ((markers.length > 2) && lastSegmentIntersects(pos2D, snappedFirst))
1558
+ {
1559
+ cleanups.pop()();
1560
+ selectNextPoint(markers);
1561
+ }
1562
+ else if (snappedFirst)
1563
+ {
1564
+ // `marker2.update' makes sure marker's position has been updated from its default [0,0,0]
1565
+ // This works around an unidentified bug somewhere around OcclusionLayer, that causes error
1566
+ // [.WebGL-0x13400c47e00] GL_INVALID_OPERATION: Vertex buffer is not big enough for the draw call
1567
+ marker.update(worldPos);
1568
+
1569
+ cleanups.forEach(c => c());
1570
+ onZoneCreated(
1571
+ zonesPlugin.createZone(
1572
+ {
1573
+ id: math.createUUID(),
1574
+ geometry: {
1575
+ planeCoordinates: pos2D,
1576
+ altitude: zoneAltitude,
1577
+ height: zoneHeight
1578
+ },
1579
+ alpha: zoneAlpha,
1580
+ color: zoneColor
1581
+ }));
1582
+ }
1583
+ else
1584
+ {
1585
+ marker.update(worldPos);
1586
+ wire && wire.update(worldPos);
1587
+ selectNextPoint(markers.concat(marker));
1588
+ }
1589
+ });
1590
+ })([ ], null);
1591
+
1592
+ return {
1593
+ closeSurface: function() {
1594
+ throw "TODO";
1595
+ },
1596
+ deactivate: function() {
1597
+ deactivatePointSelection();
1598
+ cleanups.forEach(c => c());
1599
+ }
1600
+ };
1601
+ };
1602
+
1603
+ export class ZonesPolysurfaceMouseControl extends Component {
1604
+
1605
+ constructor(zonesPlugin, cfg = {}) {
1606
+ super(zonesPlugin.viewer.scene);
1607
+
1608
+ this.zonesPlugin = zonesPlugin;
1609
+ this.pointerLens = cfg.pointerLens;
1610
+ this._action = null;
1611
+ }
1612
+
1613
+ get active() {
1614
+ return !! this._action;
1615
+ }
1616
+
1617
+ activate(zoneAltitude, zoneHeight, zoneColor, zoneAlpha) {
1618
+
1619
+ if (typeof(zoneAltitude) === "object" && (zoneAltitude !== null)) {
1620
+ const params = zoneAltitude;
1621
+ const param = (name, defaultValue) => {
1622
+ if (name in params) {
1623
+ return params[name];
1624
+ } else if (defaultValue !== undefined) {
1625
+ return defaultValue;
1626
+ } else {
1627
+ throw "config missing: " + name;
1628
+ }
1629
+ };
1630
+
1631
+ zoneAltitude = param("altitude");
1632
+ zoneHeight = param("height");
1633
+ zoneColor = param("color", "#008000");
1634
+ zoneAlpha = param("alpha", 0.5);
1635
+ }
1636
+
1637
+ if (this._action) {
1638
+ return;
1639
+ }
1640
+
1641
+ const zonesPlugin = this.zonesPlugin;
1642
+ const viewer = zonesPlugin.viewer;
1643
+ const scene = viewer.scene;
1644
+ const self = this;
1645
+
1646
+ const select3dPoint = mousePointSelector(
1647
+ viewer,
1648
+ function(origin, direction) {
1649
+ return planeIntersect(zoneAltitude, math.vec3([ 0, 1, 0 ]), origin, direction);
1650
+ });
1651
+
1652
+ (function rec() {
1653
+ self._action = startPolysurfaceZoneCreateUI(
1654
+ scene, zoneAltitude, zoneHeight, zoneColor, zoneAlpha, self.pointerLens, zonesPlugin, select3dPoint,
1655
+ zone => {
1656
+ let reactivate = true;
1657
+ self._action = { deactivate: () => { reactivate = false; } };
1658
+ self.fire("zoneEnd", zone);
1659
+ if (reactivate)
1660
+ {
1661
+ rec();
1662
+ }
1663
+ });
1664
+ })();
1665
+ }
1666
+
1667
+ deactivate() {
1668
+ if (this._action)
1669
+ {
1670
+ this._action.deactivate();
1671
+ this._action = null;
1672
+ }
1673
+ }
1674
+
1675
+ destroy() {
1676
+ this.deactivate();
1677
+ super.destroy();
1678
+ }
1679
+ }
1680
+
1681
+ export class ZonesPolysurfaceTouchControl extends Component {
1682
+
1683
+ constructor(zonesPlugin, cfg = {}) {
1684
+ super(zonesPlugin.viewer.scene);
1685
+
1686
+ this.zonesPlugin = zonesPlugin;
1687
+ this.pointerLens = cfg.pointerLens;
1688
+ this.pointerCircle = new PointerCircle(zonesPlugin.viewer);
1689
+ this._action = null;
1690
+ }
1691
+
1692
+ get active() {
1693
+ return !! this._action;
1694
+ }
1695
+
1696
+ activate(zoneAltitude, zoneHeight, zoneColor, zoneAlpha) {
1697
+
1698
+ if (typeof(zoneAltitude) === "object" && (zoneAltitude !== null)) {
1699
+ const params = zoneAltitude;
1700
+ const param = (name, defaultValue) => {
1701
+ if (name in params) {
1702
+ return params[name];
1703
+ } else if (defaultValue !== undefined) {
1704
+ return defaultValue;
1705
+ } else {
1706
+ throw "config missing: " + name;
1707
+ }
1708
+ };
1709
+
1710
+ zoneAltitude = param("altitude");
1711
+ zoneHeight = param("height");
1712
+ zoneColor = param("color", "#008000");
1713
+ zoneAlpha = param("alpha", 0.5);
1714
+ }
1715
+
1716
+ if (this._action) {
1717
+ return;
1718
+ }
1719
+
1720
+ const zonesPlugin = this.zonesPlugin;
1721
+ const viewer = zonesPlugin.viewer;
1722
+ const scene = viewer.scene;
1723
+ const self = this;
1724
+
1725
+ const select3dPoint = touchPointSelector(
1726
+ viewer,
1727
+ this.pointerCircle,
1728
+ function(origin, direction) {
1729
+ return planeIntersect(zoneAltitude, math.vec3([ 0, 1, 0 ]), origin, direction);
1730
+ });
1731
+
1732
+ (function rec() {
1733
+ self._action = startPolysurfaceZoneCreateUI(
1734
+ scene, zoneAltitude, zoneHeight, zoneColor, zoneAlpha, self.pointerLens, zonesPlugin, select3dPoint,
1735
+ zone => {
1736
+ let reactivate = true;
1737
+ self._action = { deactivate: () => { reactivate = false; } };
1738
+ self.fire("zoneEnd", zone);
1739
+ if (reactivate)
1740
+ {
1741
+ rec();
1742
+ }
1743
+ });
1744
+ })();
1745
+ }
1746
+
1747
+ deactivate() {
1748
+ if (this._action)
1749
+ {
1750
+ this._action.deactivate();
1751
+ this._action = null;
1752
+ }
1753
+ }
1754
+
1755
+ destroy() {
1756
+ this.deactivate();
1757
+ super.destroy();
1758
+ }
1759
+ }
1760
+
1761
+ export class ZoneEditControl extends Component {
1762
+ constructor(zone, cfg, handleMouseEvents, handleTouchEvents) {
1763
+ super(zone.plugin.viewer.scene);
1764
+ const self = this;
1765
+
1766
+ const altitude = zone._geometry.altitude;
1767
+ const pointerLens = cfg && cfg.pointerLens;
1768
+ const updatePointerLens = (pointerLens
1769
+ ? function(canvasPos) {
1770
+ pointerLens.visible = !! canvasPos;
1771
+ if (canvasPos)
1772
+ {
1773
+ pointerLens.canvasPos = canvasPos;
1774
+ }
1775
+ }
1776
+ : () => { });
1777
+
1778
+ const dots = zone._geometry.planeCoordinates.map(planeCoord => {
1779
+ let initWorldPos, initPlaneCoord;
1780
+ const setPlaneCoord = function(coord) {
1781
+ planeCoord[0] = coord[0];
1782
+ planeCoord[1] = coord[1];
1783
+ try {
1784
+ zone._rebuildMesh();
1785
+ } catch (e) {
1786
+ if (zone._zoneMesh) {
1787
+ zone._zoneMesh.destroy();
1788
+ zone._zoneMesh = null;
1789
+ }
1790
+ }
1791
+ };
1792
+
1793
+ const dot = draggableDot3D(
1794
+ handleMouseEvents,
1795
+ handleTouchEvents,
1796
+ zone.plugin.viewer,
1797
+ math.vec3([ planeCoord[0], altitude, planeCoord[1] ]),
1798
+ zone._color,
1799
+ (orig, dir) => planeIntersect(altitude, math.vec3([ 0, 1, 0 ]), orig, dir),
1800
+ () => {
1801
+ initWorldPos = dot.getWorldPos().slice();
1802
+ initPlaneCoord = planeCoord.slice();
1803
+ set_other_dots_active(false, dot);
1804
+ },
1805
+ (canvasPos, worldPos) => {
1806
+ updatePointerLens(canvasPos);
1807
+ setPlaneCoord([ worldPos[0], worldPos[2] ]);
1808
+ },
1809
+ () => {
1810
+ if (zone._zoneMesh)
1811
+ {
1812
+ self.fire("edited");
1813
+ }
1814
+ else
1815
+ {
1816
+ dot.setWorldPos(initWorldPos);
1817
+ setPlaneCoord(initPlaneCoord);
1818
+ }
1819
+ updatePointerLens(null);
1820
+ set_other_dots_active(true, dot);
1821
+ });
1822
+ return dot;
1823
+ });
1824
+ const set_other_dots_active = (active, dot) => dots.forEach(d => (d !== dot) && d.setActive(active));
1825
+ set_other_dots_active(true);
1826
+
1827
+ const cleanup = function() {
1828
+ dots.forEach(m => m.destroy());
1829
+ updatePointerLens(null);
1830
+ };
1831
+
1832
+ const destroyCb = zone.on("destroyed", cleanup);
1833
+
1834
+ this._deactivate = function() {
1835
+ zone.off("destroyed", destroyCb);
1836
+ cleanup();
1837
+ };
1838
+ }
1839
+
1840
+ deactivate() {
1841
+ this._deactivate();
1842
+ super.destroy();
1843
+ }
1844
+ }
1845
+
1846
+ export class ZoneEditMouseControl extends ZoneEditControl {
1847
+ constructor(zone, cfg) {
1848
+ super(zone, cfg, true, false);
1849
+ }
1850
+ }
1851
+
1852
+ export class ZoneEditTouchControl extends ZoneEditControl {
1853
+ constructor(zone, cfg) {
1854
+ super(zone, cfg, false, true);
1855
+ }
1856
+ }
1857
+
1858
+
1859
+ export class ZoneTranslateControl extends Component {
1860
+ constructor(zone, cfg, handleMouseEvents, handleTouchEvents) {
1861
+ const viewer = zone.plugin.viewer;
1862
+ const scene = viewer.scene;
1863
+ const canvas = scene.canvas.canvas;
1864
+
1865
+ super(scene);
1866
+ const self = this;
1867
+
1868
+ const altitude = zone._geometry.altitude;
1869
+ const pointerLens = cfg && cfg.pointerLens;
1870
+ const updatePointerLens = (pointerLens
1871
+ ? function(canvasPos) {
1872
+ pointerLens.visible = !! canvasPos;
1873
+ if (canvasPos)
1874
+ {
1875
+ pointerLens.canvasPos = canvasPos;
1876
+ }
1877
+ }
1878
+ : () => { });
1879
+
1880
+ const ray2WorldPos = (orig, dir) => planeIntersect(altitude, math.vec3([ 0, 1, 0 ]), orig, dir);
1881
+
1882
+ const pickWorldPos = canvasPos => {
1883
+ const origin = math.vec3();
1884
+ const direction = math.vec3();
1885
+ math.canvasPosToWorldRay(canvas, scene.camera.viewMatrix, scene.camera.projMatrix, canvasPos, origin, direction);
1886
+ return ray2WorldPos(origin, direction);
1887
+ };
1888
+
1889
+ const copyCanvasPos = (event, vec2) => {
1890
+ vec2[0] = event.clientX;
1891
+ vec2[1] = event.clientY;
1892
+ transformToNode(canvas.ownerDocument.body, canvas, vec2);
1893
+ return vec2;
1894
+ };
1895
+
1896
+ const canvasHandle = function(type, cb) {
1897
+ const callback = event => {
1898
+ event.preventDefault();
1899
+ cb(event);
1900
+ };
1901
+ canvas.addEventListener(type, callback);
1902
+ return () => canvas.removeEventListener(type, callback);
1903
+ };
1904
+
1905
+ let cleanupCurrentDrag = () => { };
1906
+
1907
+ const startDrag = function(event, onMoveType, onEndType, matchesEvent) {
1908
+ const e = matchesEvent(event);
1909
+ const canvasPos = copyCanvasPos(e, math.vec2());
1910
+ const pickRecord = viewer.scene.pick({ canvasPos: canvasPos, includeEntities: [ zone._zoneMesh.id ] });
1911
+ const pickZone = pickRecord && pickRecord.entity && pickRecord.entity.zone;
1912
+
1913
+ if (pickZone === zone)
1914
+ {
1915
+ cleanupCurrentDrag();
1916
+
1917
+ canvas.style.cursor = "move";
1918
+ viewer.cameraControl.active = false;
1919
+
1920
+ const onChange = (function() {
1921
+ const initCoords = zone._geometry.planeCoordinates.map(c => c.slice());
1922
+ const initWorldPos = pickWorldPos(canvasPos);
1923
+ const initDragCoord = math.vec2([ initWorldPos[0], initWorldPos[2] ]);
1924
+ const dPos = math.vec2();
1925
+
1926
+ return function(canvasPos) {
1927
+ const worldPos = pickWorldPos(canvasPos);
1928
+ dPos[0] = worldPos[0];
1929
+ dPos[1] = worldPos[2];
1930
+ math.subVec2(initDragCoord, dPos, dPos);
1931
+
1932
+ zone._geometry.planeCoordinates.forEach((planeCoord, idx) => {
1933
+ math.subVec2(initCoords[idx], dPos, planeCoord);
1934
+ });
1935
+
1936
+ try {
1937
+ zone._rebuildMesh();
1938
+ } catch (e) {
1939
+ if (zone._zoneMesh) {
1940
+ zone._zoneMesh.destroy();
1941
+ zone._zoneMesh = null;
1942
+ }
1943
+ }
1944
+ };
1945
+ })();
1946
+
1947
+ const cleanupMove = canvasHandle(
1948
+ onMoveType,
1949
+ function(event) {
1950
+ const e = matchesEvent(event);
1951
+ if (e)
1952
+ {
1953
+ const canvasPos = copyCanvasPos(e, math.vec2());
1954
+ onChange(canvasPos);
1955
+ updatePointerLens(canvasPos);
1956
+ }
1957
+ });
1958
+
1959
+ const cleanupEnd = canvasHandle(
1960
+ onEndType,
1961
+ function(event) {
1962
+ const e = matchesEvent(event);
1963
+ if (e)
1964
+ {
1965
+ const canvasPos = copyCanvasPos(e, math.vec2());
1966
+ onChange(canvasPos);
1967
+ updatePointerLens(null);
1968
+ cleanupCurrentDrag();
1969
+ self.fire("translated");
1970
+ }
1971
+ });
1972
+
1973
+ cleanupCurrentDrag = function() {
1974
+ cleanupCurrentDrag = () => { };
1975
+ canvas.style.cursor = "default";
1976
+ viewer.cameraControl.active = true;
1977
+ cleanupMove();
1978
+ cleanupEnd();
1979
+ };
1980
+ }
1981
+ };
1982
+
1983
+ const startDragCbs = [ ];
1984
+
1985
+ if (handleMouseEvents) {
1986
+ startDragCbs.push(
1987
+ canvasHandle("mousedown", event => {
1988
+ if (event.which === 1) {
1989
+ startDrag(
1990
+ event,
1991
+ "mousemove",
1992
+ "mouseup",
1993
+ event => (event.which === 1) && event);
1994
+ }
1995
+ }));
1996
+ }
1997
+
1998
+ if (handleTouchEvents) {
1999
+ startDragCbs.push(
2000
+ canvasHandle("touchstart", event => {
2001
+ if (event.touches.length === 1) {
2002
+ const touchStartId = event.touches[0].identifier;
2003
+ startDrag(
2004
+ event,
2005
+ "touchmove",
2006
+ "touchend",
2007
+ event => [...event.changedTouches].find(e => e.identifier === touchStartId));
2008
+ }
2009
+ }));
2010
+ }
2011
+
2012
+ const cleanup = function() {
2013
+ cleanupCurrentDrag();
2014
+ startDragCbs.forEach(cb => cb());
2015
+ updatePointerLens(null);
2016
+ };
2017
+
2018
+ const destroyCb = zone.on("destroyed", cleanup);
2019
+
2020
+ this._deactivate = function() {
2021
+ zone.off("destroyed", destroyCb);
2022
+ cleanup();
2023
+ };
2024
+ }
2025
+
2026
+ deactivate() {
2027
+ this._deactivate();
2028
+ super.destroy();
2029
+ }
2030
+ }
2031
+
2032
+ export class ZoneTranslateMouseControl extends ZoneTranslateControl {
2033
+ constructor(zone, cfg) {
2034
+ super(zone, cfg, true, false);
2035
+ }
2036
+ }
2037
+
2038
+ export class ZoneTranslateTouchControl extends ZoneTranslateControl {
2039
+ constructor(zone, cfg) {
2040
+ super(zone, cfg, false, true);
2041
+ }
2042
+ }
2043
+
2044
+
2045
+ export {ZonesMouseControl}
2046
+ export {ZonesPlugin}