@eturnity/eturnity_maths 9.13.0 → 9.19.0-EPDM-19634.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eturnity/eturnity_maths",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.19.0-EPDM-19634.0",
|
|
4
4
|
"author": "Eturnity Team",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"private": false,
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"uuid": "9.0.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@babel/core": "7.
|
|
32
|
-
"@babel/preset-env": "7.
|
|
31
|
+
"@babel/core": "7.22.0",
|
|
32
|
+
"@babel/preset-env": "7.22.0",
|
|
33
33
|
"babel-jest": "29.5.0",
|
|
34
34
|
"husky": "^9.1.5",
|
|
35
35
|
"@vue/eslint-config-standard": "8.0.1",
|
package/src/geometry.js
CHANGED
|
@@ -897,7 +897,9 @@ export function projectionOnPlaneFollowingVector(p, n, o, v) {
|
|
|
897
897
|
const nv = dotProduct(n, v)
|
|
898
898
|
if (nv == 0) {
|
|
899
899
|
console.warn('projection plane and vector are coplanar')
|
|
900
|
-
throw new Error(
|
|
900
|
+
throw new Error(
|
|
901
|
+
'Cannot project: normal vector and direction vector are perpendicular'
|
|
902
|
+
)
|
|
901
903
|
}
|
|
902
904
|
const lambda = -opn / nv
|
|
903
905
|
const projectedPoint = addVector(p, multiplyVector(lambda, v))
|
|
@@ -1086,8 +1088,8 @@ export function getMarginPoint(
|
|
|
1086
1088
|
dotProduct(m, m) != 0
|
|
1087
1089
|
? addVector(B, m)
|
|
1088
1090
|
: dotProduct(n, n) != 0
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
+
? addVector(B, n)
|
|
1092
|
+
: B
|
|
1091
1093
|
} else {
|
|
1092
1094
|
const mm = dotProduct(m, m)
|
|
1093
1095
|
const nm = dotProduct(n, m)
|
|
@@ -1289,3 +1291,26 @@ export function getIndexesOfBiggestTriangleWithTwoFixedIndexes(
|
|
|
1289
1291
|
}
|
|
1290
1292
|
return maxTriangle
|
|
1291
1293
|
}
|
|
1294
|
+
|
|
1295
|
+
export function getRotatedRectBounds(width, height, angleDeg) {
|
|
1296
|
+
const rad = (angleDeg * Math.PI) / 180
|
|
1297
|
+
const c = Math.abs(Math.cos(rad))
|
|
1298
|
+
const s = Math.abs(Math.sin(rad))
|
|
1299
|
+
return {
|
|
1300
|
+
width: width * c + height * s,
|
|
1301
|
+
height: width * s + height * c,
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
export function getUnrotatedSizeFromAABB(aabbWidth, aabbHeight, angleDeg) {
|
|
1305
|
+
const rad = (angleDeg * Math.PI) / 180
|
|
1306
|
+
const c = Math.abs(Math.cos(rad))
|
|
1307
|
+
const s = Math.abs(Math.sin(rad))
|
|
1308
|
+
const det = c * c - s * s
|
|
1309
|
+
if (Math.abs(det) < 1e-10) {
|
|
1310
|
+
const side = Math.min(aabbWidth, aabbHeight) / Math.sqrt(2)
|
|
1311
|
+
return { width: side, height: side }
|
|
1312
|
+
}
|
|
1313
|
+
const width = (aabbWidth * c - aabbHeight * s) / det
|
|
1314
|
+
const height = (aabbHeight * c - aabbWidth * s) / det
|
|
1315
|
+
return { width: Math.abs(width), height: Math.abs(height) }
|
|
1316
|
+
}
|
package/src/objects/Polygon.js
CHANGED
|
@@ -404,6 +404,7 @@ export class Polygon {
|
|
|
404
404
|
} else if (this.layer == 'roof_plan_item') {
|
|
405
405
|
extraSerialization.colorIndex = this.colorIndex
|
|
406
406
|
extraSerialization.hasFillColor = this.hasFillColor
|
|
407
|
+
extraSerialization.isClosed = this.isClosed
|
|
407
408
|
}
|
|
408
409
|
return JSON.parse(
|
|
409
410
|
JSON.stringify({ ...baseSerialization, ...extraSerialization })
|
|
@@ -78,7 +78,11 @@ export function updateComputedGeometryPolygon(polygon) {
|
|
|
78
78
|
return polygon
|
|
79
79
|
}
|
|
80
80
|
let newOutline = [...polygon.outline]
|
|
81
|
+
const isOpenRoofPlanItem =
|
|
82
|
+
polygon.layer === 'roof_plan_item' && polygon.isClosed === false
|
|
83
|
+
|
|
81
84
|
if (
|
|
85
|
+
!isOpenRoofPlanItem &&
|
|
82
86
|
newOutline.length > 0 &&
|
|
83
87
|
isAlmostSamePoint2D(newOutline[0], newOutline[newOutline.length - 1], 10)
|
|
84
88
|
) {
|
|
@@ -90,6 +94,15 @@ export function updateComputedGeometryPolygon(polygon) {
|
|
|
90
94
|
}
|
|
91
95
|
newOutline = calculateValidOutlineFromPolygon(newOutline)
|
|
92
96
|
|
|
97
|
+
if (isOpenRoofPlanItem) {
|
|
98
|
+
if (newOutline.length < 2) {
|
|
99
|
+
throw new Error('outline not valid')
|
|
100
|
+
}
|
|
101
|
+
if (newOutline.length < 3) {
|
|
102
|
+
return updateComputedGeometryOpenRoofPlanItemPolygon(polygon, newOutline)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
93
106
|
if (newOutline.length < 3) {
|
|
94
107
|
throw new Error('outline not valid')
|
|
95
108
|
}
|
|
@@ -124,6 +137,9 @@ export function updateComputedGeometryPolygon(polygon) {
|
|
|
124
137
|
polygon.direction = direction
|
|
125
138
|
polygon.area = calculateArea(polygon.flatOutline) / 1000000
|
|
126
139
|
polygon.isClockwise = isClockWise(polygon.outline)
|
|
140
|
+
if (polygon.layer === 'roof_plan_item' && polygon.isClosed === false) {
|
|
141
|
+
polygon.isClockwise = false
|
|
142
|
+
}
|
|
127
143
|
polygon = updateMarginOutlinePolygon(polygon)
|
|
128
144
|
if (polygon.layer == 'moduleField') {
|
|
129
145
|
let trimedOutline = []
|
|
@@ -150,6 +166,27 @@ export function updateComputedGeometryPolygon(polygon) {
|
|
|
150
166
|
}
|
|
151
167
|
return polygon
|
|
152
168
|
}
|
|
169
|
+
|
|
170
|
+
function updateComputedGeometryOpenRoofPlanItemPolygon(polygon, newOutline) {
|
|
171
|
+
polygon.outline = newOutline
|
|
172
|
+
const normalVector = { x: 0, y: 0, z: 1 }
|
|
173
|
+
const meanPoint = meanVector(newOutline)
|
|
174
|
+
polygon.flatOutline = newOutline.map((p) => ({
|
|
175
|
+
x: p.x,
|
|
176
|
+
y: p.y,
|
|
177
|
+
z: Math.max(0, Math.min(50000, p.z)),
|
|
178
|
+
}))
|
|
179
|
+
polygon.maximumGap = 0
|
|
180
|
+
polygon.isFlat = true
|
|
181
|
+
polygon.normalVector = normalVector
|
|
182
|
+
polygon.meanPoint = meanPoint
|
|
183
|
+
polygon.incline = inclineWithNormalVector(normalVector)
|
|
184
|
+
polygon.direction = directionWithNormalVector(normalVector)
|
|
185
|
+
polygon.area = 0
|
|
186
|
+
polygon.isClockwise = false
|
|
187
|
+
return updateMarginOutlinePolygon(polygon)
|
|
188
|
+
}
|
|
189
|
+
|
|
153
190
|
export function calculateValidOutlineFromPolygon(outline) {
|
|
154
191
|
//check if two nodes are same
|
|
155
192
|
outline = [...outline]
|
package/src/objects/hydrate.js
CHANGED
|
@@ -49,6 +49,9 @@ export function hydratePolygon(serializedPolygon) {
|
|
|
49
49
|
polygon.shape = 'polygon'
|
|
50
50
|
polygon.colorIndex = serializedPolygon.colorIndex
|
|
51
51
|
polygon.hasFillColor = serializedPolygon.hasFillColor
|
|
52
|
+
if (serializedPolygon.isClosed === false) {
|
|
53
|
+
polygon.isClosed = false
|
|
54
|
+
}
|
|
52
55
|
}
|
|
53
56
|
updateComputedGeometryPolygon(polygon)
|
|
54
57
|
return polygon
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { getRotatedRectBounds, getUnrotatedSizeFromAABB } from '../../index'
|
|
2
|
+
|
|
3
|
+
function expectSizeClose(actual, expected, digits = 8) {
|
|
4
|
+
expect(actual.width).toBeCloseTo(expected.width, digits)
|
|
5
|
+
expect(actual.height).toBeCloseTo(expected.height, digits)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe('rectangle rotation helpers', () => {
|
|
9
|
+
describe('getRotatedRectBounds', () => {
|
|
10
|
+
test('returns same size at 0° (and equivalent angles)', () => {
|
|
11
|
+
expectSizeClose(getRotatedRectBounds(120, 45, 0), {
|
|
12
|
+
width: 120,
|
|
13
|
+
height: 45,
|
|
14
|
+
})
|
|
15
|
+
expectSizeClose(getRotatedRectBounds(120, 45, -180), {
|
|
16
|
+
width: 120,
|
|
17
|
+
height: 45,
|
|
18
|
+
})
|
|
19
|
+
expectSizeClose(getRotatedRectBounds(120, 45, 180), {
|
|
20
|
+
width: 120,
|
|
21
|
+
height: 45,
|
|
22
|
+
})
|
|
23
|
+
expectSizeClose(getRotatedRectBounds(120, 45, 360), {
|
|
24
|
+
width: 120,
|
|
25
|
+
height: 45,
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('swaps width/height at 90°', () => {
|
|
30
|
+
expectSizeClose(getRotatedRectBounds(120, 45, 90), {
|
|
31
|
+
width: 45,
|
|
32
|
+
height: 120,
|
|
33
|
+
})
|
|
34
|
+
expectSizeClose(getRotatedRectBounds(120, 45, -90), {
|
|
35
|
+
width: 45,
|
|
36
|
+
height: 120,
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('matches expected formula for an arbitrary angle', () => {
|
|
41
|
+
const width = 100
|
|
42
|
+
const height = 50
|
|
43
|
+
const angleDeg = 30
|
|
44
|
+
const rad = (angleDeg * Math.PI) / 180
|
|
45
|
+
const c = Math.abs(Math.cos(rad))
|
|
46
|
+
const s = Math.abs(Math.sin(rad))
|
|
47
|
+
expectSizeClose(getRotatedRectBounds(width, height, angleDeg), {
|
|
48
|
+
width: width * c + height * s,
|
|
49
|
+
height: width * s + height * c,
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
test('match for thin rectangle for an arbitrary angle', () => {
|
|
53
|
+
const width = 100
|
|
54
|
+
const height = 0.01
|
|
55
|
+
const angleDeg = 30
|
|
56
|
+
|
|
57
|
+
expectSizeClose(
|
|
58
|
+
getRotatedRectBounds(width, height, angleDeg),
|
|
59
|
+
{
|
|
60
|
+
width: (Math.sqrt(3) * 100) / 2,
|
|
61
|
+
height: 50,
|
|
62
|
+
},
|
|
63
|
+
1
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('getUnrotatedSizeFromAABB', () => {
|
|
69
|
+
test('approximately inverts getRotatedRectBounds (non-degenerate angle)', () => {
|
|
70
|
+
const original = { width: 123.4, height: 56.7 }
|
|
71
|
+
const angleDeg = 27
|
|
72
|
+
|
|
73
|
+
const aabb = getRotatedRectBounds(
|
|
74
|
+
original.width,
|
|
75
|
+
original.height,
|
|
76
|
+
angleDeg
|
|
77
|
+
)
|
|
78
|
+
const unrotated = getUnrotatedSizeFromAABB(
|
|
79
|
+
aabb.width,
|
|
80
|
+
aabb.height,
|
|
81
|
+
angleDeg
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
expectSizeClose(unrotated, original, 6)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('handles the 45° determinant edge-case by returning a square', () => {
|
|
88
|
+
const aabbWidth = 200
|
|
89
|
+
const aabbHeight = 150
|
|
90
|
+
const angleDeg = 45
|
|
91
|
+
|
|
92
|
+
const result = getUnrotatedSizeFromAABB(aabbWidth, aabbHeight, angleDeg)
|
|
93
|
+
const side = Math.min(aabbWidth, aabbHeight) / Math.sqrt(2)
|
|
94
|
+
|
|
95
|
+
expectSizeClose(result, { width: side, height: side }, 10)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
})
|