@jscad/modeling 2.6.0 → 2.7.2

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 (73) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/jscad-modeling.min.js +97 -94
  3. package/package.json +2 -2
  4. package/src/colors/colorize.js +35 -1
  5. package/src/colors/colorize.test.js +19 -4
  6. package/src/colors/hslToRgb.js +1 -1
  7. package/src/colors/hueToColorComponent.js +1 -0
  8. package/src/curves/bezier/create.js +3 -8
  9. package/src/curves/bezier/tangentAt.js +2 -2
  10. package/src/curves/index.js +1 -1
  11. package/src/geometries/geom2/clone.js +2 -12
  12. package/src/geometries/geom2/toOutlines.js +6 -11
  13. package/src/geometries/geom2/transform.js +0 -2
  14. package/src/geometries/geom3/clone.js +2 -14
  15. package/src/geometries/geom3/clone.test.js +0 -2
  16. package/src/geometries/geom3/create.js +1 -3
  17. package/src/geometries/geom3/create.test.js +0 -2
  18. package/src/geometries/geom3/fromCompactBinary.js +4 -6
  19. package/src/geometries/geom3/fromToCompactBinary.test.js +0 -6
  20. package/src/geometries/geom3/invert.js +2 -2
  21. package/src/geometries/geom3/invert.test.js +0 -2
  22. package/src/geometries/geom3/toCompactBinary.js +8 -10
  23. package/src/geometries/geom3/toPoints.js +1 -0
  24. package/src/geometries/geom3/transform.js +0 -2
  25. package/src/geometries/geom3/transform.test.js +0 -1
  26. package/src/geometries/geom3/type.d.ts +0 -1
  27. package/src/geometries/path2/clone.js +2 -13
  28. package/src/geometries/path2/transform.js +0 -2
  29. package/src/geometries/poly3/isConvex.js +1 -1
  30. package/src/geometries/poly3/measureArea.js +12 -13
  31. package/src/geometries/poly3/measureArea.test.js +15 -0
  32. package/src/geometries/poly3/plane.js +1 -2
  33. package/src/maths/line3/create.js +2 -1
  34. package/src/maths/mat4/fromRotation.js +1 -1
  35. package/src/maths/mat4/isIdentity.test.js +0 -2
  36. package/src/maths/mat4/isOnlyTransformScale.js +5 -4
  37. package/src/maths/mat4/rotate.js +1 -1
  38. package/src/maths/plane/fromPoints.js +32 -10
  39. package/src/maths/plane/fromPoints.test.js +4 -0
  40. package/src/maths/vec2/length.test.js +10 -0
  41. package/src/maths/vec3/angle.js +2 -2
  42. package/src/maths/vec3/angle.test.js +17 -0
  43. package/src/maths/vec3/length.test.js +10 -0
  44. package/src/measurements/measureBoundingBox.js +37 -121
  45. package/src/measurements/measureBoundingBox.test.js +8 -0
  46. package/src/measurements/measureCenterOfMass.js +0 -1
  47. package/src/measurements/measureEpsilon.js +3 -9
  48. package/src/operations/booleans/reTesselateCoplanarPolygons.js +1 -1
  49. package/src/operations/booleans/retessellate.js +1 -3
  50. package/src/operations/booleans/trees/PolygonTreeNode.js +0 -1
  51. package/src/operations/expansions/expand.js +2 -0
  52. package/src/operations/expansions/expand.test.js +1 -1
  53. package/src/operations/expansions/offset.js +1 -0
  54. package/src/operations/extrusions/extrudeLinear.js +1 -1
  55. package/src/operations/extrusions/extrudeRectangular.js +2 -1
  56. package/src/operations/extrusions/extrudeRotate.test.js +18 -10
  57. package/src/operations/hulls/quickhull/QuickHull.js +2 -2
  58. package/src/operations/modifiers/edges.js +1 -3
  59. package/src/operations/modifiers/generalize.js +3 -7
  60. package/src/operations/modifiers/snapPolygons.js +2 -2
  61. package/src/operations/modifiers/snapPolygons.test.js +13 -5
  62. package/src/operations/transforms/mirror.js +1 -1
  63. package/src/primitives/ellipse.js +1 -1
  64. package/src/primitives/geodesicSphere.js +2 -2
  65. package/src/primitives/index.d.ts +1 -0
  66. package/src/primitives/index.js +2 -1
  67. package/src/primitives/roundedCylinder.js +1 -1
  68. package/src/primitives/triangle.d.ts +10 -0
  69. package/src/primitives/triangle.js +164 -0
  70. package/src/primitives/triangle.test.js +95 -0
  71. package/src/utils/insertSorted.js +1 -0
  72. package/test/helpers/comparePolygons.js +1 -3
  73. package/test/helpers/nearlyEqual.js +2 -6
@@ -1,10 +1,11 @@
1
1
 
2
2
  /**
3
- * Determine whether the given matrix is translate+scale.
4
- * this code returns true for 180 rotation as it can be interpreted as scale (-1,-1)
5
- *
6
- * this method is primarily useful for measureBoundingBox
3
+ * Determine whether the given matrix is only translate and/or scale.
4
+ * This code returns true for PI rotation as it can be interpreted as scale.
7
5
  *
6
+ * @param {mat4} matrix - the matrix
7
+ * @returns {Boolean} true if matrix is for translate and/or scale
8
+ * @alias module:modeling/maths/mat4.isOnlyTransformScale
8
9
  */
9
10
  const isOnlyTransformScale = (matrix) => (
10
11
 
@@ -12,7 +12,7 @@ const copy = require('./copy')
12
12
  */
13
13
  const rotate = (out, matrix, radians, axis) => {
14
14
  let [x, y, z] = axis
15
- let len = Math.sqrt(x * x + y * y + z * z)
15
+ let len = Math.hypot(x, y, z)
16
16
 
17
17
  if (Math.abs(len) < 0.000001) {
18
18
  // axis is 0,0,0 or almost
@@ -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
  })
@@ -25,5 +25,15 @@ test('vec2: length() should return correct values', (t) => {
25
25
  const length5 = length(vec5)
26
26
  nearlyEqual(t, length5, 2.23606, EPS)
27
27
 
28
+ // huge vector
29
+ const vec6 = fromValues(1e200, 1e200)
30
+ const length6 = length(vec6)
31
+ nearlyEqual(t, length6, Math.SQRT2 * 1e200, EPS)
32
+
33
+ // tiny vector
34
+ const vec7 = fromValues(1e-200, 1e-200)
35
+ const length7 = length(vec7)
36
+ nearlyEqual(t, length7, Math.SQRT2 * 1e-200, EPS)
37
+
28
38
  t.true(true)
29
39
  })
@@ -15,8 +15,8 @@ const angle = (a, b) => {
15
15
  const bx = b[0]
16
16
  const by = b[1]
17
17
  const bz = b[2]
18
- const mag1 = Math.sqrt(ax * ax + ay * ay + az * az)
19
- const mag2 = Math.sqrt(bx * bx + by * by + bz * bz)
18
+ const mag1 = Math.hypot(ax, ay, az)
19
+ const mag2 = Math.hypot(bx, by, bz)
20
20
  const mag = mag1 * mag2
21
21
  const cosine = mag && dot(a, b) / mag
22
22
  return Math.acos(Math.min(Math.max(cosine, -1), 1))
@@ -25,5 +25,22 @@ test('vec3: angle() should return correct values', (t) => {
25
25
  const angle4 = angle(veca4, vec4)
26
26
  nearlyEqual(t, angle4, 3.14159, EPS)
27
27
 
28
+ const vec5a = fromValues(1, 0, 0)
29
+ const vec5b = fromValues(1, 1, 0)
30
+ const angle5 = angle(vec5a, vec5b)
31
+ nearlyEqual(t, angle5, 0.785398, EPS)
32
+
33
+ // tiny values
34
+ const vec6a = fromValues(1, 0, 0)
35
+ const vec6b = fromValues(1e-200, 1e-200, 0)
36
+ const angle6 = angle(vec6a, vec6b)
37
+ nearlyEqual(t, angle6, 0.785398, EPS)
38
+
39
+ // huge values
40
+ const vec7a = fromValues(1, 0, 0)
41
+ const vec7b = fromValues(1e200, 1e200, 0)
42
+ const angle7 = angle(vec7a, vec7b)
43
+ nearlyEqual(t, angle7, 0.785398, EPS)
44
+
28
45
  t.true(true)
29
46
  })
@@ -41,5 +41,15 @@ test('vec3: length() should return correct values', (t) => {
41
41
  const length9 = length(vec9)
42
42
  nearlyEqual(t, length9, 3.74165, EPS)
43
43
 
44
+ // huge vector
45
+ const vec10 = fromValues(1e200, 0, 1e200)
46
+ const length10 = length(vec10)
47
+ nearlyEqual(t, length10, Math.SQRT2 * 1e200, EPS)
48
+
49
+ // tiny vector
50
+ const vec11 = fromValues(1e-200, 0, 1e-200)
51
+ const length11 = length(vec11)
52
+ nearlyEqual(t, length11, Math.SQRT2 * 1e-200, EPS)
53
+
44
54
  t.true(true)
45
55
  })
@@ -2,7 +2,6 @@ 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')
6
5
 
7
6
  const geom2 = require('../geometries/geom2')
8
7
  const geom3 = require('../geometries/geom3')
@@ -12,133 +11,86 @@ const poly3 = require('../geometries/poly3')
12
11
  const cache = new WeakMap()
13
12
 
14
13
  /*
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
14
+ * Measure the min and max bounds of the given (path2) geometry.
15
+ * @return {Array[]} the min and max bounds for the geometry
17
16
  */
18
- const measureBoundingBoxOfPath2Points = (points) => {
19
- let boundingBox = cache.get(points)
17
+ const measureBoundingBoxOfPath2 = (geometry) => {
18
+ let boundingBox = cache.get(geometry)
20
19
  if (boundingBox) return boundingBox
21
20
 
21
+ const points = path2.toPoints(geometry)
22
+
22
23
  let minpoint
23
24
  if (points.length === 0) {
24
25
  minpoint = vec2.create()
25
26
  } else {
26
27
  minpoint = vec2.clone(points[0])
27
28
  }
28
- const maxpoint = vec2.clone(minpoint)
29
+ let maxpoint = vec2.clone(minpoint)
29
30
 
30
31
  points.forEach((point) => {
31
32
  vec2.min(minpoint, minpoint, point)
32
33
  vec2.max(maxpoint, maxpoint, point)
33
34
  })
35
+ minpoint = [minpoint[0], minpoint[1], 0]
36
+ maxpoint = [maxpoint[0], maxpoint[1], 0]
34
37
 
35
- boundingBox = [[minpoint[0], minpoint[1], 0], [maxpoint[0], maxpoint[1], 0]]
36
- cache.set(points, boundingBox)
38
+ boundingBox = [minpoint, maxpoint]
39
+
40
+ cache.set(geometry, boundingBox)
37
41
 
38
42
  return boundingBox
39
43
  }
40
44
 
41
45
  /*
42
- * Measure the min and max bounds of the given (path2) geometry.
46
+ * Measure the min and max bounds of the given (geom2) geometry.
43
47
  * @return {Array[]} the min and max bounds for the geometry
44
48
  */
45
- const measureBoundingBoxOfPath2 = (geometry) => {
49
+ const measureBoundingBoxOfGeom2 = (geometry) => {
46
50
  let boundingBox = cache.get(geometry)
47
51
  if (boundingBox) return boundingBox
48
52
 
49
- if (mat4.isOnlyTransformScale(geometry.transforms)) {
50
- // get boundingBox of original points and transform it
51
- boundingBox = transformBoundingBox(measureBoundingBoxOfPath2Points(geometry.points), geometry.transforms)
53
+ const points = geom2.toPoints(geometry)
54
+
55
+ let minpoint
56
+ if (points.length === 0) {
57
+ minpoint = vec2.create()
52
58
  } else {
53
- // transform the points and then caclulate the boundingBox
54
- boundingBox = measureBoundingBoxOfPath2Points(path2.toPoints(geometry))
59
+ minpoint = vec2.clone(points[0])
55
60
  }
61
+ let maxpoint = vec2.clone(minpoint)
56
62
 
57
- cache.set(geometry, boundingBox)
58
-
59
- return boundingBox
60
- }
61
-
62
- /*
63
- * Measure the min and max bounds of the given (geom2) geometry.points/sides.
64
- * @return {Array[]} the min and max bounds for the geometr.points/sidesy
65
- */
66
- const measureBoundingBoxOfGeom2Points = ({ points, sides }) => {
67
- const cacheKey = points || sides
68
- let boundingBox = cache.get(cacheKey)
69
- if (boundingBox) return boundingBox
70
-
71
- let minpoint, maxpoint
72
-
73
- if (points) {
74
- if (points.length === 0) {
75
- minpoint = vec2.create()
76
- } else {
77
- minpoint = vec2.clone(points[0])
78
- }
79
- maxpoint = vec2.clone(minpoint)
63
+ points.forEach((point) => {
64
+ vec2.min(minpoint, minpoint, point)
65
+ vec2.max(maxpoint, maxpoint, point)
66
+ })
80
67
 
81
- points.forEach((point) => {
82
- vec2.min(minpoint, minpoint, point)
83
- vec2.max(maxpoint, maxpoint, point)
84
- })
85
- } else { // sides
86
- // to avoid calling costly toPoints, we take advantage of the knowlege how the toPoints works
87
- if (sides.length === 0) {
88
- minpoint = vec2.create()
89
- } else {
90
- minpoint = vec2.clone(sides[0][0])
91
- }
92
- maxpoint = vec2.clone(minpoint)
93
-
94
- sides.forEach((side) => {
95
- vec2.min(minpoint, minpoint, side[0])
96
- vec2.max(maxpoint, maxpoint, side[0])
97
- })
98
- }
68
+ minpoint = [minpoint[0], minpoint[1], 0]
69
+ maxpoint = [maxpoint[0], maxpoint[1], 0]
99
70
 
100
- boundingBox = [[minpoint[0], minpoint[1], 0], [maxpoint[0], maxpoint[1], 0]]
71
+ boundingBox = [minpoint, maxpoint]
101
72
 
102
- cache.set(cacheKey, boundingBox)
73
+ cache.set(geometry, boundingBox)
103
74
 
104
75
  return boundingBox
105
76
  }
106
77
 
107
78
  /*
108
- * Measure the min and max bounds of the given (geom2) geometry.
79
+ * Measure the min and max bounds of the given (geom3) geometry.
109
80
  * @return {Array[]} the min and max bounds for the geometry
110
81
  */
111
- const measureBoundingBoxOfGeom2 = (geometry) => {
82
+ const measureBoundingBoxOfGeom3 = (geometry) => {
112
83
  let boundingBox = cache.get(geometry)
113
84
  if (boundingBox) return boundingBox
114
85
 
115
- if (mat4.isOnlyTransformScale(geometry.transforms)) {
116
- // get boundingBox of original points and transform it
117
- boundingBox = transformBoundingBox(measureBoundingBoxOfGeom2Points(geometry), geometry.transforms)
118
- } else {
119
- // transform the points and then caclulate the boundingBox
120
- boundingBox = measureBoundingBoxOfGeom2Points({ points: geom2.toPoints(geometry) })
121
- }
122
-
123
- cache.set(geometry, boundingBox)
124
-
125
- return boundingBox
126
- }
127
-
128
- /*
129
- * Measure the min and max bounds of the given (geom3) geometry.polygons.
130
- * @return {Array[]} the min and max bounds for the geometry.polygons
131
- */
132
- const measureBoundingBoxOfGeom3Polygons = (polygons) => {
133
- let boundingBox = cache.get(polygons)
134
- if (boundingBox) return boundingBox
86
+ const polygons = geom3.toPolygons(geometry)
135
87
 
136
- const minpoint = vec3.create()
88
+ let minpoint = vec3.create()
137
89
  if (polygons.length > 0) {
138
90
  const points = poly3.toPoints(polygons[0])
139
91
  vec3.copy(minpoint, points[0])
140
92
  }
141
- const maxpoint = vec3.clone(minpoint)
93
+ let maxpoint = vec3.clone(minpoint)
142
94
 
143
95
  polygons.forEach((polygon) => {
144
96
  poly3.toPoints(polygon).forEach((point) => {
@@ -147,41 +99,16 @@ const measureBoundingBoxOfGeom3Polygons = (polygons) => {
147
99
  })
148
100
  })
149
101
 
150
- boundingBox = [[minpoint[0], minpoint[1], minpoint[2]], [maxpoint[0], maxpoint[1], maxpoint[2]]]
102
+ minpoint = [minpoint[0], minpoint[1], minpoint[2]]
103
+ maxpoint = [maxpoint[0], maxpoint[1], maxpoint[2]]
151
104
 
152
- cache.set(polygons, boundingBox)
153
-
154
- return boundingBox
155
- }
156
-
157
- /*
158
- * Measure the min and max bounds of the given (geom3) geometry.
159
- * @return {Array[]} the min and max bounds for the geometry
160
- */
161
- const measureBoundingBoxOfGeom3 = (geometry) => {
162
- let boundingBox = cache.get(geometry)
163
- if (boundingBox) return boundingBox
164
-
165
- if (mat4.isOnlyTransformScale(geometry.transforms)) {
166
- // get boundingBox of original points and transform it
167
- boundingBox = transformBoundingBox(measureBoundingBoxOfGeom3Polygons(geometry.polygons), geometry.transforms)
168
- } else {
169
- // transform the points and then caclulate the boundingBox
170
- boundingBox = measureBoundingBoxOfGeom3Polygons(geom3.toPolygons(geometry))
171
- }
105
+ boundingBox = [minpoint, maxpoint]
172
106
 
173
107
  cache.set(geometry, boundingBox)
174
108
 
175
109
  return boundingBox
176
110
  }
177
111
 
178
- const transformBoundingBox = (boundingBox, transforms) => {
179
- if (transforms && !mat4.isIdentity(transforms)) {
180
- return [vec3.transform(vec3.create(), boundingBox[0], transforms), vec3.transform(vec3.create(), boundingBox[1], transforms)]
181
- }
182
- return boundingBox
183
- }
184
-
185
112
  /**
186
113
  * Measure the min and max bounds of the given geometries.
187
114
  * @param {...Object} geometries - the geometries to measure
@@ -204,15 +131,4 @@ const measureBoundingBox = (...geometries) => {
204
131
  return results.length === 1 ? results[0] : results
205
132
  }
206
133
 
207
- /**
208
- * Shortcut for geometries that are complex but have a fast way to calculate bounding box.
209
- * Ellipsoid, or cylinder can provide boundingBox that pre-calculated based on parameters without traversing points.
210
- *
211
- * Another option is to calculate boundingBox durint toPoints (so the boundingBox could be calculated during transform)
212
- *
213
- * NOTE: It seems that measureBoundingBox is used all over the place, and it would be wise to allow
214
- * shortcuts for calculating it, as default implementation goes through all points in geometry which is bound to be slow.
215
- */
216
- measureBoundingBox.setCache = (geometry, boundingBox) => cache.set(geometry, boundingBox)
217
-
218
134
  module.exports = measureBoundingBox
@@ -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
+ })
@@ -4,7 +4,6 @@ const vec3 = require('../maths/vec3')
4
4
 
5
5
  const geom2 = require('../geometries/geom2')
6
6
  const geom3 = require('../geometries/geom3')
7
- const path2 = require('../geometries/path2')
8
7
 
9
8
  const cacheOfCenterOfMass = new WeakMap()
10
9
 
@@ -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)
@@ -10,9 +10,7 @@ const reTesselateCoplanarPolygons = require('./reTesselateCoplanarPolygons')
10
10
  const NEPS = 1e-13
11
11
 
12
12
  // Compare two normals (unit vectors) for equality.
13
- const aboutEqualNormals = (a, b) => {
14
- return (Math.abs(a[0] - b[0]) <= NEPS && Math.abs(a[1] - b[1]) <= NEPS && Math.abs(a[2] - b[2]) <= NEPS)
15
- }
13
+ const aboutEqualNormals = (a, b) => (Math.abs(a[0] - b[0]) <= NEPS && Math.abs(a[1] - b[1]) <= NEPS && Math.abs(a[2] - b[2]) <= NEPS)
16
14
 
17
15
  const coplanar = (plane1, plane2) => {
18
16
  // expect the same distance from the origin, within tolerance
@@ -20,7 +20,6 @@ const splitPolygonByPlane = require('./splitPolygonByPlane')
20
20
  // remove() removes a polygon from the tree. Once a polygon is removed, the parent polygons are invalidated
21
21
  // since they are no longer intact.
22
22
  class PolygonTreeNode {
23
-
24
23
  // constructor creates the root node
25
24
  constructor () {
26
25
  this.parent = null
@@ -10,6 +10,8 @@ const expandPath2 = require('./expandPath2')
10
10
 
11
11
  /**
12
12
  * Expand the given geometry using the given options.
13
+ * Both interal and external space is expanded for 2D and 3D shapes.
14
+ *
13
15
  * Note: Contract is expand using a negative delta.
14
16
  * @param {Object} options - options for expand
15
17
  * @param {Number} [options.delta=1] - delta (+/-) of expansion
@@ -120,7 +120,7 @@ test('expand: expanding of a geom3 produces expected changes to polygons', (t) =
120
120
  const geometry2 = sphere({ radius: 5, segments: 8 })
121
121
  const obs2 = expand({ delta: 5 }, geometry2)
122
122
  const pts2 = geom3.toPoints(obs2)
123
- t.is(pts2.length, 2059)
123
+ t.is(pts2.length, 2065)
124
124
  })
125
125
 
126
126
  test('expand (options): offsetting of a complex geom2 produces expected offset geom2', (t) => {
@@ -8,6 +8,7 @@ const offsetPath2 = require('./offsetPath2')
8
8
 
9
9
  /**
10
10
  * Create offset geometry from the given geometry using the given options.
11
+ * Offsets from internal and external space are created.
11
12
  * @param {Object} options - options for offset
12
13
  * @param {Float} [options.delta=1] - delta of offset (+ to exterior, - from interior)
13
14
  * @param {String} [options.corners='edge'] - type of corner to create after offseting; edge, chamfer, round
@@ -7,7 +7,7 @@ const extrudeLinearGeom2 = require('./extrudeLinearGeom2')
7
7
  /**
8
8
  * Extrude the given geometry in an upward linear direction using the given options.
9
9
  * @param {Object} options - options for extrude
10
- * @param {Array} [options.height=1] the height of the extrusion
10
+ * @param {Number} [options.height=1] the height of the extrusion
11
11
  * @param {Number} [options.twistAngle=0] the final rotation (RADIANS) about the origin of the shape (if any)
12
12
  * @param {Integer} [options.twistSteps=1] the resolution of the twist about the axis (if any)
13
13
  * @param {...Object} geometry - the list of geometry to extrude
@@ -17,7 +17,8 @@ const extrudeRectangularGeom2 = require('./extrudeRectangularGeom2')
17
17
  * @alias module:modeling/extrusions.extrudeRectangular
18
18
  *
19
19
  * @example
20
- * let mywalls = extrudeRectangular({offset: [0,0,10]}, square())
20
+ * let mywalls = extrudeRectangular({size: 1, height: 3}, square({size: 20}))
21
+ * let mywalls = extrudeRectangular({size: 1, height: 300, twistAngle: Math.PI}, square({size: 20}))
21
22
  */
22
23
  const extrudeRectangular = (options, ...objects) => {
23
24
  const defaults = {
@@ -107,10 +107,14 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
107
107
  [[7, -4.898587196589413e-16, -8], [4.898587196589413e-16, -2.999519565323715e-32, -8], [9.184850993605148e-16, 7, -8]],
108
108
  [[7, 4.898587196589413e-16, 8], [7, -4.898587196589413e-16, -8], [9.184850993605148e-16, 7, -8]],
109
109
  [[7, 4.898587196589413e-16, 8], [9.184850993605148e-16, 7, -8], [-6.123233995736767e-17, 7, 8]],
110
- [[-4.898587196589414e-16, 0, 8], [-6.123233995736777e-17, 6.999999999999999, 8],
111
- [9.18485099360515e-16, 7.000000000000001, -8], [4.898587196589413e-16, 0, -8]],
112
- [[7, 4.898587196589413e-16, 8], [0, 4.898587196589413e-16, 8],
113
- [0, -4.898587196589413e-16, -8], [7, -4.898587196589413e-16, -8]]
110
+ [
111
+ [-4.898587196589414e-16, 0, 8], [-6.123233995736777e-17, 6.999999999999999, 8],
112
+ [9.18485099360515e-16, 7.000000000000001, -8], [4.898587196589413e-16, 0, -8]
113
+ ],
114
+ [
115
+ [7, 4.898587196589413e-16, 8], [0, 4.898587196589413e-16, 8],
116
+ [0, -4.898587196589413e-16, -8], [7, -4.898587196589413e-16, -8]
117
+ ]
114
118
  ]
115
119
  t.is(pts.length, 6)
116
120
  t.true(comparePolygonsAsPoints(pts, exp))
@@ -133,12 +137,16 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
133
137
  [[0.7071067811865472, 0.7071067811865478, 8], [1.414213562373095, 1.4142135623730951, 4], [-1.2246467991473532e-16, 2, 4]],
134
138
  [[0.7071067811865472, 0.7071067811865478, 8], [-1.2246467991473532e-16, 2, 4], [-4.286263797015736e-16, 1, 8]],
135
139
  [[-3.4638242249419727e-16, 3.4638242249419736e-16, 8], [0.7071067811865472, 0.7071067811865478, 8], [-4.286263797015736e-16, 1, 8]],
136
- [[-4.898587196589412e-16, 0, 8], [-4.2862637970157346e-16, 0.9999999999999998, 8],
137
- [-1.2246467991473475e-16, 2.0000000000000004, 3.9999999999999964], [5.510910596163092e-16, 1.0000000000000004, -8],
138
- [ 4.898587196589414e-16, 0, -8]],
139
- [[0, -4.898587196589413e-16, -8.000000000000002], [1.0000000000000027, -4.898587196589413e-16, -8.000000000000002],
140
- [2.000000000000001, 2.449293598294702e-16, 3.9999999999999964], [1.0000000000000004, 4.898587196589411e-16, 8],
141
- [0, 4.898587196589411e-16, 8]]
140
+ [
141
+ [-4.898587196589412e-16, 0, 8], [-4.2862637970157346e-16, 0.9999999999999998, 8],
142
+ [-1.2246467991473475e-16, 2.0000000000000004, 3.9999999999999964], [5.510910596163092e-16, 1.0000000000000004, -8],
143
+ [4.898587196589414e-16, 0, -8]
144
+ ],
145
+ [
146
+ [0, -4.898587196589413e-16, -8.000000000000002], [1.0000000000000027, -4.898587196589413e-16, -8.000000000000002],
147
+ [2.000000000000001, 2.449293598294702e-16, 3.9999999999999964], [1.0000000000000004, 4.898587196589411e-16, 8],
148
+ [0, 4.898587196589411e-16, 8]
149
+ ]
142
150
  ]
143
151
  t.is(pts.length, 14)
144
152
  t.true(comparePolygonsAsPoints(pts, exp))
@@ -722,7 +722,7 @@ class QuickHull {
722
722
  for (let i = 0; i < this.newFaces.length; i += 1) {
723
723
  const face = this.newFaces[i]
724
724
  if (face.mark === VISIBLE) {
725
- while (this.doAdjacentMerge(face, MERGE_NON_CONVEX_WRT_LARGER_FACE)) {}
725
+ while (this.doAdjacentMerge(face, MERGE_NON_CONVEX_WRT_LARGER_FACE)) {} // eslint-disable-line no-empty
726
726
  }
727
727
  }
728
728
 
@@ -733,7 +733,7 @@ class QuickHull {
733
733
  const face = this.newFaces[i]
734
734
  if (face.mark === NON_CONVEX) {
735
735
  face.mark = VISIBLE
736
- while (this.doAdjacentMerge(face, MERGE_NON_CONVEX)) {}
736
+ while (this.doAdjacentMerge(face, MERGE_NON_CONVEX)) {} // eslint-disable-line no-empty
737
737
  }
738
738
  }
739
739
 
@@ -99,9 +99,7 @@ const splitPolygon = (openedge, polygon, eps) => {
99
99
  /*
100
100
  * TBD This should be part of vec3.
101
101
  */
102
- const almostEquals = (eps, v1, v2) => {
103
- return (Math.abs(v1[0] - v2[0]) <= eps && Math.abs(v1[1] - v2[1]) <= eps && Math.abs(v1[2] - v2[2]) <= eps)
104
- }
102
+ const almostEquals = (eps, v1, v2) => (Math.abs(v1[0] - v2[0]) <= eps && Math.abs(v1[1] - v2[1]) <= eps && Math.abs(v1[2] - v2[2]) <= eps)
105
103
 
106
104
  const enclosedEdge = (openedge, edge, eps) => {
107
105
  if (openedge.distance < edge.distance) {
@@ -15,16 +15,11 @@ const repairTjunctions = require('./repairTjunctions')
15
15
 
16
16
  /*
17
17
  */
18
- const generalizePath2 = (options, geometry) => {
19
- return geometry
20
- }
21
-
18
+ const generalizePath2 = (options, geometry) => geometry
22
19
 
23
20
  /*
24
21
  */
25
- const generalizeGeom2 = (options, geometry) => {
26
- return geometry
27
- }
22
+ const generalizeGeom2 = (options, geometry) => geometry
28
23
 
29
24
  /*
30
25
  */
@@ -78,6 +73,7 @@ const generalizeGeom3 = (options, geometry) => {
78
73
  * @param {Boolean} [options.simplify=false] the geometries should be simplified
79
74
  * @param {Boolean} [options.triangulate=false] the geometries should be triangulated
80
75
  * @param {Boolean} [options.repair=false] the geometries should be repaired
76
+ * @param {...Object} geometries - the geometries to generalize
81
77
  * @return {Object|Array} the modified geometry, or a list of modified geometries
82
78
  * @alias module:modeling/modifiers.generalize
83
79
  */
@@ -3,7 +3,7 @@ const vec3 = require('../../maths/vec3')
3
3
  const poly3 = require('../../geometries/poly3')
4
4
 
5
5
  const isValidPoly3 = (epsilon, polygon) => {
6
- const area = poly3.measureArea(polygon)
6
+ const area = Math.abs(poly3.measureArea(polygon))
7
7
  return (Number.isFinite(area) && area > epsilon)
8
8
  }
9
9
 
@@ -17,7 +17,7 @@ const snapPolygons = (epsilon, polygons) => {
17
17
  const newvertices = []
18
18
  for (let i = 0; i < snapvertices.length; i++) {
19
19
  const j = (i + 1) % snapvertices.length
20
- if (! vec3.equals(snapvertices[i], snapvertices[j])) newvertices.push(snapvertices[i])
20
+ if (!vec3.equals(snapvertices[i], snapvertices[j])) newvertices.push(snapvertices[i])
21
21
  }
22
22
  const newpolygon = poly3.create(newvertices)
23
23
  if (polygon.color) newpolygon.color = polygon.color