@eturnity/eturnity_maths 7.20.0 → 7.24.0
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 +272 -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
|
@@ -1,280 +1,301 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from
|
|
2
|
+
meanVector,
|
|
3
|
+
substractVector,
|
|
4
|
+
multiplyVector,
|
|
5
|
+
addVector
|
|
6
|
+
} from '../../vector'
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from
|
|
17
|
-
import { maximumGapLimit, mmTolerance } from
|
|
18
|
-
import { SVD } from
|
|
19
|
-
import { updateMarginOutlinePolygon } from
|
|
8
|
+
verticalProjectionOnPlane,
|
|
9
|
+
inclineWithNormalVector,
|
|
10
|
+
directionWithNormalVector,
|
|
11
|
+
isClockWise,
|
|
12
|
+
calculateArea,
|
|
13
|
+
getConcaveOutline,
|
|
14
|
+
normalizedVectorTowardInsideAngle,
|
|
15
|
+
isAlmostSamePoint2D
|
|
16
|
+
} from '../../geometry'
|
|
17
|
+
import { maximumGapLimit, mmTolerance } from '../../config'
|
|
18
|
+
import { SVD } from 'svd-js'
|
|
19
|
+
import { updateMarginOutlinePolygon } from './AddMargin'
|
|
20
20
|
|
|
21
21
|
//This function calculate derived field from polygon outline and margin outline
|
|
22
22
|
export function updateComputedGeometryState(state) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
state.polygons.forEach((polygon) => {
|
|
24
|
+
updateComputedGeometryPolygon(polygon)
|
|
25
|
+
})
|
|
26
|
+
return state
|
|
27
27
|
}
|
|
28
28
|
export function updateOutlineFromInclineDirection(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
incline,
|
|
30
|
+
direction,
|
|
31
|
+
outline,
|
|
32
|
+
initialAverageHeight,
|
|
33
|
+
isRoofOnRoof = false
|
|
34
34
|
) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Math.sin((incline * Math.PI) / 180) * Math.sin((direction * Math.PI) / 180)
|
|
38
|
-
|
|
39
|
-
Math.sin((incline * Math.PI) / 180) * Math.cos((direction * Math.PI) / 180)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
35
|
+
const newNormalVector = {}
|
|
36
|
+
newNormalVector.x =
|
|
37
|
+
Math.sin((incline * Math.PI) / 180) * Math.sin((direction * Math.PI) / 180)
|
|
38
|
+
newNormalVector.y =
|
|
39
|
+
Math.sin((incline * Math.PI) / 180) * Math.cos((direction * Math.PI) / 180)
|
|
40
|
+
newNormalVector.z = Math.cos((incline * Math.PI) / 180)
|
|
41
|
+
let meanPoint = meanVector(outline)
|
|
42
|
+
meanPoint.z = initialAverageHeight
|
|
43
|
+
let newOutline = outline.map((p) => {
|
|
44
|
+
return verticalProjectionOnPlane(p, newNormalVector, meanPoint)
|
|
45
|
+
})
|
|
46
|
+
//if some points are with negative altitude, we offset the whole roof
|
|
47
|
+
let altitudeOffset
|
|
48
|
+
let minAltitude = Math.min(...outline.map((p) => p.z))
|
|
49
|
+
let maxAltitude = Math.max(...outline.map((p) => p.z))
|
|
50
|
+
let newMinAltitude = Math.min(...newOutline.map((p) => p.z))
|
|
51
|
+
let newMaxAltitude = Math.max(...newOutline.map((p) => p.z))
|
|
52
|
+
if (isRoofOnRoof && maxAltitude != 0) {
|
|
53
|
+
altitudeOffset = newMaxAltitude - maxAltitude
|
|
54
|
+
} else {
|
|
55
|
+
altitudeOffset = newMinAltitude - minAltitude
|
|
56
|
+
}
|
|
57
|
+
newOutline.forEach((p) => (p.z -= altitudeOffset))
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
//if some points are with altitude>50000, we limit those points
|
|
60
|
+
newOutline.forEach((p) => (p.z = Math.max(0, Math.min(p.z, 50000))))
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
return newOutline
|
|
63
63
|
}
|
|
64
64
|
export function updateComputedGeometryPolygon(polygon) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
65
|
+
if (!['roof', 'obstacle', 'moduleField'].includes(polygon.layer)) {
|
|
66
|
+
return polygon
|
|
67
|
+
}
|
|
68
|
+
let newOutline = [...polygon.outline]
|
|
69
|
+
if (
|
|
70
|
+
newOutline.length > 0 &&
|
|
71
|
+
isAlmostSamePoint2D(newOutline[0], newOutline[newOutline.length - 1], 10)
|
|
72
|
+
) {
|
|
73
|
+
console.log(
|
|
74
|
+
'Outline warning: first and last points of outline not far enough:',
|
|
75
|
+
newOutline
|
|
76
|
+
)
|
|
77
|
+
newOutline.pop()
|
|
78
|
+
}
|
|
79
|
+
newOutline = calculateValidOutlineFromPolygon(newOutline)
|
|
80
|
+
|
|
81
|
+
if (newOutline.length < 3) {
|
|
82
|
+
throw new Error('outline not valid')
|
|
83
|
+
}
|
|
84
|
+
polygon.outline = newOutline
|
|
85
|
+
const {
|
|
86
|
+
projectedOutline,
|
|
87
|
+
maximumGap,
|
|
88
|
+
normalVector,
|
|
89
|
+
meanPoint,
|
|
90
|
+
incline,
|
|
91
|
+
direction
|
|
92
|
+
} = calculateBestFittingPlanePolygon(polygon.outline)
|
|
93
|
+
polygon.flatOutline = projectedOutline
|
|
94
|
+
polygon.maximumGap = maximumGap
|
|
95
|
+
polygon.isFlat = maximumGap < maximumGapLimit
|
|
96
|
+
polygon.normalVector = normalVector
|
|
97
|
+
polygon.meanPoint = meanPoint
|
|
98
|
+
polygon.incline = incline
|
|
99
|
+
polygon.direction = direction
|
|
100
|
+
polygon.area = calculateArea(polygon.flatOutline) / 1000000
|
|
101
|
+
polygon.isClockwise = isClockWise(polygon.outline)
|
|
102
|
+
polygon = updateMarginOutlinePolygon(polygon)
|
|
103
|
+
if (polygon.layer == 'moduleField') {
|
|
104
|
+
let trimedOutline = []
|
|
105
|
+
if (polygon.panels.length > 0) {
|
|
106
|
+
trimedOutline = getConcaveOutline(
|
|
107
|
+
polygon.panels,
|
|
108
|
+
polygon.panels[0].outline
|
|
109
|
+
)
|
|
110
|
+
trimedOutline = trimedOutline.map((p) => {
|
|
111
|
+
return {
|
|
112
|
+
x: p.x,
|
|
113
|
+
y: p.y,
|
|
114
|
+
z: verticalProjectionOnPlane(
|
|
115
|
+
p,
|
|
116
|
+
polygon.roof.normalVector,
|
|
117
|
+
polygon.roof.flatOutline[0]
|
|
118
|
+
).z
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
polygon.trimedOutline = trimedOutline
|
|
123
|
+
}
|
|
124
|
+
return polygon
|
|
119
125
|
}
|
|
120
126
|
export function calculateValidOutlineFromPolygon(outline) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
//check if two nodes are same
|
|
128
|
+
outline = [...outline]
|
|
129
|
+
const nodeIndexToSplit = []
|
|
130
|
+
const nodesToRemove = []
|
|
131
|
+
for (let k = 0; k < outline.length - 1; k++) {
|
|
132
|
+
for (let i = k + 1; i < outline.length; i++) {
|
|
133
|
+
if (isAlmostSamePoint2D(outline[k], outline[i], 2 * mmTolerance)) {
|
|
134
|
+
if (i == k + 1) {
|
|
135
|
+
nodesToRemove.push(i)
|
|
136
|
+
} else {
|
|
137
|
+
nodeIndexToSplit.push([k, i])
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
135
142
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
for (let coupleToSplit of nodeIndexToSplit) {
|
|
144
|
+
const A = outline[(coupleToSplit[0] - 1 + outline.length) % outline.length]
|
|
145
|
+
const B = outline[coupleToSplit[0]]
|
|
146
|
+
const C = outline[(coupleToSplit[0] + 1) % outline.length]
|
|
147
|
+
const D = outline[(coupleToSplit[1] - 1 + outline.length) % outline.length]
|
|
148
|
+
const E = outline[coupleToSplit[1]]
|
|
149
|
+
const F = outline[(coupleToSplit[1] + 1) % outline.length]
|
|
150
|
+
let u = normalizedVectorTowardInsideAngle(A, B, C)
|
|
151
|
+
let v = normalizedVectorTowardInsideAngle(D, E, F)
|
|
145
152
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
153
|
+
outline[coupleToSplit[0]] = {
|
|
154
|
+
...outline[coupleToSplit[0]],
|
|
155
|
+
...addVector(multiplyVector(-mmTolerance, u), B),
|
|
156
|
+
z: outline[coupleToSplit[0]].z
|
|
157
|
+
}
|
|
158
|
+
outline[coupleToSplit[1]] = {
|
|
159
|
+
...outline[coupleToSplit[1]],
|
|
160
|
+
...addVector(multiplyVector(mmTolerance, v), E),
|
|
161
|
+
z: outline[coupleToSplit[1]].z
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
for (let index = nodesToRemove.length - 1; index >= 0; index--) {
|
|
165
|
+
outline.splice(nodesToRemove[index], 1)
|
|
166
|
+
}
|
|
167
|
+
if (nodeIndexToSplit.length) {
|
|
168
|
+
console.log(
|
|
169
|
+
'two nodes of the outline are too close one to another:',
|
|
170
|
+
nodeIndexToSplit,
|
|
171
|
+
outline,
|
|
172
|
+
' - tolerance:',
|
|
173
|
+
2 * mmTolerance,
|
|
174
|
+
'mm'
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
if (nodesToRemove.length) {
|
|
178
|
+
console.log(
|
|
179
|
+
'two consecutive nodes of the outline are too close one to another:',
|
|
180
|
+
nodesToRemove,
|
|
181
|
+
outline,
|
|
182
|
+
' - tolerance:',
|
|
183
|
+
2 * mmTolerance,
|
|
184
|
+
'mm'
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
return outline
|
|
161
188
|
}
|
|
162
189
|
|
|
163
190
|
export function calculateBestFittingPlanePolygon(fullOutline) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
const meanPoint = meanVector(outline);
|
|
191
|
+
if (fullOutline.length < 3 || fullOutline.some((p) => isNaN(p.z))) {
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
let outline = fullOutline
|
|
195
|
+
if (outline.length < 3) {
|
|
196
|
+
outline = fullOutline
|
|
197
|
+
}
|
|
198
|
+
const meanPoint = meanVector(outline)
|
|
174
199
|
|
|
175
|
-
|
|
176
|
-
|
|
200
|
+
const centralizedOutline = outline.map((p) => substractVector(p, meanPoint))
|
|
201
|
+
const a = centralizedOutline.map((p) => [p.x, p.y, p.z])
|
|
177
202
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
203
|
+
let { v, q } = SVD(a, false)
|
|
204
|
+
let vt = [[], [], []]
|
|
205
|
+
for (let i in v) {
|
|
206
|
+
for (let j in v[i]) {
|
|
207
|
+
vt[j][i] = v[i][j]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
v = vt
|
|
211
|
+
let normalVectorIndex = 0
|
|
212
|
+
for (let index in q) {
|
|
213
|
+
if (!isNaN(q[index]) && q[index] < q[normalVectorIndex]) {
|
|
214
|
+
normalVectorIndex = index
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
let normalVectorArray = v[normalVectorIndex]
|
|
218
|
+
let normalVector = { x: 0, y: 0, z: 1 }
|
|
219
|
+
if (!normalVectorArray || isNaN(normalVectorArray[0])) {
|
|
220
|
+
console.error(
|
|
221
|
+
'no normalVector found for:q',
|
|
222
|
+
q,
|
|
223
|
+
'v',
|
|
224
|
+
v,
|
|
225
|
+
'meanPoint',
|
|
226
|
+
meanPoint,
|
|
227
|
+
'a',
|
|
228
|
+
a,
|
|
229
|
+
'normal index',
|
|
230
|
+
normalVectorIndex,
|
|
231
|
+
'v',
|
|
232
|
+
v,
|
|
233
|
+
'outline',
|
|
234
|
+
outline
|
|
235
|
+
)
|
|
236
|
+
} else {
|
|
237
|
+
normalVector.x = normalVectorArray[0]
|
|
238
|
+
normalVector.y = normalVectorArray[1]
|
|
239
|
+
normalVector.z = normalVectorArray[2]
|
|
240
|
+
}
|
|
216
241
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
+
if (normalVector.z < 0) {
|
|
243
|
+
normalVector = multiplyVector(-1, normalVector)
|
|
244
|
+
}
|
|
245
|
+
//gard to prevent crazy inclinaison roof
|
|
246
|
+
if (normalVector.z < 0.1) {
|
|
247
|
+
normalVector = { x: 0, y: 0, z: 1 }
|
|
248
|
+
}
|
|
249
|
+
//plane equation is ax+by+cz=d
|
|
250
|
+
//where normalvector=(a,b,c)
|
|
251
|
+
//let's calculate d knowing our plane passes by the point with the
|
|
252
|
+
const projectedOutline = fullOutline.map((p) => {
|
|
253
|
+
const proj = verticalProjectionOnPlane(p, normalVector, meanPoint)
|
|
254
|
+
return {
|
|
255
|
+
x: proj.x,
|
|
256
|
+
y: proj.y,
|
|
257
|
+
z: Math.max(0, Math.min(50000, proj.z))
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
const maximumGap = Math.max(
|
|
261
|
+
...fullOutline.map((p, index) => {
|
|
262
|
+
let gap = Math.abs(p.z - projectedOutline[index].z)
|
|
263
|
+
return gap
|
|
264
|
+
})
|
|
265
|
+
)
|
|
266
|
+
//let's find the the biggest positive gap to reposition the plan passing by this point in order to have an always visible plane
|
|
242
267
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
);
|
|
248
|
-
projectedOutline.forEach((p) => (p.z += biggestPositivGap));
|
|
268
|
+
let biggestPositivGap = Math.max(
|
|
269
|
+
...fullOutline.map((p, index) => p.z - projectedOutline[index].z)
|
|
270
|
+
)
|
|
271
|
+
projectedOutline.forEach((p) => (p.z += biggestPositivGap))
|
|
249
272
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
let p = fullOutline[index];
|
|
258
|
-
let hasWarning =
|
|
273
|
+
//if less than 3 unselected node=>flat roof on top.
|
|
274
|
+
const biggestAbsoluteGap = Math.max(
|
|
275
|
+
...fullOutline.map((p, index) => Math.abs(p.z - projectedOutline[index].z))
|
|
276
|
+
)
|
|
277
|
+
for (let index in fullOutline) {
|
|
278
|
+
let p = fullOutline[index]
|
|
279
|
+
let hasWarning =
|
|
259
280
|
Math.abs(p.z - projectedOutline[index].z) == biggestAbsoluteGap &&
|
|
260
|
-
biggestAbsoluteGap > maximumGapLimit
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
281
|
+
biggestAbsoluteGap > maximumGapLimit
|
|
282
|
+
//I add warning if hasWarning and I remove it if shift is smaller then maxGapLimit. Otherwise, I do not change anything
|
|
283
|
+
p.hasWarning = hasWarning
|
|
284
|
+
? hasWarning
|
|
285
|
+
: Math.abs(p.z - projectedOutline[index].z) < maximumGapLimit
|
|
286
|
+
? false
|
|
287
|
+
: p.hasWarning
|
|
288
|
+
}
|
|
268
289
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
290
|
+
//set warning to the furthest
|
|
291
|
+
const incline = inclineWithNormalVector(normalVector)
|
|
292
|
+
const direction = directionWithNormalVector(normalVector)
|
|
293
|
+
return {
|
|
294
|
+
projectedOutline,
|
|
295
|
+
maximumGap,
|
|
296
|
+
normalVector,
|
|
297
|
+
meanPoint,
|
|
298
|
+
incline,
|
|
299
|
+
direction
|
|
300
|
+
}
|
|
280
301
|
}
|