@jscad/modeling 2.7.0 → 2.8.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.
Files changed (185) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/jscad-modeling.min.js +142 -139
  3. package/package.json +2 -2
  4. package/src/colors/hslToRgb.js +1 -1
  5. package/src/colors/hueToColorComponent.js +1 -0
  6. package/src/curves/bezier/create.js +3 -8
  7. package/src/curves/bezier/tangentAt.js +2 -2
  8. package/src/curves/bezier/tangentAt.test.js +1 -1
  9. package/src/curves/bezier/valueAt.test.js +1 -1
  10. package/src/curves/index.js +1 -1
  11. package/src/geometries/geom2/index.js +10 -0
  12. package/src/geometries/geom2/isA.js +2 -2
  13. package/src/geometries/geom2/toCompactBinary.js +4 -4
  14. package/src/geometries/geom2/toOutlines.js +6 -11
  15. package/src/geometries/geom2/toString.js +1 -1
  16. package/src/geometries/geom2/transform.test.js +1 -1
  17. package/src/geometries/geom3/create.js +1 -1
  18. package/src/geometries/geom3/fromCompactBinary.js +1 -1
  19. package/src/geometries/geom3/index.js +17 -0
  20. package/src/geometries/geom3/invert.js +2 -2
  21. package/src/geometries/geom3/isA.js +2 -2
  22. package/src/geometries/geom3/toCompactBinary.js +4 -4
  23. package/src/geometries/geom3/toPoints.js +1 -0
  24. package/src/geometries/geom3/toString.js +1 -1
  25. package/src/geometries/geom3/transform.test.js +1 -1
  26. package/src/geometries/index.js +8 -1
  27. package/src/geometries/path2/eachPoint.js +3 -3
  28. package/src/geometries/path2/index.js +11 -0
  29. package/src/geometries/path2/isA.js +2 -2
  30. package/src/geometries/path2/reverse.js +4 -4
  31. package/src/geometries/path2/toCompactBinary.js +6 -6
  32. package/src/geometries/path2/toString.js +1 -1
  33. package/src/geometries/path2/transform.test.js +1 -1
  34. package/src/geometries/poly2/arePointsInside.test.js +1 -1
  35. package/src/geometries/poly2/index.js +6 -0
  36. package/src/geometries/poly3/index.js +7 -1
  37. package/src/geometries/poly3/isA.js +2 -2
  38. package/src/geometries/poly3/isConvex.js +2 -2
  39. package/src/geometries/poly3/measureArea.js +4 -4
  40. package/src/geometries/poly3/measureBoundingBox.js +2 -2
  41. package/src/geometries/poly3/measureBoundingSphere.js +2 -2
  42. package/src/geometries/poly3/measureSignedVolume.js +4 -4
  43. package/src/geometries/poly3/toPoints.js +2 -2
  44. package/src/geometries/poly3/toString.js +2 -2
  45. package/src/geometries/poly3/transform.js +2 -2
  46. package/src/maths/index.js +1 -1
  47. package/src/maths/line2/equals.js +2 -2
  48. package/src/maths/line2/fromValues.js +2 -2
  49. package/src/maths/line2/intersectPointOfLines.js +1 -1
  50. package/src/maths/line2/intersectPointOfLines.test.js +1 -1
  51. package/src/maths/line2/reverse.test.js +1 -1
  52. package/src/maths/line2/transform.test.js +1 -1
  53. package/src/maths/line3/create.js +2 -1
  54. package/src/maths/line3/equals.js +2 -2
  55. package/src/maths/line3/reverse.test.js +1 -1
  56. package/src/maths/line3/transform.test.js +1 -1
  57. package/src/maths/mat4/fromRotation.js +1 -1
  58. package/src/maths/mat4/fromVectorRotation.js +1 -1
  59. package/src/maths/mat4/fromVectorRotation.test.js +1 -1
  60. package/src/maths/mat4/identity.test.js +1 -1
  61. package/src/maths/mat4/invert.js +18 -18
  62. package/src/maths/mat4/isIdentity.js +1 -1
  63. package/src/maths/mat4/isIdentity.test.js +0 -2
  64. package/src/maths/mat4/isMirroring.js +4 -4
  65. package/src/maths/mat4/isMirroring.test.js +1 -1
  66. package/src/maths/mat4/isOnlyTransformScale.js +5 -4
  67. package/src/maths/mat4/leftMultiplyVec3.js +2 -2
  68. package/src/maths/mat4/rotate.js +1 -1
  69. package/src/maths/mat4/toString.js +2 -2
  70. package/src/maths/mat4/translate.test.js +1 -1
  71. package/src/maths/plane/flip.test.js +1 -1
  72. package/src/maths/plane/fromPoints.d.ts +1 -1
  73. package/src/maths/plane/fromPoints.js +1 -3
  74. package/src/maths/plane/signedDistanceToPoint.js +1 -1
  75. package/src/maths/plane/transform.test.js +1 -1
  76. package/src/maths/utils/aboutEqualNormals.js +2 -2
  77. package/src/maths/vec2/abs.d.ts +1 -1
  78. package/src/maths/vec2/add.test.js +1 -1
  79. package/src/maths/vec2/angleDegrees.d.ts +1 -1
  80. package/src/maths/vec2/angleRadians.d.ts +1 -1
  81. package/src/maths/vec2/create.js +1 -1
  82. package/src/maths/vec2/cross.test.js +1 -1
  83. package/src/maths/vec2/divide.test.js +1 -1
  84. package/src/maths/vec2/fromAngleDegrees.js +1 -1
  85. package/src/maths/vec2/fromScalar.js +1 -1
  86. package/src/maths/vec2/length.d.ts +1 -1
  87. package/src/maths/vec2/length.js +1 -1
  88. package/src/maths/vec2/length.test.js +10 -0
  89. package/src/maths/vec2/lerp.test.js +1 -1
  90. package/src/maths/vec2/multiply.test.js +1 -1
  91. package/src/maths/vec2/negate.test.js +1 -1
  92. package/src/maths/vec2/normal.js +1 -1
  93. package/src/maths/vec2/normalize.d.ts +1 -1
  94. package/src/maths/vec2/normalize.test.js +1 -1
  95. package/src/maths/vec2/rotate.test.js +1 -1
  96. package/src/maths/vec2/squaredLength.d.ts +1 -1
  97. package/src/maths/vec2/squaredLength.js +3 -3
  98. package/src/maths/vec2/subtract.test.js +1 -1
  99. package/src/maths/vec2/toString.js +1 -1
  100. package/src/maths/vec2/transform.test.js +1 -1
  101. package/src/maths/vec3/abs.d.ts +1 -1
  102. package/src/maths/vec3/add.test.js +1 -1
  103. package/src/maths/vec3/angle.js +2 -2
  104. package/src/maths/vec3/angle.test.js +17 -0
  105. package/src/maths/vec3/cross.test.js +1 -1
  106. package/src/maths/vec3/divide.test.js +1 -1
  107. package/src/maths/vec3/fromScalar.js +1 -1
  108. package/src/maths/vec3/fromVec2.d.ts +1 -1
  109. package/src/maths/vec3/fromVec2.js +3 -3
  110. package/src/maths/vec3/length.d.ts +1 -1
  111. package/src/maths/vec3/length.js +4 -4
  112. package/src/maths/vec3/length.test.js +10 -0
  113. package/src/maths/vec3/lerp.test.js +1 -1
  114. package/src/maths/vec3/multiply.test.js +1 -1
  115. package/src/maths/vec3/negate.d.ts +1 -1
  116. package/src/maths/vec3/negate.test.js +1 -1
  117. package/src/maths/vec3/normalize.d.ts +1 -1
  118. package/src/maths/vec3/normalize.test.js +1 -1
  119. package/src/maths/vec3/rotateX.test.js +1 -1
  120. package/src/maths/vec3/rotateY.test.js +1 -1
  121. package/src/maths/vec3/rotateZ.test.js +1 -1
  122. package/src/maths/vec3/scale.test.js +1 -1
  123. package/src/maths/vec3/squaredLength.d.ts +1 -1
  124. package/src/maths/vec3/squaredLength.js +4 -4
  125. package/src/maths/vec3/subtract.test.js +1 -1
  126. package/src/maths/vec3/toString.js +1 -1
  127. package/src/maths/vec3/transform.test.js +1 -1
  128. package/src/maths/vec4/toString.js +1 -1
  129. package/src/maths/vec4/transform.test.js +1 -1
  130. package/src/measurements/measureBoundingBox.js +38 -128
  131. package/src/measurements/measureBoundingSphere.js +4 -4
  132. package/src/measurements/measureCenterOfMass.js +1 -1
  133. package/src/operations/booleans/mayOverlap.js +3 -3
  134. package/src/operations/booleans/retessellate.js +3 -5
  135. package/src/operations/booleans/scission.js +1 -1
  136. package/src/operations/booleans/subtract.js +1 -1
  137. package/src/operations/booleans/union.test.js +1 -1
  138. package/src/operations/booleans/unionGeom3Sub.js +1 -1
  139. package/src/operations/expansions/expand.js +3 -1
  140. package/src/operations/expansions/expand.test.js +3 -35
  141. package/src/operations/expansions/expandShell.js +24 -18
  142. package/src/operations/expansions/offset.js +2 -1
  143. package/src/operations/expansions/offset.test.js +25 -89
  144. package/src/operations/expansions/offsetFromPoints.js +11 -6
  145. package/src/operations/extrusions/extrudeLinear.js +7 -3
  146. package/src/operations/extrusions/extrudeLinear.test.js +25 -1
  147. package/src/operations/extrusions/extrudeLinearPath2.js +24 -0
  148. package/src/operations/extrusions/extrudeRectangular.js +3 -2
  149. package/src/operations/extrusions/extrudeRectangular.test.js +2 -2
  150. package/src/operations/extrusions/project.js +1 -1
  151. package/src/operations/extrusions/slice/isA.js +2 -2
  152. package/src/operations/extrusions/slice/toPolygons.js +1 -1
  153. package/src/operations/hulls/hull.test.js +1 -1
  154. package/src/operations/hulls/hullChain.js +1 -1
  155. package/src/operations/hulls/hullGeom2.js +1 -1
  156. package/src/operations/hulls/hullPath2.js +6 -4
  157. package/src/operations/hulls/hullPath2.test.js +16 -0
  158. package/src/operations/hulls/hullPoints2.test.js +1 -1
  159. package/src/operations/hulls/quickhull/QuickHull.js +2 -2
  160. package/src/operations/modifiers/edges.js +2 -4
  161. package/src/operations/modifiers/generalize.js +4 -7
  162. package/src/operations/modifiers/snap.test.js +3 -3
  163. package/src/operations/transforms/align.d.ts +1 -1
  164. package/src/operations/transforms/center.js +17 -17
  165. package/src/operations/transforms/mirror.js +11 -11
  166. package/src/operations/transforms/rotate.js +12 -12
  167. package/src/operations/transforms/scale.js +19 -19
  168. package/src/operations/transforms/transform.js +3 -3
  169. package/src/operations/transforms/translate.js +14 -14
  170. package/src/primitives/arc.js +1 -1
  171. package/src/primitives/cylinderElliptic.test.js +0 -2
  172. package/src/primitives/ellipse.js +1 -1
  173. package/src/primitives/ellipsoid.js +1 -1
  174. package/src/primitives/ellipsoid.test.js +0 -2
  175. package/src/primitives/geodesicSphere.d.ts +0 -1
  176. package/src/primitives/geodesicSphere.js +2 -2
  177. package/src/primitives/polyhedron.js +1 -1
  178. package/src/primitives/roundedCylinder.js +1 -1
  179. package/src/primitives/torus.d.ts +0 -1
  180. package/src/primitives/triangle.js +2 -2
  181. package/src/text/vectorText.js +2 -2
  182. package/src/utils/insertSorted.js +1 -0
  183. package/src/utils/padArrayToLength.js +1 -1
  184. package/test/helpers/comparePolygons.js +1 -3
  185. package/test/helpers/nearlyEqual.js +2 -6
@@ -7,7 +7,7 @@ const { Tree } = require('./trees')
7
7
  * Return a new 3D geometry representing the space in the given geometries.
8
8
  * @param {geom3} geometry1 - geometry to union
9
9
  * @param {geom3} geometry2 - geometry to union
10
- * @returns {goem3} new 3D geometry
10
+ * @returns {geom3} new 3D geometry
11
11
  */
12
12
  const unionSub = (geometry1, geometry2) => {
13
13
  if (!mayOverlap(geometry1, geometry2)) {
@@ -10,12 +10,14 @@ const expandPath2 = require('./expandPath2')
10
10
 
11
11
  /**
12
12
  * Expand the given geometry using the given options.
13
+ * Both internal 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
16
18
  * @param {String} [options.corners='edge'] - type of corner to create after expanding; edge, chamfer, round
17
19
  * @param {Integer} [options.segments=16] - number of segments when creating round corners
18
- * @param {...Objects} geometry - the list of geometry to expand
20
+ * @param {...Objects} objects - the geometries to expand
19
21
  * @return {Object|Array} new geometry, or list of new geometries
20
22
  * @alias module:modeling/expansions.expand
21
23
  *
@@ -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, 2065)
123
+ t.is(pts2.length, 1612)
124
124
  })
125
125
 
126
126
  test('expand (options): offsetting of a complex geom2 produces expected offset geom2', (t) => {
@@ -151,59 +151,27 @@ test('expand (options): offsetting of a complex geom2 produces expected offset g
151
151
  const obs = expand({ delta: 2, corners: 'edge' }, geometry)
152
152
  const pts = geom2.toPoints(obs)
153
153
  const exp = [
154
- [-77, -77],
155
- [-75, -77],
156
- [75, -77],
157
154
  [77, -77],
158
- [77, -75],
159
- [77, 75],
160
155
  [77, 77],
161
- [75, 77],
162
- [40, 77],
163
156
  [38, 77],
164
- [38, 75],
165
157
  [38, 2],
166
158
  [-38, 2],
167
- [-38, 75],
168
159
  [-37.99999999999999, 77],
169
- [-40, 77],
170
- [-75, 77],
171
160
  [-77, 77],
172
- [-77, 75],
173
- [17, -40],
174
161
  [16.999999999999996, -42],
175
- [15, -42],
176
- [8, -42],
177
162
  [6, -42],
178
- [6, -40],
179
163
  [6, -27],
180
164
  [-6, -27],
181
- [-6, -40],
182
165
  [-6.000000000000001, -42],
183
- [-8, -42],
184
- [-15, -42],
185
166
  [-17, -42],
186
- [-17, -40],
187
- [-17, -10],
188
167
  [-16.999999999999996, -8],
189
- [-15, -8],
190
- [15, -8],
191
168
  [17, -8.000000000000004],
192
- [17, -10],
193
- [-4, -19],
194
169
  [-4, -21],
195
- [-2, -21],
196
- [1.9999999999999998, -21],
197
170
  [3.9999999999999996, -21],
198
- [4, -19],
199
- [4, -15],
200
171
  [4, -13],
201
- [2, -13],
202
- [-1.9999999999999998, -13],
203
172
  [-4, -13],
204
- [-4, -15],
205
- [-77, -75]
173
+ [-77, -77]
206
174
  ]
207
- t.is(pts.length, 52)
175
+ t.is(pts.length, 20)
208
176
  t.true(comparePoints(pts, exp))
209
177
  })
@@ -15,37 +15,43 @@ const unionGeom3Sub = require('../booleans/unionGeom3Sub')
15
15
 
16
16
  const extrudePolygon = require('./extrudePolygon')
17
17
 
18
+ /**
19
+ * Collect all planes adjacent to each vertex
20
+ */
18
21
  const mapPlaneToVertex = (map, vertex, plane) => {
19
- const i = map.findIndex((item) => vec3.equals(item[0], vertex))
20
- if (i < 0) {
22
+ const key = vertex.toString()
23
+ if (!map.has(key)) {
21
24
  const entry = [vertex, [plane]]
22
- map.push(entry)
23
- return map.length
25
+ map.set(key, entry)
26
+ } else {
27
+ const planes = map.get(key)[1]
28
+ planes.push(plane)
24
29
  }
25
- const planes = map[i][1]
26
- planes.push(plane)
27
- return i
28
30
  }
29
31
 
32
+ /**
33
+ * Collect all planes adjacent to each edge.
34
+ * Combine undirected edges, no need for duplicate cylinders.
35
+ */
30
36
  const mapPlaneToEdge = (map, edge, plane) => {
31
- const i = map.findIndex((item) => (vec3.equals(item[0], edge[0]) && vec3.equals(item[1], edge[1])) || (vec3.equals(item[0], edge[1]) && vec3.equals(item[1], edge[0])))
32
- if (i < 0) {
37
+ const key0 = edge[0].toString()
38
+ const key1 = edge[1].toString()
39
+ // Sort keys to make edges undirected
40
+ const key = key0 < key1 ? `${key0},${key1}` : `${key1},${key0}`
41
+ if (!map.has(key)) {
33
42
  const entry = [edge, [plane]]
34
- map.push(entry)
35
- return map.length
43
+ map.set(key, entry)
44
+ } else {
45
+ const planes = map.get(key)[1]
46
+ planes.push(plane)
36
47
  }
37
- const planes = map[i][1]
38
- planes.push(plane)
39
- return i
40
48
  }
41
49
 
42
50
  const addUniqueAngle = (map, angle) => {
43
51
  const i = map.findIndex((item) => item === angle)
44
52
  if (i < 0) {
45
53
  map.push(angle)
46
- return map.length
47
54
  }
48
- return i
49
55
  }
50
56
 
51
57
  /*
@@ -65,8 +71,8 @@ const expandShell = (options, geometry) => {
65
71
  const { delta, segments } = Object.assign({ }, defaults, options)
66
72
 
67
73
  let result = geom3.create()
68
- const vertices2planes = [] // contents: [vertex, [plane, ...]]
69
- const edges2planes = [] // contents: [[vertex, vertex], [plane, ...]]
74
+ const vertices2planes = new Map() // {vertex: [vertex, [plane, ...]]}
75
+ const edges2planes = new Map() // {edge: [[vertex, vertex], [plane, ...]]}
70
76
 
71
77
  const v1 = vec3.create()
72
78
  const v2 = vec3.create()
@@ -8,11 +8,12 @@ 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
14
15
  * @param {Integer} [options.segments=16] - number of segments when creating round corners
15
- * @param {...Object} geometry - the list of geometry to offset
16
+ * @param {...Object} objects - the geometries to offset
16
17
  * @return {Object|Array} new geometry, or list of new geometries
17
18
  * @alias module:modeling/expansions.offset
18
19
  *
@@ -46,7 +46,7 @@ test('offset: offsetting a bent line produces expected geometry', (t) => {
46
46
  // offset it by -2.
47
47
  offsetLinePath2 = offset({ delta: -2, corners: 'edge', segments: 8 }, linePath2)
48
48
  offsetPoints = path2.toPoints(offsetLinePath2)
49
- t.is(offsetPoints.length, 7)
49
+ t.is(offsetPoints.length, 5)
50
50
  boundingBox = measureBoundingBox(offsetLinePath2)
51
51
  t.true(comparePoints(boundingBox, [[-2, 0, 0], [10, 12, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
52
52
  })
@@ -124,20 +124,12 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
124
124
  let pts = path2.toPoints(obs)
125
125
  let exp = [
126
126
  [-5, -6],
127
- [5, -6],
128
127
  [6, -6],
129
- [6, -5],
130
- [6, 5],
131
128
  [6, 6],
132
- [5, 6],
133
- [3, 6],
134
129
  [2, 6],
135
- [2, 5],
136
130
  [2, 1],
137
131
  [-2, 1],
138
- [-2, 5],
139
132
  [-1.9999999999999996, 6],
140
- [-3, 6],
141
133
  [-5, 6]
142
134
  ]
143
135
  t.true(comparePoints(pts, exp))
@@ -145,26 +137,14 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
145
137
  obs = offset({ delta: 1, corners: 'edge' }, closeline)
146
138
  pts = path2.toPoints(obs)
147
139
  exp = [
148
- [-6, -6],
149
- [-5, -6],
150
- [5, -6],
151
140
  [6, -6],
152
- [6, -5],
153
- [6, 5],
154
141
  [6, 6],
155
- [5, 6],
156
- [3, 6],
157
142
  [2, 6],
158
- [2, 5],
159
143
  [2, 1],
160
144
  [-2, 1],
161
- [-2, 5],
162
145
  [-1.9999999999999996, 6],
163
- [-3, 6],
164
- [-5, 6],
165
146
  [-6, 6],
166
- [-6, 5],
167
- [-6, -5]
147
+ [-6, -6]
168
148
  ]
169
149
  t.true(comparePoints(pts, exp))
170
150
 
@@ -175,12 +155,8 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
175
155
  [4.5, -4.5],
176
156
  [4.5, 4.5],
177
157
  [3.5, 4.5],
178
- [3.5, -3.061616997868383e-17],
179
158
  [3.4999999999999996, -0.5],
180
- [3, -0.5],
181
- [-3, -0.5],
182
159
  [-3.5, -0.4999999999999996],
183
- [-3.5, 3.061616997868383e-17],
184
160
  [-3.5, 4.5],
185
161
  [-5, 4.5]
186
162
  ]
@@ -193,12 +169,8 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
193
169
  [4.5, -4.5],
194
170
  [4.5, 4.5],
195
171
  [3.5, 4.5],
196
- [3.5, -3.061616997868383e-17],
197
172
  [3.4999999999999996, -0.5],
198
- [3, -0.5],
199
- [-3, -0.5],
200
173
  [-3.5, -0.4999999999999996],
201
- [-3.5, 3.061616997868383e-17],
202
174
  [-3.5, 4.5],
203
175
  [-4.5, 4.5]
204
176
  ]
@@ -373,26 +345,14 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
373
345
  obs = offset({ delta: 1, corners: 'edge' }, geometry)
374
346
  pts = geom2.toPoints(obs)
375
347
  exp = [
376
- [-6, -6],
377
- [-5, -6],
378
- [5, -6],
379
348
  [6, -6],
380
- [6, -5],
381
- [6, 5],
382
349
  [6, 6],
383
- [5, 6],
384
- [3, 6],
385
350
  [2, 6],
386
- [2, 5],
387
351
  [2, 1],
388
352
  [-2, 1],
389
- [-2, 5],
390
353
  [-1.9999999999999996, 6],
391
- [-3, 6],
392
- [-5, 6],
393
354
  [-6, 6],
394
- [-6, 5],
395
- [-6, -5]
355
+ [-6, -6]
396
356
  ]
397
357
  t.true(comparePoints(pts, exp))
398
358
 
@@ -422,78 +382,54 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
422
382
 
423
383
  test('offset (options): offsetting of a complex geom2 produces expected offset geom2', (t) => {
424
384
  const geometry = geom2.create([
425
- [[-75.00000, 75.00000], [-75.00000, -75.00000]],
426
- [[-75.00000, -75.00000], [75.00000, -75.00000]],
427
- [[75.00000, -75.00000], [75.00000, 75.00000]],
428
- [[-40.00000, 75.00000], [-75.00000, 75.00000]],
429
- [[75.00000, 75.00000], [40.00000, 75.00000]],
430
- [[40.00000, 75.00000], [40.00000, 0.00000]],
431
- [[40.00000, 0.00000], [-40.00000, 0.00000]],
432
- [[-40.00000, 0.00000], [-40.00000, 75.00000]],
433
- [[15.00000, -10.00000], [15.00000, -40.00000]],
434
- [[-15.00000, -10.00000], [15.00000, -10.00000]],
435
- [[-15.00000, -40.00000], [-15.00000, -10.00000]],
436
- [[-8.00000, -40.00000], [-15.00000, -40.00000]],
437
- [[15.00000, -40.00000], [8.00000, -40.00000]],
438
- [[-8.00000, -25.00000], [-8.00000, -40.00000]],
439
- [[8.00000, -25.00000], [-8.00000, -25.00000]],
440
- [[8.00000, -40.00000], [8.00000, -25.00000]],
441
- [[-2.00000, -15.00000], [-2.00000, -19.00000]],
442
- [[-2.00000, -19.00000], [2.00000, -19.00000]],
443
- [[2.00000, -19.00000], [2.00000, -15.00000]],
444
- [[2.00000, -15.00000], [-2.00000, -15.00000]]
385
+ [[-75, 75], [-75, -75]],
386
+ [[-75, -75], [75, -75]],
387
+ [[75, -75], [75, 75]],
388
+ [[-40, 75], [-75, 75]],
389
+ [[75, 75], [40, 75]],
390
+ [[40, 75], [40, 0]],
391
+ [[40, 0], [-40, 0]],
392
+ [[-40, 0], [-40, 75]],
393
+ [[15, -10], [15, -40]],
394
+ [[-15, -10], [15, -10]],
395
+ [[-15, -40], [-15, -10]],
396
+ [[-8, -40], [-15, -40]],
397
+ [[15, -40], [8, -40]],
398
+ [[-8, -25], [-8, -40]],
399
+ [[8, -25], [-8, -25]],
400
+ [[8, -40], [8, -25]],
401
+ [[-2, -15], [-2, -19]],
402
+ [[-2, -19], [2, -19]],
403
+ [[2, -19], [2, -15]],
404
+ [[2, -15], [-2, -15]]
445
405
  ])
446
406
 
447
407
  // expand +
448
408
  const obs = offset({ delta: 2, corners: 'edge' }, geometry)
449
409
  const pts = geom2.toPoints(obs)
450
410
  const exp = [
451
- [-77, -77],
452
- [-75, -77],
453
- [75, -77],
454
411
  [77, -77],
455
- [77, -75],
456
- [77, 75],
457
412
  [77, 77],
458
- [75, 77],
459
- [40, 77],
460
413
  [38, 77],
461
- [38, 75],
462
414
  [38, 2],
463
415
  [-38, 2],
464
- [-38, 75],
465
416
  [-37.99999999999999, 77],
466
- [-40, 77],
467
- [-75, 77],
468
417
  [-77, 77],
469
- [-77, 75],
470
418
  [13, -12],
471
419
  [13, -38],
472
420
  [10, -38],
473
- [10, -25],
474
421
  [10, -23],
475
- [8, -23],
476
- [-8, -23],
477
422
  [-10, -23],
478
- [-10, -25],
479
423
  [-10, -38],
480
424
  [-13, -38],
481
425
  [-13, -12],
482
- [-4, -19],
483
426
  [-4, -21],
484
- [-2, -21],
485
- [1.9999999999999998, -21],
486
427
  [3.9999999999999996, -21],
487
- [4, -19],
488
- [4, -15],
489
428
  [4, -13],
490
- [2, -13],
491
- [-1.9999999999999998, -13],
492
429
  [-4, -13],
493
- [-4, -15],
494
- [-77, -75]
430
+ [-77, -77]
495
431
  ]
496
- t.is(pts.length, 44)
432
+ t.is(pts.length, 20)
497
433
  t.true(comparePoints(pts, exp))
498
434
  })
499
435
 
@@ -34,7 +34,7 @@ const offsetFromPoints = (options, points) => {
34
34
  delta = Math.abs(delta) // sign is no longer required
35
35
 
36
36
  let previousSegment = null
37
- const newPoints = []
37
+ let newPoints = []
38
38
  const newCorners = []
39
39
  const of = vec2.create()
40
40
  const n = points.length
@@ -94,6 +94,10 @@ const offsetFromPoints = (options, points) => {
94
94
  // generate corners if necessary
95
95
 
96
96
  if (corners === 'edge') {
97
+ // map for fast point index lookup
98
+ const pointIndex = new Map() // {point: index}
99
+ newPoints.forEach((point, index) => pointIndex.set(point, index))
100
+
97
101
  // create edge corners
98
102
  const line0 = line2.create()
99
103
  const line1 = line2.create()
@@ -103,16 +107,17 @@ const offsetFromPoints = (options, points) => {
103
107
  const ip = line2.intersectPointOfLines(line0, line1)
104
108
  if (Number.isFinite(ip[0]) && Number.isFinite(ip[1])) {
105
109
  const p0 = corner.s0[1]
106
- let i = newPoints.findIndex((point) => vec2.equals(p0, point))
107
- i = (i + 1) % newPoints.length
108
- newPoints.splice(i, 0, ip)
110
+ const i = pointIndex.get(p0)
111
+ newPoints[i] = ip
112
+ newPoints[(i + 1) % newPoints.length] = undefined
109
113
  } else {
110
114
  // paralell segments, drop one
111
115
  const p0 = corner.s1[0]
112
- const i = newPoints.findIndex((point) => vec2.equals(p0, point))
113
- newPoints.splice(i, 1)
116
+ const i = pointIndex.get(p0)
117
+ newPoints[i] = undefined
114
118
  }
115
119
  })
120
+ newPoints = newPoints.filter((p) => p !== undefined)
116
121
  }
117
122
 
118
123
  if (corners === 'round') {
@@ -1,16 +1,20 @@
1
1
  const flatten = require('../../utils/flatten')
2
2
 
3
3
  const geom2 = require('../../geometries/geom2')
4
+ const path2 = require('../../geometries/path2')
4
5
 
5
6
  const extrudeLinearGeom2 = require('./extrudeLinearGeom2')
7
+ const extrudeLinearPath2 = require('./extrudeLinearPath2')
6
8
 
7
9
  /**
8
10
  * Extrude the given geometry in an upward linear direction using the given options.
11
+ * Accepts path2 or geom2 objects as input. Paths must be closed.
12
+ *
9
13
  * @param {Object} options - options for extrude
10
- * @param {Array} [options.height=1] the height of the extrusion
14
+ * @param {Number} [options.height=1] the height of the extrusion
11
15
  * @param {Number} [options.twistAngle=0] the final rotation (RADIANS) about the origin of the shape (if any)
12
16
  * @param {Integer} [options.twistSteps=1] the resolution of the twist about the axis (if any)
13
- * @param {...Object} geometry - the list of geometry to extrude
17
+ * @param {...Object} objects - the geometries to extrude
14
18
  * @return {Object|Array} the extruded geometry, or a list of extruded geometry
15
19
  * @alias module:modeling/extrusions.extrudeLinear
16
20
  *
@@ -31,7 +35,7 @@ const extrudeLinear = (options, ...objects) => {
31
35
  options = { offset: [0, 0, height], twistAngle: twistAngle, twistSteps: twistSteps }
32
36
 
33
37
  const results = objects.map((object) => {
34
- // if (path.isA(object)) return pathextrude(options, object)
38
+ if (path2.isA(object)) return extrudeLinearPath2(options, object)
35
39
  if (geom2.isA(object)) return extrudeLinearGeom2(options, object)
36
40
  // if (geom3.isA(object)) return geom3.extrude(options, object)
37
41
  return object
@@ -2,7 +2,7 @@ const test = require('ava')
2
2
 
3
3
  const comparePolygonsAsPoints = require('../../../test/helpers/comparePolygonsAsPoints')
4
4
 
5
- const { geom2, geom3 } = require('../../geometries')
5
+ const { geom2, geom3, path2 } = require('../../geometries')
6
6
 
7
7
  const { extrudeLinear } = require('./index')
8
8
 
@@ -166,3 +166,27 @@ test('extrudeLinear (holes)', (t) => {
166
166
  t.is(pts.length, 24)
167
167
  t.true(comparePolygonsAsPoints(pts, exp))
168
168
  })
169
+
170
+ test('extrudeLinear (path2)', (t) => {
171
+ const geometry2 = path2.fromPoints({ closed: true }, [[0, 0], [12, 0], [6, 10]])
172
+ const geometry3 = extrudeLinear({ height: 15 }, geometry2)
173
+ t.true(geom3.isA(geometry3))
174
+ const pts = geom3.toPoints(geometry3)
175
+ const exp = [
176
+ [[6, 10, 0], [0, 0, 0], [0, 0, 15]],
177
+ [[6, 10, 0], [0, 0, 15], [6, 10, 15]],
178
+ [[0, 0, 0], [12, 0, 0], [12, 0, 15]],
179
+ [[0, 0, 0], [12, 0, 15], [0, 0, 15]],
180
+ [[12, 0, 0], [6, 10, 0], [6, 10, 15]],
181
+ [[12, 0, 0], [6, 10, 15], [12, 0, 15]],
182
+ [[0, 0, 15], [11.999999999999998, 0, 15], [6, 10, 15]],
183
+ [[6.000000000000001, 10, 0], [12, 0, 0], [8.881784197001252e-16, 0, 0]]
184
+ ]
185
+ t.is(pts.length, 8)
186
+ t.true(comparePolygonsAsPoints(pts, exp))
187
+ })
188
+
189
+ test('extrudeLinear (unclosed path throws error)', (t) => {
190
+ const geometry2 = path2.fromPoints({ closed: false }, [[0, 0], [12, 0], [6, 10]])
191
+ t.throws(() => extrudeLinear({}, geometry2))
192
+ })
@@ -0,0 +1,24 @@
1
+ const geom2 = require('../../geometries/geom2')
2
+ const path2 = require('../../geometries/path2')
3
+
4
+ const extrudeLinearGeom2 = require('./extrudeLinearGeom2')
5
+
6
+ /*
7
+ * Extrude the given geometry using the given options.
8
+ *
9
+ * @param {Object} [options] - options for extrude
10
+ * @param {Array} [options.offset] - the direction of the extrusion as a 3D vector
11
+ * @param {Number} [options.twistAngle] - the final rotation (RADIANS) about the origin
12
+ * @param {Integer} [options.twistSteps] - the number of steps created to produce the twist (if any)
13
+ * @param {path2} geometry - the geometry to extrude
14
+ * @returns {geom3} the extruded 3D geometry
15
+ */
16
+ const extrudePath2 = (options, geometry) => {
17
+ if (!geometry.isClosed) throw new Error('extruded path must be closed')
18
+ // Convert path2 to geom2
19
+ const points = path2.toPoints(geometry)
20
+ const geometry2 = geom2.fromPoints(points)
21
+ return extrudeLinearGeom2(options, geometry2)
22
+ }
23
+
24
+ module.exports = extrudePath2
@@ -12,12 +12,13 @@ const extrudeRectangularGeom2 = require('./extrudeRectangularGeom2')
12
12
  * @param {Object} options - options for extrusion, if any
13
13
  * @param {Number} [options.size=1] - size of the rectangle
14
14
  * @param {Number} [options.height=1] - height of the extrusion
15
- * @param {...Object} geometry - the list of geometry to extrude
15
+ * @param {...Object} objects - the geometries to extrude
16
16
  * @return {Object|Array} the extruded object, or a list of extruded objects
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 = {
@@ -12,11 +12,11 @@ test('extrudeRectangular (defaults)', (t) => {
12
12
 
13
13
  let obs = extrudeRectangular({ }, geometry1)
14
14
  let pts = geom3.toPoints(obs)
15
- t.is(pts.length, 50)
15
+ t.is(pts.length, 34)
16
16
 
17
17
  obs = extrudeRectangular({ }, geometry2)
18
18
  pts = geom3.toPoints(obs)
19
- t.is(pts.length, 40)
19
+ t.is(pts.length, 24)
20
20
  })
21
21
 
22
22
  test('extrudeRectangular (chamfer)', (t) => {
@@ -60,7 +60,7 @@ const projectGeom3 = (options, geometry) => {
60
60
  * @param {Object} options - options for project
61
61
  * @param {Array} [options.axis=[0,0,1]] the axis of the plane (default is Z axis)
62
62
  * @param {Array} [options.origin=[0,0,0]] the origin of the plane
63
- * @param {...Object} geometry - the list of 3D geometry to project
63
+ * @param {...Object} objects - the list of 3D geometry to project
64
64
  * @return {geom2|Array} the projected 2D geometry, or a list of 2D projected geometry
65
65
  * @alias module:modeling/extrusions.project
66
66
  *
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Determin if the given object is a slice.
3
- * @param {slice} object - the object to interogate
2
+ * Determine if the given object is a slice.
3
+ * @param {slice} object - the object to interrogate
4
4
  * @returns {Boolean} true if the object matches a slice
5
5
  * @alias module:modeling/extrusions/slice.isA
6
6
  */
@@ -60,7 +60,7 @@ const toPolygons = (slice) => {
60
60
  const wallPolygons = edges.map((edge) => toPolygon3D(splane, edge))
61
61
  const walls = geom3.create(wallPolygons)
62
62
 
63
- // make an insection of the base and the walls, creating... a set of polygons!
63
+ // make an intersection of the base and the walls, creating... a set of polygons!
64
64
  const geometry3 = intersectGeom3Sub(base, walls)
65
65
 
66
66
  // return only those polygons from the base
@@ -43,7 +43,7 @@ test('hull (single, geom2)', (t) => {
43
43
  t.is(pts.length, 7)
44
44
  })
45
45
 
46
- test('hull (multiple, overlaping, geom2)', (t) => {
46
+ test('hull (multiple, overlapping, geom2)', (t) => {
47
47
  const geometry1 = geom2.fromPoints([[5, 5], [-5, 5], [-5, -5], [5, -5]])
48
48
  const geometry2 = geom2.fromPoints([[3, 3], [-3, 3], [-3, -3], [3, -3]])
49
49
  const geometry3 = geom2.fromPoints([[6, 3], [-6, 3], [-6, -3], [6, -3]])
@@ -5,7 +5,7 @@ const union = require('../booleans/union')
5
5
  const hull = require('./hull')
6
6
 
7
7
  /**
8
- * Create a chain of hulled geometries from the given gemetries.
8
+ * Create a chain of hulled geometries from the given geometries.
9
9
  * Essentially hull A+B, B+C, C+D, etc., then union the results.
10
10
  * The given geometries should be of the same type, either geom2 or geom3 or path2.
11
11
  *
@@ -30,7 +30,7 @@ const hullGeom2 = (...geometries) => {
30
30
 
31
31
  const hullpoints = hullPoints2(uniquepoints)
32
32
 
33
- // NOTE: more then three points are required to create a new geometry
33
+ // NOTE: more than three points are required to create a new geometry
34
34
  if (hullpoints.length < 3) return geom2.create()
35
35
 
36
36
  // assemble a new geometry from the list of points
@@ -1,7 +1,5 @@
1
1
  const flatten = require('../../utils/flatten')
2
2
 
3
- const vec2 = require('../../maths/vec2')
4
-
5
3
  const path2 = require('../../geometries/path2')
6
4
 
7
5
  const hullPoints2 = require('./hullPoints2')
@@ -16,11 +14,15 @@ const hullPath2 = (...geometries) => {
16
14
 
17
15
  // extract the unique points from the geometries
18
16
  const uniquepoints = []
17
+ const found = new Set()
19
18
  geometries.forEach((geometry) => {
20
19
  const points = path2.toPoints(geometry)
21
20
  points.forEach((point) => {
22
- const index = uniquepoints.findIndex((unique) => vec2.equals(unique, point))
23
- if (index < 0) uniquepoints.push(point)
21
+ const key = point.toString()
22
+ if (!found.has(key)) {
23
+ uniquepoints.push(point)
24
+ found.add(key)
25
+ }
24
26
  })
25
27
  })
26
28
 
@@ -0,0 +1,16 @@
1
+ const test = require('ava')
2
+
3
+ const { path2 } = require('../../geometries')
4
+
5
+ const hullPath2 = require('./hullPath2')
6
+
7
+ test('hullPath2', (t) => {
8
+ const closed = true
9
+ const geometry1 = path2.fromPoints({ closed }, [[0, 0], [-4, 4], [-4, -4]])
10
+ const geometry2 = path2.fromPoints({ closed }, [[0, 0], [4, -4], [4, 4]])
11
+
12
+ const obs = hullPath2(geometry1, geometry2)
13
+ t.true(path2.isA(obs))
14
+ const pts = path2.toPoints(obs)
15
+ t.is(pts.length, 4)
16
+ })