@xeokit/xeokit-sdk 2.6.28 → 2.6.30

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