@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.
@@ -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._updatePositions = () => {
294
- if (! this.__visible) {
295
- return;
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
- if (this._betweenWires) {
298
- const visibleA = clipSegment(scene, parentElement, this._start, this._mid, tmpVec2a, tmpVec2b);
299
- const visibleB = clipSegment(scene, parentElement, this._end, this._mid, tmpVec2c, tmpVec2d);
300
- this._label.setCulled(! (visibleA || visibleB));
301
- if (visibleA && visibleB) {
302
- tmpVec2b[0] += tmpVec2d[0];
303
- tmpVec2b[1] += tmpVec2d[1];
304
- math.mulVec2Scalar(tmpVec2b, .5);
305
-
306
- tmpVec2b[0] += tmpVec2a[0] + tmpVec2c[0];
307
- tmpVec2b[1] += tmpVec2a[1] + tmpVec2c[1];
308
- math.mulVec2Scalar(tmpVec2b, 1/3);
309
- this._label.setPos(tmpVec2b[0], tmpVec2b[1]);
310
- } else if (visibleA) {
311
- setPosOnWire(tmpVec2a, tmpVec2b, 0);
312
- } else if (visibleB) {
313
- setPosOnWire(tmpVec2c, tmpVec2d, 0);
314
- }
315
- } else {
316
- const visible = (clipSegment(scene, parentElement, this._start, this._end, tmpVec2a, tmpVec2b)
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._betweenWires = false;
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._betweenWires = true;
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
+ };