@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/jscad-modeling.min.js +341 -323
  3. package/package.json +2 -2
  4. package/src/colors/colorize.js +4 -5
  5. package/src/colors/colorize.test.js +19 -12
  6. package/src/curves/bezier/create.js +3 -8
  7. package/src/curves/index.js +1 -1
  8. package/src/geometries/geom2/applyTransforms.js +1 -1
  9. package/src/geometries/geom2/clone.js +2 -12
  10. package/src/geometries/geom2/transform.js +2 -6
  11. package/src/geometries/geom3/applyTransforms.js +1 -1
  12. package/src/geometries/geom3/clone.js +2 -14
  13. package/src/geometries/geom3/clone.test.js +0 -2
  14. package/src/geometries/geom3/create.js +1 -3
  15. package/src/geometries/geom3/create.test.js +0 -2
  16. package/src/geometries/geom3/fromCompactBinary.js +4 -6
  17. package/src/geometries/geom3/fromToCompactBinary.test.js +0 -6
  18. package/src/geometries/geom3/invert.test.js +0 -2
  19. package/src/geometries/geom3/toCompactBinary.js +8 -10
  20. package/src/geometries/geom3/transform.js +2 -7
  21. package/src/geometries/geom3/transform.test.js +0 -1
  22. package/src/geometries/geom3/type.d.ts +0 -1
  23. package/src/geometries/path2/applyTransforms.js +1 -1
  24. package/src/geometries/path2/clone.js +2 -13
  25. package/src/geometries/path2/transform.js +2 -7
  26. package/src/geometries/poly3/isConvex.js +1 -1
  27. package/src/geometries/poly3/measureArea.js +12 -13
  28. package/src/geometries/poly3/measureArea.test.js +15 -0
  29. package/src/geometries/poly3/plane.js +1 -2
  30. package/src/maths/line3/create.js +2 -1
  31. package/src/maths/mat4/index.js +1 -0
  32. package/src/maths/mat4/isOnlyTransformScale.js +22 -0
  33. package/src/maths/mat4/isOnlyTransformScale.test.js +27 -0
  34. package/src/maths/plane/fromPoints.js +32 -10
  35. package/src/maths/plane/fromPoints.test.js +4 -0
  36. package/src/measurements/index.js +4 -1
  37. package/src/measurements/measureBoundingBox.test.js +8 -0
  38. package/src/measurements/measureBoundingSphere.js +146 -0
  39. package/src/measurements/measureBoundingSphere.test.js +59 -0
  40. package/src/measurements/measureCenter.js +28 -0
  41. package/src/measurements/measureCenter.test.js +58 -0
  42. package/src/measurements/measureCenterOfMass.js +106 -0
  43. package/src/measurements/measureCenterOfMass.test.js +58 -0
  44. package/src/measurements/measureDimensions.js +28 -0
  45. package/src/measurements/measureDimensions.test.js +58 -0
  46. package/src/measurements/measureEpsilon.js +3 -9
  47. package/src/operations/booleans/reTesselateCoplanarPolygons.js +1 -1
  48. package/src/operations/booleans/trees/PolygonTreeNode.js +0 -1
  49. package/src/operations/expansions/expand.js +2 -0
  50. package/src/operations/expansions/expand.test.js +1 -1
  51. package/src/operations/expansions/offset.js +1 -0
  52. package/src/operations/extrusions/extrudeRectangular.js +2 -1
  53. package/src/operations/extrusions/extrudeRotate.test.js +18 -10
  54. package/src/operations/modifiers/generalize.js +0 -1
  55. package/src/operations/modifiers/snapPolygons.js +2 -2
  56. package/src/operations/modifiers/snapPolygons.test.js +13 -5
  57. package/src/primitives/ellipse.js +1 -1
  58. package/src/primitives/index.d.ts +1 -0
  59. package/src/primitives/index.js +2 -1
  60. package/src/primitives/triangle.d.ts +10 -0
  61. package/src/primitives/triangle.js +164 -0
  62. package/src/primitives/triangle.test.js +95 -0
  63. 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, a, b, c) => {
14
- const ba = vec3.subtract(vec3.create(), b, a)
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
- out[0] = ba[0]
21
- out[1] = ba[1]
22
- out[2] = ba[2]
23
- out[3] = w
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)