@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.
@@ -1,280 +1,301 @@
1
1
  import {
2
- meanVector,
3
- substractVector,
4
- multiplyVector,
5
- addVector,
6
- } from "../../vector";
2
+ meanVector,
3
+ substractVector,
4
+ multiplyVector,
5
+ addVector
6
+ } from '../../vector'
7
7
  import {
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";
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
- state.polygons.forEach((polygon) => {
24
- polygon = updateComputedGeometryPolygon(polygon);
25
- });
26
- return state;
23
+ state.polygons.forEach((polygon) => {
24
+ updateComputedGeometryPolygon(polygon)
25
+ })
26
+ return state
27
27
  }
28
28
  export function updateOutlineFromInclineDirection(
29
- incline,
30
- direction,
31
- outline,
32
- initialAverageHeight,
33
- isRoofOnRoof = false
29
+ incline,
30
+ direction,
31
+ outline,
32
+ initialAverageHeight,
33
+ isRoofOnRoof = false
34
34
  ) {
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));
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
- //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))));
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
- return newOutline;
62
+ return newOutline
63
63
  }
64
64
  export function updateComputedGeometryPolygon(polygon) {
65
- if (!["roof", "obstacle", "moduleField"].includes(polygon.layer)) {
66
- return polygon;
67
- }
68
- if (
69
- polygon.outline.length > 0 &&
70
- isAlmostSamePoint2D(
71
- polygon.outline[0],
72
- polygon.outline[polygon.outline.length - 1],
73
- 10
74
- )
75
- ) {
76
- polygon.outline.pop();
77
- }
78
- polygon.outline = calculateValidOutlineFromPolygon(polygon.outline);
79
- const {
80
- projectedOutline,
81
- maximumGap,
82
- normalVector,
83
- meanPoint,
84
- incline,
85
- direction,
86
- } = calculateBestFittingPlanePolygon(polygon.outline);
87
- polygon.flatOutline = projectedOutline;
88
- polygon.maximumGap = maximumGap;
89
- polygon.isFlat = maximumGap < maximumGapLimit;
90
- polygon.normalVector = normalVector;
91
- polygon.meanPoint = meanPoint;
92
- polygon.incline = incline;
93
- polygon.direction = direction;
94
- polygon.area = calculateArea(polygon.flatOutline) / 1000000;
95
- polygon.isClockwise = isClockWise(polygon.outline);
96
- polygon = updateMarginOutlinePolygon(polygon);
97
- if (polygon.layer == "moduleField") {
98
- let trimedOutline = [];
99
- if (polygon.panels.length > 0) {
100
- trimedOutline = getConcaveOutline(
101
- polygon.panels,
102
- polygon.panels[0].outline
103
- );
104
- trimedOutline = trimedOutline.map((p) => {
105
- return {
106
- x: p.x,
107
- y: p.y,
108
- z: verticalProjectionOnPlane(
109
- p,
110
- polygon.roof.normalVector,
111
- polygon.roof.flatOutline[0]
112
- ).z,
113
- };
114
- });
115
- }
116
- polygon.trimedOutline = trimedOutline;
117
- }
118
- return polygon;
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
- //check if two nodes are same
122
- const nodeIndexToSplit = [];
123
- const nodesToRemove = [];
124
- for (let k = 0; k < outline.length - 1; k++) {
125
- for (let i = k + 1; i < outline.length; i++) {
126
- if (isAlmostSamePoint2D(outline[k], outline[i], 2 * mmTolerance)) {
127
- if (i == k + 1) {
128
- nodesToRemove.push(i);
129
- } else {
130
- nodeIndexToSplit.push([k, i]);
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
- for (let coupleToSplit of nodeIndexToSplit) {
137
- const A = outline[(coupleToSplit[0] - 1 + outline.length) % outline.length];
138
- const B = outline[coupleToSplit[0]];
139
- const C = outline[(coupleToSplit[0] + 1) % outline.length];
140
- const D = outline[(coupleToSplit[1] - 1 + outline.length) % outline.length];
141
- const E = outline[coupleToSplit[1]];
142
- const F = outline[(coupleToSplit[1] + 1) % outline.length];
143
- let u = normalizedVectorTowardInsideAngle(A, B, C);
144
- let v = normalizedVectorTowardInsideAngle(D, E, F);
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
- outline[coupleToSplit[0]] = {
147
- ...outline[coupleToSplit[0]],
148
- ...addVector(multiplyVector(-mmTolerance, u), B),
149
- z: outline[coupleToSplit[0]].z,
150
- };
151
- outline[coupleToSplit[1]] = {
152
- ...outline[coupleToSplit[1]],
153
- ...addVector(multiplyVector(mmTolerance, v), E),
154
- z: outline[coupleToSplit[1]].z,
155
- };
156
- }
157
- for (let index = nodesToRemove.length - 1; index >= 0; index--) {
158
- outline.splice(nodesToRemove[index], 1);
159
- }
160
- return outline;
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
- if (fullOutline.length < 3) {
165
- return null;
166
- }
167
- let isIncludingAllNodes = false;
168
- let outline = fullOutline;
169
- if (outline.length < 3) {
170
- isIncludingAllNodes = true;
171
- outline = fullOutline;
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
- const centralizedOutline = outline.map((p) => substractVector(p, meanPoint));
176
- const a = centralizedOutline.map((p) => [p.x, p.y, p.z]);
200
+ const centralizedOutline = outline.map((p) => substractVector(p, meanPoint))
201
+ const a = centralizedOutline.map((p) => [p.x, p.y, p.z])
177
202
 
178
- let { v, q } = SVD(a, false);
179
- let vt = [[], [], []];
180
- for (let i in v) {
181
- for (let j in v[i]) {
182
- vt[j][i] = v[i][j];
183
- }
184
- }
185
- v = vt;
186
- let normalVectorIndex = 0;
187
- for (let index in q) {
188
- if (!isNaN(q[index]) && q[index] < q[normalVectorIndex]) {
189
- normalVectorIndex = index;
190
- }
191
- }
192
- let normalVectorArray = v[normalVectorIndex];
193
- let normalVector = { x: 0, y: 0, z: 1 };
194
- if (!normalVectorArray) {
195
- console.error(
196
- "no normalVector found for:q",
197
- q,
198
- "v",
199
- v,
200
- "meanPoint",
201
- meanPoint,
202
- "a",
203
- a,
204
- "normal index",
205
- normalVectorIndex,
206
- "v",
207
- v,
208
- "outline",
209
- outline
210
- );
211
- } else {
212
- normalVector.x = normalVectorArray[0];
213
- normalVector.y = normalVectorArray[1];
214
- normalVector.z = normalVectorArray[2];
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
- if (normalVector.z < 0) {
218
- normalVector = multiplyVector(-1, normalVector);
219
- }
220
- //gard to prevent crazy inclinaison roof
221
- if (normalVector.z < 0.1) {
222
- normalVector = { x: 0, y: 0, z: 1 };
223
- }
224
- //plane equation is ax+by+cz=d
225
- //where normalvector=(a,b,c)
226
- //let's calculate d knowing our plane passes by the point with the
227
- const projectedOutline = fullOutline.map((p) => {
228
- const proj = verticalProjectionOnPlane(p, normalVector, meanPoint);
229
- return {
230
- x: proj.x,
231
- y: proj.y,
232
- z: Math.max(0, Math.min(50000, proj.z)),
233
- };
234
- });
235
- const maximumGap = Math.max(
236
- ...fullOutline.map((p, index) => {
237
- let gap = Math.abs(p.z - projectedOutline[index].z);
238
- return gap;
239
- })
240
- );
241
- //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
+ 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
- let biggestPositivGap = Math.max(
244
- ...fullOutline.map((p, index) =>
245
- p.selected && !isIncludingAllNodes ? 0 : p.z - projectedOutline[index].z
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
- //if less than 3 unselected node=>flat roof on top.
251
- const biggestAbsoluteGap = Math.max(
252
- ...fullOutline.map((p, index) =>
253
- p.selected ? 0 : Math.abs(p.z - projectedOutline[index].z)
254
- )
255
- );
256
- for (let index in fullOutline) {
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
- //I add warning if hasWarning and I remove it if shift is smaller then maxGapLimit. Otherwise, I do not change anything
262
- p.hasWarning = hasWarning
263
- ? hasWarning
264
- : Math.abs(p.z - projectedOutline[index].z) < maximumGapLimit
265
- ? false
266
- : p.hasWarning;
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
- //set warning to the furthest
270
- const incline = inclineWithNormalVector(normalVector);
271
- const direction = directionWithNormalVector(normalVector);
272
- return {
273
- projectedOutline,
274
- maximumGap,
275
- normalVector,
276
- meanPoint,
277
- incline,
278
- direction,
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
  }