@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/.eslintrc.js +28 -0
- package/.prettierrc +7 -0
- package/babel.config.js +2 -2
- package/eslint.config.mjs +10 -0
- package/package.json +5 -3
- package/src/config.js +110 -50
- package/src/coords.js +21 -21
- package/src/geo.js +111 -100
- package/src/geometry.js +920 -819
- package/src/index.js +10 -11
- package/src/intersectionPolygon.js +34 -28
- package/src/lib/concaveman.js +181 -181
- package/src/matrix.js +56 -50
- package/src/miscellaneous.js +45 -0
- package/src/objects/Circle.js +76 -41
- package/src/objects/Line.js +275 -177
- package/src/objects/Point.js +30 -27
- package/src/objects/Polygon.js +324 -243
- package/src/objects/derivedState/AddMargin.js +142 -145
- package/src/objects/derivedState/updateComputedGeometryPolygon.js +274 -253
- package/src/objects/graph/DFS.js +69 -69
- package/src/objects/graph/graphCreation.js +47 -44
- package/src/objects/hydrate.js +26 -26
- package/src/snap.js +24 -18
- package/src/splitMergePolygons.js +585 -521
- package/src/test/maths.test.js +7 -7
- package/src/vector.js +66 -66
package/src/geometry.js
CHANGED
|
@@ -1,535 +1,534 @@
|
|
|
1
|
-
import { polygonCloseTolerance } from
|
|
1
|
+
import { polygonCloseTolerance } from './config'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from
|
|
13
|
-
import { inverse3x3matrix, multiplyMatrices } from
|
|
14
|
-
import { Point } from
|
|
15
|
-
import { Line } from
|
|
16
|
-
import concaveman from
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
154
|
+
return getDegree(u, { x: 0, y: 0, z: 0 }, v)
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
export function getDegree(H, I, J) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
//if 3 points are aligned
|
|
176
|
+
if (isNaN(angle)) {
|
|
177
|
+
angle = c < a ? 0 : 180
|
|
178
|
+
}
|
|
180
179
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
208
|
+
return A.x == B.x && A.y == B.y
|
|
210
209
|
}
|
|
211
210
|
export function isAlmostSamePoint2D(A, B, tolerance) {
|
|
212
|
-
|
|
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
|
-
|
|
216
|
-
isSamePoint2D(seg_0[0], seg_1[0]) && isSamePoint2D(seg_0[1], seg_1[1])
|
|
217
|
-
|
|
218
|
-
isSamePoint2D(seg_0[0], seg_1[1]) && isSamePoint2D(seg_0[1], seg_1[0])
|
|
219
|
-
|
|
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
|
-
|
|
221
|
+
let check_1 =
|
|
223
222
|
isAlmostSamePoint2D(seg_0[0], seg_1[0], tolerance) &&
|
|
224
|
-
isAlmostSamePoint2D(seg_0[1], seg_1[1], tolerance)
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
isSamePoint3D(seg_0[0], seg_1[0]) && isSamePoint3D(seg_0[1], seg_1[1])
|
|
233
|
-
|
|
234
|
-
isSamePoint3D(seg_0[0], seg_1[1]) && isSamePoint3D(seg_0[1], seg_1[0])
|
|
235
|
-
|
|
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
|
-
|
|
237
|
+
let check_1 =
|
|
239
238
|
isAlmostSamePoint3D(seg_0[0], seg_1[0], tolerance) &&
|
|
240
|
-
isAlmostSamePoint3D(seg_0[1], seg_1[1], tolerance)
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
439
|
+
return isInsidePolygon(point, vs) && !isOnBorderOfPolygon(point, vs)
|
|
441
440
|
}
|
|
442
441
|
|
|
443
442
|
export function isInsidePolygon(point, vs) {
|
|
444
|
-
|
|
445
|
-
|
|
443
|
+
// ray-casting algorithm based on
|
|
444
|
+
// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html/pnpoly.html
|
|
446
445
|
|
|
447
|
-
|
|
448
|
-
|
|
446
|
+
const x = point.x
|
|
447
|
+
const y = point.y
|
|
449
448
|
|
|
450
|
-
|
|
449
|
+
let inside = false
|
|
451
450
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
459
|
-
yi > y !== yj > y && x <= ((xj - xi) * (y - yi)) / (yj - yi) + xi
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
469
|
+
return innerOutline.every((p) => isInsidePolygon(p, outterOutline))
|
|
471
470
|
}
|
|
472
471
|
|
|
473
472
|
export function get3PathsFromPolyAndSplittingPath(
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
473
|
+
outline,
|
|
474
|
+
splittingPath,
|
|
475
|
+
mmPerPx
|
|
477
476
|
) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
-
|
|
706
|
-
|
|
712
|
+
return {
|
|
713
|
+
x:
|
|
707
714
|
Math.sin((direction * Math.PI) / 180) *
|
|
708
715
|
Math.sin((incline * Math.PI) / 180),
|
|
709
|
-
|
|
716
|
+
y:
|
|
710
717
|
Math.cos((direction * Math.PI) / 180) *
|
|
711
718
|
Math.sin((incline * Math.PI) / 180),
|
|
712
|
-
|
|
713
|
-
|
|
719
|
+
z: Math.cos((incline * Math.PI) / 180)
|
|
720
|
+
}
|
|
714
721
|
}
|
|
715
722
|
export function inclineWithNormalVector(normalVector) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
757
|
+
var total = 0
|
|
751
758
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
-
|
|
759
|
-
|
|
760
|
-
|
|
765
|
+
total += addX * addY * 0.5
|
|
766
|
+
total -= subX * subY * 0.5
|
|
767
|
+
}
|
|
761
768
|
|
|
762
|
-
|
|
769
|
+
return Math.abs(total)
|
|
763
770
|
}
|
|
764
771
|
|
|
765
772
|
export function pointProjectionOnPlane(p, n, o) {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
883
|
+
A,
|
|
884
|
+
B,
|
|
885
|
+
C,
|
|
886
|
+
marginA,
|
|
887
|
+
marginB,
|
|
888
|
+
clockwiseNormalVector
|
|
882
889
|
) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
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
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
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
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
}
|
|
977
|
-
export function
|
|
978
|
-
|
|
979
|
-
}
|
|
980
|
-
export function
|
|
981
|
-
|
|
982
|
-
|
|
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
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
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
|
}
|