@eturnity/eturnity_maths 1.0.2

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.
@@ -0,0 +1,760 @@
1
+ import {
2
+ polygonCloseTolerance,
3
+ } from './config'
4
+
5
+ import {addVector,
6
+ substractVector,
7
+ multiplyVector,
8
+ dotProduct,
9
+ crossProduct,
10
+ meanVector,
11
+ vectorLength,
12
+ normalizeVector} from './vector'
13
+ import {Point} from './objects/Point'
14
+ import {Line} from './objects/Line'
15
+
16
+ export function getSnapedValue(value, snaps, tolerance) {
17
+ let closeSnapsItem = snaps.reduce(
18
+ (acc, cur) => {
19
+ let distance = Math.abs(cur - value)
20
+ if (distance <= Math.min(tolerance, acc.distance)) {
21
+ acc = { value: cur, distance }
22
+ }
23
+ return acc
24
+ },
25
+ { value, distance: tolerance }
26
+ )
27
+ return closeSnapsItem.value
28
+ }
29
+ export function getAngleInDegFromCanvasVector(v) {
30
+ const angle = Math.atan2(v.y, v.x)
31
+ return ((angle * 180) / Math.PI + 90 + 360) % 360
32
+ }
33
+ export function getOrthogonalLineABPassingInB(A, B) {
34
+ if (isSamePoint3D(A, B)) {
35
+ console.error("A == B Can't make a 90° line")
36
+ return null
37
+ }
38
+ const C = new Point(A.x + A.y - B.y, A.y + B.x - A.x)
39
+ return new Line(A, C, '')
40
+ }
41
+ // Return orthogonal line passing by C
42
+ export function getOrthogonalLinePassingByPoint(A, B, C) {
43
+ if (isSamePoint3D(A, B)) {
44
+ console.error("A == B Can't make a orthogonal line")
45
+ return null
46
+ }
47
+ const M = new Point(C.x + B.y - A.y, C.y + A.x - B.x)
48
+ return new Line(C, M, '')
49
+ }
50
+
51
+ export function getParallelLinePassingByPoint(A, B, C) {
52
+ if (isSamePoint3D(A, B)) {
53
+ console.error("A == B Can't make a parallel line")
54
+ return null
55
+ }
56
+ const D = new Point(C.x + B.x - A.x, C.y + B.y - A.y)
57
+ return new Line(C, D, '')
58
+ }
59
+ export function vectorFromAngleInDegCanvas(angle) {
60
+ return {
61
+ x: Math.sin((angle * Math.PI) / 180),
62
+ y: -Math.cos((angle * Math.PI) / 180)
63
+ }
64
+ }
65
+ export function getDistanceBetweenPoints(firstPoint, secondPoint) {
66
+ const distance = Math.hypot(
67
+ firstPoint.x - secondPoint.x,
68
+ firstPoint.y - secondPoint.y
69
+ )
70
+ return distance
71
+ }
72
+
73
+ export function getDegree(H, I, J) {
74
+ const a = getDistanceBetweenPoints(H, I)
75
+ const b = getDistanceBetweenPoints(I, J)
76
+ const c = getDistanceBetweenPoints(H, J)
77
+
78
+ let angle = Math.round(
79
+ (Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180) / Math.PI
80
+ )
81
+ //if 3 points are aligned
82
+ if (isNaN(angle)) {
83
+ angle = c < a ? 0 : 180
84
+ }
85
+ return angle
86
+ }
87
+
88
+ export function midPoint(firstPoint, secondPoint) {
89
+ firstPoint.z = firstPoint.z || 0
90
+ secondPoint.z = secondPoint.z || 0
91
+ return {
92
+ x: (firstPoint.x + secondPoint.x) / 2,
93
+ y: (firstPoint.y + secondPoint.y) / 2,
94
+ z: (firstPoint.z + secondPoint.z) / 2
95
+ }
96
+ }
97
+
98
+ export function isSamePoint3D(A, B) {
99
+ return A.x == B.x && A.y == B.y && A.z == B.z
100
+ }
101
+ export function isSamePoint2D(A, B) {
102
+ return A.x == B.x && A.y == B.y
103
+ }
104
+ export function isAlmostSamePoint2D(A, B, tolerance) {
105
+ return Math.abs(A.x - B.x) < tolerance && Math.abs(A.y - B.y) < tolerance
106
+ }
107
+ export function isSameSegment2D(seg_0, seg_1) {
108
+ let check_1 =
109
+ isSamePoint2D(seg_0[0], seg_1[0]) && isSamePoint2D(seg_0[1], seg_1[1])
110
+ let check_2 =
111
+ isSamePoint2D(seg_0[0], seg_1[1]) && isSamePoint2D(seg_0[1], seg_1[0])
112
+ return check_1 || check_2
113
+ }
114
+ export function isAlmostSameSegment2D(seg_0, seg_1, tolerance) {
115
+ let check_1 =
116
+ isAlmostSamePoint2D(seg_0[0], seg_1[0], tolerance) &&
117
+ isAlmostSamePoint2D(seg_0[1], seg_1[1], tolerance)
118
+ let check_2 =
119
+ isAlmostSamePoint2D(seg_0[0], seg_1[1], tolerance) &&
120
+ isAlmostSamePoint2D(seg_0[1], seg_1[0], tolerance)
121
+ return check_1 || check_2
122
+ }
123
+ export function isSameSegment3D(seg_0, seg_1) {
124
+ let check_1 =
125
+ isSamePoint3D(seg_0[0], seg_1[0]) && isSamePoint3D(seg_0[1], seg_1[1])
126
+ let check_2 =
127
+ isSamePoint3D(seg_0[0], seg_1[1]) && isSamePoint3D(seg_0[1], seg_1[0])
128
+ return check_1 || check_2
129
+ }
130
+ export function isSameLine(AB, CD) {
131
+ if (!AB || !CD) {
132
+ console.error('AB or CD not defined')
133
+ return false
134
+ }
135
+ if (AB.type != 'line' || CD.type != 'line') {
136
+ return false
137
+ }
138
+ // if(AB.outline.length<2){return false}
139
+ // if(CD.outline.length<2){return false}
140
+ const A = AB.outline[0]
141
+ const B = AB.outline[1]
142
+ const C = CD.outline[0]
143
+ const D = CD.outline[1]
144
+ //is A on (CD)
145
+ const P = getPointOnLine(A, C, D)
146
+ if (getDistanceBetweenPoints(A, P) > 1) {
147
+ return false
148
+ }
149
+ //is B on (CD)
150
+ const M = getPointOnLine(B, C, D)
151
+ if (getDistanceBetweenPoints(B, M) > 1) {
152
+ return false
153
+ }
154
+
155
+ return true
156
+ }
157
+ export function distance2DToPolygon(point, vs) {
158
+ let distance = Infinity
159
+ if (isInsidePolygon(point, vs)) {
160
+ return 0
161
+ } else {
162
+ for (let v of vs) {
163
+ let distanceToNode = getDistanceBetweenPoints(point, v)
164
+ distance = Math.min(distanceToNode, distance)
165
+ }
166
+ for (let k in vs) {
167
+ let A = vs[k]
168
+ let B = vs[(k + 1) % vs.length]
169
+ //M projection of point on AB line
170
+ if (!isSamePoint2D(A, B)) {
171
+ let M = getPointOnLine(point, A, B)
172
+ if (isInsideEdge2D(M, A, B)) {
173
+ distance = Math.min(distance, getDistanceBetweenPoints(point, M))
174
+ }
175
+ }
176
+ }
177
+ return distance
178
+ }
179
+ }
180
+ export function get2DBoundOfPolygon(vs) {
181
+ const bound = {
182
+ xMin: Infinity,
183
+ xMax: -Infinity,
184
+ yMin: Infinity,
185
+ yMax: -Infinity
186
+ }
187
+ for (let v of vs) {
188
+ bound.xMin = Math.min(bound.xMin, v.x)
189
+ bound.xMax = Math.max(bound.xMax, v.x)
190
+ bound.yMin = Math.min(bound.yMin, v.y)
191
+ bound.yMax = Math.max(bound.yMax, v.y)
192
+ }
193
+ return bound
194
+ }
195
+ export function getPointInsideOutline(vs, holes = []) {
196
+ //draw an horizontal line between top and bottom
197
+ const bound = get2DBoundOfPolygon(vs)
198
+ const y = (bound.yMax + bound.yMin) / 2
199
+ let xOfCrossingEdgeY = []
200
+ for (let index = 0; index < vs.length; index++) {
201
+ let nextIndex = (index + 1) % vs.length
202
+ const A = vs[index]
203
+ const B = vs[nextIndex]
204
+ if ((A.y <= y && B.y > y) || (A.y >= y && B.y < y)) {
205
+ let x = A.x + (B.x - A.x) * ((y - A.y) / (B.y - A.y))
206
+ xOfCrossingEdgeY.push(x)
207
+ }
208
+ }
209
+ if (xOfCrossingEdgeY.length < 2) {
210
+ console.error('not enaugh edge crossing mid cut', vs)
211
+ return
212
+ }
213
+ xOfCrossingEdgeY.sort((a, b) => a - b)
214
+ let xLeft = xOfCrossingEdgeY[0]
215
+ let xRight = xOfCrossingEdgeY[1]
216
+ //dichotomy to get point outside of holes
217
+ for (let hole of holes) {
218
+ if (isPolygonInsidePolygon(vs, hole)) {
219
+ return { x: (xLeft + xRight) / 2, y }
220
+ }
221
+ }
222
+ let t = 0.1
223
+ let count = 0
224
+ let isInside = false
225
+ let testPoint
226
+ while (count < 10 && isInside == false) {
227
+ count++
228
+ let x = xLeft + t * (xRight - xLeft)
229
+ testPoint = { x, y }
230
+ isInside = true
231
+ for (let hole of holes) {
232
+ if (isInsidePolygon(testPoint, hole)) {
233
+ isInside = false
234
+ break
235
+ }
236
+ }
237
+ if (isInside) {
238
+ return testPoint
239
+ } else {
240
+ t = (Math.cos((count * Math.PI) / 11) + 1) / 2
241
+ }
242
+ }
243
+ return testPoint
244
+ }
245
+
246
+ export function distanceToEdge2D(M, A, B) {
247
+ const pA = { x: A.x, y: A.y, z: 0 }
248
+ const pB = { x: B.x, y: B.y, z: 0 }
249
+ const pM = { x: M.x, y: M.y, z: 0 }
250
+ const AM = substractVector(pM, pA)
251
+ const AB = substractVector(pB, pA)
252
+ const det = AM.x * AB.y - AM.y * AB.x
253
+ const ABLength = vectorLength(AB)
254
+ const AMLength = vectorLength(AM)
255
+ if (ABLength == 0 && isSamePoint2D(pM, pA)) {
256
+ return true
257
+ } else if (ABLength == 0 && !isSamePoint2D(pM, pA)) {
258
+ return false
259
+ } else if (AMLength == 0) {
260
+ return true
261
+ }
262
+ const dotP = dotProduct(AM, AB) / (ABLength * AMLength)
263
+ if (Math.abs(det) < 0.01 && dotP <= 1 && dotP >= 0) {
264
+ return true
265
+ } else {
266
+ return false
267
+ }
268
+ }
269
+
270
+ export function isInsideEdge2D(M, A, B) {
271
+ const pA = { x: A.x, y: A.y, z: 0 }
272
+ const pB = { x: B.x, y: B.y, z: 0 }
273
+ const pM = { x: M.x, y: M.y, z: 0 }
274
+ const AM = substractVector(pM, pA)
275
+ const BM = substractVector(pM, pB)
276
+ const AB = substractVector(pB, pA)
277
+ const det = AM.x * AB.y - AM.y * AB.x
278
+ const ABLength = vectorLength(AB)
279
+ const BMLength = vectorLength(BM)
280
+ const AMLength = vectorLength(AM)
281
+ if (ABLength == 0 && isSamePoint2D(pM, pA)) {
282
+ return true
283
+ } else if (ABLength == 0 && !isSamePoint2D(pM, pA)) {
284
+ return false
285
+ } else if (AMLength == 0) {
286
+ return true
287
+ }
288
+ if (Math.abs(det) < 0.01 && BMLength < ABLength && AMLength < ABLength) {
289
+ return true
290
+ } else {
291
+ return false
292
+ }
293
+ }
294
+
295
+ export function polygonsHasSame2DOutline(outline1, outline2) {
296
+ let sameLength = outline1.length == outline2.length
297
+ return (
298
+ sameLength &&
299
+ outline1.every((p) => outline2.find((v) => v.x == p.x && v.y == p.y)) &&
300
+ outline2.every((p) => outline1.find((v) => v.x == p.x && v.y == p.y))
301
+ )
302
+ }
303
+
304
+ export function isOnBorderOfPolygon(point, vs) {
305
+ let inside = false
306
+ for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {
307
+ if (isInsideEdge2D(point, vs[i], vs[j])) {
308
+ inside = true
309
+ }
310
+ }
311
+ return inside
312
+ }
313
+
314
+ export function isStrictlyInsidePolygon(point, vs) {
315
+ return isInsidePolygon(point, vs) && !isOnBorderOfPolygon(point, vs)
316
+ }
317
+
318
+ export function isInsidePolygon(point, vs) {
319
+ // ray-casting algorithm based on
320
+ // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html/pnpoly.html
321
+
322
+ const x = point.x
323
+ const y = point.y
324
+
325
+ let inside = false
326
+
327
+ for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) {
328
+ const xi = vs[i].x
329
+ const yi = vs[i].y
330
+ const xj = vs[j].x
331
+ const yj = vs[j].y
332
+
333
+ const intersect =
334
+ yi > y !== yj > y && x <= ((xj - xi) * (y - yi)) / (yj - yi) + xi
335
+ if (intersect) inside = !inside
336
+ }
337
+ //if not really inside, let's check of edge
338
+ if (!inside) {
339
+ inside = isOnBorderOfPolygon(point, vs)
340
+ }
341
+ return inside
342
+ }
343
+
344
+ export function isPolygonInsidePolygon(innerOutline, outterOutline) {
345
+ return innerOutline.every((p) => isInsidePolygon(p, outterOutline))
346
+ }
347
+
348
+ export function get3PathsFromPolyAndSplittingPath(
349
+ outline,
350
+ splittingPath,
351
+ mmPerPx
352
+ ) {
353
+ //both outline and splittingPath are in Reality coordinate system
354
+
355
+ //starting and ending points are defined by the splitting path
356
+ let startingPoint = splittingPath[0]
357
+ let endingPoint = splittingPath[splittingPath.length - 1]
358
+ //We then identify which polygon vertex index is link to these points
359
+ let startingIndex = outline.findIndex(
360
+ (p) =>
361
+ getDistanceBetweenPoints(startingPoint, p) <
362
+ polygonCloseTolerance * mmPerPx
363
+ )
364
+ let endingIndex = outline.findIndex(
365
+ (p) =>
366
+ getDistanceBetweenPoints(endingPoint, p) < polygonCloseTolerance * mmPerPx
367
+ )
368
+ //let's set altitude of every created splitting point
369
+ let nberSplittingPoint = splittingPath.length
370
+ let startingAltitude = outline[startingIndex].z
371
+ let endingAltitude = outline[endingIndex].z
372
+ for (let k = 0; k < nberSplittingPoint; k++) {
373
+ splittingPath[k].z =
374
+ ((endingAltitude - startingAltitude) * k) / nberSplittingPoint +
375
+ startingAltitude
376
+ }
377
+
378
+ let path1 = splittingPath
379
+
380
+ if (startingIndex > endingIndex) {
381
+ let tmp = startingIndex
382
+ startingIndex = endingIndex
383
+ endingIndex = tmp
384
+
385
+ tmp = endingPoint
386
+ endingPoint = startingPoint
387
+ startingPoint = tmp
388
+ path1.reverse()
389
+ }
390
+
391
+ let path2 = outline
392
+ //rotate polygon to have startingIndex point at index 0
393
+ //rotate "startingIndex" times to the left
394
+ for (let k = 0; k < startingIndex; k++) {
395
+ path2.push(path2.shift())
396
+ }
397
+ let path3 = path2.splice(0, 1 + endingIndex - startingIndex) // start and finish with the commun points
398
+ path2 = [endingPoint, ...path2, startingPoint]
399
+ path2.reverse()
400
+ return [path1, path2, path3]
401
+ }
402
+
403
+ export function get2PolygonsOutlineFrom3Paths(path1, path2, path3) {
404
+ //check if path starts and ends at the same point
405
+ if (
406
+ !(
407
+ path1[0].x == path2[0].x &&
408
+ path2[0].x == path3[0].x &&
409
+ path1[0].y == path2[0].y &&
410
+ path2[0].y == path3[0].y &&
411
+ path1[path1.length - 1].x == path2[path2.length - 1].x &&
412
+ path2[path2.length - 1].x == path3[path3.length - 1].x &&
413
+ path1[path1.length - 1].y == path2[path2.length - 1].y &&
414
+ path2[path2.length - 1].y == path3[path3.length - 1].y
415
+ )
416
+ ) {
417
+ console.error('path problem')
418
+ }
419
+ let refPoint1 = midPoint(path1[0], path1[1])
420
+ let refPoint2 = midPoint(path2[0], path2[1])
421
+ let poly1 = { outline: [] }
422
+ let poly2 = { outline: [] }
423
+
424
+ if (isInsidePolygon(refPoint1, [...path2, ...path3])) {
425
+ //path1 is inside path2 & path 3
426
+ path1.reverse()
427
+ path1.pop()
428
+ path1.shift()
429
+ poly1.outline = [...path1, ...path2]
430
+ poly2.outline = [...path1, ...path3]
431
+ } else if (isInsidePolygon(refPoint2, [...path1, ...path3])) {
432
+ //path2 is inside path1 & path 3
433
+ path2.reverse()
434
+ path2.pop()
435
+ path2.shift()
436
+ poly1.outline = [...path2, ...path1]
437
+ poly2.outline = [...path2, ...path3]
438
+ } else {
439
+ //path3 is inside path1 & path 2
440
+ path3.reverse()
441
+ path3.pop()
442
+ path3.shift()
443
+ poly1.outline = [...path3, ...path1]
444
+ poly2.outline = [...path3, ...path2]
445
+ }
446
+
447
+ return [poly1, poly2]
448
+ }
449
+
450
+ export function getPointOnLine(M, A, B) {
451
+ //Calcul of P, Projection of M on line AB
452
+ if (isSamePoint3D(A, B)) {
453
+ console.error("A and B don't make a line : A==B")
454
+ return null
455
+ }
456
+ let P = {}
457
+ const AM = {
458
+ x: M.x - A.x,
459
+ y: M.y - A.y
460
+ }
461
+ const AB = {
462
+ x: B.x - A.x,
463
+ y: B.y - A.y
464
+ }
465
+
466
+ const dot = AM.x * AB.x + AM.y * AB.y
467
+ const distanceAB = getDistanceBetweenPoints(A, B)
468
+ let param = -1
469
+ if (distanceAB == 0) {
470
+ //A et B sont confondu
471
+ console.error("A and B don't make a line : A==B")
472
+ P = M
473
+ } else {
474
+ // in case of 0 length line
475
+ const distanceAP = dot / distanceAB
476
+ P.x = A.x + (distanceAP * AB.x) / distanceAB
477
+ P.y = A.y + (distanceAP * AB.y) / distanceAB
478
+ }
479
+ //projection of the point to the line
480
+ return P
481
+ }
482
+ export function translate2D(point, vector) {
483
+ return {
484
+ ...point,
485
+ x: point.x + vector.x,
486
+ y: point.y + vector.y,
487
+ z: point.z
488
+ }
489
+ }
490
+
491
+
492
+ export function normalizedVectorTowardInsideAngle(H, I, J) {
493
+ const IH_angle = Math.atan2(H.y - I.y, H.x - I.x)
494
+ const JI_angle = Math.atan2(J.y - I.y, J.x - I.x)
495
+ const angle = (IH_angle + JI_angle) / 2
496
+ return { x: Math.cos(angle), y: Math.sin(angle) }
497
+ }
498
+
499
+
500
+ export function isPointBetweenSegment(A, B, C) {
501
+ const AB = getDistanceBetweenPoints(A, B)
502
+ const AC = getDistanceBetweenPoints(A, C)
503
+ const BC = getDistanceBetweenPoints(C, B)
504
+ const isBetween = AB < BC && AC < BC
505
+ return isBetween
506
+ }
507
+ export function get3pointNotAlignedFromOutline(outline) {
508
+ if (outline.length < 3) {
509
+ return null
510
+ }
511
+ const A = outline[0]
512
+ const B = outline[1]
513
+ let C = outline[2]
514
+ let k = 2
515
+ while (k < outline.length && getDegree(A, B, C) % 180 == 0) {
516
+ C = outline[k]
517
+ k++
518
+ }
519
+ if (k == outline.length) {
520
+ return null
521
+ }
522
+ return [A, B, C]
523
+ }
524
+ export function getNormalVectortoEdgeTowardPolygon(A, B, outline) {
525
+ const AB = substractVector(B, A)
526
+ const ABC = get3pointNotAlignedFromOutline(outline)
527
+ if (!ABC) {
528
+ return null
529
+ }
530
+ const PolyNormalVector = getNormalVectorFrom3Points(...ABC)
531
+ let ABnormal = crossProduct(AB, PolyNormalVector)
532
+ let u = normalizeVector(ABnormal)
533
+ const outlineMeanPoint = meanVector(outline)
534
+ const M = midPoint(A, B)
535
+ const dotProd = dotProduct(u, substractVector(outlineMeanPoint, M))
536
+
537
+ if (dotProd >= 0) {
538
+ return u
539
+ } else {
540
+ return multiplyVector(-1, u)
541
+ }
542
+ }
543
+ export function getNormalVectorFrom3Points(A, B, C) {
544
+ const AB = substractVector(B, A)
545
+ const BC = substractVector(C, B)
546
+ const normalVector = normalizeVector(crossProduct(AB, BC))
547
+ return normalVector
548
+ }
549
+
550
+ export function inclineWithNormalVector(normalVector) {
551
+ const angleRad = Math.acos(dotProduct(normalVector, { x: 0, y: 0, z: 1 }))
552
+ const angleDeg = (angleRad * 180) / Math.PI
553
+ return angleDeg
554
+ }
555
+
556
+ export function directionWithNormalVector(normalVector) {
557
+ if (normalVector.z < 0) {
558
+ normalVector = multiplyVector(-1, normalVector)
559
+ }
560
+ const normalVectorProjectionToGround = normalizeVector({
561
+ ...normalVector,
562
+ z: 0
563
+ })
564
+ if (isSamePoint3D(normalVectorProjectionToGround, { x: 0, y: 0, z: 0 }))
565
+ return 0
566
+
567
+ const angleRad = Math.acos(
568
+ dotProduct(normalVectorProjectionToGround, { x: 0, y: 1, z: 0 })
569
+ )
570
+ let angleDeg = (angleRad * 180) / Math.PI
571
+ if (normalVectorProjectionToGround.x < 0) {
572
+ angleDeg = 360 - angleDeg
573
+ }
574
+ return angleDeg
575
+ }
576
+
577
+ export function verticalProjectionOnPlane(p, n, o) {
578
+ const d = dotProduct(n, o)
579
+ const projectedHeight = (d - n.x * p.x - n.y * p.y) / n.z
580
+ const projectedPoint = { ...p, z: projectedHeight }
581
+ return projectedPoint
582
+ }
583
+
584
+ export function calcPolygonArea(vertices) {
585
+ var total = 0
586
+
587
+ for (var i = 0, l = vertices.length; i < l; i++) {
588
+ var addX = vertices[i].x
589
+ var addY = vertices[i == vertices.length - 1 ? 0 : i + 1].y
590
+ var subX = vertices[i == vertices.length - 1 ? 0 : i + 1].x
591
+ var subY = vertices[i].y
592
+
593
+ total += addX * addY * 0.5
594
+ total -= subX * subY * 0.5
595
+ }
596
+
597
+ return Math.abs(total)
598
+ }
599
+
600
+ export function pointProjectionOnPlane(p, n, o) {
601
+ //formula for p', projection of p on plane defined by normalvector n passing by o
602
+ //p' = p - (n ⋅ (p - o)) × n
603
+ const pPrim = substractVector(
604
+ p,
605
+ multiplyVector(dotProduct(n, substractVector(p, o)), n)
606
+ )
607
+ return pPrim
608
+ }
609
+
610
+ export function isClockWise(outline) {
611
+ //find smallest x and y coord
612
+ const length = outline.length
613
+ let smallest_x = Infinity
614
+ let smallest_y = Infinity
615
+ let hullIndex = null
616
+ outline.forEach((p, index) => {
617
+ if (p.x <= smallest_x) {
618
+ smallest_x = p.x
619
+ if (p.y < smallest_y) {
620
+ smallest_y = p.y
621
+ hullIndex = index
622
+ }
623
+ }
624
+ })
625
+ const B = outline[hullIndex]
626
+ const A = outline[(hullIndex - 1 + length) % length]
627
+ const C = outline[(hullIndex + 1) % length]
628
+ const det = (B.x - A.x) * (C.y - A.y) - (C.x - A.x) * (B.y - A.y)
629
+ return det < 0
630
+ }
631
+
632
+ // Function to check if two indexes are adjacent
633
+ export function areAdjacent(objects) {
634
+ // objects with 2D indexes
635
+ // Check if the objects form an adjacent group
636
+ let visited = []
637
+ let queue = [0]
638
+
639
+ while (queue.length > 0) {
640
+ const currentObjectIndex = queue.pop()
641
+ if (visited.indexOf(currentObjectIndex) == -1) {
642
+ //if next queued index hasn't been visited, we mark it as visited and we collect all neighbourg to queue
643
+ visited.push(currentObjectIndex)
644
+ let x = objects[currentObjectIndex].index[0]
645
+ let y = objects[currentObjectIndex].index[1]
646
+ const left = objects.findIndex(
647
+ (o) => o.index[0] == x - 1 && o.index[1] == y
648
+ )
649
+ const right = objects.findIndex(
650
+ (o) => o.index[0] == x + 1 && o.index[1] == y
651
+ )
652
+ const top = objects.findIndex(
653
+ (o) => o.index[0] == x && o.index[1] == y - 1
654
+ )
655
+ const bottom = objects.findIndex(
656
+ (o) => o.index[0] == x && o.index[1] == y + 1
657
+ )
658
+ if (left != -1 && visited.indexOf(left) == -1) queue.push(left)
659
+ if (right != -1 && visited.indexOf(right) == -1) queue.push(right)
660
+ if (top != -1 && visited.indexOf(top) == -1) queue.push(top)
661
+ if (bottom != -1 && visited.indexOf(bottom) == -1) queue.push(bottom)
662
+ }
663
+ }
664
+ return visited.length == objects.length
665
+ }
666
+ export function getMarginPoint(
667
+ A,
668
+ B,
669
+ C,
670
+ marginA,
671
+ marginB,
672
+ clockwiseNormalVector
673
+ ) {
674
+ const n = multiplyVector(
675
+ marginA,
676
+ normalizeVector(crossProduct(substractVector(B, A), clockwiseNormalVector))
677
+ )
678
+ const m = multiplyVector(
679
+ marginB,
680
+ normalizeVector(crossProduct(substractVector(C, B), clockwiseNormalVector))
681
+ )
682
+ //K=B+[(m.m-n.m)/AB.m]*AB
683
+ const AB = substractVector(B, A)
684
+ const mAB = dotProduct(AB, m)
685
+
686
+ let K
687
+ if (mAB == 0) {
688
+ K =
689
+ dotProduct(m, m) != 0
690
+ ? addVector(B, m)
691
+ : dotProduct(n, n) != 0
692
+ ? addVector(B, n)
693
+ : B
694
+ } else {
695
+ const mm = dotProduct(m, m)
696
+ const nm = dotProduct(n, m)
697
+ const lambda = (mm - nm) / mAB
698
+ K = addVector(addVector(B, n), multiplyVector(lambda, AB))
699
+ }
700
+ return K
701
+ }
702
+ export function calculateArea(vertices) {
703
+ var n = vertices.length
704
+ var area = 0
705
+ for (var i = 0; i < n; i++) {
706
+ var v_current = substractVector(vertices[i], vertices[0])
707
+ var v_next = substractVector(vertices[(i + 1) % n], vertices[0])
708
+ area += 0.5 * vectorLength(crossProduct(v_current, v_next))
709
+ }
710
+ return area
711
+ }
712
+
713
+ export function hasNaN(arr) {
714
+ for (let i = 0; i < arr.length; i++) {
715
+ // check if array value is false or NaN
716
+ if (Number.isNaN(arr[i])) {
717
+ return true
718
+ }
719
+ }
720
+ return false
721
+ }
722
+
723
+
724
+ export function calculatePositionSubHandle(polygon, i, mmPerPx) {
725
+ const outline = polygon.outline
726
+ const length = outline.length
727
+
728
+ const h = (i - 1 + length) % length
729
+ const j = (i + 1) % length
730
+
731
+ //calculation of the angle where we need to insert the handle.
732
+ const IH_angle = Math.atan2(
733
+ outline[h].y - outline[i].y,
734
+ outline[h].x - outline[i].x
735
+ )
736
+ const JI_angle = Math.atan2(
737
+ outline[j].y - outline[i].y,
738
+ outline[j].x - outline[i].x
739
+ )
740
+ const handle_angle = (IH_angle + JI_angle) / 2
741
+
742
+ //d is the handle distance in px inside polygon vertex
743
+ const d = 20 * mmPerPx
744
+ const r = d / 4
745
+ let handleX = outline[i].x + d * Math.cos(handle_angle)
746
+ let handleY = outline[i].y + d * Math.sin(handle_angle)
747
+ let handleZ = outline[i].z
748
+
749
+ //check if handle is inside the polygon, if not, change where the handle is displayed
750
+ const isInside = isInsidePolygon(
751
+ { x: handleX, y: handleY, z: handleZ },
752
+ outline
753
+ )
754
+ if (!isInside) {
755
+ handleX = outline[i].x - d * Math.cos(handle_angle)
756
+ handleY = outline[i].y - d * Math.sin(handle_angle)
757
+ }
758
+ return { x: handleX, y: handleY, z: outline[i].z }
759
+ }
760
+