@jscad/modeling 2.5.3 → 2.7.1
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 +341 -323
- package/package.json +2 -2
- package/src/colors/colorize.js +4 -5
- package/src/colors/colorize.test.js +19 -12
- package/src/curves/bezier/create.js +3 -8
- package/src/curves/index.js +1 -1
- 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 +1 -3
- 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/line3/create.js +2 -1
- package/src/maths/mat4/index.js +1 -0
- package/src/maths/mat4/isOnlyTransformScale.js +22 -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/measurements/index.js +4 -1
- 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.js +2 -0
- package/src/operations/expansions/expand.test.js +1 -1
- package/src/operations/expansions/offset.js +1 -0
- package/src/operations/extrusions/extrudeRectangular.js +2 -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/ellipse.js +1 -1
- 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/utils/insertSorted.js +1 -0
|
@@ -10,17 +10,39 @@ const vec3 = require('../vec3')
|
|
|
10
10
|
* @returns {plane} out
|
|
11
11
|
* @alias module:modeling/maths/plane.fromPoints
|
|
12
12
|
*/
|
|
13
|
-
const fromPoints = (out,
|
|
14
|
-
const
|
|
15
|
-
const ca = vec3.subtract(vec3.create(), c, a)
|
|
16
|
-
vec3.cross(ba, ba, ca)
|
|
17
|
-
vec3.normalize(ba, ba) // normal part
|
|
18
|
-
const w = vec3.dot(ba, a)
|
|
13
|
+
const fromPoints = (out, ...vertices) => {
|
|
14
|
+
const len = vertices.length
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
// Calculate normal vector for a single vertex
|
|
17
|
+
// Inline to avoid allocations
|
|
18
|
+
const ba = vec3.create()
|
|
19
|
+
const ca = vec3.create()
|
|
20
|
+
const vertexNormal = (index) => {
|
|
21
|
+
const a = vertices[index]
|
|
22
|
+
const b = vertices[(index + 1) % len]
|
|
23
|
+
const c = vertices[(index + 2) % len]
|
|
24
|
+
vec3.subtract(ba, b, a) // ba = b - a
|
|
25
|
+
vec3.subtract(ca, c, a) // ca = c - a
|
|
26
|
+
vec3.cross(ba, ba, ca) // ba = ba x ca
|
|
27
|
+
vec3.normalize(ba, ba)
|
|
28
|
+
return ba
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
out[0] = 0
|
|
32
|
+
out[1] = 0
|
|
33
|
+
out[2] = 0
|
|
34
|
+
if (len === 3) {
|
|
35
|
+
// optimization for triangles, which are always coplanar
|
|
36
|
+
vec3.copy(out, vertexNormal(0))
|
|
37
|
+
} else {
|
|
38
|
+
// sum of vertex normals
|
|
39
|
+
vertices.forEach((v, i) => {
|
|
40
|
+
vec3.add(out, out, vertexNormal(i))
|
|
41
|
+
})
|
|
42
|
+
// renormalize normal vector
|
|
43
|
+
vec3.normalize(out, out)
|
|
44
|
+
}
|
|
45
|
+
out[3] = vec3.dot(out, vertices[0])
|
|
24
46
|
return out
|
|
25
47
|
}
|
|
26
48
|
|
|
@@ -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
|
/**
|
|
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
|
}
|
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const flatten = require('../utils/flatten')
|
|
2
|
+
|
|
3
|
+
const vec3 = require('../maths/vec3')
|
|
4
|
+
|
|
5
|
+
const geom2 = require('../geometries/geom2')
|
|
6
|
+
const geom3 = require('../geometries/geom3')
|
|
7
|
+
|
|
8
|
+
const cacheOfCenterOfMass = new WeakMap()
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* Measure the center of mass for the given geometry.
|
|
12
|
+
*
|
|
13
|
+
* @see http://paulbourke.net/geometry/polygonmesh/
|
|
14
|
+
* @return {Array} the center of mass for the geometry
|
|
15
|
+
*/
|
|
16
|
+
const measureCenterOfMassGeom2 = (geometry) => {
|
|
17
|
+
let centerOfMass = cacheOfCenterOfMass.get(geometry)
|
|
18
|
+
if (centerOfMass !== undefined) return centerOfMass
|
|
19
|
+
|
|
20
|
+
const sides = geom2.toSides(geometry)
|
|
21
|
+
|
|
22
|
+
let area = 0
|
|
23
|
+
let x = 0
|
|
24
|
+
let y = 0
|
|
25
|
+
if (sides.length > 0) {
|
|
26
|
+
for (let i = 0; i < sides.length; i++) {
|
|
27
|
+
const p1 = sides[i][0]
|
|
28
|
+
const p2 = sides[i][1]
|
|
29
|
+
|
|
30
|
+
const a = p1[0] * p2[1] - p1[1] * p2[0]
|
|
31
|
+
area += a
|
|
32
|
+
x += (p1[0] + p2[0]) * a
|
|
33
|
+
y += (p1[1] + p2[1]) * a
|
|
34
|
+
}
|
|
35
|
+
area /= 2
|
|
36
|
+
|
|
37
|
+
const f = 1 / (area * 6)
|
|
38
|
+
x *= f
|
|
39
|
+
y *= f
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
centerOfMass = vec3.fromValues(x, y, 0)
|
|
43
|
+
|
|
44
|
+
cacheOfCenterOfMass.set(geometry, centerOfMass)
|
|
45
|
+
return centerOfMass
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
* Measure the center of mass for the given geometry.
|
|
50
|
+
* @return {Array} the center of mass for the geometry
|
|
51
|
+
*/
|
|
52
|
+
const measureCenterOfMassGeom3 = (geometry) => {
|
|
53
|
+
let centerOfMass = cacheOfCenterOfMass.get(geometry)
|
|
54
|
+
if (centerOfMass !== undefined) return centerOfMass
|
|
55
|
+
|
|
56
|
+
centerOfMass = vec3.create() // 0, 0, 0
|
|
57
|
+
|
|
58
|
+
const polygons = geom3.toPolygons(geometry)
|
|
59
|
+
if (polygons.length === 0) return centerOfMass
|
|
60
|
+
|
|
61
|
+
let totalVolume = 0
|
|
62
|
+
const vector = vec3.create() // for speed
|
|
63
|
+
polygons.forEach((polygon) => {
|
|
64
|
+
// calculate volume and center of each tetrahedon
|
|
65
|
+
const vertices = polygon.vertices
|
|
66
|
+
for (let i = 0; i < vertices.length - 2; i++) {
|
|
67
|
+
vec3.cross(vector, vertices[i + 1], vertices[i + 2])
|
|
68
|
+
const volume = vec3.dot(vertices[0], vector) / 6
|
|
69
|
+
|
|
70
|
+
totalVolume += volume
|
|
71
|
+
|
|
72
|
+
vec3.add(vector, vertices[0], vertices[i + 1])
|
|
73
|
+
vec3.add(vector, vector, vertices[i + 2])
|
|
74
|
+
const weightedCenter = vec3.scale(vector, vector, 1 / 4 * volume)
|
|
75
|
+
|
|
76
|
+
vec3.add(centerOfMass, centerOfMass, weightedCenter)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
vec3.scale(centerOfMass, centerOfMass, 1 / totalVolume)
|
|
80
|
+
|
|
81
|
+
cacheOfCenterOfMass.set(geometry, centerOfMass)
|
|
82
|
+
return centerOfMass
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Measure the center of mass for the given geometries.
|
|
87
|
+
* @param {...Object} geometries - the geometries to measure
|
|
88
|
+
* @return {Array} the center of mass for each geometry, i.e. [X, Y, Z]
|
|
89
|
+
* @alias module:modeling/measurements.measureCenterOfMass
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* let center = measureCenterOfMass(sphere())
|
|
93
|
+
*/
|
|
94
|
+
const measureCenterOfMass = (...geometries) => {
|
|
95
|
+
geometries = flatten(geometries)
|
|
96
|
+
|
|
97
|
+
const results = geometries.map((geometry) => {
|
|
98
|
+
// NOTE: center of mass for geometry path2 is not possible
|
|
99
|
+
if (geom2.isA(geometry)) return measureCenterOfMassGeom2(geometry)
|
|
100
|
+
if (geom3.isA(geometry)) return measureCenterOfMassGeom3(geometry)
|
|
101
|
+
return [0, 0, 0]
|
|
102
|
+
})
|
|
103
|
+
return results.length === 1 ? results[0] : results
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = measureCenterOfMass
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { geom2, geom3, path2 } = require('../geometries')
|
|
4
|
+
|
|
5
|
+
const { ellipsoid, line, rectangle, cuboid } = require('../primitives')
|
|
6
|
+
|
|
7
|
+
const { measureCenterOfMass } = require('./index')
|
|
8
|
+
|
|
9
|
+
test('measureCenterOfMass (single objects)', (t) => {
|
|
10
|
+
const aline = line([[10, 10], [15, 15]])
|
|
11
|
+
const arect = rectangle({ center: [5, 5] })
|
|
12
|
+
const acube = cuboid({ size: [3, 3, 3], center: [-15, -5, -10] })
|
|
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 = measureCenterOfMass(aline)
|
|
23
|
+
const rcenter = measureCenterOfMass(arect)
|
|
24
|
+
const ccenter = measureCenterOfMass(acube)
|
|
25
|
+
|
|
26
|
+
const p2center = measureCenterOfMass(apath2)
|
|
27
|
+
const g2center = measureCenterOfMass(ageom2)
|
|
28
|
+
const g3center = measureCenterOfMass(ageom3)
|
|
29
|
+
|
|
30
|
+
const ncenter = measureCenterOfMass(n)
|
|
31
|
+
const ocenter = measureCenterOfMass(o)
|
|
32
|
+
const xcenter = measureCenterOfMass(x)
|
|
33
|
+
|
|
34
|
+
t.deepEqual(lcenter, [0, 0, 0])
|
|
35
|
+
t.deepEqual(rcenter, [5, 5, 0])
|
|
36
|
+
t.deepEqual(ccenter, [-15, -5, -10])
|
|
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('measureCenterOfMass (multiple objects)', (t) => {
|
|
48
|
+
const aline = line([[10, 10], [15, 15]])
|
|
49
|
+
const arect = rectangle({ size: [10, 20], center: [10, -10] })
|
|
50
|
+
const asphere = ellipsoid({ radius: [5, 10, 15], center: [5, -5, 50] })
|
|
51
|
+
const o = {}
|
|
52
|
+
|
|
53
|
+
let allcenters = measureCenterOfMass(aline, arect, asphere, o)
|
|
54
|
+
t.deepEqual(allcenters, [[0, 0, 0], [10, -10, 0], [4.99999999999999, -5.000000000000007, 49.99999999999992], [0, 0, 0]])
|
|
55
|
+
|
|
56
|
+
allcenters = measureCenterOfMass(aline, arect, asphere, o)
|
|
57
|
+
t.deepEqual(allcenters, [[0, 0, 0], [10, -10, 0], [4.99999999999999, -5.000000000000007, 49.99999999999992], [0, 0, 0]])
|
|
58
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const flatten = require('../utils/flatten')
|
|
2
|
+
|
|
3
|
+
const measureBoundingBox = require('./measureBoundingBox')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Measure the dimensions of the given geometries.
|
|
7
|
+
* @param {...Object} geometries - the geometries to measure
|
|
8
|
+
* @return {Array} the dimensions for each geometry, i.e. [width, depth, height]
|
|
9
|
+
* @alias module:modeling/measurements.measureDimensions
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* let dimensions = measureDimensions(sphere())
|
|
13
|
+
*/
|
|
14
|
+
const measureDimensions = (...geometries) => {
|
|
15
|
+
geometries = flatten(geometries)
|
|
16
|
+
|
|
17
|
+
const results = geometries.map((geometry) => {
|
|
18
|
+
const boundingBox = measureBoundingBox(geometry)
|
|
19
|
+
return [
|
|
20
|
+
boundingBox[1][0] - boundingBox[0][0],
|
|
21
|
+
boundingBox[1][1] - boundingBox[0][1],
|
|
22
|
+
boundingBox[1][2] - boundingBox[0][2]
|
|
23
|
+
]
|
|
24
|
+
})
|
|
25
|
+
return results.length === 1 ? results[0] : results
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = measureDimensions
|
|
@@ -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 { measureDimensions } = require('./index')
|
|
8
|
+
|
|
9
|
+
test('measureDimensions (single objects)', (t) => {
|
|
10
|
+
const aline = line([[10, 10], [15, 15]])
|
|
11
|
+
const arect = rectangle()
|
|
12
|
+
const acube = cuboid()
|
|
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 = measureDimensions(aline)
|
|
23
|
+
const rbounds = measureDimensions(arect)
|
|
24
|
+
const cbounds = measureDimensions(acube)
|
|
25
|
+
|
|
26
|
+
const p2bounds = measureDimensions(apath2)
|
|
27
|
+
const g2bounds = measureDimensions(ageom2)
|
|
28
|
+
const g3bounds = measureDimensions(ageom3)
|
|
29
|
+
|
|
30
|
+
const nbounds = measureDimensions(n)
|
|
31
|
+
const obounds = measureDimensions(o)
|
|
32
|
+
const xbounds = measureDimensions(x)
|
|
33
|
+
|
|
34
|
+
t.deepEqual(lbounds, [5, 5, 0])
|
|
35
|
+
t.deepEqual(rbounds, [2, 2, 0])
|
|
36
|
+
t.deepEqual(cbounds, [2, 2, 2])
|
|
37
|
+
|
|
38
|
+
t.deepEqual(p2bounds, [0, 0, 0])
|
|
39
|
+
t.deepEqual(g2bounds, [0, 0, 0])
|
|
40
|
+
t.deepEqual(g3bounds, [0, 0, 0])
|
|
41
|
+
|
|
42
|
+
t.deepEqual(nbounds, [0, 0, 0])
|
|
43
|
+
t.deepEqual(obounds, [0, 0, 0])
|
|
44
|
+
t.deepEqual(xbounds, [0, 0, 0])
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('measureDimensions (multiple objects)', (t) => {
|
|
48
|
+
const aline = line([[10, 10], [15, 15]])
|
|
49
|
+
const arect = rectangle({ size: [10, 20] })
|
|
50
|
+
const acube = cuboid()
|
|
51
|
+
const o = {}
|
|
52
|
+
|
|
53
|
+
let allbounds = measureDimensions(aline, arect, acube, o)
|
|
54
|
+
t.deepEqual(allbounds, [[5, 5, 0], [10, 20, 0], [2, 2, 2], [0, 0, 0]])
|
|
55
|
+
|
|
56
|
+
allbounds = measureDimensions(aline, arect, acube, o)
|
|
57
|
+
t.deepEqual(allbounds, [[5, 5, 0], [10, 20, 0], [2, 2, 2], [0, 0, 0]])
|
|
58
|
+
})
|
|
@@ -8,25 +8,19 @@ const measureBoundingBox = require('./measureBoundingBox')
|
|
|
8
8
|
* Measure the epsilon of the given (path2) geometry.
|
|
9
9
|
* @return {Number} the epsilon (precision) of the geometry
|
|
10
10
|
*/
|
|
11
|
-
const measureEpsilonOfPath2 = (geometry) =>
|
|
12
|
-
return calculateEpsilonFromBounds(measureBoundingBox(geometry), 2)
|
|
13
|
-
}
|
|
11
|
+
const measureEpsilonOfPath2 = (geometry) => calculateEpsilonFromBounds(measureBoundingBox(geometry), 2)
|
|
14
12
|
|
|
15
13
|
/*
|
|
16
14
|
* Measure the epsilon of the given (geom2) geometry.
|
|
17
15
|
* @return {Number} the epsilon (precision) of the geometry
|
|
18
16
|
*/
|
|
19
|
-
const measureEpsilonOfGeom2 = (geometry) =>
|
|
20
|
-
return calculateEpsilonFromBounds(measureBoundingBox(geometry), 2)
|
|
21
|
-
}
|
|
17
|
+
const measureEpsilonOfGeom2 = (geometry) => calculateEpsilonFromBounds(measureBoundingBox(geometry), 2)
|
|
22
18
|
|
|
23
19
|
/*
|
|
24
20
|
* Measure the epsilon of the given (geom3) geometry.
|
|
25
21
|
* @return {Float} the epsilon (precision) of the geometry
|
|
26
22
|
*/
|
|
27
|
-
const measureEpsilonOfGeom3 = (geometry) =>
|
|
28
|
-
return calculateEpsilonFromBounds(measureBoundingBox(geometry), 3)
|
|
29
|
-
}
|
|
23
|
+
const measureEpsilonOfGeom3 = (geometry) => calculateEpsilonFromBounds(measureBoundingBox(geometry), 3)
|
|
30
24
|
|
|
31
25
|
/**
|
|
32
26
|
* Measure the epsilon of the given geometries.
|
|
@@ -309,7 +309,7 @@ const reTesselateCoplanarPolygons = (sourcepolygons) => {
|
|
|
309
309
|
const polygon = poly3.fromPointsAndPlane(vertices3d, plane) // TODO support shared
|
|
310
310
|
|
|
311
311
|
// if we let empty polygon out, next retesselate will crash
|
|
312
|
-
if(polygon.vertices.length) destpolygons.push(polygon)
|
|
312
|
+
if (polygon.vertices.length) destpolygons.push(polygon)
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
} // if(yindex > 0)
|