@jscad/modeling 2.5.2 → 2.7.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/CHANGELOG.md +57 -0
- package/dist/jscad-modeling.min.js +217 -202
- package/package.json +2 -2
- package/src/colors/colorize.js +4 -5
- package/src/colors/colorize.test.js +19 -12
- package/src/geometries/geom2/applyTransforms.js +1 -1
- package/src/geometries/geom2/clone.js +2 -12
- package/src/geometries/geom2/transform.js +2 -6
- package/src/geometries/geom3/applyTransforms.js +1 -1
- package/src/geometries/geom3/clone.js +2 -14
- package/src/geometries/geom3/clone.test.js +0 -2
- package/src/geometries/geom3/create.js +0 -2
- package/src/geometries/geom3/create.test.js +0 -2
- package/src/geometries/geom3/fromCompactBinary.js +4 -6
- package/src/geometries/geom3/fromToCompactBinary.test.js +0 -6
- package/src/geometries/geom3/invert.test.js +0 -2
- package/src/geometries/geom3/toCompactBinary.js +8 -10
- package/src/geometries/geom3/transform.js +2 -7
- package/src/geometries/geom3/transform.test.js +0 -1
- package/src/geometries/geom3/type.d.ts +0 -1
- package/src/geometries/path2/applyTransforms.js +1 -1
- package/src/geometries/path2/clone.js +2 -13
- package/src/geometries/path2/transform.js +2 -7
- package/src/geometries/poly3/isConvex.js +1 -1
- package/src/geometries/poly3/measureArea.js +12 -13
- package/src/geometries/poly3/measureArea.test.js +15 -0
- package/src/geometries/poly3/plane.js +1 -2
- package/src/maths/mat4/index.js +1 -0
- package/src/maths/mat4/isOnlyTransformScale.js +21 -0
- package/src/maths/mat4/isOnlyTransformScale.test.js +27 -0
- package/src/maths/plane/fromPoints.js +32 -10
- package/src/maths/plane/fromPoints.test.js +4 -0
- package/src/maths/utils/index.d.ts +0 -1
- package/src/maths/utils/index.js +0 -1
- package/src/maths/vec2/rotate.js +1 -1
- package/src/maths/vec2/rotate.test.js +3 -3
- package/src/measurements/index.js +4 -1
- package/src/measurements/measureBoundingBox.js +128 -38
- package/src/measurements/measureBoundingBox.test.js +8 -0
- package/src/measurements/measureBoundingSphere.js +146 -0
- package/src/measurements/measureBoundingSphere.test.js +59 -0
- package/src/measurements/measureCenter.js +28 -0
- package/src/measurements/measureCenter.test.js +58 -0
- package/src/measurements/measureCenterOfMass.js +106 -0
- package/src/measurements/measureCenterOfMass.test.js +58 -0
- package/src/measurements/measureDimensions.js +28 -0
- package/src/measurements/measureDimensions.test.js +58 -0
- package/src/measurements/measureEpsilon.js +3 -9
- package/src/operations/booleans/reTesselateCoplanarPolygons.js +1 -1
- package/src/operations/booleans/trees/PolygonTreeNode.js +0 -1
- package/src/operations/expansions/expand.test.js +1 -1
- package/src/operations/extrusions/extrudeRotate.test.js +18 -10
- package/src/operations/modifiers/generalize.js +0 -1
- package/src/operations/modifiers/snapPolygons.js +2 -2
- package/src/operations/modifiers/snapPolygons.test.js +13 -5
- package/src/primitives/index.d.ts +1 -0
- package/src/primitives/index.js +2 -1
- package/src/primitives/triangle.d.ts +10 -0
- package/src/primitives/triangle.js +164 -0
- package/src/primitives/triangle.test.js +95 -0
- package/src/maths/utils/clamp.d.ts +0 -3
- package/src/maths/utils/clamp.js +0 -4
- package/src/maths/utils/quantizeForSpace.d.ts +0 -3
- package/src/maths/utils/quantizeForSpace.js +0 -6
|
@@ -13,4 +13,8 @@ test('plane: fromPoints() should return a new plane with correct values', (t) =>
|
|
|
13
13
|
// planes created from the same points results in an invalid plane
|
|
14
14
|
const obs3 = fromPoints(obs1, [0, 6, 0], [0, 6, 0], [0, 6, 0])
|
|
15
15
|
t.true(compareVectors(obs3, [0 / 0, 0 / 0, 0 / 0, 0 / 0]))
|
|
16
|
+
|
|
17
|
+
// polygon with co-linear vertices
|
|
18
|
+
const obs4 = fromPoints(obs1, [0, 0, 0], [1, 0, 0], [2, 0, 0], [0, 1, 0])
|
|
19
|
+
t.true(compareVectors(obs4, [0, 0, 1, 0]))
|
|
16
20
|
})
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { default as aboutEqualNormals } from './aboutEqualNormals'
|
|
2
2
|
export { default as area } from './area'
|
|
3
|
-
export { default as clamp } from './clamp'
|
|
4
3
|
export { default as interpolateBetween2DPointsForY } from './interpolateBetween2DPointsForY'
|
|
5
4
|
export { default as intersect } from './intersect'
|
|
6
5
|
export { default as solve2Linear } from './solve2Linear'
|
package/src/maths/utils/index.js
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
module.exports = {
|
|
8
8
|
aboutEqualNormals: require('./aboutEqualNormals'),
|
|
9
9
|
area: require('./area'),
|
|
10
|
-
clamp: require('./clamp'),
|
|
11
10
|
interpolateBetween2DPointsForY: require('./interpolateBetween2DPointsForY'),
|
|
12
11
|
intersect: require('./intersect'),
|
|
13
12
|
solve2Linear: require('./solve2Linear')
|
package/src/maths/vec2/rotate.js
CHANGED
|
@@ -22,7 +22,7 @@ test('vec2: rotate() called with three paramerters should update a vec2 with cor
|
|
|
22
22
|
t.true(compareVectors(ret3, [2, -1], 1e-15))
|
|
23
23
|
|
|
24
24
|
const obs4 = fromValues(0, 0)
|
|
25
|
-
const ret4 = rotate(obs4, [-1, 2], [
|
|
26
|
-
t.true(compareVectors(obs4, [2,
|
|
27
|
-
t.true(compareVectors(ret4, [2,
|
|
25
|
+
const ret4 = rotate(obs4, [-1, 2], [-3, -3], -radians)
|
|
26
|
+
t.true(compareVectors(obs4, [2, -5], 1e-15))
|
|
27
|
+
t.true(compareVectors(ret4, [2, -5], 1e-15))
|
|
28
28
|
})
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* All shapes (primitives or the results of operations) can be measured, e.g. calculate volume, etc.
|
|
3
|
-
* In all cases, the function returns the results, and never changes the original shapes.
|
|
4
3
|
* @module modeling/measurements
|
|
5
4
|
* @example
|
|
6
5
|
* const { measureArea, measureBoundingBox, measureVolume } = require('@jscad/modeling').measurements
|
|
@@ -12,6 +11,10 @@ module.exports = {
|
|
|
12
11
|
measureAggregateVolume: require('./measureAggregateVolume'),
|
|
13
12
|
measureArea: require('./measureArea'),
|
|
14
13
|
measureBoundingBox: require('./measureBoundingBox'),
|
|
14
|
+
measureBoundingSphere: require('./measureBoundingSphere'),
|
|
15
|
+
measureCenter: require('./measureCenter'),
|
|
16
|
+
measureCenterOfMass: require('./measureCenterOfMass'),
|
|
17
|
+
measureDimensions: require('./measureDimensions'),
|
|
15
18
|
measureEpsilon: require('./measureEpsilon'),
|
|
16
19
|
measureVolume: require('./measureVolume')
|
|
17
20
|
}
|
|
@@ -2,6 +2,7 @@ const flatten = require('../utils/flatten')
|
|
|
2
2
|
|
|
3
3
|
const vec2 = require('../maths/vec2')
|
|
4
4
|
const vec3 = require('../maths/vec3')
|
|
5
|
+
const mat4 = require('../maths/mat4')
|
|
5
6
|
|
|
6
7
|
const geom2 = require('../geometries/geom2')
|
|
7
8
|
const geom3 = require('../geometries/geom3')
|
|
@@ -11,86 +12,129 @@ const poly3 = require('../geometries/poly3')
|
|
|
11
12
|
const cache = new WeakMap()
|
|
12
13
|
|
|
13
14
|
/*
|
|
14
|
-
* Measure the min and max bounds of the given (path2) geometry.
|
|
15
|
-
* @return {Array[]} the min and max bounds for the geometry
|
|
15
|
+
* Measure the min and max bounds of the given (path2) geometry.points.
|
|
16
|
+
* @return {Array[]} the min and max bounds for the geometry.points
|
|
16
17
|
*/
|
|
17
|
-
const
|
|
18
|
-
let boundingBox = cache.get(
|
|
18
|
+
const measureBoundingBoxOfPath2Points = (points) => {
|
|
19
|
+
let boundingBox = cache.get(points)
|
|
19
20
|
if (boundingBox) return boundingBox
|
|
20
21
|
|
|
21
|
-
const points = path2.toPoints(geometry)
|
|
22
|
-
|
|
23
22
|
let minpoint
|
|
24
23
|
if (points.length === 0) {
|
|
25
24
|
minpoint = vec2.create()
|
|
26
25
|
} else {
|
|
27
26
|
minpoint = vec2.clone(points[0])
|
|
28
27
|
}
|
|
29
|
-
|
|
28
|
+
const maxpoint = vec2.clone(minpoint)
|
|
30
29
|
|
|
31
30
|
points.forEach((point) => {
|
|
32
31
|
vec2.min(minpoint, minpoint, point)
|
|
33
32
|
vec2.max(maxpoint, maxpoint, point)
|
|
34
33
|
})
|
|
35
|
-
|
|
36
|
-
maxpoint = [maxpoint[0], maxpoint[1], 0]
|
|
37
|
-
|
|
38
|
-
boundingBox = [minpoint, maxpoint]
|
|
39
|
-
|
|
40
|
-
cache.set(geometry, boundingBox)
|
|
34
|
+
boundingBox = [[minpoint[0], minpoint[1], 0], [maxpoint[0], maxpoint[1], 0]]
|
|
41
35
|
|
|
36
|
+
cache.set(points, boundingBox)
|
|
42
37
|
return boundingBox
|
|
43
38
|
}
|
|
44
39
|
|
|
45
40
|
/*
|
|
46
|
-
* Measure the min and max bounds of the given (
|
|
41
|
+
* Measure the min and max bounds of the given (path2) geometry.
|
|
47
42
|
* @return {Array[]} the min and max bounds for the geometry
|
|
48
43
|
*/
|
|
49
|
-
const
|
|
44
|
+
const measureBoundingBoxOfPath2 = (geometry) => {
|
|
50
45
|
let boundingBox = cache.get(geometry)
|
|
51
46
|
if (boundingBox) return boundingBox
|
|
52
47
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (points.length === 0) {
|
|
57
|
-
minpoint = vec2.create()
|
|
48
|
+
if (mat4.isOnlyTransformScale(geometry.transforms)) {
|
|
49
|
+
// get boundingBox of original points and transform it
|
|
50
|
+
boundingBox = transformBoundingBox(measureBoundingBoxOfPath2Points(geometry.points), geometry.transforms)
|
|
58
51
|
} else {
|
|
59
|
-
|
|
52
|
+
// transform the points and then caclulate the boundingBox
|
|
53
|
+
boundingBox = measureBoundingBoxOfPath2Points(path2.toPoints(geometry))
|
|
60
54
|
}
|
|
61
|
-
let maxpoint = vec2.clone(minpoint)
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
})
|
|
56
|
+
cache.set(geometry, boundingBox)
|
|
57
|
+
return boundingBox
|
|
58
|
+
}
|
|
67
59
|
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
/*
|
|
61
|
+
* Measure the min and max bounds of the given (geom2) geometry.points/sides.
|
|
62
|
+
* @return {Array[]} the min and max bounds for the geometr.points/sidesy
|
|
63
|
+
*/
|
|
64
|
+
const measureBoundingBoxOfGeom2Points = ({ points, sides }) => {
|
|
65
|
+
const cacheKey = points || sides
|
|
70
66
|
|
|
71
|
-
boundingBox =
|
|
67
|
+
let boundingBox = cache.get(cacheKey)
|
|
68
|
+
if (boundingBox) return boundingBox
|
|
72
69
|
|
|
73
|
-
|
|
70
|
+
let minpoint, maxpoint
|
|
71
|
+
|
|
72
|
+
if (points) {
|
|
73
|
+
if (points.length === 0) {
|
|
74
|
+
minpoint = vec2.create()
|
|
75
|
+
} else {
|
|
76
|
+
minpoint = vec2.clone(points[0])
|
|
77
|
+
}
|
|
78
|
+
maxpoint = vec2.clone(minpoint)
|
|
79
|
+
|
|
80
|
+
points.forEach((point) => {
|
|
81
|
+
vec2.min(minpoint, minpoint, point)
|
|
82
|
+
vec2.max(maxpoint, maxpoint, point)
|
|
83
|
+
})
|
|
84
|
+
} else { // sides
|
|
85
|
+
// to avoid calling costly toPoints, we take advantage of the knowlege how the toPoints works
|
|
86
|
+
if (sides.length === 0) {
|
|
87
|
+
minpoint = vec2.create()
|
|
88
|
+
} else {
|
|
89
|
+
minpoint = vec2.clone(sides[0][0])
|
|
90
|
+
}
|
|
91
|
+
maxpoint = vec2.clone(minpoint)
|
|
92
|
+
|
|
93
|
+
sides.forEach((side) => {
|
|
94
|
+
vec2.min(minpoint, minpoint, side[0])
|
|
95
|
+
vec2.max(maxpoint, maxpoint, side[0])
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
boundingBox = [[minpoint[0], minpoint[1], 0], [maxpoint[0], maxpoint[1], 0]]
|
|
74
99
|
|
|
100
|
+
cache.set(cacheKey, boundingBox)
|
|
75
101
|
return boundingBox
|
|
76
102
|
}
|
|
77
103
|
|
|
78
104
|
/*
|
|
79
|
-
* Measure the min and max bounds of the given (
|
|
105
|
+
* Measure the min and max bounds of the given (geom2) geometry.
|
|
80
106
|
* @return {Array[]} the min and max bounds for the geometry
|
|
81
107
|
*/
|
|
82
|
-
const
|
|
108
|
+
const measureBoundingBoxOfGeom2 = (geometry) => {
|
|
83
109
|
let boundingBox = cache.get(geometry)
|
|
84
110
|
if (boundingBox) return boundingBox
|
|
85
111
|
|
|
86
|
-
|
|
112
|
+
if (mat4.isOnlyTransformScale(geometry.transforms)) {
|
|
113
|
+
// get boundingBox of original points and transform it
|
|
114
|
+
boundingBox = transformBoundingBox(measureBoundingBoxOfGeom2Points(geometry), geometry.transforms)
|
|
115
|
+
} else {
|
|
116
|
+
// transform the points and then caclulate the boundingBox
|
|
117
|
+
boundingBox = measureBoundingBoxOfGeom2Points({ points: geom2.toPoints(geometry) })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
cache.set(geometry, boundingBox)
|
|
121
|
+
return boundingBox
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/*
|
|
125
|
+
* Measure the min and max bounds of the given (geom3) geometry.polygons.
|
|
126
|
+
* @return {Array[]} the min and max bounds for the geometry.polygons
|
|
127
|
+
*/
|
|
128
|
+
const measureBoundingBoxOfGeom3Polygons = (polygons) => {
|
|
129
|
+
let boundingBox = cache.get(polygons)
|
|
130
|
+
if (boundingBox) return boundingBox
|
|
87
131
|
|
|
88
|
-
|
|
132
|
+
const minpoint = vec3.create()
|
|
89
133
|
if (polygons.length > 0) {
|
|
90
134
|
const points = poly3.toPoints(polygons[0])
|
|
91
135
|
vec3.copy(minpoint, points[0])
|
|
92
136
|
}
|
|
93
|
-
|
|
137
|
+
const maxpoint = vec3.clone(minpoint)
|
|
94
138
|
|
|
95
139
|
polygons.forEach((polygon) => {
|
|
96
140
|
poly3.toPoints(polygon).forEach((point) => {
|
|
@@ -99,13 +143,59 @@ const measureBoundingBoxOfGeom3 = (geometry) => {
|
|
|
99
143
|
})
|
|
100
144
|
})
|
|
101
145
|
|
|
102
|
-
|
|
103
|
-
maxpoint = [maxpoint[0], maxpoint[1], maxpoint[2]]
|
|
146
|
+
boundingBox = [[minpoint[0], minpoint[1], minpoint[2]], [maxpoint[0], maxpoint[1], maxpoint[2]]]
|
|
104
147
|
|
|
105
|
-
|
|
148
|
+
cache.set(polygons, boundingBox)
|
|
149
|
+
return boundingBox
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/*
|
|
153
|
+
* Measure the min and max bounds of the given (geom3) geometry.
|
|
154
|
+
* @return {Array[]} the min and max bounds for the geometry
|
|
155
|
+
*/
|
|
156
|
+
const measureBoundingBoxOfGeom3 = (geometry) => {
|
|
157
|
+
let boundingBox = cache.get(geometry)
|
|
158
|
+
if (boundingBox) return boundingBox
|
|
159
|
+
|
|
160
|
+
if (mat4.isOnlyTransformScale(geometry.transforms)) {
|
|
161
|
+
// get boundingBox of original points and transform it
|
|
162
|
+
boundingBox = transformBoundingBox(measureBoundingBoxOfGeom3Polygons(geometry.polygons), geometry.transforms)
|
|
163
|
+
} else {
|
|
164
|
+
// transform the points and then caclulate the boundingBox
|
|
165
|
+
boundingBox = measureBoundingBoxOfGeom3Polygons(geom3.toPolygons(geometry))
|
|
166
|
+
}
|
|
106
167
|
|
|
107
168
|
cache.set(geometry, boundingBox)
|
|
169
|
+
return boundingBox
|
|
170
|
+
}
|
|
108
171
|
|
|
172
|
+
/*
|
|
173
|
+
* swap values if specific axis value in the second vector has lower value than the axis value in first vector
|
|
174
|
+
*/
|
|
175
|
+
const fixBound = (i, v1, v2) => {
|
|
176
|
+
if (v1[i] > v2[i]) {
|
|
177
|
+
const tmp = v1[i]
|
|
178
|
+
v1[i] = v2[i]
|
|
179
|
+
v2[i] = tmp
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
* Transform the given bounding box
|
|
185
|
+
*/
|
|
186
|
+
const transformBoundingBox = (boundingBox, transforms) => {
|
|
187
|
+
if (!mat4.isIdentity(transforms)) {
|
|
188
|
+
vec3.transform(boundingBox[0], boundingBox[0], transforms)
|
|
189
|
+
vec3.transform(boundingBox[1], boundingBox[1], transforms)
|
|
190
|
+
|
|
191
|
+
// we now have a new 2 vectors: [v1,v2] => [ [x1,y1,z1], [x2,y2,z2] ]
|
|
192
|
+
// transform can move bounding box corner in such way that it is no longer true that
|
|
193
|
+
// - v1 = [min(x1,x2),min(y1,y2),min(z1,z2)]
|
|
194
|
+
// - v2 = [max(x1,x2),max(y1,y2),max(z1,z2)]
|
|
195
|
+
fixBound(0, ...boundingBox) // swap x, if higher value is in first vector
|
|
196
|
+
fixBound(1, ...boundingBox) // swap y, if higher value is in first vector
|
|
197
|
+
fixBound(2, ...boundingBox) // swap z, if higher value is in first vector
|
|
198
|
+
}
|
|
109
199
|
return boundingBox
|
|
110
200
|
}
|
|
111
201
|
|
|
@@ -4,6 +4,8 @@ const { geom2, geom3, path2 } = require('../geometries')
|
|
|
4
4
|
|
|
5
5
|
const { line, rectangle, cuboid } = require('../primitives')
|
|
6
6
|
|
|
7
|
+
const { mirror } = require('../operations/transforms')
|
|
8
|
+
|
|
7
9
|
const { measureBoundingBox } = require('./index')
|
|
8
10
|
|
|
9
11
|
test('measureBoundingBox (single objects)', (t) => {
|
|
@@ -56,3 +58,9 @@ test('measureBoundingBox (multiple objects)', (t) => {
|
|
|
56
58
|
allbounds = measureBoundingBox(aline, arect, acube, o)
|
|
57
59
|
t.deepEqual(allbounds, [[[10, 10, 0], [15, 15, 0]], [[-5, -10, 0], [5, 10, 0]], [[-1, -1, -1], [1, 1, 1]], [[0, 0, 0], [0, 0, 0]]])
|
|
58
60
|
})
|
|
61
|
+
|
|
62
|
+
test('measureBoundingBox invert', (t) => {
|
|
63
|
+
const acube = mirror({}, cuboid())
|
|
64
|
+
const cbounds = measureBoundingBox(acube)
|
|
65
|
+
t.deepEqual(cbounds, [[-1, -1, -1], [1, 1, 1]])
|
|
66
|
+
})
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const flatten = require('../utils/flatten')
|
|
2
|
+
|
|
3
|
+
const vec2 = require('../maths/vec2')
|
|
4
|
+
const vec3 = require('../maths/vec3')
|
|
5
|
+
|
|
6
|
+
const geom2 = require('../geometries/geom2')
|
|
7
|
+
const geom3 = require('../geometries/geom3')
|
|
8
|
+
const path2 = require('../geometries/path2')
|
|
9
|
+
const poly3 = require('../geometries/poly3')
|
|
10
|
+
|
|
11
|
+
const cacheOfBoundingSpheres = new WeakMap()
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* Measure the bounding sphere of the given (path2) geometry.
|
|
15
|
+
* @return {[[x, y, z], radius]} the bounding sphere for the geometry
|
|
16
|
+
*/
|
|
17
|
+
const measureBoundingSphereOfPath2 = (geometry) => {
|
|
18
|
+
let boundingSphere = cacheOfBoundingSpheres.get(geometry)
|
|
19
|
+
if (boundingSphere !== undefined) return boundingSphere
|
|
20
|
+
|
|
21
|
+
const centroid = vec3.create()
|
|
22
|
+
let radius = 0
|
|
23
|
+
|
|
24
|
+
const points = path2.toPoints(geometry)
|
|
25
|
+
|
|
26
|
+
if (points.length > 0) {
|
|
27
|
+
// calculate the centriod of the geometry
|
|
28
|
+
let numPoints = 0
|
|
29
|
+
const temp = vec3.create()
|
|
30
|
+
points.forEach((point) => {
|
|
31
|
+
vec3.add(centroid, centroid, vec3.fromVec2(temp, point, 0))
|
|
32
|
+
numPoints++
|
|
33
|
+
})
|
|
34
|
+
vec3.scale(centroid, centroid, 1 / numPoints)
|
|
35
|
+
|
|
36
|
+
// find the farthest point from the centroid
|
|
37
|
+
points.forEach((point) => {
|
|
38
|
+
radius = Math.max(radius, vec2.squaredDistance(centroid, point))
|
|
39
|
+
})
|
|
40
|
+
radius = Math.sqrt(radius)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
boundingSphere = [centroid, radius]
|
|
44
|
+
cacheOfBoundingSpheres.set(geometry, boundingSphere)
|
|
45
|
+
|
|
46
|
+
return boundingSphere
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
* Measure the bounding sphere of the given (geom2) geometry.
|
|
51
|
+
* @return {[[x, y, z], radius]} the bounding sphere for the geometry
|
|
52
|
+
*/
|
|
53
|
+
const measureBoundingSphereOfGeom2 = (geometry) => {
|
|
54
|
+
let boundingSphere = cacheOfBoundingSpheres.get(geometry)
|
|
55
|
+
if (boundingSphere !== undefined) return boundingSphere
|
|
56
|
+
|
|
57
|
+
const centroid = vec3.create()
|
|
58
|
+
let radius = 0
|
|
59
|
+
|
|
60
|
+
const sides = geom2.toSides(geometry)
|
|
61
|
+
|
|
62
|
+
if (sides.length > 0) {
|
|
63
|
+
// calculate the centriod of the geometry
|
|
64
|
+
let numPoints = 0
|
|
65
|
+
const temp = vec3.create()
|
|
66
|
+
sides.forEach((side) => {
|
|
67
|
+
vec3.add(centroid, centroid, vec3.fromVec2(temp, side[0], 0))
|
|
68
|
+
numPoints++
|
|
69
|
+
})
|
|
70
|
+
vec3.scale(centroid, centroid, 1 / numPoints)
|
|
71
|
+
|
|
72
|
+
// find the farthest point from the centroid
|
|
73
|
+
sides.forEach((side) => {
|
|
74
|
+
radius = Math.max(radius, vec2.squaredDistance(centroid, side[0]))
|
|
75
|
+
})
|
|
76
|
+
radius = Math.sqrt(radius)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
boundingSphere = [centroid, radius]
|
|
80
|
+
cacheOfBoundingSpheres.set(geometry, boundingSphere)
|
|
81
|
+
|
|
82
|
+
return boundingSphere
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* Measure the bounding sphere of the given (geom3) geometry.
|
|
87
|
+
* @return {[[x, y, z], radius]} the bounding sphere for the geometry
|
|
88
|
+
*/
|
|
89
|
+
const measureBoundingSphereOfGeom3 = (geometry) => {
|
|
90
|
+
let boundingSphere = cacheOfBoundingSpheres.get(geometry)
|
|
91
|
+
if (boundingSphere !== undefined) return boundingSphere
|
|
92
|
+
|
|
93
|
+
const centroid = vec3.create()
|
|
94
|
+
let radius = 0
|
|
95
|
+
|
|
96
|
+
const polygons = geom3.toPolygons(geometry)
|
|
97
|
+
|
|
98
|
+
if (polygons.length > 0) {
|
|
99
|
+
// calculate the centriod of the geometry
|
|
100
|
+
let numPoints = 0
|
|
101
|
+
polygons.forEach((polygon) => {
|
|
102
|
+
poly3.toPoints(polygon).forEach((point) => {
|
|
103
|
+
vec3.add(centroid, centroid, point)
|
|
104
|
+
numPoints++
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
vec3.scale(centroid, centroid, 1 / numPoints)
|
|
108
|
+
|
|
109
|
+
// find the farthest point from the centroid
|
|
110
|
+
polygons.forEach((polygon) => {
|
|
111
|
+
poly3.toPoints(polygon).forEach((point) => {
|
|
112
|
+
radius = Math.max(radius, vec3.squaredDistance(centroid, point))
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
radius = Math.sqrt(radius)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
boundingSphere = [centroid, radius]
|
|
119
|
+
cacheOfBoundingSpheres.set(geometry, boundingSphere)
|
|
120
|
+
|
|
121
|
+
return boundingSphere
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Measure the (aproximate) bounding sphere of the given geometries.
|
|
126
|
+
* @see https://en.wikipedia.org/wiki/Bounding_sphere
|
|
127
|
+
* @param {...Object} geometries - the geometries to measure
|
|
128
|
+
* @return {Array} the bounding sphere for each geometry, i.e. [centroid, radius]
|
|
129
|
+
* @alias module:modeling/measurements.measureBoundingSphere
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* let bounds = measureBoundingSphere(cube())
|
|
133
|
+
*/
|
|
134
|
+
const measureBoundingSphere = (...geometries) => {
|
|
135
|
+
geometries = flatten(geometries)
|
|
136
|
+
|
|
137
|
+
const results = geometries.map((geometry) => {
|
|
138
|
+
if (path2.isA(geometry)) return measureBoundingSphereOfPath2(geometry)
|
|
139
|
+
if (geom2.isA(geometry)) return measureBoundingSphereOfGeom2(geometry)
|
|
140
|
+
if (geom3.isA(geometry)) return measureBoundingSphereOfGeom3(geometry)
|
|
141
|
+
return [[0, 0, 0], 0]
|
|
142
|
+
})
|
|
143
|
+
return results.length === 1 ? results[0] : results
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = measureBoundingSphere
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { geom2, geom3, path2 } = require('../geometries')
|
|
4
|
+
|
|
5
|
+
const { line, rectangle, ellipsoid } = require('../primitives')
|
|
6
|
+
|
|
7
|
+
const { measureBoundingSphere } = require('./index')
|
|
8
|
+
|
|
9
|
+
test('measureBoundingSphere (single objects)', (t) => {
|
|
10
|
+
const aline = line([[10, 10], [15, 15]])
|
|
11
|
+
const arect = rectangle()
|
|
12
|
+
const aellipsoid = ellipsoid({ radius: [5, 10, 15], center: [5, 5, 5] })
|
|
13
|
+
|
|
14
|
+
const apath2 = path2.create()
|
|
15
|
+
const ageom2 = geom2.create()
|
|
16
|
+
const ageom3 = geom3.create()
|
|
17
|
+
|
|
18
|
+
const n = null
|
|
19
|
+
const o = {}
|
|
20
|
+
const x = 'hi'
|
|
21
|
+
|
|
22
|
+
const lbounds = measureBoundingSphere(aline)
|
|
23
|
+
const rbounds = measureBoundingSphere(arect)
|
|
24
|
+
const cbounds = measureBoundingSphere(aellipsoid)
|
|
25
|
+
|
|
26
|
+
const p2bounds = measureBoundingSphere(apath2)
|
|
27
|
+
const g2bounds = measureBoundingSphere(ageom2)
|
|
28
|
+
const g3bounds = measureBoundingSphere(ageom3)
|
|
29
|
+
|
|
30
|
+
const nbounds = measureBoundingSphere(n)
|
|
31
|
+
const obounds = measureBoundingSphere(o)
|
|
32
|
+
const xbounds = measureBoundingSphere(x)
|
|
33
|
+
|
|
34
|
+
t.deepEqual(lbounds, [[12.5, 12.5, 0], 3.5355339059327378])
|
|
35
|
+
t.deepEqual(rbounds, [[0, 0, 0], 1.4142135623730951])
|
|
36
|
+
t.deepEqual(cbounds, [[5.000000000000018, 4.999999999999983, 5.000000000000001], 15])
|
|
37
|
+
|
|
38
|
+
t.deepEqual(p2bounds, [[0, 0, 0], 0])
|
|
39
|
+
t.deepEqual(g2bounds, [[0, 0, 0], 0])
|
|
40
|
+
t.deepEqual(g3bounds, [[0, 0, 0], 0])
|
|
41
|
+
|
|
42
|
+
t.deepEqual(nbounds, [[0, 0, 0], 0])
|
|
43
|
+
t.deepEqual(obounds, [[0, 0, 0], 0])
|
|
44
|
+
t.deepEqual(xbounds, [[0, 0, 0], 0])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('measureBoundingSphere (multiple objects)', (t) => {
|
|
48
|
+
const aline = line([[10, 10], [15, 15]])
|
|
49
|
+
const arect = rectangle()
|
|
50
|
+
const aellipsoid = ellipsoid({ radius: [5, 10, 15], center: [5, 5, 5] })
|
|
51
|
+
const o = {}
|
|
52
|
+
|
|
53
|
+
let allbounds = measureBoundingSphere(aline, arect, aellipsoid, o)
|
|
54
|
+
t.deepEqual(allbounds, [[[12.5, 12.5, 0], 3.5355339059327378], [[0, 0, 0], 1.4142135623730951], [[5.000000000000018, 4.999999999999983, 5.000000000000001], 15], [[0, 0, 0], 0]])
|
|
55
|
+
|
|
56
|
+
// test caching
|
|
57
|
+
allbounds = measureBoundingSphere(aline, arect, aellipsoid, o)
|
|
58
|
+
t.deepEqual(allbounds, [[[12.5, 12.5, 0], 3.5355339059327378], [[0, 0, 0], 1.4142135623730951], [[5.000000000000018, 4.999999999999983, 5.000000000000001], 15], [[0, 0, 0], 0]])
|
|
59
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const flatten = require('../utils/flatten')
|
|
2
|
+
|
|
3
|
+
const measureBoundingBox = require('./measureBoundingBox')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Measure the center of the given geometries.
|
|
7
|
+
* @param {...Object} geometries - the geometries to measure
|
|
8
|
+
* @return {Array} the center point for each geometry, i.e. [X, Y, Z]
|
|
9
|
+
* @alias module:modeling/measurements.measureCenter
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* let center = measureCenter(sphere())
|
|
13
|
+
*/
|
|
14
|
+
const measureCenter = (...geometries) => {
|
|
15
|
+
geometries = flatten(geometries)
|
|
16
|
+
|
|
17
|
+
const results = geometries.map((geometry) => {
|
|
18
|
+
const bounds = measureBoundingBox(geometry)
|
|
19
|
+
return [
|
|
20
|
+
(bounds[0][0] + ((bounds[1][0] - bounds[0][0]) / 2)),
|
|
21
|
+
(bounds[0][1] + ((bounds[1][1] - bounds[0][1]) / 2)),
|
|
22
|
+
(bounds[0][2] + ((bounds[1][2] - bounds[0][2]) / 2))
|
|
23
|
+
]
|
|
24
|
+
})
|
|
25
|
+
return results.length === 1 ? results[0] : results
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = measureCenter
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { geom2, geom3, path2 } = require('../geometries')
|
|
4
|
+
|
|
5
|
+
const { line, rectangle, cuboid } = require('../primitives')
|
|
6
|
+
|
|
7
|
+
const { measureCenter } = require('./index')
|
|
8
|
+
|
|
9
|
+
test('measureCenter (single objects)', (t) => {
|
|
10
|
+
const aline = line([[10, 10], [15, 15]])
|
|
11
|
+
const arect = rectangle({ center: [5, 5] })
|
|
12
|
+
const acube = cuboid({ center: [-5, -5, -5] })
|
|
13
|
+
|
|
14
|
+
const apath2 = path2.create()
|
|
15
|
+
const ageom2 = geom2.create()
|
|
16
|
+
const ageom3 = geom3.create()
|
|
17
|
+
|
|
18
|
+
const n = null
|
|
19
|
+
const o = {}
|
|
20
|
+
const x = 'hi'
|
|
21
|
+
|
|
22
|
+
const lcenter = measureCenter(aline)
|
|
23
|
+
const rcenter = measureCenter(arect)
|
|
24
|
+
const ccenter = measureCenter(acube)
|
|
25
|
+
|
|
26
|
+
const p2center = measureCenter(apath2)
|
|
27
|
+
const g2center = measureCenter(ageom2)
|
|
28
|
+
const g3center = measureCenter(ageom3)
|
|
29
|
+
|
|
30
|
+
const ncenter = measureCenter(n)
|
|
31
|
+
const ocenter = measureCenter(o)
|
|
32
|
+
const xcenter = measureCenter(x)
|
|
33
|
+
|
|
34
|
+
t.deepEqual(lcenter, [12.5, 12.5, 0])
|
|
35
|
+
t.deepEqual(rcenter, [5, 5, 0])
|
|
36
|
+
t.deepEqual(ccenter, [-5, -5, -5])
|
|
37
|
+
|
|
38
|
+
t.deepEqual(p2center, [0, 0, 0])
|
|
39
|
+
t.deepEqual(g2center, [0, 0, 0])
|
|
40
|
+
t.deepEqual(g3center, [0, 0, 0])
|
|
41
|
+
|
|
42
|
+
t.deepEqual(ncenter, [0, 0, 0])
|
|
43
|
+
t.deepEqual(ocenter, [0, 0, 0])
|
|
44
|
+
t.deepEqual(xcenter, [0, 0, 0])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('measureCenter (multiple objects)', (t) => {
|
|
48
|
+
const aline = line([[10, 10], [15, 15]])
|
|
49
|
+
const arect = rectangle({ size: [10, 20] })
|
|
50
|
+
const acube = cuboid({ center: [-5, -5, -5] })
|
|
51
|
+
const o = {}
|
|
52
|
+
|
|
53
|
+
let allcenters = measureCenter(aline, arect, acube, o)
|
|
54
|
+
t.deepEqual(allcenters, [[12.5, 12.5, 0], [0, 0, 0], [-5, -5, -5], [0, 0, 0]])
|
|
55
|
+
|
|
56
|
+
allcenters = measureCenter(aline, arect, acube, o)
|
|
57
|
+
t.deepEqual(allcenters, [[12.5, 12.5, 0], [0, 0, 0], [-5, -5, -5], [0, 0, 0]])
|
|
58
|
+
})
|