@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.
- package/babel.config.js +3 -0
- package/package.json +30 -0
- package/src/assets/theme.js +45 -0
- package/src/config.js +77 -0
- package/src/coords.js +24 -0
- package/src/geo.js +51 -0
- package/src/geometry.js +760 -0
- package/src/index.js +10 -0
- package/src/intersectionPolygon.js +623 -0
- package/src/matrix.js +53 -0
- package/src/objects/Circle.js +51 -0
- package/src/objects/Line.js +110 -0
- package/src/objects/Point.js +57 -0
- package/src/objects/Polygon.js +240 -0
- package/src/objects/derivedState/AddMargin.js +109 -0
- package/src/objects/derivedState/UpdateRoofModuleFieldRelations.js +119 -0
- package/src/objects/derivedState/UpdateRoofObstacleRelations.js +86 -0
- package/src/objects/derivedState/index.js +2 -0
- package/src/objects/derivedState/updateComputedGeometryPolygon.js +168 -0
- package/src/objects/graph/DFS.js +80 -0
- package/src/objects/graph/graphCreation.js +44 -0
- package/src/objects/hydrate.js +24 -0
- package/src/objects/index.js +8 -0
- package/src/splitMergePolygons.js +553 -0
- package/src/test/maths.test.js +10 -0
- package/src/vector.js +56 -0
- package/webpack.config.js +11 -0
package/src/geometry.js
ADDED
|
@@ -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
|
+
|