@jscad/modeling 2.7.2 → 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 (159) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/jscad-modeling.min.js +136 -133
  3. package/package.json +2 -2
  4. package/src/curves/bezier/tangentAt.test.js +1 -1
  5. package/src/curves/bezier/valueAt.test.js +1 -1
  6. package/src/geometries/geom2/index.js +10 -0
  7. package/src/geometries/geom2/isA.js +2 -2
  8. package/src/geometries/geom2/toCompactBinary.js +4 -4
  9. package/src/geometries/geom2/toString.js +1 -1
  10. package/src/geometries/geom2/transform.test.js +1 -1
  11. package/src/geometries/geom3/fromCompactBinary.js +1 -1
  12. package/src/geometries/geom3/index.js +17 -0
  13. package/src/geometries/geom3/isA.js +2 -2
  14. package/src/geometries/geom3/toCompactBinary.js +4 -4
  15. package/src/geometries/geom3/toString.js +1 -1
  16. package/src/geometries/geom3/transform.test.js +1 -1
  17. package/src/geometries/index.js +8 -1
  18. package/src/geometries/path2/eachPoint.js +3 -3
  19. package/src/geometries/path2/index.js +11 -0
  20. package/src/geometries/path2/isA.js +2 -2
  21. package/src/geometries/path2/reverse.js +4 -4
  22. package/src/geometries/path2/toCompactBinary.js +6 -6
  23. package/src/geometries/path2/toString.js +1 -1
  24. package/src/geometries/path2/transform.test.js +1 -1
  25. package/src/geometries/poly2/arePointsInside.test.js +1 -1
  26. package/src/geometries/poly2/index.js +6 -0
  27. package/src/geometries/poly3/index.js +7 -1
  28. package/src/geometries/poly3/isA.js +2 -2
  29. package/src/geometries/poly3/isConvex.js +2 -2
  30. package/src/geometries/poly3/measureArea.js +4 -4
  31. package/src/geometries/poly3/measureBoundingBox.js +2 -2
  32. package/src/geometries/poly3/measureBoundingSphere.js +2 -2
  33. package/src/geometries/poly3/measureSignedVolume.js +4 -4
  34. package/src/geometries/poly3/toPoints.js +2 -2
  35. package/src/geometries/poly3/toString.js +2 -2
  36. package/src/geometries/poly3/transform.js +2 -2
  37. package/src/maths/index.js +1 -1
  38. package/src/maths/line2/equals.js +2 -2
  39. package/src/maths/line2/fromValues.js +2 -2
  40. package/src/maths/line2/intersectPointOfLines.js +1 -1
  41. package/src/maths/line2/intersectPointOfLines.test.js +1 -1
  42. package/src/maths/line2/reverse.test.js +1 -1
  43. package/src/maths/line2/transform.test.js +1 -1
  44. package/src/maths/line3/equals.js +2 -2
  45. package/src/maths/line3/reverse.test.js +1 -1
  46. package/src/maths/line3/transform.test.js +1 -1
  47. package/src/maths/mat4/fromVectorRotation.js +1 -1
  48. package/src/maths/mat4/fromVectorRotation.test.js +1 -1
  49. package/src/maths/mat4/identity.test.js +1 -1
  50. package/src/maths/mat4/invert.js +18 -18
  51. package/src/maths/mat4/isIdentity.js +1 -1
  52. package/src/maths/mat4/isMirroring.js +4 -4
  53. package/src/maths/mat4/isMirroring.test.js +1 -1
  54. package/src/maths/mat4/leftMultiplyVec3.js +2 -2
  55. package/src/maths/mat4/toString.js +2 -2
  56. package/src/maths/mat4/translate.test.js +1 -1
  57. package/src/maths/plane/flip.test.js +1 -1
  58. package/src/maths/plane/fromPoints.d.ts +1 -1
  59. package/src/maths/plane/fromPoints.js +1 -3
  60. package/src/maths/plane/signedDistanceToPoint.js +1 -1
  61. package/src/maths/plane/transform.test.js +1 -1
  62. package/src/maths/utils/aboutEqualNormals.js +2 -2
  63. package/src/maths/vec2/abs.d.ts +1 -1
  64. package/src/maths/vec2/add.test.js +1 -1
  65. package/src/maths/vec2/angleDegrees.d.ts +1 -1
  66. package/src/maths/vec2/angleRadians.d.ts +1 -1
  67. package/src/maths/vec2/create.js +1 -1
  68. package/src/maths/vec2/cross.test.js +1 -1
  69. package/src/maths/vec2/divide.test.js +1 -1
  70. package/src/maths/vec2/fromAngleDegrees.js +1 -1
  71. package/src/maths/vec2/fromScalar.js +1 -1
  72. package/src/maths/vec2/length.d.ts +1 -1
  73. package/src/maths/vec2/length.js +1 -1
  74. package/src/maths/vec2/lerp.test.js +1 -1
  75. package/src/maths/vec2/multiply.test.js +1 -1
  76. package/src/maths/vec2/negate.test.js +1 -1
  77. package/src/maths/vec2/normal.js +1 -1
  78. package/src/maths/vec2/normalize.d.ts +1 -1
  79. package/src/maths/vec2/normalize.test.js +1 -1
  80. package/src/maths/vec2/rotate.test.js +1 -1
  81. package/src/maths/vec2/squaredLength.d.ts +1 -1
  82. package/src/maths/vec2/squaredLength.js +3 -3
  83. package/src/maths/vec2/subtract.test.js +1 -1
  84. package/src/maths/vec2/toString.js +1 -1
  85. package/src/maths/vec2/transform.test.js +1 -1
  86. package/src/maths/vec3/abs.d.ts +1 -1
  87. package/src/maths/vec3/add.test.js +1 -1
  88. package/src/maths/vec3/cross.test.js +1 -1
  89. package/src/maths/vec3/divide.test.js +1 -1
  90. package/src/maths/vec3/fromScalar.js +1 -1
  91. package/src/maths/vec3/fromVec2.d.ts +1 -1
  92. package/src/maths/vec3/fromVec2.js +3 -3
  93. package/src/maths/vec3/length.d.ts +1 -1
  94. package/src/maths/vec3/length.js +4 -4
  95. package/src/maths/vec3/lerp.test.js +1 -1
  96. package/src/maths/vec3/multiply.test.js +1 -1
  97. package/src/maths/vec3/negate.d.ts +1 -1
  98. package/src/maths/vec3/negate.test.js +1 -1
  99. package/src/maths/vec3/normalize.d.ts +1 -1
  100. package/src/maths/vec3/normalize.test.js +1 -1
  101. package/src/maths/vec3/rotateX.test.js +1 -1
  102. package/src/maths/vec3/rotateY.test.js +1 -1
  103. package/src/maths/vec3/rotateZ.test.js +1 -1
  104. package/src/maths/vec3/scale.test.js +1 -1
  105. package/src/maths/vec3/squaredLength.d.ts +1 -1
  106. package/src/maths/vec3/squaredLength.js +4 -4
  107. package/src/maths/vec3/subtract.test.js +1 -1
  108. package/src/maths/vec3/toString.js +1 -1
  109. package/src/maths/vec3/transform.test.js +1 -1
  110. package/src/maths/vec4/toString.js +1 -1
  111. package/src/maths/vec4/transform.test.js +1 -1
  112. package/src/measurements/measureBoundingSphere.js +4 -4
  113. package/src/measurements/measureCenterOfMass.js +1 -1
  114. package/src/operations/booleans/mayOverlap.js +3 -3
  115. package/src/operations/booleans/retessellate.js +2 -2
  116. package/src/operations/booleans/scission.js +1 -1
  117. package/src/operations/booleans/subtract.js +1 -1
  118. package/src/operations/booleans/union.test.js +1 -1
  119. package/src/operations/booleans/unionGeom3Sub.js +1 -1
  120. package/src/operations/expansions/expand.js +2 -2
  121. package/src/operations/expansions/expand.test.js +3 -35
  122. package/src/operations/expansions/expandShell.js +24 -18
  123. package/src/operations/expansions/offset.js +1 -1
  124. package/src/operations/expansions/offset.test.js +25 -89
  125. package/src/operations/expansions/offsetFromPoints.js +11 -6
  126. package/src/operations/extrusions/extrudeLinear.js +6 -2
  127. package/src/operations/extrusions/extrudeLinear.test.js +25 -1
  128. package/src/operations/extrusions/extrudeLinearPath2.js +24 -0
  129. package/src/operations/extrusions/extrudeRectangular.js +1 -1
  130. package/src/operations/extrusions/extrudeRectangular.test.js +2 -2
  131. package/src/operations/extrusions/project.js +1 -1
  132. package/src/operations/extrusions/slice/isA.js +2 -2
  133. package/src/operations/extrusions/slice/toPolygons.js +1 -1
  134. package/src/operations/hulls/hull.test.js +1 -1
  135. package/src/operations/hulls/hullChain.js +1 -1
  136. package/src/operations/hulls/hullGeom2.js +1 -1
  137. package/src/operations/hulls/hullPath2.js +6 -4
  138. package/src/operations/hulls/hullPath2.test.js +16 -0
  139. package/src/operations/hulls/hullPoints2.test.js +1 -1
  140. package/src/operations/modifiers/edges.js +1 -1
  141. package/src/operations/modifiers/generalize.js +1 -1
  142. package/src/operations/modifiers/snap.test.js +3 -3
  143. package/src/operations/transforms/align.d.ts +1 -1
  144. package/src/operations/transforms/center.js +17 -17
  145. package/src/operations/transforms/mirror.js +12 -12
  146. package/src/operations/transforms/rotate.js +12 -12
  147. package/src/operations/transforms/scale.js +19 -19
  148. package/src/operations/transforms/transform.js +3 -3
  149. package/src/operations/transforms/translate.js +14 -14
  150. package/src/primitives/arc.js +1 -1
  151. package/src/primitives/cylinderElliptic.test.js +0 -2
  152. package/src/primitives/ellipsoid.js +1 -1
  153. package/src/primitives/ellipsoid.test.js +0 -2
  154. package/src/primitives/geodesicSphere.d.ts +0 -1
  155. package/src/primitives/polyhedron.js +1 -1
  156. package/src/primitives/torus.d.ts +0 -1
  157. package/src/primitives/triangle.js +1 -1
  158. package/src/text/vectorText.js +2 -2
  159. package/src/utils/padArrayToLength.js +1 -1
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Convert the given vector to a representative string.
3
- * @param {vec3} vector - vector of reference
3
+ * @param {vec3} vec - vector of reference
4
4
  * @returns {String} string representation
5
5
  * @alias module:modeling/maths/vec3.toString
6
6
  */
@@ -3,7 +3,7 @@ const { transform, fromValues } = require('./index')
3
3
 
4
4
  const { compareVectors } = require('../../../test/helpers/index')
5
5
 
6
- test('vec3: transform() called with three paramerters should update a vec3 with correct values', (t) => {
6
+ test('vec3: transform() called with three parameters should update a vec3 with correct values', (t) => {
7
7
  const identityMatrix = [
8
8
  1, 0, 0, 0,
9
9
  0, 1, 0, 0,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Convert the given vector to a representative string.
3
3
  *
4
- * @param {vec4} vector - vector to convert
4
+ * @param {vec4} vec - vector to convert
5
5
  * @returns {String} representative string
6
6
  * @alias module:modeling/maths/vec4.toString
7
7
  */
@@ -3,7 +3,7 @@ const { transform, fromValues } = require('./index')
3
3
 
4
4
  const { compareVectors } = require('../../../test/helpers/index')
5
5
 
6
- test('vec4: transform() called with three paramerters should update a vec4 with correct values', (t) => {
6
+ test('vec4: transform() called with three parameters should update a vec4 with correct values', (t) => {
7
7
  const identityMatrix = [
8
8
  1, 0, 0, 0,
9
9
  0, 1, 0, 0,
@@ -24,7 +24,7 @@ const measureBoundingSphereOfPath2 = (geometry) => {
24
24
  const points = path2.toPoints(geometry)
25
25
 
26
26
  if (points.length > 0) {
27
- // calculate the centriod of the geometry
27
+ // calculate the centroid of the geometry
28
28
  let numPoints = 0
29
29
  const temp = vec3.create()
30
30
  points.forEach((point) => {
@@ -60,7 +60,7 @@ const measureBoundingSphereOfGeom2 = (geometry) => {
60
60
  const sides = geom2.toSides(geometry)
61
61
 
62
62
  if (sides.length > 0) {
63
- // calculate the centriod of the geometry
63
+ // calculate the centroid of the geometry
64
64
  let numPoints = 0
65
65
  const temp = vec3.create()
66
66
  sides.forEach((side) => {
@@ -96,7 +96,7 @@ const measureBoundingSphereOfGeom3 = (geometry) => {
96
96
  const polygons = geom3.toPolygons(geometry)
97
97
 
98
98
  if (polygons.length > 0) {
99
- // calculate the centriod of the geometry
99
+ // calculate the centroid of the geometry
100
100
  let numPoints = 0
101
101
  polygons.forEach((polygon) => {
102
102
  poly3.toPoints(polygon).forEach((point) => {
@@ -122,7 +122,7 @@ const measureBoundingSphereOfGeom3 = (geometry) => {
122
122
  }
123
123
 
124
124
  /**
125
- * Measure the (aproximate) bounding sphere of the given geometries.
125
+ * Measure the (approximate) bounding sphere of the given geometries.
126
126
  * @see https://en.wikipedia.org/wiki/Bounding_sphere
127
127
  * @param {...Object} geometries - the geometries to measure
128
128
  * @return {Array} the bounding sphere for each geometry, i.e. [centroid, radius]
@@ -61,7 +61,7 @@ const measureCenterOfMassGeom3 = (geometry) => {
61
61
  let totalVolume = 0
62
62
  const vector = vec3.create() // for speed
63
63
  polygons.forEach((polygon) => {
64
- // calculate volume and center of each tetrahedon
64
+ // calculate volume and center of each tetrahedron
65
65
  const vertices = polygon.vertices
66
66
  for (let i = 0; i < vertices.length - 2; i++) {
67
67
  vec3.cross(vector, vertices[i + 1], vertices[i + 2])
@@ -4,9 +4,9 @@ const measureBoundingBox = require('../../measurements/measureBoundingBox')
4
4
 
5
5
  /*
6
6
  * Determine if the given geometries overlap by comparing min and max bounds.
7
- * NOTE: This is used in union for performace gains.
8
- * @param {geom3} geometry1 - geometry for comparision
9
- * @param {geom3} geometry2 - geometry for comparision
7
+ * NOTE: This is used in union for performance gains.
8
+ * @param {geom3} geometry1 - geometry for comparison
9
+ * @param {geom3} geometry2 - geometry for comparison
10
10
  * @returns {boolean} true if the geometries overlap
11
11
  */
12
12
  const mayOverlap = (geometry1, geometry2) => {
@@ -3,8 +3,8 @@ const poly3 = require('../../geometries/poly3')
3
3
 
4
4
  const reTesselateCoplanarPolygons = require('./reTesselateCoplanarPolygons')
5
5
 
6
- // Normals are directional vectors with component values from 0 to 1.0, requiring specialized comparision
7
- // This EPS is derived from a serieas of tests to determine the optimal precision for comparing coplanar polygons,
6
+ // Normals are directional vectors with component values from 0 to 1.0, requiring specialized comparison
7
+ // This EPS is derived from a series of tests to determine the optimal precision for comparing coplanar polygons,
8
8
  // as provided by the sphere primitive at high segmentation
9
9
  // This EPS is for 64 bit Number values
10
10
  const NEPS = 1e-13
@@ -9,7 +9,7 @@ const scissionGeom3 = require('./scissionGeom3')
9
9
  /**
10
10
  * Scission (divide) the given geometry into the component pieces.
11
11
  *
12
- * @param {...Object} geometries - list of geometries
12
+ * @param {...Object} objects - list of geometries
13
13
  * @returns {Array} list of pieces from each geometry
14
14
  * @alias module:modeling/booleans.scission
15
15
  *
@@ -17,7 +17,7 @@ const subtractGeom3 = require('./subtractGeom3')
17
17
  * @alias module:modeling/booleans.subtract
18
18
  *
19
19
  * @example
20
- * let myshape = subtract(cubiod({size: [5,5,5]}), cubiod({size: [5,5,5], center: [5,5,5]}))
20
+ * let myshape = subtract(cuboid({size: [5,5,5]}), cuboid({size: [5,5,5], center: [5,5,5]}))
21
21
  *
22
22
  * @example
23
23
  * +-------+ +-------+
@@ -198,7 +198,7 @@ test('union of one or more geom3 objects produces expected geometry', (t) => {
198
198
 
199
199
  test('union of geom3 with rounding issues #137', (t) => {
200
200
  const geometry1 = center({ relativeTo: [0, 0, -1] }, cuboid({ size: [44, 26, 5] }))
201
- const geometry2 = center({ relativeTo: [0, 0, -4.400001] }, cuboid({ size: [44, 26, 1.8] })) // introduce percision error
201
+ const geometry2 = center({ relativeTo: [0, 0, -4.400001] }, cuboid({ size: [44, 26, 1.8] })) // introduce precision error
202
202
 
203
203
  const obs = union(geometry1, geometry2)
204
204
  const pts = geom3.toPoints(obs)
@@ -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,14 +10,14 @@ 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.
13
+ * Both internal and external space is expanded for 2D and 3D shapes.
14
14
  *
15
15
  * Note: Contract is expand using a negative delta.
16
16
  * @param {Object} options - options for expand
17
17
  * @param {Number} [options.delta=1] - delta (+/-) of expansion
18
18
  * @param {String} [options.corners='edge'] - type of corner to create after expanding; edge, chamfer, round
19
19
  * @param {Integer} [options.segments=16] - number of segments when creating round corners
20
- * @param {...Objects} geometry - the list of geometry to expand
20
+ * @param {...Objects} objects - the geometries to expand
21
21
  * @return {Object|Array} new geometry, or list of new geometries
22
22
  * @alias module:modeling/expansions.expand
23
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()
@@ -13,7 +13,7 @@ const offsetPath2 = require('./offsetPath2')
13
13
  * @param {Float} [options.delta=1] - delta of offset (+ to exterior, - from interior)
14
14
  * @param {String} [options.corners='edge'] - type of corner to create after offseting; edge, chamfer, round
15
15
  * @param {Integer} [options.segments=16] - number of segments when creating round corners
16
- * @param {...Object} geometry - the list of geometry to offset
16
+ * @param {...Object} objects - the geometries to offset
17
17
  * @return {Object|Array} new geometry, or list of new geometries
18
18
  * @alias module:modeling/expansions.offset
19
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
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