@xeokit/xeokit-sdk 2.6.71 → 2.6.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/extras/ContextMenu/ContextMenu.js +11 -9
- package/src/plugins/AnnotationsPlugin/Annotation.js +28 -16
- package/src/plugins/NavCubePlugin/CubeTextureCanvas.js +2 -2
- package/src/plugins/NavCubePlugin/NavCubePlugin.js +10 -10
- package/src/plugins/SectionPlanesPlugin/Control.js +2 -2
- package/src/plugins/ZonesPlugin/ZonesPlugin.js +6 -151
- package/src/plugins/lib/ui/index.js +592 -34
- package/src/viewer/scene/geometry/ReadableGeometry.js +60 -0
- package/src/viewer/scene/geometry/builders/buildPlaneGeometry.js +1 -1
- package/src/viewer/scene/scene/Scene.js +2 -3
- package/src/viewer/scene/sectionCaps/SectionCaps.js +46 -65
- package/types/plugins/NavCubePlugin/NavCubePlugin.d.ts +1 -1
- package/types/viewer/scene/ImagePlane/ImagePlane.d.ts +397 -0
- package/types/viewer/scene/ImagePlane/index.d.ts +1 -0
- package/types/viewer/scene/geometry/builders/index.d.ts +1 -0
- package/types/viewer/scene/index.d.ts +1 -0
|
@@ -15,9 +15,28 @@ const tmpVec4b = math.vec4();
|
|
|
15
15
|
const tmpVec4c = math.vec4();
|
|
16
16
|
const tmpVec4d = math.vec4();
|
|
17
17
|
const tmpMat4 = math.mat4();
|
|
18
|
+
const tmpRay = {
|
|
19
|
+
origin: math.vec3(),
|
|
20
|
+
direction: math.vec3()
|
|
21
|
+
};
|
|
18
22
|
|
|
19
23
|
const nop = () => { };
|
|
20
24
|
|
|
25
|
+
const planeIntersect = function(p0, n, origin, direction) {
|
|
26
|
+
const t = (p0 - math.dotVec3(origin, n)) / math.dotVec3(direction, n);
|
|
27
|
+
if (t < 0)
|
|
28
|
+
{
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
else
|
|
32
|
+
{
|
|
33
|
+
const worldPos = math.vec3();
|
|
34
|
+
math.mulVec3Scalar(direction, t, worldPos);
|
|
35
|
+
math.addVec3(origin, worldPos, worldPos);
|
|
36
|
+
return worldPos;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
21
40
|
export function transformToNode(from, to, vec) {
|
|
22
41
|
const fromRec = from.getBoundingClientRect();
|
|
23
42
|
const toRec = to.getBoundingClientRect();
|
|
@@ -280,9 +299,15 @@ export class Label3D {
|
|
|
280
299
|
this._mid = math.vec3();
|
|
281
300
|
this._end = math.vec3();
|
|
282
301
|
this._yOff = 0;
|
|
283
|
-
this._betweenWires = false;
|
|
284
302
|
this.__visible = true;
|
|
285
303
|
|
|
304
|
+
this._updatePos = () => {
|
|
305
|
+
toClipSpace(camera, this._start, tmpVec4a);
|
|
306
|
+
math.mulVec4Scalar(tmpVec4a, 1.0 / tmpVec4a[3]);
|
|
307
|
+
toCanvasSpace(scene.canvas.canvas, parentElement, tmpVec4a, tmpVec2a);
|
|
308
|
+
this._label.setPos(tmpVec2a[0], tmpVec2a[1]);
|
|
309
|
+
};
|
|
310
|
+
|
|
286
311
|
const setPosOnWire = (p0, p1, yOff) => {
|
|
287
312
|
p0[0] += p1[0];
|
|
288
313
|
p0[1] += p1[1];
|
|
@@ -290,36 +315,44 @@ export class Label3D {
|
|
|
290
315
|
this._label.setPos(p0[0], p0[1] + yOff);
|
|
291
316
|
};
|
|
292
317
|
|
|
293
|
-
this.
|
|
294
|
-
|
|
295
|
-
|
|
318
|
+
this._updatePosBetween = () => {
|
|
319
|
+
const visibleA = clipSegment(scene, parentElement, this._start, this._mid, tmpVec2a, tmpVec2b);
|
|
320
|
+
const visibleB = clipSegment(scene, parentElement, this._end, this._mid, tmpVec2c, tmpVec2d);
|
|
321
|
+
this._label.setCulled(! (visibleA || visibleB));
|
|
322
|
+
if (visibleA && visibleB) {
|
|
323
|
+
tmpVec2b[0] += tmpVec2d[0];
|
|
324
|
+
tmpVec2b[1] += tmpVec2d[1];
|
|
325
|
+
math.mulVec2Scalar(tmpVec2b, .5);
|
|
326
|
+
|
|
327
|
+
tmpVec2b[0] += tmpVec2a[0] + tmpVec2c[0];
|
|
328
|
+
tmpVec2b[1] += tmpVec2a[1] + tmpVec2c[1];
|
|
329
|
+
math.mulVec2Scalar(tmpVec2b, 1/3);
|
|
330
|
+
this._label.setPos(tmpVec2b[0], tmpVec2b[1]);
|
|
331
|
+
} else if (visibleA) {
|
|
332
|
+
setPosOnWire(tmpVec2a, tmpVec2b, 0);
|
|
333
|
+
} else if (visibleB) {
|
|
334
|
+
setPosOnWire(tmpVec2c, tmpVec2d, 0);
|
|
296
335
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
&&
|
|
318
|
-
(math.distVec2(tmpVec2a, tmpVec2b) >= this._labelMinAxisLength));
|
|
319
|
-
this._label.setCulled(!visible);
|
|
320
|
-
if (visible) {
|
|
321
|
-
setPosOnWire(tmpVec2a, tmpVec2b, this._yOff);
|
|
322
|
-
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
this._updatePosOnWire = () => {
|
|
339
|
+
const visible = (clipSegment(scene, parentElement, this._start, this._end, tmpVec2a, tmpVec2b)
|
|
340
|
+
&&
|
|
341
|
+
(math.distVec2(tmpVec2a, tmpVec2b) >= this._labelMinAxisLength));
|
|
342
|
+
this._label.setCulled(!visible);
|
|
343
|
+
if (visible) {
|
|
344
|
+
setPosOnWire(tmpVec2a, tmpVec2b, this._yOff);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
let posUpdate = () => { };
|
|
349
|
+
this._setUpdatePositions = (_posUpdate) => {
|
|
350
|
+
posUpdate = _posUpdate;
|
|
351
|
+
this._updatePositions();
|
|
352
|
+
};
|
|
353
|
+
this._updatePositions = () => {
|
|
354
|
+
if (this.__visible) {
|
|
355
|
+
posUpdate();
|
|
323
356
|
}
|
|
324
357
|
};
|
|
325
358
|
|
|
@@ -337,21 +370,24 @@ export class Label3D {
|
|
|
337
370
|
};
|
|
338
371
|
}
|
|
339
372
|
|
|
373
|
+
setPos(p0) {
|
|
374
|
+
this._start.set(p0);
|
|
375
|
+
this._setUpdatePositions(this._updatePos);
|
|
376
|
+
}
|
|
377
|
+
|
|
340
378
|
setPosOnWire(p0, p1, yOff, labelMinAxisLength) {
|
|
341
379
|
this._start.set(p0);
|
|
342
380
|
this._end.set(p1);
|
|
343
381
|
this._yOff = yOff;
|
|
344
382
|
this._labelMinAxisLength = labelMinAxisLength;
|
|
345
|
-
this.
|
|
346
|
-
this._updatePositions();
|
|
383
|
+
this._setUpdatePositions(this._updatePosOnWire);
|
|
347
384
|
}
|
|
348
385
|
|
|
349
386
|
setPosBetween(p0, p1, p2) {
|
|
350
387
|
this._start.set(p0);
|
|
351
388
|
this._mid.set(p1);
|
|
352
389
|
this._end.set(p2);
|
|
353
|
-
this.
|
|
354
|
-
this._updatePositions();
|
|
390
|
+
this._setUpdatePositions(this._updatePosBetween);
|
|
355
391
|
}
|
|
356
392
|
|
|
357
393
|
setFillColor(value) {
|
|
@@ -454,6 +490,51 @@ export class Wire3D {
|
|
|
454
490
|
|
|
455
491
|
}
|
|
456
492
|
|
|
493
|
+
export const marker3D = function(scene, color) {
|
|
494
|
+
const marker = new Dot3D(scene, {}, scene.canvas.canvas.parentNode, {
|
|
495
|
+
borderColor: "white",
|
|
496
|
+
fillColor: color,
|
|
497
|
+
zIndex: 100
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
update: function(worldPos) {
|
|
502
|
+
if (worldPos)
|
|
503
|
+
{
|
|
504
|
+
marker.worldPos = worldPos;
|
|
505
|
+
}
|
|
506
|
+
marker.setVisible(!!worldPos);
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
setFillColor: c => marker.setFillColor(c),
|
|
510
|
+
setHighlighted: h => marker.setHighlighted(h),
|
|
511
|
+
getCanvasPos: () => marker.canvasPos,
|
|
512
|
+
getWorldPos: () => marker.worldPos,
|
|
513
|
+
destroy: () => marker.destroy()
|
|
514
|
+
};
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
export const wire3D = function(scene, color) {
|
|
518
|
+
const wire = new Wire3D(scene, scene.canvas.canvas.ownerDocument.body, {
|
|
519
|
+
color: color,
|
|
520
|
+
thickness: 1,
|
|
521
|
+
thicknessClickable: 6 // TODO: Remove to make not clickable?
|
|
522
|
+
});
|
|
523
|
+
wire.setVisible(false);
|
|
524
|
+
return {
|
|
525
|
+
update: (startWorldPos, endWorldPos) => {
|
|
526
|
+
if (endWorldPos)
|
|
527
|
+
{
|
|
528
|
+
wire.setEnds(startWorldPos, endWorldPos);
|
|
529
|
+
}
|
|
530
|
+
wire.setVisible(!!endWorldPos);
|
|
531
|
+
},
|
|
532
|
+
|
|
533
|
+
setColor: c => wire.setColor(c),
|
|
534
|
+
destroy: () => wire.destroy()
|
|
535
|
+
};
|
|
536
|
+
};
|
|
537
|
+
|
|
457
538
|
export function activateDraggableDot(dot, cfg) {
|
|
458
539
|
const extractCFG = function(propName, defaultValue) {
|
|
459
540
|
if (propName in cfg) {
|
|
@@ -768,3 +849,480 @@ export const touchPointSelector = function(viewer, pointerCircle, ray2WorldPos)
|
|
|
768
849
|
return cleanup;
|
|
769
850
|
};
|
|
770
851
|
};
|
|
852
|
+
|
|
853
|
+
export const triangulateEarClipping = function(planeCoords) {
|
|
854
|
+
const polygonVertices = [ ];
|
|
855
|
+
for (let i = 0; i < planeCoords.length; ++i)
|
|
856
|
+
polygonVertices.push(i);
|
|
857
|
+
|
|
858
|
+
const isCCW = (function() {
|
|
859
|
+
const ba = math.vec2();
|
|
860
|
+
const bc = math.vec2();
|
|
861
|
+
|
|
862
|
+
let anglesSum = 0;
|
|
863
|
+
const angles = [ ];
|
|
864
|
+
|
|
865
|
+
for (let i = 0; i < polygonVertices.length; ++i)
|
|
866
|
+
{
|
|
867
|
+
const a = planeCoords[polygonVertices[i]];
|
|
868
|
+
const b = planeCoords[polygonVertices[(i + 1) % polygonVertices.length]];
|
|
869
|
+
const c = planeCoords[polygonVertices[(i + 2) % polygonVertices.length]];
|
|
870
|
+
|
|
871
|
+
math.subVec2(a, b, ba);
|
|
872
|
+
math.subVec2(c, b, bc);
|
|
873
|
+
|
|
874
|
+
const theta = math.dotVec2(ba, bc) / Math.sqrt(math.sqLenVec2(ba) * math.sqLenVec2(bc));
|
|
875
|
+
const angle = Math.acos(Math.max(-1, Math.min(theta, 1)));
|
|
876
|
+
const convex = (ba[0] * bc[1] - ba[1] * bc[0]) >= 0;
|
|
877
|
+
anglesSum += convex ? angle : (2 * Math.PI - angle);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return anglesSum < (polygonVertices.length * Math.PI);
|
|
881
|
+
})();
|
|
882
|
+
|
|
883
|
+
const pointInTriangle = (function() {
|
|
884
|
+
const sign = (p1, p2, p3) => {
|
|
885
|
+
return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]);
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
return (pt, v1, v2, v3) => {
|
|
889
|
+
const d1 = sign(pt, v1, v2);
|
|
890
|
+
const d2 = sign(pt, v2, v3);
|
|
891
|
+
const d3 = sign(pt, v3, v1);
|
|
892
|
+
|
|
893
|
+
const has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
|
|
894
|
+
const has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
|
|
895
|
+
|
|
896
|
+
return !(has_neg && has_pos);
|
|
897
|
+
};
|
|
898
|
+
})();
|
|
899
|
+
|
|
900
|
+
const baseTriangles = [ ];
|
|
901
|
+
|
|
902
|
+
const vertices = (isCCW ? polygonVertices : polygonVertices.slice(0).reverse()).map(i => ({ idx: i }));
|
|
903
|
+
vertices.forEach((v, i) => {
|
|
904
|
+
v.prev = vertices[(i - 1 + vertices.length) % vertices.length];
|
|
905
|
+
v.next = vertices[(i + 1) % vertices.length];
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
const ba = math.vec2();
|
|
909
|
+
const bc = math.vec2();
|
|
910
|
+
|
|
911
|
+
while (vertices.length > 2) {
|
|
912
|
+
let earIdx = 0;
|
|
913
|
+
while (true) {
|
|
914
|
+
if (earIdx >= vertices.length)
|
|
915
|
+
{
|
|
916
|
+
throw `isCCW = ${isCCW}; earIdx = ${earIdx}; len = ${vertices.length}`;
|
|
917
|
+
}
|
|
918
|
+
const v = vertices[earIdx];
|
|
919
|
+
|
|
920
|
+
const a = planeCoords[v.prev.idx];
|
|
921
|
+
const b = planeCoords[v.idx];
|
|
922
|
+
const c = planeCoords[v.next.idx];
|
|
923
|
+
|
|
924
|
+
math.subVec2(a, b, ba);
|
|
925
|
+
math.subVec2(c, b, bc);
|
|
926
|
+
|
|
927
|
+
if (((ba[0] * bc[1] - ba[1] * bc[0]) >= 0) // a convex vertex
|
|
928
|
+
&&
|
|
929
|
+
vertices.every( // no other vertices inside
|
|
930
|
+
vv => ((vv === v)
|
|
931
|
+
||
|
|
932
|
+
(vv === v.prev)
|
|
933
|
+
||
|
|
934
|
+
(vv === v.next)
|
|
935
|
+
||
|
|
936
|
+
!pointInTriangle(planeCoords[vv.idx], a, b, c))))
|
|
937
|
+
break;
|
|
938
|
+
++earIdx;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const ear = vertices[earIdx];
|
|
942
|
+
vertices.splice(earIdx, 1);
|
|
943
|
+
|
|
944
|
+
baseTriangles.push([ ear.idx, ear.next.idx, ear.prev.idx ]);
|
|
945
|
+
|
|
946
|
+
const prev = ear.prev;
|
|
947
|
+
prev.next = ear.next;
|
|
948
|
+
const next = ear.next;
|
|
949
|
+
next.prev = ear.prev;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return [ planeCoords, baseTriangles, isCCW ];
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
export const addMousePressListener = function(element, onChange) {
|
|
956
|
+
const moveTolerance = 4;
|
|
957
|
+
|
|
958
|
+
const copyElementPos = (event, out) => {
|
|
959
|
+
out[0] = event.clientX;
|
|
960
|
+
out[1] = event.clientY;
|
|
961
|
+
transformToNode(element.ownerDocument.documentElement, element, out);
|
|
962
|
+
return out;
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
let buttonDown = false;
|
|
966
|
+
|
|
967
|
+
const cleanups = [ ];
|
|
968
|
+
const cleanup = () => cleanups.forEach(c => c());
|
|
969
|
+
|
|
970
|
+
const addElementEventListener = (type, listener) => {
|
|
971
|
+
element.addEventListener(type, listener);
|
|
972
|
+
cleanups.push(() => element.removeEventListener(type, listener));
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
const downPos = math.vec2();
|
|
976
|
+
addElementEventListener("mousedown", function(event) {
|
|
977
|
+
if (event.which === 1) {
|
|
978
|
+
buttonDown = true;
|
|
979
|
+
onChange(copyElementPos(event, downPos));
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
addElementEventListener("mousemove", function(event) {
|
|
984
|
+
copyElementPos(event, tmpVec2a);
|
|
985
|
+
if (buttonDown && (math.distVec2(downPos, tmpVec2a) > moveTolerance)) {
|
|
986
|
+
buttonDown = false;
|
|
987
|
+
}
|
|
988
|
+
if (buttonDown || (! event.buttons & 1)) {
|
|
989
|
+
onChange(tmpVec2a);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
addElementEventListener("mouseup", function(event) {
|
|
994
|
+
if ((event.which === 1) && buttonDown) {
|
|
995
|
+
const commit = onChange(copyElementPos(event, tmpVec2a));
|
|
996
|
+
if (commit) {
|
|
997
|
+
cleanup();
|
|
998
|
+
commit();
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
return cleanup;
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
export const addTouchPressListener = function(element, cameraControl, pointerCircle, onChange) {
|
|
1007
|
+
const longTouchTimeoutMs = 300;
|
|
1008
|
+
const moveTolerance = 20;
|
|
1009
|
+
const startPos = math.vec2();
|
|
1010
|
+
|
|
1011
|
+
const copyElementPos = (event, out) => {
|
|
1012
|
+
out[0] = event.clientX;
|
|
1013
|
+
out[1] = event.clientY;
|
|
1014
|
+
transformToNode(element.ownerDocument.documentElement, element, out);
|
|
1015
|
+
return out;
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
let longTouchTimeout = null;
|
|
1019
|
+
let onSingleTouchMove = nop;
|
|
1020
|
+
let startTouchIdentifier;
|
|
1021
|
+
|
|
1022
|
+
const resetAction = function() {
|
|
1023
|
+
clearTimeout(longTouchTimeout);
|
|
1024
|
+
pointerCircle.stop();
|
|
1025
|
+
cameraControl.active = true;
|
|
1026
|
+
onSingleTouchMove = nop;
|
|
1027
|
+
startTouchIdentifier = null;
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
const cleanups = [ ];
|
|
1031
|
+
const cleanup = () => cleanups.forEach(c => c());
|
|
1032
|
+
|
|
1033
|
+
const addElementEventListener = (type, listener) => {
|
|
1034
|
+
element.addEventListener(type, listener, {passive: true});
|
|
1035
|
+
cleanups.push(() => element.removeEventListener(type, listener));
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
addElementEventListener("touchstart", function(event) {
|
|
1039
|
+
const touches = event.touches;
|
|
1040
|
+
|
|
1041
|
+
if (touches.length !== 1)
|
|
1042
|
+
{
|
|
1043
|
+
resetAction();
|
|
1044
|
+
onChange(null);
|
|
1045
|
+
}
|
|
1046
|
+
else
|
|
1047
|
+
{
|
|
1048
|
+
const touch = touches[0];
|
|
1049
|
+
copyElementPos(touch, startPos);
|
|
1050
|
+
|
|
1051
|
+
startTouchIdentifier = touch.identifier;
|
|
1052
|
+
|
|
1053
|
+
onSingleTouchMove = elementPos => {
|
|
1054
|
+
if (math.distVec2(startPos, elementPos) > moveTolerance)
|
|
1055
|
+
{
|
|
1056
|
+
resetAction();
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
longTouchTimeout = setTimeout(
|
|
1061
|
+
function() {
|
|
1062
|
+
pointerCircle.start(startPos);
|
|
1063
|
+
|
|
1064
|
+
longTouchTimeout = setTimeout(
|
|
1065
|
+
function() {
|
|
1066
|
+
pointerCircle.stop();
|
|
1067
|
+
cameraControl.active = false;
|
|
1068
|
+
onSingleTouchMove = onChange;
|
|
1069
|
+
onSingleTouchMove(startPos);
|
|
1070
|
+
},
|
|
1071
|
+
longTouchTimeoutMs);
|
|
1072
|
+
},
|
|
1073
|
+
250);
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
// element.addEventListener("touchcancel", e => console.log("touchcancel", e), {passive: true});
|
|
1078
|
+
|
|
1079
|
+
addElementEventListener("touchmove", function(event) {
|
|
1080
|
+
const touch = [...event.changedTouches].find(e => e.identifier === startTouchIdentifier);
|
|
1081
|
+
if (touch)
|
|
1082
|
+
{
|
|
1083
|
+
onSingleTouchMove(copyElementPos(touch, tmpVec2a));
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
addElementEventListener("touchend", function(event) {
|
|
1088
|
+
const touch = [...event.changedTouches].find(e => e.identifier === startTouchIdentifier);
|
|
1089
|
+
if (touch)
|
|
1090
|
+
{
|
|
1091
|
+
const commit = onChange(copyElementPos(touch, tmpVec2a));
|
|
1092
|
+
resetAction();
|
|
1093
|
+
if (commit) {
|
|
1094
|
+
cleanup();
|
|
1095
|
+
commit();
|
|
1096
|
+
} else {
|
|
1097
|
+
onChange(null);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
return () => {
|
|
1103
|
+
resetAction();
|
|
1104
|
+
cleanup();
|
|
1105
|
+
};
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
export const startPolygonCreate = function(scene, pointerLens, addPressListener, pickRayResult, onChange, onConclude) {
|
|
1109
|
+
const canvas = scene.canvas.canvas;
|
|
1110
|
+
|
|
1111
|
+
const updatePointerLens = (pointerLens
|
|
1112
|
+
? function(canvasPos, isSnapped) {
|
|
1113
|
+
pointerLens.visible = !! canvasPos;
|
|
1114
|
+
if (canvasPos)
|
|
1115
|
+
{
|
|
1116
|
+
pointerLens.canvasPos = canvasPos;
|
|
1117
|
+
pointerLens.snapped = !! isSnapped;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
: () => { });
|
|
1121
|
+
|
|
1122
|
+
const testLastSegmentIntersects = (function() {
|
|
1123
|
+
const onSegment = (p, q, r) => ((q[0] <= Math.max(p[0], r[0])) &&
|
|
1124
|
+
(q[0] >= Math.min(p[0], r[0])) &&
|
|
1125
|
+
(q[1] <= Math.max(p[1], r[1])) &&
|
|
1126
|
+
(q[1] >= Math.min(p[1], r[1])));
|
|
1127
|
+
|
|
1128
|
+
const orient = (p, q, r) => {
|
|
1129
|
+
const val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]);
|
|
1130
|
+
// collinear
|
|
1131
|
+
// clockwise
|
|
1132
|
+
// counterclockwise
|
|
1133
|
+
return ((val === 0) ? 0 : ((val > 0) ? 1 : 2));
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
return function(pos2D, lastSegmentClosesLoop) {
|
|
1137
|
+
const s = lastSegmentClosesLoop ? 1 : 0;
|
|
1138
|
+
const a = pos2D[(pos2D.length - 2 + s) % pos2D.length];
|
|
1139
|
+
const b = pos2D[(pos2D.length - 1 + s) % pos2D.length];
|
|
1140
|
+
|
|
1141
|
+
for (let i = s; i < pos2D.length - 2 - 1 + s; ++i) {
|
|
1142
|
+
const c = pos2D[i];
|
|
1143
|
+
const d = pos2D[i + 1];
|
|
1144
|
+
|
|
1145
|
+
const o1 = orient(a, b, c);
|
|
1146
|
+
const o2 = orient(a, b, d);
|
|
1147
|
+
const o3 = orient(c, d, a);
|
|
1148
|
+
const o4 = orient(c, d, b);
|
|
1149
|
+
|
|
1150
|
+
if (((o1 !== o2) && (o3 !== o4)) || // General case
|
|
1151
|
+
((o1 === 0) && onSegment(a, c, b)) || // a, b and c are collinear and c lies on segment ab
|
|
1152
|
+
((o2 === 0) && onSegment(a, d, b)) || // a, b and d are collinear and d lies on segment ab
|
|
1153
|
+
((o3 === 0) && onSegment(c, a, d)) || // c, d and a are collinear and a lies on segment cd
|
|
1154
|
+
((o4 === 0) && onSegment(c, b, d))) // c, d and b are collinear and b lies on segment cd
|
|
1155
|
+
{
|
|
1156
|
+
return true;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
return false;
|
|
1161
|
+
};
|
|
1162
|
+
})();
|
|
1163
|
+
|
|
1164
|
+
const getPlane = (points) => (points.length >= 3) && (function() {
|
|
1165
|
+
const u = math.normalizeVec3(math.subVec3(points[1], points[0], math.vec3()));
|
|
1166
|
+
const v20 = math.normalizeVec3(math.subVec3(points[2], points[0], tmpVec3a));
|
|
1167
|
+
const normal = math.normalizeVec3(math.cross3Vec3(u, v20, math.vec3()));
|
|
1168
|
+
const v = math.normalizeVec3(math.cross3Vec3(normal, u, math.vec3()));
|
|
1169
|
+
return {
|
|
1170
|
+
normal: normal,
|
|
1171
|
+
origin: math.vec3(points[0]),
|
|
1172
|
+
u: u,
|
|
1173
|
+
v: v
|
|
1174
|
+
};
|
|
1175
|
+
})();
|
|
1176
|
+
|
|
1177
|
+
const vertices = [ ];
|
|
1178
|
+
let currentInteraction;
|
|
1179
|
+
|
|
1180
|
+
(function selectNextPoint() {
|
|
1181
|
+
const plane = getPlane(vertices);
|
|
1182
|
+
|
|
1183
|
+
const canvasPos2Ray = (canvasPos, dst) => (math.canvasPosToWorldRay(canvas, scene.camera.viewMatrix, scene.camera.projMatrix, scene.camera.projection, canvasPos, dst.origin, dst.direction), dst);
|
|
1184
|
+
|
|
1185
|
+
const pickRay = (ray) => {
|
|
1186
|
+
const pickResult = pickRayResult(ray);
|
|
1187
|
+
const snapWorldPos = pickResult && pickResult.entity && ((! plane) || pickResult.snapped) && pickResult.worldPos;
|
|
1188
|
+
if (plane) {
|
|
1189
|
+
const originDistance = math.dotVec3(plane.normal, plane.origin);
|
|
1190
|
+
const snapPlaneDist = (snapWorldPos
|
|
1191
|
+
? ((originDistance - math.dotVec3(snapWorldPos, plane.normal)))
|
|
1192
|
+
: window.Infinity);
|
|
1193
|
+
if (Math.abs(snapPlaneDist) < 0.0001) {
|
|
1194
|
+
const ret = math.vec3();
|
|
1195
|
+
return {
|
|
1196
|
+
worldPos: math.addVec3(snapWorldPos, math.mulVec3Scalar(plane.normal, snapPlaneDist, ret), ret),
|
|
1197
|
+
snapped: true
|
|
1198
|
+
};
|
|
1199
|
+
} else {
|
|
1200
|
+
return {
|
|
1201
|
+
worldPos: planeIntersect(originDistance, plane.normal, ray.origin, ray.direction),
|
|
1202
|
+
snapped: false
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
} else {
|
|
1206
|
+
return {
|
|
1207
|
+
worldPos: snapWorldPos,
|
|
1208
|
+
snapped: pickResult && pickResult.snapped
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
let placeVertex = false;
|
|
1214
|
+
const removePressListener = addPressListener(
|
|
1215
|
+
(inputCanvasPos) => {
|
|
1216
|
+
const rayPick = inputCanvasPos && pickRay(canvasPos2Ray(inputCanvasPos, tmpRay));
|
|
1217
|
+
const worldPos = rayPick && rayPick.worldPos;
|
|
1218
|
+
return onWorldPos(worldPos, inputCanvasPos, rayPick && rayPick.snapped);
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
const onWorldPos = (worldPos, inputCanvasPos, rayPickSnapped) => {
|
|
1222
|
+
const canvasPos = worldPos ? scene.camera.projectWorldPos(worldPos) : inputCanvasPos;
|
|
1223
|
+
const firstMarker = (vertices.length > 0) && (function() {
|
|
1224
|
+
const v = vertices[0];
|
|
1225
|
+
return {
|
|
1226
|
+
canvasPos: scene.camera.projectWorldPos(v),
|
|
1227
|
+
worldPos: v
|
|
1228
|
+
};
|
|
1229
|
+
})();
|
|
1230
|
+
const snapToFirst = (vertices.length >= 3) && ((canvasPos && (math.distVec2(canvasPos, firstMarker.canvasPos) < 10))
|
|
1231
|
+
||
|
|
1232
|
+
(worldPos && math.compareVec3(worldPos, firstMarker.worldPos)));
|
|
1233
|
+
|
|
1234
|
+
updatePointerLens(snapToFirst ? firstMarker.canvasPos : canvasPos, snapToFirst || rayPickSnapped);
|
|
1235
|
+
|
|
1236
|
+
const lastPointOverlaps = (inputCanvasPos
|
|
1237
|
+
&&
|
|
1238
|
+
((! worldPos)
|
|
1239
|
+
||
|
|
1240
|
+
((vertices.length < 3)
|
|
1241
|
+
? vertices.some(v => math.compareVec3(v, worldPos))
|
|
1242
|
+
: math.compareVec3(vertices[vertices.length - 1], worldPos))));
|
|
1243
|
+
|
|
1244
|
+
const points = vertices.concat(((! snapToFirst) && worldPos && (! lastPointOverlaps)) ? [worldPos] : []);
|
|
1245
|
+
|
|
1246
|
+
const curPlane = (! lastPointOverlaps) && (plane || getPlane(points));
|
|
1247
|
+
|
|
1248
|
+
const uvs = curPlane && points.map(p => {
|
|
1249
|
+
math.subVec3(p, curPlane.origin, tmpVec3a);
|
|
1250
|
+
return math.vec2([ math.dotVec3(tmpVec3a, curPlane.u), math.dotVec3(tmpVec3a, curPlane.v) ]);
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
const isValid = (! lastPointOverlaps) && (! (uvs && testLastSegmentIntersects(uvs, snapToFirst)));
|
|
1254
|
+
const geometry = isValid && uvs && (snapToFirst || (! testLastSegmentIntersects(uvs, true))) && (function() {
|
|
1255
|
+
try {
|
|
1256
|
+
const [ baseVertices, baseTriangles, isCCW ] = triangulateEarClipping(uvs);
|
|
1257
|
+
return {
|
|
1258
|
+
faces: baseTriangles,
|
|
1259
|
+
vertices: baseVertices.map(uv => math.addVec3(
|
|
1260
|
+
curPlane.origin,
|
|
1261
|
+
math.addVec3(
|
|
1262
|
+
math.mulVec3Scalar(curPlane.u, uv[0], tmpVec3a),
|
|
1263
|
+
math.mulVec3Scalar(curPlane.v, uv[1], tmpVec3b),
|
|
1264
|
+
tmpVec3a),
|
|
1265
|
+
math.vec3()))
|
|
1266
|
+
};
|
|
1267
|
+
} catch (e) {
|
|
1268
|
+
console.warn("e", e);
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
})();
|
|
1272
|
+
|
|
1273
|
+
onChange(points, snapToFirst, isValid, geometry);
|
|
1274
|
+
|
|
1275
|
+
return placeVertex = (isValid && (snapToFirst
|
|
1276
|
+
? () => {
|
|
1277
|
+
updatePointerLens(null);
|
|
1278
|
+
onConclude();
|
|
1279
|
+
}
|
|
1280
|
+
: () => {
|
|
1281
|
+
vertices.push(math.vec3(worldPos));
|
|
1282
|
+
updatePointerLens(null);
|
|
1283
|
+
selectNextPoint();
|
|
1284
|
+
}));
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
currentInteraction = {
|
|
1288
|
+
closePolygon: (() => {
|
|
1289
|
+
const commit = (vertices.length >= 3) && onWorldPos(vertices[0]);
|
|
1290
|
+
if (commit) {
|
|
1291
|
+
removePressListener();
|
|
1292
|
+
commit();
|
|
1293
|
+
}
|
|
1294
|
+
return !! commit;
|
|
1295
|
+
}),
|
|
1296
|
+
placeVertex: () => {
|
|
1297
|
+
if (placeVertex) {
|
|
1298
|
+
removePressListener();
|
|
1299
|
+
placeVertex();
|
|
1300
|
+
}
|
|
1301
|
+
return !!placeVertex;
|
|
1302
|
+
},
|
|
1303
|
+
popVertex: () => {
|
|
1304
|
+
if (vertices.length > 0) {
|
|
1305
|
+
removePressListener();
|
|
1306
|
+
vertices.pop();
|
|
1307
|
+
selectNextPoint();
|
|
1308
|
+
return true;
|
|
1309
|
+
} else {
|
|
1310
|
+
return false;
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
removePressListener: removePressListener,
|
|
1314
|
+
updateOnChange: () => onWorldPos()
|
|
1315
|
+
};
|
|
1316
|
+
})();
|
|
1317
|
+
|
|
1318
|
+
return {
|
|
1319
|
+
cancel: () => {
|
|
1320
|
+
currentInteraction.removePressListener();
|
|
1321
|
+
updatePointerLens(null);
|
|
1322
|
+
},
|
|
1323
|
+
closePolygon: () => currentInteraction.closePolygon(),
|
|
1324
|
+
placeVertex: () => currentInteraction.placeVertex(),
|
|
1325
|
+
popVertex: () => currentInteraction.popVertex(),
|
|
1326
|
+
updateOnChange: () => currentInteraction.updateOnChange()
|
|
1327
|
+
};
|
|
1328
|
+
};
|