@eturnity/eturnity_maths 7.20.0 → 7.24.1

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/src/geometry.js CHANGED
@@ -1,535 +1,534 @@
1
- import { polygonCloseTolerance } from "./config";
1
+ import { polygonCloseTolerance } from './config'
2
2
 
3
3
  import {
4
- addVector,
5
- substractVector,
6
- multiplyVector,
7
- dotProduct,
8
- crossProduct,
9
- meanVector,
10
- vectorLength,
11
- normalizeVector,
12
- } from "./vector";
13
- import { inverse3x3matrix, multiplyMatrices } from "./matrix";
14
- import { Point } from "./objects/Point";
15
- import { Line } from "./objects/Line";
16
- import concaveman from "./lib/concaveman";
4
+ addVector,
5
+ substractVector,
6
+ multiplyVector,
7
+ dotProduct,
8
+ crossProduct,
9
+ meanVector,
10
+ vectorLength,
11
+ normalizeVector
12
+ } from './vector'
13
+ import { inverse3x3matrix, multiplyMatrices } from './matrix'
14
+ import { Point } from './objects/Point'
15
+ import { Line } from './objects/Line'
16
+ import concaveman from './lib/concaveman'
17
17
 
18
18
  export function getConcaveOutlines(selectedPanels, onePanelOutline) {
19
- let buckets = groupAdjacentObjects(selectedPanels);
20
- const outlines = [];
21
- for (let bucketIndex = 0; bucketIndex < buckets.length; bucketIndex++) {
22
- outlines.push(
23
- getConcaveOutline(
24
- selectedPanels.filter((p, i) => buckets[bucketIndex].includes(i)),
25
- onePanelOutline
26
- )
27
- );
28
- }
29
- return { outlines, buckets };
19
+ let buckets = groupAdjacentObjects(selectedPanels)
20
+ const outlines = []
21
+ for (let bucketIndex = 0; bucketIndex < buckets.length; bucketIndex++) {
22
+ outlines.push(
23
+ getConcaveOutline(
24
+ selectedPanels.filter((p, i) => buckets[bucketIndex].includes(i)),
25
+ onePanelOutline
26
+ )
27
+ )
28
+ }
29
+ return { outlines, buckets }
30
30
  }
31
31
 
32
32
  export function getConcaveOutline(selectedPanels, onePanelOutline) {
33
- const points = selectedPanels.reduce((acc, cur) => {
34
- acc.push(...cur.outline.map((p) => [p.x, p.y]));
35
- return acc;
36
- }, []);
37
- let AB = getDistanceBetweenPoints(onePanelOutline[0], onePanelOutline[1]);
38
- let AD = getDistanceBetweenPoints(onePanelOutline[0], onePanelOutline[3]);
39
- let longEdgeLength = Math.max(AB, AD) + 100;
40
- var concaveResult = concaveman(points, 1, longEdgeLength);
41
- concaveResult.pop();
42
- let moduleFieldOutline = concaveResult.map((p) => {
43
- return {
44
- x: p[0],
45
- y: p[1],
46
- z: 0,
47
- };
48
- });
49
- //remove aligned nodes
50
- moduleFieldOutline = simplifyOutline(moduleFieldOutline);
51
- return moduleFieldOutline;
33
+ const points = selectedPanels.reduce((acc, cur) => {
34
+ acc.push(...cur.outline.map((p) => [p.x, p.y]))
35
+ return acc
36
+ }, [])
37
+ let AB = getDistanceBetweenPoints(onePanelOutline[0], onePanelOutline[1])
38
+ let AD = getDistanceBetweenPoints(onePanelOutline[0], onePanelOutline[3])
39
+ let longEdgeLength = Math.max(AB, AD) + 100
40
+ var concaveResult = concaveman(points, 1, longEdgeLength)
41
+ concaveResult.pop()
42
+ let moduleFieldOutline = concaveResult.map((p) => {
43
+ return {
44
+ x: p[0],
45
+ y: p[1],
46
+ z: 0
47
+ }
48
+ })
49
+ //remove aligned nodes
50
+ moduleFieldOutline = simplifyOutline(moduleFieldOutline)
51
+ return moduleFieldOutline
52
52
  }
53
53
  export function simplifyOutline(initialOutline) {
54
- const simplifiedOutline = [];
55
- initialOutline.forEach((p, k, outline) => {
56
- let len = outline.length;
57
- let A = outline[(k - 1 + len) % len];
58
- let B = outline[(k + 1) % len];
59
- let M = outline[k % len];
60
- if (!isInsideEdge2D(M, A, B)) {
61
- simplifiedOutline.push(M);
62
- }
63
- });
64
- return simplifiedOutline;
54
+ const simplifiedOutline = []
55
+ initialOutline.forEach((p, k, outline) => {
56
+ let len = outline.length
57
+ let A = outline[(k - 1 + len) % len]
58
+ let B = outline[(k + 1) % len]
59
+ let M = outline[k % len]
60
+ if (!isInsideEdge2D(M, A, B)) {
61
+ simplifiedOutline.push(M)
62
+ }
63
+ })
64
+ return simplifiedOutline
65
65
  }
66
66
  export function getSnapedValue(value, snaps, tolerance) {
67
- let closeSnapsItem = snaps.reduce(
68
- (acc, cur) => {
69
- let distance = Math.abs(cur - value);
70
- if (distance <= Math.min(tolerance, acc.distance)) {
71
- acc = { value: cur, distance };
72
- }
73
- return acc;
74
- },
75
- { value, distance: tolerance }
76
- );
77
- return closeSnapsItem.value;
67
+ let closeSnapsItem = snaps.reduce(
68
+ (acc, cur) => {
69
+ let distance = Math.abs(cur - value)
70
+ if (distance <= Math.min(tolerance, acc.distance)) {
71
+ acc = { value: cur, distance }
72
+ }
73
+ return acc
74
+ },
75
+ { value, distance: tolerance }
76
+ )
77
+ return closeSnapsItem.value
78
78
  }
79
79
  export function getAngleInDegFromCanvasVector(v) {
80
- const angle = Math.atan2(v.y, v.x);
81
- return ((angle * 180) / Math.PI + 90 + 360) % 360;
80
+ const angle = Math.atan2(v.y, v.x)
81
+ return ((angle * 180) / Math.PI + 90 + 360) % 360
82
82
  }
83
83
  export function getOrthogonalLineABPassingInB(A, B) {
84
- if (isSamePoint3D(A, B)) {
85
- console.error("A == B Can't make a 90° line");
86
- return null;
87
- }
88
- const C = new Point(A.x + A.y - B.y, A.y + B.x - A.x);
89
- return new Line(A, C, "");
84
+ if (isSamePoint3D(A, B)) {
85
+ console.error('A == B Can\'t make a 90° line')
86
+ return null
87
+ }
88
+ const C = new Point(A.x + A.y - B.y, A.y + B.x - A.x)
89
+ return new Line(A, C, '')
90
90
  }
91
91
  // Return orthogonal line passing by C
92
92
  export function getOrthogonalLinePassingByPoint(A, B, C) {
93
- if (isSamePoint3D(A, B)) {
94
- console.error("A == B Can't make a orthogonal line");
95
- return null;
96
- }
97
- const M = new Point(C.x + B.y - A.y, C.y + A.x - B.x);
98
- return new Line(C, M, "");
93
+ if (isSamePoint3D(A, B)) {
94
+ console.error('A == B Can\'t make a orthogonal line')
95
+ return null
96
+ }
97
+ const M = new Point(C.x + B.y - A.y, C.y + A.x - B.x)
98
+ return new Line(C, M, '')
99
99
  }
100
100
 
101
101
  export function getParallelLinePassingByPoint(A, B, C) {
102
- if (isSamePoint3D(A, B)) {
103
- console.error("A == B Can't make a parallel line");
104
- return null;
105
- }
106
- const D = new Point(C.x + B.x - A.x, C.y + B.y - A.y, C.z + B.z - A.z);
107
- return new Line(C, D, "");
102
+ if (isSamePoint3D(A, B)) {
103
+ console.error('A == B Can\'t make a parallel line')
104
+ return null
105
+ }
106
+ const D = new Point(C.x + B.x - A.x, C.y + B.y - A.y, C.z + B.z - A.z)
107
+ return new Line(C, D, '')
108
108
  }
109
109
 
110
110
  export function getDataAboutTwo3DLines(A, u, B, v) {
111
- let w = crossProduct(u, v);
112
- let m = [
113
- [u.x, v.x, w.x],
114
- [u.y, v.y, w.y],
115
- [u.z, v.z, w.z],
116
- ];
117
- let mInv = inverse3x3matrix(m);
118
- if (!mInv) {
119
- return null;
120
- }
121
- let AB = substractVector(B, A);
122
- let [t1, t2, t3] = multiplyMatrices(mInv, [[AB.x], [AB.y], [AB.z]]);
123
- //M point on Au
124
- let M = addVector(A, multiplyVector(t1, u));
125
- //N point on Bv
126
- let N = addVector(B, multiplyVector(-t2, v));
127
- let distance = get3DDistanceBetweenPoints(M, N);
128
- return { m, mInv, M, N, A, B, u, v, w, distance, t1, t2, t3 };
111
+ let w = crossProduct(u, v)
112
+ let m = [
113
+ [u.x, v.x, w.x],
114
+ [u.y, v.y, w.y],
115
+ [u.z, v.z, w.z]
116
+ ]
117
+ let mInv = inverse3x3matrix(m)
118
+ if (!mInv) {
119
+ return null
120
+ }
121
+ let AB = substractVector(B, A)
122
+ let [t1, t2, t3] = multiplyMatrices(mInv, [[AB.x], [AB.y], [AB.z]])
123
+ //M point on Au
124
+ let M = addVector(A, multiplyVector(t1, u))
125
+ //N point on Bv
126
+ let N = addVector(B, multiplyVector(-t2, v))
127
+ let distance = get3DDistanceBetweenPoints(M, N)
128
+ return { m, mInv, M, N, A, B, u, v, w, distance, t1, t2, t3 }
129
129
  }
130
130
  export function vectorFromAngleInDegCanvas(angle) {
131
- return {
132
- x: Math.sin((angle * Math.PI) / 180),
133
- y: -Math.cos((angle * Math.PI) / 180),
134
- };
131
+ return {
132
+ x: Math.sin((angle * Math.PI) / 180),
133
+ y: -Math.cos((angle * Math.PI) / 180)
134
+ }
135
135
  }
136
136
  export function getDistanceBetweenPoints(firstPoint, secondPoint) {
137
- const distance = Math.hypot(
138
- firstPoint.x - secondPoint.x,
139
- firstPoint.y - secondPoint.y
140
- );
141
- return distance;
137
+ const distance = Math.hypot(
138
+ firstPoint.x - secondPoint.x,
139
+ firstPoint.y - secondPoint.y
140
+ )
141
+ return distance
142
142
  }
143
143
 
144
144
  export function get3DDistanceBetweenPoints(firstPoint, secondPoint) {
145
- const distance = Math.hypot(
146
- firstPoint.x - secondPoint.x,
147
- firstPoint.y - secondPoint.y,
148
- firstPoint.z - secondPoint.z
149
- );
150
- return distance;
145
+ const distance = Math.hypot(
146
+ firstPoint.x - secondPoint.x,
147
+ firstPoint.y - secondPoint.y,
148
+ firstPoint.z - secondPoint.z
149
+ )
150
+ return distance
151
151
  }
152
152
 
153
153
  export function getDegreeVectors(u, v) {
154
- return getDegree(u, { x: 0, y: 0, z: 0 }, v);
154
+ return getDegree(u, { x: 0, y: 0, z: 0 }, v)
155
155
  }
156
156
 
157
157
  export function getDegree(H, I, J) {
158
- let distanceFunction = get3DDistanceBetweenPoints;
159
- if (
160
- H.z == undefined ||
158
+ let distanceFunction = get3DDistanceBetweenPoints
159
+ if (
160
+ H.z == undefined ||
161
161
  I.z == undefined ||
162
162
  J.z == undefined ||
163
163
  isNaN(H.z) ||
164
164
  isNaN(I.z) ||
165
165
  isNaN(J.z)
166
- ) {
167
- distanceFunction = getDistanceBetweenPoints;
168
- }
169
- const a = distanceFunction(H, I);
170
- const b = distanceFunction(I, J);
171
- const c = distanceFunction(H, J);
166
+ ) {
167
+ distanceFunction = getDistanceBetweenPoints
168
+ }
169
+ const a = distanceFunction(H, I)
170
+ const b = distanceFunction(I, J)
171
+ const c = distanceFunction(H, J)
172
172
 
173
- let angle =
174
- (Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180) / Math.PI;
173
+ let angle = (Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180) / Math.PI
175
174
 
176
- //if 3 points are aligned
177
- if (isNaN(angle)) {
178
- angle = c < a ? 0 : 180;
179
- }
175
+ //if 3 points are aligned
176
+ if (isNaN(angle)) {
177
+ angle = c < a ? 0 : 180
178
+ }
180
179
 
181
- let sgn = -Math.sign(
182
- crossProduct(substractVector(H, I), substractVector(J, I)).z
183
- );
184
- angle = sgn * angle;
185
- return angle;
180
+ let sgn = -Math.sign(
181
+ crossProduct(substractVector(H, I), substractVector(J, I)).z
182
+ )
183
+ angle = sgn * angle
184
+ return angle
186
185
  }
187
186
 
188
187
  export function midPoint(firstPoint, secondPoint) {
189
- firstPoint.z = firstPoint.z || 0;
190
- secondPoint.z = secondPoint.z || 0;
191
- return {
192
- x: (firstPoint.x + secondPoint.x) / 2,
193
- y: (firstPoint.y + secondPoint.y) / 2,
194
- z: (firstPoint.z + secondPoint.z) / 2,
195
- };
188
+ firstPoint.z = firstPoint.z || 0
189
+ secondPoint.z = secondPoint.z || 0
190
+ return {
191
+ x: (firstPoint.x + secondPoint.x) / 2,
192
+ y: (firstPoint.y + secondPoint.y) / 2,
193
+ z: (firstPoint.z + secondPoint.z) / 2
194
+ }
196
195
  }
197
196
 
198
197
  export function isSamePoint3D(A, B) {
199
- return A.x == B.x && A.y == B.y && A.z == B.z;
198
+ return A.x == B.x && A.y == B.y && A.z == B.z
200
199
  }
201
200
  export function isAlmostSamePoint3D(A, B, tolerance) {
202
- return (
203
- Math.abs(A.x - B.x) < tolerance &&
201
+ return (
202
+ Math.abs(A.x - B.x) < tolerance &&
204
203
  Math.abs(A.y - B.y) < tolerance &&
205
204
  Math.abs(A.z - B.z) < tolerance
206
- );
205
+ )
207
206
  }
208
207
  export function isSamePoint2D(A, B) {
209
- return A.x == B.x && A.y == B.y;
208
+ return A.x == B.x && A.y == B.y
210
209
  }
211
210
  export function isAlmostSamePoint2D(A, B, tolerance) {
212
- return Math.abs(A.x - B.x) < tolerance && Math.abs(A.y - B.y) < tolerance;
211
+ return Math.abs(A.x - B.x) < tolerance && Math.abs(A.y - B.y) < tolerance
213
212
  }
214
213
  export function isSameSegment2D(seg_0, seg_1) {
215
- let check_1 =
216
- isSamePoint2D(seg_0[0], seg_1[0]) && isSamePoint2D(seg_0[1], seg_1[1]);
217
- let check_2 =
218
- isSamePoint2D(seg_0[0], seg_1[1]) && isSamePoint2D(seg_0[1], seg_1[0]);
219
- return check_1 || check_2;
214
+ let check_1 =
215
+ isSamePoint2D(seg_0[0], seg_1[0]) && isSamePoint2D(seg_0[1], seg_1[1])
216
+ let check_2 =
217
+ isSamePoint2D(seg_0[0], seg_1[1]) && isSamePoint2D(seg_0[1], seg_1[0])
218
+ return check_1 || check_2
220
219
  }
221
220
  export function isAlmostSameSegment2D(seg_0, seg_1, tolerance) {
222
- let check_1 =
221
+ let check_1 =
223
222
  isAlmostSamePoint2D(seg_0[0], seg_1[0], tolerance) &&
224
- isAlmostSamePoint2D(seg_0[1], seg_1[1], tolerance);
225
- let check_2 =
223
+ isAlmostSamePoint2D(seg_0[1], seg_1[1], tolerance)
224
+ let check_2 =
226
225
  isAlmostSamePoint2D(seg_0[0], seg_1[1], tolerance) &&
227
- isAlmostSamePoint2D(seg_0[1], seg_1[0], tolerance);
228
- return check_1 || check_2;
226
+ isAlmostSamePoint2D(seg_0[1], seg_1[0], tolerance)
227
+ return check_1 || check_2
229
228
  }
230
229
  export function isSameSegment3D(seg_0, seg_1) {
231
- let check_1 =
232
- isSamePoint3D(seg_0[0], seg_1[0]) && isSamePoint3D(seg_0[1], seg_1[1]);
233
- let check_2 =
234
- isSamePoint3D(seg_0[0], seg_1[1]) && isSamePoint3D(seg_0[1], seg_1[0]);
235
- return check_1 || check_2;
230
+ let check_1 =
231
+ isSamePoint3D(seg_0[0], seg_1[0]) && isSamePoint3D(seg_0[1], seg_1[1])
232
+ let check_2 =
233
+ isSamePoint3D(seg_0[0], seg_1[1]) && isSamePoint3D(seg_0[1], seg_1[0])
234
+ return check_1 || check_2
236
235
  }
237
236
  export function isAlmostSameSegment3D(seg_0, seg_1, tolerance) {
238
- let check_1 =
237
+ let check_1 =
239
238
  isAlmostSamePoint3D(seg_0[0], seg_1[0], tolerance) &&
240
- isAlmostSamePoint3D(seg_0[1], seg_1[1], tolerance);
241
- let check_2 =
239
+ isAlmostSamePoint3D(seg_0[1], seg_1[1], tolerance)
240
+ let check_2 =
242
241
  isAlmostSamePoint3D(seg_0[0], seg_1[1], tolerance) &&
243
- isAlmostSamePoint3D(seg_0[1], seg_1[0], tolerance);
244
- return check_1 || check_2;
242
+ isAlmostSamePoint3D(seg_0[1], seg_1[0], tolerance)
243
+ return check_1 || check_2
245
244
  }
246
245
  export function isSameLine(AB, CD) {
247
- if (!AB || !CD) {
248
- console.error("AB or CD not defined");
249
- return false;
250
- }
251
- if (AB.type != "line" || CD.type != "line") {
252
- return false;
253
- }
254
- // if(AB.outline.length<2){return false}
255
- // if(CD.outline.length<2){return false}
256
- const A = AB.outline[0];
257
- const B = AB.outline[1];
258
- const C = CD.outline[0];
259
- const D = CD.outline[1];
260
- //is A on (CD)
261
- const P = getPointOnLine(A, C, D);
262
- if (getDistanceBetweenPoints(A, P) > 1) {
263
- return false;
264
- }
265
- //is B on (CD)
266
- const M = getPointOnLine(B, C, D);
267
- if (getDistanceBetweenPoints(B, M) > 1) {
268
- return false;
269
- }
270
-
271
- return true;
246
+ if (!AB || !CD) {
247
+ console.error('AB or CD not defined')
248
+ return false
249
+ }
250
+ if (AB.type != 'line' || CD.type != 'line') {
251
+ return false
252
+ }
253
+ // if(AB.outline.length<2){return false}
254
+ // if(CD.outline.length<2){return false}
255
+ const A = AB.outline[0]
256
+ const B = AB.outline[1]
257
+ const C = CD.outline[0]
258
+ const D = CD.outline[1]
259
+ //is A on (CD)
260
+ const P = getPointOnLine(A, C, D)
261
+ if (getDistanceBetweenPoints(A, P) > 1) {
262
+ return false
263
+ }
264
+ //is B on (CD)
265
+ const M = getPointOnLine(B, C, D)
266
+ if (getDistanceBetweenPoints(B, M) > 1) {
267
+ return false
268
+ }
269
+
270
+ return true
272
271
  }
273
272
  export function distance2DToPolygon(point, vs) {
274
- let distance = Infinity;
275
- if (isInsidePolygon(point, vs)) {
276
- return 0;
277
- } else {
278
- for (let v of vs) {
279
- let distanceToNode = getDistanceBetweenPoints(point, v);
280
- distance = Math.min(distanceToNode, distance);
281
- }
282
- for (let k in vs) {
283
- let A = vs[k];
284
- let B = vs[(k + 1) % vs.length];
285
- //M projection of point on AB line
286
- if (!isSamePoint2D(A, B)) {
287
- let M = getPointOnLine(point, A, B);
288
- if (isInsideEdge2D(M, A, B)) {
289
- distance = Math.min(distance, getDistanceBetweenPoints(point, M));
290
- }
291
- }
292
- }
293
- return distance;
294
- }
273
+ let distance = Infinity
274
+ if (isInsidePolygon(point, vs)) {
275
+ return 0
276
+ } else {
277
+ for (let v of vs) {
278
+ let distanceToNode = getDistanceBetweenPoints(point, v)
279
+ distance = Math.min(distanceToNode, distance)
280
+ }
281
+ for (let k in vs) {
282
+ let A = vs[k]
283
+ let B = vs[(k + 1) % vs.length]
284
+ //M projection of point on AB line
285
+ if (!isSamePoint2D(A, B)) {
286
+ let M = getPointOnLine(point, A, B)
287
+ if (isInsideEdge2D(M, A, B)) {
288
+ distance = Math.min(distance, getDistanceBetweenPoints(point, M))
289
+ }
290
+ }
291
+ }
292
+ return distance
293
+ }
295
294
  }
296
295
  export function get2DBoundOfPolygon(vs) {
297
- const bound = {
298
- xMin: Infinity,
299
- xMax: -Infinity,
300
- yMin: Infinity,
301
- yMax: -Infinity,
302
- };
303
- for (let v of vs) {
304
- bound.xMin = Math.min(bound.xMin, v.x);
305
- bound.xMax = Math.max(bound.xMax, v.x);
306
- bound.yMin = Math.min(bound.yMin, v.y);
307
- bound.yMax = Math.max(bound.yMax, v.y);
308
- }
309
- return bound;
296
+ const bound = {
297
+ xMin: Infinity,
298
+ xMax: -Infinity,
299
+ yMin: Infinity,
300
+ yMax: -Infinity
301
+ }
302
+ for (let v of vs) {
303
+ bound.xMin = Math.min(bound.xMin, v.x)
304
+ bound.xMax = Math.max(bound.xMax, v.x)
305
+ bound.yMin = Math.min(bound.yMin, v.y)
306
+ bound.yMax = Math.max(bound.yMax, v.y)
307
+ }
308
+ return bound
310
309
  }
311
310
  export function getPointInsideOutline(vs, holes = []) {
312
- //draw an horizontal line between top and bottom
313
- const bound = get2DBoundOfPolygon(vs);
314
- const y = (bound.yMax + bound.yMin) / 2;
315
- let xOfCrossingEdgeY = [];
316
- for (let index = 0; index < vs.length; index++) {
317
- let nextIndex = (index + 1) % vs.length;
318
- const A = vs[index];
319
- const B = vs[nextIndex];
320
- if ((A.y <= y && B.y > y) || (A.y >= y && B.y < y)) {
321
- let x = A.x + (B.x - A.x) * ((y - A.y) / (B.y - A.y));
322
- xOfCrossingEdgeY.push(x);
323
- }
324
- }
325
- if (xOfCrossingEdgeY.length < 2) {
326
- console.error("not enaugh edge crossing mid cut", vs);
327
- return;
328
- }
329
- xOfCrossingEdgeY.sort((a, b) => a - b);
330
- let xLeft = xOfCrossingEdgeY[0];
331
- let xRight = xOfCrossingEdgeY[1];
332
- //dichotomy to get point outside of holes
333
- for (let hole of holes) {
334
- if (isPolygonInsidePolygon(vs, hole)) {
335
- return { x: (xLeft + xRight) / 2, y };
336
- }
337
- }
338
- let t = 0.1;
339
- let count = 0;
340
- let isInside = false;
341
- let testPoint;
342
- while (count < 10 && isInside == false) {
343
- count++;
344
- let x = xLeft + t * (xRight - xLeft);
345
- testPoint = { x, y };
346
- isInside = true;
347
- for (let hole of holes) {
348
- if (isInsidePolygon(testPoint, hole)) {
349
- isInside = false;
350
- break;
351
- }
352
- }
353
- if (isInside) {
354
- return testPoint;
355
- } else {
356
- t = (Math.cos((count * Math.PI) / 11) + 1) / 2;
357
- }
358
- }
359
- return testPoint;
311
+ //draw an horizontal line between top and bottom
312
+ const bound = get2DBoundOfPolygon(vs)
313
+ const y = (bound.yMax + bound.yMin) / 2
314
+ let xOfCrossingEdgeY = []
315
+ for (let index = 0; index < vs.length; index++) {
316
+ let nextIndex = (index + 1) % vs.length
317
+ const A = vs[index]
318
+ const B = vs[nextIndex]
319
+ if ((A.y <= y && B.y > y) || (A.y >= y && B.y < y)) {
320
+ let x = A.x + (B.x - A.x) * ((y - A.y) / (B.y - A.y))
321
+ xOfCrossingEdgeY.push(x)
322
+ }
323
+ }
324
+ if (xOfCrossingEdgeY.length < 2) {
325
+ console.error('not enaugh edge crossing mid cut', vs)
326
+ return
327
+ }
328
+ xOfCrossingEdgeY.sort((a, b) => a - b)
329
+ let xLeft = xOfCrossingEdgeY[0]
330
+ let xRight = xOfCrossingEdgeY[1]
331
+ //dichotomy to get point outside of holes
332
+ for (let hole of holes) {
333
+ if (isPolygonInsidePolygon(vs, hole)) {
334
+ return { x: (xLeft + xRight) / 2, y }
335
+ }
336
+ }
337
+ let t = 0.1
338
+ let count = 0
339
+ let isInside = false
340
+ let testPoint
341
+ while (count < 10 && isInside == false) {
342
+ count++
343
+ let x = xLeft + t * (xRight - xLeft)
344
+ testPoint = { x, y }
345
+ isInside = true
346
+ for (let hole of holes) {
347
+ if (isInsidePolygon(testPoint, hole)) {
348
+ isInside = false
349
+ break
350
+ }
351
+ }
352
+ if (isInside) {
353
+ return testPoint
354
+ } else {
355
+ t = (Math.cos((count * Math.PI) / 11) + 1) / 2
356
+ }
357
+ }
358
+ return testPoint
360
359
  }
361
360
 
362
361
  export function distanceToEdge2D(M, A, B) {
363
- const pA = { x: A.x, y: A.y, z: 0 };
364
- const pB = { x: B.x, y: B.y, z: 0 };
365
- const pM = { x: M.x, y: M.y, z: 0 };
366
- const AM = substractVector(pM, pA);
367
- const AB = substractVector(pB, pA);
368
- const det = AM.x * AB.y - AM.y * AB.x;
369
- const ABLength = vectorLength(AB);
370
- const AMLength = vectorLength(AM);
371
- if (ABLength == 0 && isSamePoint2D(pM, pA)) {
372
- return true;
373
- } else if (ABLength == 0 && !isSamePoint2D(pM, pA)) {
374
- return false;
375
- } else if (AMLength == 0) {
376
- return true;
377
- }
378
- const dotP = dotProduct(AM, AB) / (ABLength * AMLength);
379
- if (Math.abs(det) < 0.01 && dotP <= 1 && dotP >= 0) {
380
- return true;
381
- } else {
382
- return false;
383
- }
362
+ const pA = { x: A.x, y: A.y, z: 0 }
363
+ const pB = { x: B.x, y: B.y, z: 0 }
364
+ const pM = { x: M.x, y: M.y, z: 0 }
365
+ const AM = substractVector(pM, pA)
366
+ const AB = substractVector(pB, pA)
367
+ const det = AM.x * AB.y - AM.y * AB.x
368
+ const ABLength = vectorLength(AB)
369
+ const AMLength = vectorLength(AM)
370
+ if (ABLength == 0 && isSamePoint2D(pM, pA)) {
371
+ return true
372
+ } else if (ABLength == 0 && !isSamePoint2D(pM, pA)) {
373
+ return false
374
+ } else if (AMLength == 0) {
375
+ return true
376
+ }
377
+ const dotP = dotProduct(AM, AB) / (ABLength * AMLength)
378
+ if (Math.abs(det) < 0.01 && dotP <= 1 && dotP >= 0) {
379
+ return true
380
+ } else {
381
+ return false
382
+ }
384
383
  }
385
384
 
386
385
  export function isInsideEdge2D(M, A, B) {
387
- const pA = { x: A.x, y: A.y, z: 0 };
388
- const pB = { x: B.x, y: B.y, z: 0 };
389
- const pM = { x: M.x, y: M.y, z: 0 };
390
- const AM = substractVector(pM, pA);
391
- const BM = substractVector(pM, pB);
392
- const AB = substractVector(pB, pA);
393
- const det = AM.x * AB.y - AM.y * AB.x;
394
- const ABLength = vectorLength(AB);
395
- const BMLength = vectorLength(BM);
396
- const AMLength = vectorLength(AM);
397
- if (ABLength == 0 && isSamePoint2D(pM, pA)) {
398
- return true;
399
- } else if (ABLength == 0 && !isSamePoint2D(pM, pA)) {
400
- return false;
401
- } else if (AMLength == 0) {
402
- return true;
403
- }
404
- if (Math.abs(det) < 0.01 && BMLength < ABLength && AMLength < ABLength) {
405
- return true;
406
- } else {
407
- return false;
408
- }
386
+ const pA = { x: A.x, y: A.y, z: 0 }
387
+ const pB = { x: B.x, y: B.y, z: 0 }
388
+ const pM = { x: M.x, y: M.y, z: 0 }
389
+ const AM = substractVector(pM, pA)
390
+ const BM = substractVector(pM, pB)
391
+ const AB = substractVector(pB, pA)
392
+ const det = AM.x * AB.y - AM.y * AB.x
393
+ const ABLength = vectorLength(AB)
394
+ const BMLength = vectorLength(BM)
395
+ const AMLength = vectorLength(AM)
396
+ if (ABLength == 0 && isSamePoint2D(pM, pA)) {
397
+ return true
398
+ } else if (ABLength == 0 && !isSamePoint2D(pM, pA)) {
399
+ return false
400
+ } else if (AMLength == 0) {
401
+ return true
402
+ }
403
+ if (Math.abs(det) < 0.01 && BMLength < ABLength && AMLength < ABLength) {
404
+ return true
405
+ } else {
406
+ return false
407
+ }
409
408
  }
410
409
 
411
410
  export function polygonsHaveSame2DOutline(outline1, outline2) {
412
- let sameLength = outline1.length == outline2.length;
413
- return (
414
- sameLength &&
411
+ let sameLength = outline1.length == outline2.length
412
+ return (
413
+ sameLength &&
415
414
  outline1.every((p) => outline2.find((v) => v.x == p.x && v.y == p.y)) &&
416
415
  outline2.every((p) => outline1.find((v) => v.x == p.x && v.y == p.y))
417
- );
416
+ )
418
417
  }
419
418
 
420
419
  export function polygonsHaveSame3DOutline(outline1, outline2) {
421
- let sameLength = outline1.length == outline2.length;
422
- return (
423
- sameLength &&
420
+ let sameLength = outline1.length == outline2.length
421
+ return (
422
+ sameLength &&
424
423
  outline1.every((p) => outline2.find((v) => v.x == p.x && v.y == p.y)) &&
425
424
  outline2.every((p) => outline1.find((v) => v.x == p.x && v.y == p.y))
426
- );
425
+ )
427
426
  }
428
427
 
429
428
  export function isOnBorderOfPolygon(point, vs) {
430
- let inside = false;
431
- for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {
432
- if (isInsideEdge2D(point, vs[i], vs[j])) {
433
- inside = true;
434
- }
435
- }
436
- return inside;
429
+ let inside = false
430
+ for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {
431
+ if (isInsideEdge2D(point, vs[i], vs[j])) {
432
+ inside = true
433
+ }
434
+ }
435
+ return inside
437
436
  }
438
437
 
439
438
  export function isStrictlyInsidePolygon(point, vs) {
440
- return isInsidePolygon(point, vs) && !isOnBorderOfPolygon(point, vs);
439
+ return isInsidePolygon(point, vs) && !isOnBorderOfPolygon(point, vs)
441
440
  }
442
441
 
443
442
  export function isInsidePolygon(point, vs) {
444
- // ray-casting algorithm based on
445
- // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html/pnpoly.html
443
+ // ray-casting algorithm based on
444
+ // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html/pnpoly.html
446
445
 
447
- const x = point.x;
448
- const y = point.y;
446
+ const x = point.x
447
+ const y = point.y
449
448
 
450
- let inside = false;
449
+ let inside = false
451
450
 
452
- for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {
453
- const xi = vs[i].x;
454
- const yi = vs[i].y;
455
- const xj = vs[j].x;
456
- const yj = vs[j].y;
451
+ for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {
452
+ const xi = vs[i].x
453
+ const yi = vs[i].y
454
+ const xj = vs[j].x
455
+ const yj = vs[j].y
457
456
 
458
- const intersect =
459
- yi > y !== yj > y && x <= ((xj - xi) * (y - yi)) / (yj - yi) + xi;
460
- if (intersect) inside = !inside;
461
- }
462
- //if not really inside, let's check of edge
463
- if (!inside) {
464
- inside = isOnBorderOfPolygon(point, vs);
465
- }
466
- return inside;
457
+ const intersect =
458
+ yi > y !== yj > y && x <= ((xj - xi) * (y - yi)) / (yj - yi) + xi
459
+ if (intersect) inside = !inside
460
+ }
461
+ //if not really inside, let's check of edge
462
+ if (!inside) {
463
+ inside = isOnBorderOfPolygon(point, vs)
464
+ }
465
+ return inside
467
466
  }
468
467
 
469
468
  export function isPolygonInsidePolygon(innerOutline, outterOutline) {
470
- return innerOutline.every((p) => isInsidePolygon(p, outterOutline));
469
+ return innerOutline.every((p) => isInsidePolygon(p, outterOutline))
471
470
  }
472
471
 
473
472
  export function get3PathsFromPolyAndSplittingPath(
474
- outline,
475
- splittingPath,
476
- mmPerPx
473
+ outline,
474
+ splittingPath,
475
+ mmPerPx
477
476
  ) {
478
- //both outline and splittingPath are in Reality coordinate system
479
-
480
- //starting and ending points are defined by the splitting path
481
- let startingPoint = splittingPath[0];
482
- let endingPoint = splittingPath[splittingPath.length - 1];
483
- //We then identify which polygon vertex index is link to these points
484
- let startingIndex = outline.findIndex(
485
- (p) =>
486
- getDistanceBetweenPoints(startingPoint, p) <
477
+ //both outline and splittingPath are in Reality coordinate system
478
+
479
+ //starting and ending points are defined by the splitting path
480
+ let startingPoint = splittingPath[0]
481
+ let endingPoint = splittingPath[splittingPath.length - 1]
482
+ //We then identify which polygon vertex index is link to these points
483
+ let startingIndex = outline.findIndex(
484
+ (p) =>
485
+ getDistanceBetweenPoints(startingPoint, p) <
487
486
  polygonCloseTolerance * mmPerPx
488
- );
489
- let endingIndex = outline.findIndex(
490
- (p) =>
491
- getDistanceBetweenPoints(endingPoint, p) < polygonCloseTolerance * mmPerPx
492
- );
493
- //let's set altitude of every created splitting point
494
- let nberSplittingPoint = splittingPath.length;
495
- let startingAltitude = outline[startingIndex].z;
496
- let endingAltitude = outline[endingIndex].z;
497
- for (let k = 0; k < nberSplittingPoint; k++) {
498
- splittingPath[k].z =
487
+ )
488
+ let endingIndex = outline.findIndex(
489
+ (p) =>
490
+ getDistanceBetweenPoints(endingPoint, p) < polygonCloseTolerance * mmPerPx
491
+ )
492
+ //let's set altitude of every created splitting point
493
+ let nberSplittingPoint = splittingPath.length
494
+ let startingAltitude = outline[startingIndex].z
495
+ let endingAltitude = outline[endingIndex].z
496
+ for (let k = 0; k < nberSplittingPoint; k++) {
497
+ splittingPath[k].z =
499
498
  ((endingAltitude - startingAltitude) * k) / nberSplittingPoint +
500
- startingAltitude;
501
- }
502
-
503
- let path1 = splittingPath;
504
-
505
- if (startingIndex > endingIndex) {
506
- let tmp = startingIndex;
507
- startingIndex = endingIndex;
508
- endingIndex = tmp;
509
-
510
- tmp = endingPoint;
511
- endingPoint = startingPoint;
512
- startingPoint = tmp;
513
- path1.reverse();
514
- }
515
-
516
- let path2 = outline;
517
- //rotate polygon to have startingIndex point at index 0
518
- //rotate "startingIndex" times to the left
519
- for (let k = 0; k < startingIndex; k++) {
520
- path2.push(path2.shift());
521
- }
522
- let path3 = path2.splice(0, 1 + endingIndex - startingIndex); // start and finish with the commun points
523
- path2 = [endingPoint, ...path2, startingPoint];
524
- path2.reverse();
525
- return [path1, path2, path3];
499
+ startingAltitude
500
+ }
501
+
502
+ let path1 = splittingPath
503
+
504
+ if (startingIndex > endingIndex) {
505
+ let tmp = startingIndex
506
+ startingIndex = endingIndex
507
+ endingIndex = tmp
508
+
509
+ tmp = endingPoint
510
+ endingPoint = startingPoint
511
+ startingPoint = tmp
512
+ path1.reverse()
513
+ }
514
+
515
+ let path2 = outline
516
+ //rotate polygon to have startingIndex point at index 0
517
+ //rotate "startingIndex" times to the left
518
+ for (let k = 0; k < startingIndex; k++) {
519
+ path2.push(path2.shift())
520
+ }
521
+ let path3 = path2.splice(0, 1 + endingIndex - startingIndex) // start and finish with the commun points
522
+ path2 = [endingPoint, ...path2, startingPoint]
523
+ path2.reverse()
524
+ return [path1, path2, path3]
526
525
  }
527
526
 
528
527
  export function get2PolygonsOutlineFrom3Paths(path1, path2, path3) {
529
- //check if path starts and ends at the same point
530
- if (
531
- !(
532
- path1[0].x == path2[0].x &&
528
+ //check if path starts and ends at the same point
529
+ if (
530
+ !(
531
+ path1[0].x == path2[0].x &&
533
532
  path2[0].x == path3[0].x &&
534
533
  path1[0].y == path2[0].y &&
535
534
  path2[0].y == path3[0].y &&
@@ -537,474 +536,576 @@ export function get2PolygonsOutlineFrom3Paths(path1, path2, path3) {
537
536
  path2[path2.length - 1].x == path3[path3.length - 1].x &&
538
537
  path1[path1.length - 1].y == path2[path2.length - 1].y &&
539
538
  path2[path2.length - 1].y == path3[path3.length - 1].y
540
- )
541
- ) {
542
- console.error("path problem");
543
- }
544
- let refPoint1 = midPoint(path1[0], path1[1]);
545
- let refPoint2 = midPoint(path2[0], path2[1]);
546
- let poly1 = { outline: [] };
547
- let poly2 = { outline: [] };
548
-
549
- if (isInsidePolygon(refPoint1, [...path2, ...path3])) {
550
- //path1 is inside path2 & path 3
551
- path1.reverse();
552
- path1.pop();
553
- path1.shift();
554
- poly1.outline = [...path1, ...path2];
555
- poly2.outline = [...path1, ...path3];
556
- } else if (isInsidePolygon(refPoint2, [...path1, ...path3])) {
557
- //path2 is inside path1 & path 3
558
- path2.reverse();
559
- path2.pop();
560
- path2.shift();
561
- poly1.outline = [...path2, ...path1];
562
- poly2.outline = [...path2, ...path3];
563
- } else {
564
- //path3 is inside path1 & path 2
565
- path3.reverse();
566
- path3.pop();
567
- path3.shift();
568
- poly1.outline = [...path3, ...path1];
569
- poly2.outline = [...path3, ...path2];
570
- }
571
-
572
- return [poly1, poly2];
539
+ )
540
+ ) {
541
+ console.error('path problem')
542
+ }
543
+ let refPoint1 = midPoint(path1[0], path1[1])
544
+ let refPoint2 = midPoint(path2[0], path2[1])
545
+ let poly1 = { outline: [] }
546
+ let poly2 = { outline: [] }
547
+
548
+ if (isInsidePolygon(refPoint1, [...path2, ...path3])) {
549
+ //path1 is inside path2 & path 3
550
+ path1.reverse()
551
+ path1.pop()
552
+ path1.shift()
553
+ poly1.outline = [...path1, ...path2]
554
+ poly2.outline = [...path1, ...path3]
555
+ } else if (isInsidePolygon(refPoint2, [...path1, ...path3])) {
556
+ //path2 is inside path1 & path 3
557
+ path2.reverse()
558
+ path2.pop()
559
+ path2.shift()
560
+ poly1.outline = [...path2, ...path1]
561
+ poly2.outline = [...path2, ...path3]
562
+ } else {
563
+ //path3 is inside path1 & path 2
564
+ path3.reverse()
565
+ path3.pop()
566
+ path3.shift()
567
+ poly1.outline = [...path3, ...path1]
568
+ poly2.outline = [...path3, ...path2]
569
+ }
570
+
571
+ return [poly1, poly2]
573
572
  }
574
573
 
575
574
  export function getPointOnLine(M, A, B) {
576
- function zValueNotDefined(P) {
577
- return P.z == undefined || isNaN(P.z);
578
- }
579
- //Calcul of P, Projection of M on line AB
580
- if (isSamePoint3D(A, B)) {
581
- console.error("A and B don't make a line : A==B");
582
- return null;
583
- }
584
- let P = {};
585
- if (zValueNotDefined(M) || zValueNotDefined(A) || zValueNotDefined(B)) {
586
- const AM = {
587
- x: M.x - A.x,
588
- y: M.y - A.y,
589
- };
590
- const AB = {
591
- x: B.x - A.x,
592
- y: B.y - A.y,
593
- };
594
-
595
- const dot = AM.x * AB.x + AM.y * AB.y;
596
- const distanceAB = getDistanceBetweenPoints(A, B);
597
- let param = -1;
598
- if (distanceAB == 0) {
599
- //A et B sont confondu
600
- console.error("A and B don't make a line : A==B");
601
- P = M;
602
- } else {
603
- // in case of 0 length line
604
- const distanceAP = dot / distanceAB;
605
- P.x = A.x + (distanceAP * AB.x) / distanceAB;
606
- P.y = A.y + (distanceAP * AB.y) / distanceAB;
607
- }
608
- } else {
609
- //make 3D projection on line
610
- const AM = {
611
- x: M.x - A.x,
612
- y: M.y - A.y,
613
- z: M.z - A.z,
614
- };
615
- const AB = {
616
- x: B.x - A.x,
617
- y: B.y - A.y,
618
- z: B.z - A.z,
619
- };
620
-
621
- const dot = AM.x * AB.x + AM.y * AB.y + AM.z * AB.z;
622
- const distanceAB = get3DDistanceBetweenPoints(A, B);
623
- let param = -1;
624
- if (distanceAB == 0) {
625
- //A et B sont confondu
626
- console.error("A and B don't make a line : A==B");
627
- P = M;
628
- } else {
629
- // in case of 0 length line
630
- const distanceAP = dot / distanceAB;
631
- P.x = A.x + (distanceAP * AB.x) / distanceAB;
632
- P.y = A.y + (distanceAP * AB.y) / distanceAB;
633
- P.z = A.z + (distanceAP * AB.z) / distanceAB;
634
- }
635
- }
636
- //projection of the point to the line
637
- return P;
575
+ function zValueNotDefined(P) {
576
+ return P.z == undefined || isNaN(P.z)
577
+ }
578
+ //Calcul of P, Projection of M on line AB
579
+ if (isSamePoint3D(A, B)) {
580
+ console.error('A and B don\'t make a line : A==B')
581
+ return null
582
+ }
583
+ let P = {}
584
+ if (zValueNotDefined(M) || zValueNotDefined(A) || zValueNotDefined(B)) {
585
+ const AM = {
586
+ x: M.x - A.x,
587
+ y: M.y - A.y
588
+ }
589
+ const AB = {
590
+ x: B.x - A.x,
591
+ y: B.y - A.y
592
+ }
593
+
594
+ const dot = AM.x * AB.x + AM.y * AB.y
595
+ const distanceAB = getDistanceBetweenPoints(A, B)
596
+ let param = -1
597
+ if (distanceAB == 0) {
598
+ //A et B sont confondu
599
+ console.error('A and B don\'t make a line : A==B')
600
+ P = M
601
+ } else {
602
+ // in case of 0 length line
603
+ const distanceAP = dot / distanceAB
604
+ P.x = A.x + (distanceAP * AB.x) / distanceAB
605
+ P.y = A.y + (distanceAP * AB.y) / distanceAB
606
+ }
607
+ } else {
608
+ //make 3D projection on line
609
+ const AM = {
610
+ x: M.x - A.x,
611
+ y: M.y - A.y,
612
+ z: M.z - A.z
613
+ }
614
+ const AB = {
615
+ x: B.x - A.x,
616
+ y: B.y - A.y,
617
+ z: B.z - A.z
618
+ }
619
+
620
+ const dot = AM.x * AB.x + AM.y * AB.y + AM.z * AB.z
621
+ const distanceAB = get3DDistanceBetweenPoints(A, B)
622
+ let param = -1
623
+ if (distanceAB == 0) {
624
+ //A et B sont confondu
625
+ console.error('A and B don\'t make a line : A==B')
626
+ P = M
627
+ } else {
628
+ // in case of 0 length line
629
+ const distanceAP = dot / distanceAB
630
+ P.x = A.x + (distanceAP * AB.x) / distanceAB
631
+ P.y = A.y + (distanceAP * AB.y) / distanceAB
632
+ P.z = A.z + (distanceAP * AB.z) / distanceAB
633
+ }
634
+ }
635
+ //projection of the point to the line
636
+ return P
638
637
  }
639
638
  export function translate2D(point, vector) {
640
- return {
641
- ...point,
642
- x: point.x + vector.x,
643
- y: point.y + vector.y,
644
- z: point.z,
645
- };
639
+ return {
640
+ ...point,
641
+ x: point.x + vector.x,
642
+ y: point.y + vector.y,
643
+ z: point.z
644
+ }
646
645
  }
647
646
 
648
647
  export function normalizedVectorTowardInsideAngle(H, I, J) {
649
- const IH = normalizeVector(substractVector(H, I));
650
- const IJ = normalizeVector(substractVector(J, I));
651
- const res = normalizeVector(addVector(IH, IJ));
652
- return res;
648
+ const IH = normalizeVector(substractVector(H, I))
649
+ const IJ = normalizeVector(substractVector(J, I))
650
+ const sumVector = addVector(IH, IJ)
651
+ let res = {}
652
+ if (vectorLength(sumVector) < 0.1) {
653
+ res.x = IJ.y
654
+ res.y = -IJ.x
655
+ res.z = 0
656
+ } else {
657
+ res = normalizeVector(sumVector)
658
+ }
659
+ return res
653
660
  }
654
661
 
655
662
  export function isPointBetweenSegment(A, B, C) {
656
- const AB = getDistanceBetweenPoints(A, B);
657
- const AC = getDistanceBetweenPoints(A, C);
658
- const BC = getDistanceBetweenPoints(C, B);
659
- const isBetween = AB < BC && AC < BC;
660
- return isBetween;
663
+ const AB = getDistanceBetweenPoints(A, B)
664
+ const AC = getDistanceBetweenPoints(A, C)
665
+ const BC = getDistanceBetweenPoints(C, B)
666
+ const isBetween = AB < BC && AC < BC
667
+ return isBetween
661
668
  }
662
669
  export function get3pointNotAlignedFromOutline(outline) {
663
- if (outline.length < 3) {
664
- return null;
665
- }
666
- const A = outline[0];
667
- const B = outline[1];
668
- let C = outline[2];
669
- let k = 2;
670
- while (k < outline.length && getDegree(A, B, C) % 180 == 0) {
671
- C = outline[k];
672
- k++;
673
- }
674
- if (k == outline.length) {
675
- return null;
676
- }
677
- return [A, B, C];
670
+ if (outline.length < 3) {
671
+ return null
672
+ }
673
+ const A = outline[0]
674
+ const B = outline[1]
675
+ let C = outline[2]
676
+ let k = 2
677
+ while (k < outline.length && getDegree(A, B, C) % 180 == 0) {
678
+ C = outline[k]
679
+ k++
680
+ }
681
+ if (k == outline.length) {
682
+ return null
683
+ }
684
+ return [A, B, C]
678
685
  }
679
686
  export function getNormalVectortoEdgeTowardPolygon(A, B, outline) {
680
- const AB = substractVector(B, A);
681
- const ABC = get3pointNotAlignedFromOutline(outline);
682
- if (!ABC) {
683
- return null;
684
- }
685
- const PolyNormalVector = getNormalVectorFrom3Points(...ABC);
686
- let ABnormal = crossProduct(AB, PolyNormalVector);
687
- let u = normalizeVector(ABnormal);
688
- const outlineMeanPoint = meanVector(outline);
689
- const M = midPoint(A, B);
690
- const dotProd = dotProduct(u, substractVector(outlineMeanPoint, M));
691
-
692
- if (dotProd >= 0) {
693
- return u;
694
- } else {
695
- return multiplyVector(-1, u);
696
- }
687
+ const AB = substractVector(B, A)
688
+ const ABC = get3pointNotAlignedFromOutline(outline)
689
+ if (!ABC) {
690
+ return null
691
+ }
692
+ const PolyNormalVector = getNormalVectorFrom3Points(...ABC)
693
+ let ABnormal = crossProduct(AB, PolyNormalVector)
694
+ let u = normalizeVector(ABnormal)
695
+ const outlineMeanPoint = meanVector(outline)
696
+ const M = midPoint(A, B)
697
+ const dotProd = dotProduct(u, substractVector(outlineMeanPoint, M))
698
+
699
+ if (dotProd >= 0) {
700
+ return u
701
+ } else {
702
+ return multiplyVector(-1, u)
703
+ }
697
704
  }
698
705
  export function getNormalVectorFrom3Points(A, B, C) {
699
- const AB = substractVector(B, A);
700
- const BC = substractVector(C, B);
701
- const normalVector = normalizeVector(crossProduct(AB, BC));
702
- return normalVector;
706
+ const AB = substractVector(B, A)
707
+ const BC = substractVector(C, B)
708
+ const normalVector = normalizeVector(crossProduct(AB, BC))
709
+ return normalVector
703
710
  }
704
711
  export function normalVectorWithDirectionAndIncline(direction, incline) {
705
- return {
706
- x:
712
+ return {
713
+ x:
707
714
  Math.sin((direction * Math.PI) / 180) *
708
715
  Math.sin((incline * Math.PI) / 180),
709
- y:
716
+ y:
710
717
  Math.cos((direction * Math.PI) / 180) *
711
718
  Math.sin((incline * Math.PI) / 180),
712
- z: Math.cos((incline * Math.PI) / 180),
713
- };
719
+ z: Math.cos((incline * Math.PI) / 180)
720
+ }
714
721
  }
715
722
  export function inclineWithNormalVector(normalVector) {
716
- const angleRad = Math.acos(dotProduct(normalVector, { x: 0, y: 0, z: 1 }));
717
- const angleDeg = (angleRad * 180) / Math.PI;
718
- return angleDeg;
723
+ const angleRad = Math.acos(dotProduct(normalVector, { x: 0, y: 0, z: 1 }))
724
+ const angleDeg = (angleRad * 180) / Math.PI
725
+ return angleDeg
719
726
  }
720
727
 
721
728
  export function directionWithNormalVector(normalVector) {
722
- if (normalVector.z < 0) {
723
- normalVector = multiplyVector(-1, normalVector);
724
- }
725
- const normalVectorProjectionToGround = normalizeVector({
726
- ...normalVector,
727
- z: 0,
728
- });
729
- if (isSamePoint3D(normalVectorProjectionToGround, { x: 0, y: 0, z: 0 }))
730
- return 0;
731
-
732
- const angleRad = Math.acos(
733
- dotProduct(normalVectorProjectionToGround, { x: 0, y: 1, z: 0 })
734
- );
735
- let angleDeg = (angleRad * 180) / Math.PI;
736
- if (normalVectorProjectionToGround.x < 0) {
737
- angleDeg = 360 - angleDeg;
738
- }
739
- return angleDeg;
729
+ if (normalVector.z < 0) {
730
+ normalVector = multiplyVector(-1, normalVector)
731
+ }
732
+ const normalVectorProjectionToGround = normalizeVector({
733
+ ...normalVector,
734
+ z: 0
735
+ })
736
+ if (isSamePoint3D(normalVectorProjectionToGround, { x: 0, y: 0, z: 0 }))
737
+ return 0
738
+
739
+ const angleRad = Math.acos(
740
+ dotProduct(normalVectorProjectionToGround, { x: 0, y: 1, z: 0 })
741
+ )
742
+ let angleDeg = (angleRad * 180) / Math.PI
743
+ if (normalVectorProjectionToGround.x < 0) {
744
+ angleDeg = 360 - angleDeg
745
+ }
746
+ return angleDeg
740
747
  }
741
748
 
742
749
  export function verticalProjectionOnPlane(p, n, o) {
743
- const d = dotProduct(n, o);
744
- const projectedHeight = (d - n.x * p.x - n.y * p.y) / n.z;
745
- const projectedPoint = { ...p, z: projectedHeight };
746
- return projectedPoint;
750
+ const d = dotProduct(n, o)
751
+ const projectedHeight = (d - n.x * p.x - n.y * p.y) / n.z
752
+ const projectedPoint = { ...p, z: projectedHeight }
753
+ return projectedPoint
747
754
  }
748
755
 
749
756
  export function calcPolygonArea(vertices) {
750
- var total = 0;
757
+ var total = 0
751
758
 
752
- for (var i = 0, l = vertices.length; i < l; i++) {
753
- var addX = vertices[i].x;
754
- var addY = vertices[i == vertices.length - 1 ? 0 : i + 1].y;
755
- var subX = vertices[i == vertices.length - 1 ? 0 : i + 1].x;
756
- var subY = vertices[i].y;
759
+ for (var i = 0, l = vertices.length; i < l; i++) {
760
+ var addX = vertices[i].x
761
+ var addY = vertices[i == vertices.length - 1 ? 0 : i + 1].y
762
+ var subX = vertices[i == vertices.length - 1 ? 0 : i + 1].x
763
+ var subY = vertices[i].y
757
764
 
758
- total += addX * addY * 0.5;
759
- total -= subX * subY * 0.5;
760
- }
765
+ total += addX * addY * 0.5
766
+ total -= subX * subY * 0.5
767
+ }
761
768
 
762
- return Math.abs(total);
769
+ return Math.abs(total)
763
770
  }
764
771
 
765
772
  export function pointProjectionOnPlane(p, n, o) {
766
- //formula for p', projection of p on plane defined by normalvector n passing by o
767
- //p' = p - (n ⋅ (p - o)) × n
768
- const pPrim = substractVector(
769
- p,
770
- multiplyVector(dotProduct(n, substractVector(p, o)), n)
771
- );
772
- return pPrim;
773
+ //formula for p', projection of p on plane defined by normalvector n passing by o
774
+ //p' = p - (n (p - o)) × n
775
+ const pPrim = substractVector(
776
+ p,
777
+ multiplyVector(dotProduct(n, substractVector(p, o)), n)
778
+ )
779
+ return pPrim
773
780
  }
774
781
 
775
782
  export function isClockWise(outline) {
776
- //find smallest x and y coord
777
- const length = outline.length;
778
- let smallest_x = Infinity;
779
- let smallest_y = Infinity;
780
- let hullIndex = null;
781
- outline.forEach((p, index) => {
782
- if (p.x <= smallest_x) {
783
- smallest_x = p.x;
784
- if (p.y < smallest_y) {
785
- smallest_y = p.y;
786
- hullIndex = index;
787
- }
788
- }
789
- });
790
- const B = outline[hullIndex];
791
- const A = outline[(hullIndex - 1 + length) % length];
792
- const C = outline[(hullIndex + 1) % length];
793
- const det = (B.x - A.x) * (C.y - A.y) - (C.x - A.x) * (B.y - A.y);
794
- return det < 0;
783
+ //find smallest x and y coord
784
+ const length = outline.length
785
+ let smallest_x = Infinity
786
+ let smallest_y = Infinity
787
+ let hullIndex = null
788
+ outline.forEach((p, index) => {
789
+ if (p.x <= smallest_x) {
790
+ smallest_x = p.x
791
+ if (p.y < smallest_y) {
792
+ smallest_y = p.y
793
+ hullIndex = index
794
+ }
795
+ }
796
+ })
797
+ const B = outline[hullIndex]
798
+ const A = outline[(hullIndex - 1 + length) % length]
799
+ const C = outline[(hullIndex + 1) % length]
800
+ const det = (B.x - A.x) * (C.y - A.y) - (C.x - A.x) * (B.y - A.y)
801
+ return det < 0
795
802
  }
796
803
  export function groupAdjacentObjects(objects) {
797
- if (!objects || objects.length == 0) return [];
798
- // objects with 2D indexes
799
- // Check if the objects form an adjacent group
800
- let numberOfObjects = objects.length;
801
- let indexList = Array.from({ length: numberOfObjects }, (e, i) => i);
802
- let buckets = [];
803
- let inBucket = 0;
804
- while (inBucket < objects.length) {
805
- let detatchedIndex = indexList.find((i) => !buckets.flat().includes(i));
806
- if (detatchedIndex == null) break;
807
- let queue = [detatchedIndex];
808
- let visited = [];
809
- while (queue.length > 0) {
810
- const currentObjectIndex = queue.pop();
811
- if (visited.indexOf(currentObjectIndex) == -1) {
812
- //if next queued index hasn't been visited, we mark it as visited and we collect all neighbourg to queue
813
- visited.push(currentObjectIndex);
814
- let x = objects[currentObjectIndex].index[0];
815
- let y = objects[currentObjectIndex].index[1];
816
- const left = objects.findIndex(
817
- (o) => o.index[0] == x - 1 && o.index[1] == y
818
- );
819
- const right = objects.findIndex(
820
- (o) => o.index[0] == x + 1 && o.index[1] == y
821
- );
822
- const top = objects.findIndex(
823
- (o) => o.index[0] == x && o.index[1] == y - 1
824
- );
825
- const bottom = objects.findIndex(
826
- (o) => o.index[0] == x && o.index[1] == y + 1
827
- );
828
- if (left != -1 && visited.indexOf(left) == -1) queue.push(left);
829
- if (right != -1 && visited.indexOf(right) == -1) queue.push(right);
830
- if (top != -1 && visited.indexOf(top) == -1) queue.push(top);
831
- if (bottom != -1 && visited.indexOf(bottom) == -1) queue.push(bottom);
832
- }
833
- }
834
- inBucket += visited.length;
835
- buckets.push(visited);
836
- }
837
-
838
- return buckets;
804
+ if (!objects || objects.length == 0) return []
805
+ // objects with 2D indexes
806
+ // Check if the objects form an adjacent group
807
+ let numberOfObjects = objects.length
808
+ let indexList = Array.from({ length: numberOfObjects }, (e, i) => i)
809
+ let buckets = []
810
+ let inBucket = 0
811
+ while (inBucket < objects.length) {
812
+ let detatchedIndex = indexList.find((i) => !buckets.flat().includes(i))
813
+ if (detatchedIndex == null) break
814
+ let queue = [detatchedIndex]
815
+ let visited = []
816
+ while (queue.length > 0) {
817
+ const currentObjectIndex = queue.pop()
818
+ if (visited.indexOf(currentObjectIndex) == -1) {
819
+ //if next queued index hasn't been visited, we mark it as visited and we collect all neighbourg to queue
820
+ visited.push(currentObjectIndex)
821
+ let x = objects[currentObjectIndex].index[0]
822
+ let y = objects[currentObjectIndex].index[1]
823
+ const left = objects.findIndex(
824
+ (o) => o.index[0] == x - 1 && o.index[1] == y
825
+ )
826
+ const right = objects.findIndex(
827
+ (o) => o.index[0] == x + 1 && o.index[1] == y
828
+ )
829
+ const top = objects.findIndex(
830
+ (o) => o.index[0] == x && o.index[1] == y - 1
831
+ )
832
+ const bottom = objects.findIndex(
833
+ (o) => o.index[0] == x && o.index[1] == y + 1
834
+ )
835
+ if (left != -1 && visited.indexOf(left) == -1) queue.push(left)
836
+ if (right != -1 && visited.indexOf(right) == -1) queue.push(right)
837
+ if (top != -1 && visited.indexOf(top) == -1) queue.push(top)
838
+ if (bottom != -1 && visited.indexOf(bottom) == -1) queue.push(bottom)
839
+ }
840
+ }
841
+ inBucket += visited.length
842
+ buckets.push(visited)
843
+ }
844
+
845
+ return buckets
839
846
  }
840
847
  // Function to check if two indexes are adjacent
841
848
  export function areAdjacent(objects) {
842
- if (!objects || objects.length == 0) return false;
843
- // objects with 2D indexes
844
- // Check if the objects form an adjacent group
845
- let visited = [];
846
- let queue = [0];
847
-
848
- while (queue.length > 0) {
849
- const currentObjectIndex = queue.pop();
850
- if (visited.indexOf(currentObjectIndex) == -1) {
851
- //if next queued index hasn't been visited, we mark it as visited and we collect all neighbourg to queue
852
- visited.push(currentObjectIndex);
853
- let x = objects[currentObjectIndex].index[0];
854
- let y = objects[currentObjectIndex].index[1];
855
- const left = objects.findIndex(
856
- (o) => o.index[0] == x - 1 && o.index[1] == y
857
- );
858
- const right = objects.findIndex(
859
- (o) => o.index[0] == x + 1 && o.index[1] == y
860
- );
861
- const top = objects.findIndex(
862
- (o) => o.index[0] == x && o.index[1] == y - 1
863
- );
864
- const bottom = objects.findIndex(
865
- (o) => o.index[0] == x && o.index[1] == y + 1
866
- );
867
- if (left != -1 && visited.indexOf(left) == -1) queue.push(left);
868
- if (right != -1 && visited.indexOf(right) == -1) queue.push(right);
869
- if (top != -1 && visited.indexOf(top) == -1) queue.push(top);
870
- if (bottom != -1 && visited.indexOf(bottom) == -1) queue.push(bottom);
871
- }
872
- }
873
- return visited.length == objects.length;
849
+ if (!objects || objects.length == 0) return false
850
+ // objects with 2D indexes
851
+ // Check if the objects form an adjacent group
852
+ let visited = []
853
+ let queue = [0]
854
+
855
+ while (queue.length > 0) {
856
+ const currentObjectIndex = queue.pop()
857
+ if (visited.indexOf(currentObjectIndex) == -1) {
858
+ //if next queued index hasn't been visited, we mark it as visited and we collect all neighbourg to queue
859
+ visited.push(currentObjectIndex)
860
+ let x = objects[currentObjectIndex].index[0]
861
+ let y = objects[currentObjectIndex].index[1]
862
+ const left = objects.findIndex(
863
+ (o) => o.index[0] == x - 1 && o.index[1] == y
864
+ )
865
+ const right = objects.findIndex(
866
+ (o) => o.index[0] == x + 1 && o.index[1] == y
867
+ )
868
+ const top = objects.findIndex(
869
+ (o) => o.index[0] == x && o.index[1] == y - 1
870
+ )
871
+ const bottom = objects.findIndex(
872
+ (o) => o.index[0] == x && o.index[1] == y + 1
873
+ )
874
+ if (left != -1 && visited.indexOf(left) == -1) queue.push(left)
875
+ if (right != -1 && visited.indexOf(right) == -1) queue.push(right)
876
+ if (top != -1 && visited.indexOf(top) == -1) queue.push(top)
877
+ if (bottom != -1 && visited.indexOf(bottom) == -1) queue.push(bottom)
878
+ }
879
+ }
880
+ return visited.length == objects.length
874
881
  }
875
882
  export function getMarginPoint(
876
- A,
877
- B,
878
- C,
879
- marginA,
880
- marginB,
881
- clockwiseNormalVector
883
+ A,
884
+ B,
885
+ C,
886
+ marginA,
887
+ marginB,
888
+ clockwiseNormalVector
882
889
  ) {
883
- const n = multiplyVector(
884
- marginA,
885
- normalizeVector(crossProduct(substractVector(B, A), clockwiseNormalVector))
886
- );
887
- const m = multiplyVector(
888
- marginB,
889
- normalizeVector(crossProduct(substractVector(C, B), clockwiseNormalVector))
890
- );
891
- //K=B+[(m.m-n.m)/AB.m]*AB
892
- const AB = substractVector(B, A);
893
- const mAB = dotProduct(AB, m);
894
-
895
- let K;
896
- if (mAB == 0) {
897
- K =
890
+ const n = multiplyVector(
891
+ marginA,
892
+ normalizeVector(crossProduct(substractVector(B, A), clockwiseNormalVector))
893
+ )
894
+ const m = multiplyVector(
895
+ marginB,
896
+ normalizeVector(crossProduct(substractVector(C, B), clockwiseNormalVector))
897
+ )
898
+ //K=B+[(m.m-n.m)/AB.m]*AB
899
+ const AB = substractVector(B, A)
900
+ const mAB = dotProduct(AB, m)
901
+
902
+ let K
903
+ if (mAB == 0) {
904
+ K =
898
905
  dotProduct(m, m) != 0
899
- ? addVector(B, m)
900
- : dotProduct(n, n) != 0
901
- ? addVector(B, n)
902
- : B;
903
- } else {
904
- const mm = dotProduct(m, m);
905
- const nm = dotProduct(n, m);
906
- const lambda = (mm - nm) / mAB;
907
- K = addVector(addVector(B, n), multiplyVector(lambda, AB));
908
- }
909
- return K;
906
+ ? addVector(B, m)
907
+ : dotProduct(n, n) != 0
908
+ ? addVector(B, n)
909
+ : B
910
+ } else {
911
+ const mm = dotProduct(m, m)
912
+ const nm = dotProduct(n, m)
913
+ const lambda = (mm - nm) / mAB
914
+ K = addVector(addVector(B, n), multiplyVector(lambda, AB))
915
+ }
916
+ return K
910
917
  }
911
918
  export function calculateArea(vertices) {
912
- var n = vertices.length;
913
- var area = 0;
914
- for (var i = 0; i < n; i++) {
915
- var v_current = substractVector(vertices[i], vertices[0]);
916
- var v_next = substractVector(vertices[(i + 1) % n], vertices[0]);
917
- const product = crossProduct(v_current, v_next);
918
- const sgn = product.z > 0 ? -1 : 1;
919
- area += 0.5 * sgn * vectorLength(product);
920
- }
921
- return Math.abs(area);
919
+ var n = vertices.length
920
+ var area = 0
921
+ for (var i = 0; i < n; i++) {
922
+ var v_current = substractVector(vertices[i], vertices[0])
923
+ var v_next = substractVector(vertices[(i + 1) % n], vertices[0])
924
+ const product = crossProduct(v_current, v_next)
925
+ const sgn = product.z > 0 ? -1 : 1
926
+ area += 0.5 * sgn * vectorLength(product)
927
+ }
928
+ return Math.abs(area)
922
929
  }
923
930
 
924
931
  export function hasNaN(arr) {
925
- for (let i = 0; i < arr.length; i++) {
926
- // check if array value is false or NaN
927
- if (Number.isNaN(arr[i])) {
928
- return true;
929
- }
930
- }
931
- return false;
932
+ for (let i = 0; i < arr.length; i++) {
933
+ // check if array value is false or NaN
934
+ if (Number.isNaN(arr[i])) {
935
+ return true
936
+ }
937
+ }
938
+ return false
932
939
  }
933
940
 
934
941
  export function calculatePositionSubHandle(polygon, i, mmPerPx) {
935
- const outline = polygon.outline;
936
- const length = outline.length;
937
-
938
- const h = (i - 1 + length) % length;
939
- const j = (i + 1) % length;
940
-
941
- //calculation of the angle where we need to insert the handle.
942
- const IH_angle = Math.atan2(
943
- outline[h].y - outline[i].y,
944
- outline[h].x - outline[i].x
945
- );
946
- const JI_angle = Math.atan2(
947
- outline[j].y - outline[i].y,
948
- outline[j].x - outline[i].x
949
- );
950
- const handle_angle = (IH_angle + JI_angle) / 2;
951
-
952
- //d is the handle distance in px inside polygon vertex
953
- const d = 20 * mmPerPx;
954
- const r = d / 4;
955
- let handleX = outline[i].x + d * Math.cos(handle_angle);
956
- let handleY = outline[i].y + d * Math.sin(handle_angle);
957
- let handleZ = outline[i].z;
958
-
959
- //check if handle is inside the polygon, if not, change where the handle is displayed
960
- const isInside = isInsidePolygon(
961
- { x: handleX, y: handleY, z: handleZ },
962
- outline
963
- );
964
- if (!isInside) {
965
- handleX = outline[i].x - d * Math.cos(handle_angle);
966
- handleY = outline[i].y - d * Math.sin(handle_angle);
967
- }
968
- return { x: handleX, y: handleY, z: outline[i].z };
942
+ const outline = polygon.outline
943
+ const length = outline.length
944
+
945
+ const h = (i - 1 + length) % length
946
+ const j = (i + 1) % length
947
+
948
+ //calculation of the angle where we need to insert the handle.
949
+ const IH_angle = Math.atan2(
950
+ outline[h].y - outline[i].y,
951
+ outline[h].x - outline[i].x
952
+ )
953
+ const JI_angle = Math.atan2(
954
+ outline[j].y - outline[i].y,
955
+ outline[j].x - outline[i].x
956
+ )
957
+ const handle_angle = (IH_angle + JI_angle) / 2
958
+
959
+ //d is the handle distance in px inside polygon vertex
960
+ const d = 20 * mmPerPx
961
+ const r = d / 4
962
+ let handleX = outline[i].x + d * Math.cos(handle_angle)
963
+ let handleY = outline[i].y + d * Math.sin(handle_angle)
964
+ let handleZ = outline[i].z
965
+
966
+ //check if handle is inside the polygon, if not, change where the handle is displayed
967
+ const isInside = isInsidePolygon(
968
+ { x: handleX, y: handleY, z: handleZ },
969
+ outline
970
+ )
971
+ if (!isInside) {
972
+ handleX = outline[i].x - d * Math.cos(handle_angle)
973
+ handleY = outline[i].y - d * Math.sin(handle_angle)
974
+ }
975
+ return { x: handleX, y: handleY, z: outline[i].z }
969
976
  }
970
977
  export function triangleArea(A, B, C) {
971
- var AB = substractVector(B, A);
972
- var AC = substractVector(C, A);
973
- const product = crossProduct(AB, AC);
974
- let area = vectorLength(product) / 2;
975
- return area;
976
- }
977
- export function getIndexesOfBiggestTrangle(points) {
978
- return getIndexesOfBiggestTrangleWithFixedIndexes(points, []);
979
- }
980
- export function getIndexesOfBiggestTrangleWithFixedIndexes(
981
- points,
982
- fixedIndexes
978
+ var AB = substractVector(B, A)
979
+ var AC = substractVector(C, A)
980
+ const product = crossProduct(AB, AC)
981
+ let area = vectorLength(product) / 2
982
+ return area
983
+ }
984
+ export function getIndexesOfBiggestTriangle(points) {
985
+ return getIndexesOfBiggestTriangleWithFixedIndexes(points, [])
986
+ }
987
+ export function arrayIntersection(array1, array2) {
988
+ return array1.filter((value) => array2.includes(value))
989
+ }
990
+ export function getIndexesOfBiggestTriangleWithFixedIndexes(
991
+ points,
992
+ fixedIndexes,
993
+ preferedIndexes = [],
994
+ areaMinFactor = 4
995
+ ) {
996
+ if (points.length < 3) {
997
+ throw new Error('not enough points to make a triangle')
998
+ }
999
+ fixedIndexes = [...new Set(fixedIndexes)]
1000
+ if (fixedIndexes.length >= 3) {
1001
+ return getIndexesOfBiggestTriangleWithFixedIndexes(points, [], fixedIndexes)
1002
+ }
1003
+ let minArea = calculateArea(points) / areaMinFactor
1004
+ let preferedAndFixedIndexes = [...fixedIndexes, ...preferedIndexes]
1005
+ preferedAndFixedIndexes = [...new Set(preferedAndFixedIndexes)]
1006
+ let maxArea = minArea
1007
+ let maxNumberOfPreferedOrFixedIndexes = -1
1008
+ let maxTriangle = [0, 1, 2]
1009
+ let triangleFound = false
1010
+ for (let i = 0; i < points.length - 2; i++) {
1011
+ for (let j = i + 1; j < points.length - 1; j++) {
1012
+ for (let k = j + 1; k < points.length; k++) {
1013
+ if (
1014
+ arrayIntersection(fixedIndexes, [i, j, k]).length ==
1015
+ fixedIndexes.length
1016
+ ) {
1017
+ const numberOfPreferedOrFixedIndexes = arrayIntersection(
1018
+ preferedAndFixedIndexes,
1019
+ [i, j, k]
1020
+ ).length
1021
+ if (
1022
+ numberOfPreferedOrFixedIndexes >= maxNumberOfPreferedOrFixedIndexes
1023
+ ) {
1024
+ let area = triangleArea(points[i], points[j], points[k])
1025
+ if (
1026
+ area >= minArea &&
1027
+ numberOfPreferedOrFixedIndexes > maxNumberOfPreferedOrFixedIndexes
1028
+ ) {
1029
+ maxArea = minArea
1030
+ }
1031
+ if (area >= maxArea) {
1032
+ triangleFound = true
1033
+ maxArea = area
1034
+ maxNumberOfPreferedOrFixedIndexes = numberOfPreferedOrFixedIndexes
1035
+ maxTriangle = [i, j, k]
1036
+ }
1037
+ }
1038
+ }
1039
+ }
1040
+ }
1041
+ }
1042
+ if (!triangleFound && areaMinFactor <= 8) {
1043
+ areaMinFactor += 2
1044
+ return getIndexesOfBiggestTriangleWithFixedIndexes(
1045
+ points,
1046
+ fixedIndexes,
1047
+ preferedIndexes,
1048
+ areaMinFactor
1049
+ )
1050
+ }
1051
+ return maxTriangle
1052
+ }
1053
+ export function getIndexesOfBiggestTriangleWithTwoFixedIndexes(
1054
+ points,
1055
+ fixedIndexes,
1056
+ preferedIndexes = [],
1057
+ areaMinFactor = 4
983
1058
  ) {
984
- if (points.length < 3) {
985
- throw new Error("not enough points to make a triangle");
986
- return;
987
- }
988
- let maxArea = -1;
989
- let maxTriangle = [0, 1, 2];
990
- fixedIndexes = [...new Set(...fixedIndexes)].slice(0, 3);
991
- if (fixedIndexes.length == 3) {
992
- return fixedIndexes;
993
- }
994
- for (let i = 0; i < points.length - 2; i++) {
995
- if (fixedIndexes.length == 0 || fixedIndexes.includes(i)) {
996
- for (let j = i + 1; j < points.length - 1; j++) {
997
- if (fixedIndexes.length < 2 || fixedIndexes.includes(j)) {
998
- for (let k = j + 1; k < points.length; k++) {
999
- const area = triangleArea(points[i], points[j], points[k]);
1000
- if (area > maxArea) {
1001
- maxArea = area;
1002
- maxTriangle = [i, j, k];
1003
- }
1004
- }
1005
- }
1006
- }
1007
- }
1008
- }
1009
- return maxTriangle;
1059
+ fixedIndexes = [...new Set(fixedIndexes)]
1060
+ let preferedAndFixedIndexes = [...fixedIndexes, ...preferedIndexes]
1061
+ preferedAndFixedIndexes = [...new Set(preferedAndFixedIndexes)]
1062
+ if (points.length < 3 || fixedIndexes.length < 3) {
1063
+ throw new Error('not enough points to make a triangle')
1064
+ return
1065
+ }
1066
+ let minArea = calculateArea(points) / areaMinFactor
1067
+ let maxArea = minArea
1068
+ let maxNumberOfPreferedOrFixedIndexes = -1
1069
+ let maxTriangle = [0, 1, 2]
1070
+ let triangleFound = false
1071
+ for (let i = 0; i < points.length - 2; i++) {
1072
+ for (let j = i + 1; j < points.length - 1; j++) {
1073
+ for (let k = j + 1; k < points.length; k++) {
1074
+ if (arrayIntersection(fixedIndexes, [i, j, k]).length == 2) {
1075
+ const numberOfPreferedOrFixedIndexes = arrayIntersection(
1076
+ preferedAndFixedIndexes,
1077
+ [i, j, k]
1078
+ ).length
1079
+ if (
1080
+ numberOfPreferedOrFixedIndexes >= maxNumberOfPreferedOrFixedIndexes
1081
+ ) {
1082
+ let area = triangleArea(points[i], points[j], points[k])
1083
+ if (
1084
+ area >= minArea &&
1085
+ numberOfPreferedOrFixedIndexes > maxNumberOfPreferedOrFixedIndexes
1086
+ ) {
1087
+ maxArea = minArea
1088
+ }
1089
+ //give advantages to connected area
1090
+ if (area > maxArea) {
1091
+ triangleFound = true
1092
+ maxArea = area
1093
+ maxNumberOfPreferedOrFixedIndexes = numberOfPreferedOrFixedIndexes
1094
+ maxTriangle = [i, j, k]
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1099
+ }
1100
+ }
1101
+ if (!triangleFound && areaMinFactor <= 8) {
1102
+ areaMinFactor += 2
1103
+ return getIndexesOfBiggestTriangleWithTwoFixedIndexes(
1104
+ points,
1105
+ fixedIndexes,
1106
+ preferedIndexes,
1107
+ areaMinFactor
1108
+ )
1109
+ }
1110
+ return maxTriangle
1010
1111
  }