@jscad/modeling 3.0.0-alpha.0 → 3.0.2-alpha.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 (161) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +1 -1
  3. package/dist/jscad-modeling.es.js +2 -2
  4. package/dist/jscad-modeling.min.js +2 -2
  5. package/package.json +2 -2
  6. package/rollup.config.js +1 -1
  7. package/src/colors/colorize.js +1 -5
  8. package/src/colors/colorize.test.js +8 -8
  9. package/src/geometries/geom2/transform.js +9 -1
  10. package/src/geometries/geom2/transform.test.js +57 -0
  11. package/src/geometries/geom3/fromPointsConvex.d.ts +4 -0
  12. package/src/geometries/geom3/fromPointsConvex.js +25 -0
  13. package/src/geometries/geom3/fromPointsConvex.test.js +32 -0
  14. package/src/geometries/geom3/index.d.ts +1 -0
  15. package/src/geometries/geom3/index.js +1 -0
  16. package/src/geometries/index.js +3 -4
  17. package/src/geometries/path2/appendBezier.js +1 -1
  18. package/src/geometries/poly3/index.js +1 -1
  19. package/src/geometries/poly3/measureBoundingBox.js +2 -0
  20. package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -1
  21. package/src/geometries/poly3/measureBoundingSphere.js +25 -8
  22. package/src/geometries/poly3/measureBoundingSphere.test.js +12 -8
  23. package/src/geometries/poly3/type.d.ts +1 -1
  24. package/src/geometries/slice/validate.js +1 -2
  25. package/src/index.js +41 -0
  26. package/src/maths/index.js +1 -0
  27. package/src/maths/mat4/isOnlyTransformScale.js +1 -1
  28. package/src/measurements/measureAggregateArea.js +0 -1
  29. package/src/measurements/measureAggregateBoundingBox.js +0 -1
  30. package/src/measurements/measureAggregateEpsilon.js +0 -1
  31. package/src/measurements/measureAggregateVolume.js +0 -1
  32. package/src/measurements/measureArea.js +0 -1
  33. package/src/measurements/measureBoundingBox.js +0 -1
  34. package/src/measurements/measureBoundingSphere.js +2 -6
  35. package/src/measurements/measureEpsilon.js +0 -1
  36. package/src/measurements/measureVolume.js +0 -1
  37. package/src/operations/booleans/index.d.ts +1 -0
  38. package/src/operations/booleans/intersect.js +5 -5
  39. package/src/operations/booleans/intersect.test.js +6 -7
  40. package/src/operations/booleans/intersectGeom2.js +2 -6
  41. package/src/operations/booleans/intersectGeom2.test.js +25 -1
  42. package/src/operations/booleans/intersectGeom3.js +2 -6
  43. package/src/operations/booleans/intersectGeom3.test.js +5 -1
  44. package/src/operations/booleans/martinez/compareEvents.js +2 -7
  45. package/src/operations/booleans/martinez/connectEdges.js +30 -41
  46. package/src/operations/booleans/martinez/contour.js +1 -1
  47. package/src/operations/booleans/martinez/divideSegment.js +12 -11
  48. package/src/operations/booleans/martinez/fillQueue.js +24 -28
  49. package/src/operations/booleans/martinez/index.js +2 -1
  50. package/src/operations/booleans/martinez/possibleIntersection.js +41 -30
  51. package/src/operations/booleans/martinez/segmentIntersection.js +7 -9
  52. package/src/operations/booleans/martinez/splaytree.js +59 -457
  53. package/src/operations/booleans/martinez/subdivideSegments.js +4 -4
  54. package/src/operations/booleans/martinez/sweepEvent.js +3 -17
  55. package/src/operations/booleans/mayOverlap.js +0 -1
  56. package/src/operations/booleans/scission.d.ts +5 -0
  57. package/src/operations/booleans/scission.js +3 -5
  58. package/src/operations/booleans/scission.test.js +6 -0
  59. package/src/operations/booleans/subtract.js +5 -5
  60. package/src/operations/booleans/subtract.test.js +6 -7
  61. package/src/operations/booleans/subtractGeom2.js +2 -6
  62. package/src/operations/booleans/subtractGeom2.test.js +25 -1
  63. package/src/operations/booleans/subtractGeom3.js +2 -6
  64. package/src/operations/booleans/subtractGeom3.test.js +5 -1
  65. package/src/operations/booleans/trees/Node.js +25 -27
  66. package/src/operations/booleans/trees/PolygonTreeNode.js +153 -106
  67. package/src/operations/booleans/trees/Tree.js +9 -4
  68. package/src/operations/booleans/trees/splitLineSegmentByPlane.js +5 -3
  69. package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +33 -0
  70. package/src/operations/booleans/trees/splitPolygonByPlane.js +39 -34
  71. package/src/operations/booleans/union.js +5 -5
  72. package/src/operations/booleans/union.test.js +6 -7
  73. package/src/operations/booleans/unionGeom2.js +2 -6
  74. package/src/operations/booleans/unionGeom2.test.js +25 -1
  75. package/src/operations/booleans/unionGeom3.js +2 -6
  76. package/src/operations/booleans/unionGeom3.test.js +6 -1
  77. package/src/operations/extrusions/extrudeFromSlices.test.js +8 -1
  78. package/src/operations/extrusions/extrudeHelical.js +2 -8
  79. package/src/operations/extrusions/extrudeLinear.js +1 -5
  80. package/src/operations/extrusions/extrudeLinear.test.js +7 -1
  81. package/src/operations/extrusions/extrudeRotate.js +3 -2
  82. package/src/operations/extrusions/extrudeRotate.test.js +13 -1
  83. package/src/operations/extrusions/extrudeWalls.js +3 -1
  84. package/src/operations/extrusions/project.js +1 -5
  85. package/src/operations/hulls/hull.js +6 -5
  86. package/src/operations/hulls/hull.test.js +56 -3
  87. package/src/operations/hulls/hullChain.js +11 -6
  88. package/src/operations/hulls/hullChain.test.js +12 -2
  89. package/src/operations/hulls/hullGeom2.js +5 -6
  90. package/src/operations/hulls/hullGeom3.js +9 -18
  91. package/src/operations/hulls/hullPath2.js +6 -7
  92. package/src/operations/hulls/hullPath2.test.js +1 -1
  93. package/src/operations/hulls/hullPoints2.d.ts +3 -0
  94. package/src/operations/hulls/hullPoints2.js +24 -30
  95. package/src/operations/hulls/hullPoints3.d.ts +4 -0
  96. package/src/operations/hulls/hullPoints3.js +21 -0
  97. package/src/operations/hulls/index.d.ts +2 -0
  98. package/src/operations/hulls/index.js +3 -1
  99. package/src/operations/modifiers/generalize.js +2 -6
  100. package/src/operations/modifiers/index.js +1 -1
  101. package/src/operations/modifiers/mergePolygons.js +2 -3
  102. package/src/operations/modifiers/reTesselateCoplanarPolygons.js +7 -7
  103. package/src/operations/modifiers/snap.js +2 -6
  104. package/src/operations/offsets/offset.js +1 -5
  105. package/src/operations/offsets/offsetFromPoints.test.js +0 -1
  106. package/src/operations/offsets/offsetGeom2.test.js +1 -0
  107. package/src/operations/offsets/offsetGeom3.js +0 -2
  108. package/src/operations/offsets/offsetGeom3.test.js +9 -1
  109. package/src/operations/offsets/offsetPath2.js +3 -3
  110. package/src/operations/transforms/align.js +8 -7
  111. package/src/operations/transforms/align.test.js +2 -2
  112. package/src/operations/transforms/center.js +6 -9
  113. package/src/operations/transforms/center.test.js +19 -1
  114. package/src/operations/transforms/mirror.js +5 -8
  115. package/src/operations/transforms/mirror.test.js +7 -7
  116. package/src/operations/transforms/rotate.js +5 -8
  117. package/src/operations/transforms/scale.js +5 -8
  118. package/src/operations/transforms/transform.js +2 -5
  119. package/src/operations/transforms/translate.js +5 -8
  120. package/src/primitives/arc.js +2 -0
  121. package/src/primitives/arc.test.js +11 -11
  122. package/src/primitives/circle.test.js +18 -8
  123. package/src/primitives/cube.test.js +10 -0
  124. package/src/primitives/cuboid.test.js +10 -0
  125. package/src/primitives/cylinder.test.js +12 -0
  126. package/src/primitives/cylinderElliptic.test.js +21 -1
  127. package/src/primitives/ellipse.test.js +18 -8
  128. package/src/primitives/ellipsoid.test.js +12 -0
  129. package/src/primitives/geodesicSphere.test.js +8 -0
  130. package/src/primitives/line.test.js +1 -1
  131. package/src/primitives/polygon.d.ts +1 -0
  132. package/src/primitives/polygon.js +13 -4
  133. package/src/primitives/polygon.test.js +15 -0
  134. package/src/primitives/polyhedron.js +1 -0
  135. package/src/primitives/polyhedron.test.js +8 -2
  136. package/src/primitives/rectangle.test.js +9 -3
  137. package/src/primitives/roundedCuboid.js +1 -1
  138. package/src/primitives/roundedCuboid.test.js +20 -4
  139. package/src/primitives/roundedCylinder.js +1 -1
  140. package/src/primitives/roundedCylinder.test.js +20 -0
  141. package/src/primitives/roundedRectangle.js +1 -1
  142. package/src/primitives/roundedRectangle.test.js +15 -6
  143. package/src/primitives/sphere.test.js +12 -0
  144. package/src/primitives/square.test.js +10 -4
  145. package/src/primitives/star.test.js +14 -6
  146. package/src/primitives/torus.js +1 -1
  147. package/src/primitives/torus.test.js +11 -1
  148. package/src/primitives/triangle.test.js +17 -9
  149. package/src/utils/coalesce.d.ts +3 -0
  150. package/src/utils/coalesce.js +20 -0
  151. package/src/utils/index.js +2 -2
  152. package/src/maths/mat4/leftMultiplyVec2.d.ts +0 -4
  153. package/src/maths/mat4/leftMultiplyVec2.js +0 -26
  154. package/src/maths/mat4/leftMultiplyVec3.d.ts +0 -4
  155. package/src/maths/mat4/leftMultiplyVec3.js +0 -27
  156. package/src/maths/mat4/mirror.d.ts +0 -4
  157. package/src/maths/mat4/mirror.js +0 -32
  158. package/src/maths/mat4/rightMultiplyVec2.d.ts +0 -4
  159. package/src/maths/mat4/rightMultiplyVec2.js +0 -27
  160. package/src/maths/mat4/rightMultiplyVec3.d.ts +0 -4
  161. package/src/maths/mat4/rightMultiplyVec3.js +0 -28
@@ -1,13 +1,15 @@
1
1
  import test from 'ava'
2
2
 
3
3
  import { geom2, geom3 } from '../../geometries/index.js'
4
- import { measureArea } from '../../measurements/measureArea.js'
4
+
5
+ import { measureArea, measureVolume } from '../../measurements/index.js'
6
+
5
7
  import { square } from '../../primitives/square.js'
6
8
 
7
9
  import { hullChain } from './index.js'
8
10
 
9
11
  test('hullChain: hullChain single geometry', (t) => {
10
- const result = hullChain([ square({ size: 1 }) ])
12
+ const result = hullChain([square({ size: 1 })])
11
13
  t.notThrows(() => geom2.validate(result))
12
14
  t.is(measureArea(result), 1)
13
15
  t.is(geom2.toPoints(result).length, 4)
@@ -22,6 +24,7 @@ test('hullChain (two, geom2)', (t) => {
22
24
  let pts = geom2.toPoints(obs)
23
25
 
24
26
  t.notThrows(() => geom2.validate(obs))
27
+ t.is(measureArea(obs), 9)
25
28
  t.is(pts.length, 4)
26
29
 
27
30
  // different
@@ -29,6 +32,7 @@ test('hullChain (two, geom2)', (t) => {
29
32
  pts = geom2.toPoints(obs)
30
33
 
31
34
  t.notThrows(() => geom2.validate(obs))
35
+ t.is(measureArea(obs), 81)
32
36
  t.is(pts.length, 6)
33
37
  })
34
38
 
@@ -43,6 +47,7 @@ test('hullChain (three, geom2)', (t) => {
43
47
 
44
48
  // the sides change based on the bestplane chosen in trees/Node.js
45
49
  t.notThrows(() => geom2.validate(obs))
50
+ t.is(measureArea(obs), 126)
46
51
  t.is(pts.length, 10)
47
52
 
48
53
  // closed
@@ -51,6 +56,7 @@ test('hullChain (three, geom2)', (t) => {
51
56
 
52
57
  // the sides change based on the bestplane chosen in trees/Node.js
53
58
  t.notThrows(() => geom2.validate(obs))
59
+ t.is(measureArea(obs), 148.21875)
54
60
  t.is(pts.length, 10)
55
61
  })
56
62
 
@@ -85,6 +91,8 @@ test('hullChain (three, geom3)', (t) => {
85
91
  let pts = geom3.toPoints(obs)
86
92
 
87
93
  t.notThrows.skip(() => geom3.validate(obs))
94
+ t.is(measureArea(obs), 266.1454764345133)
95
+ t.is(measureVolume(obs), 239.2012987012987)
88
96
  t.is(pts.length, 23)
89
97
 
90
98
  // closed
@@ -92,5 +100,7 @@ test('hullChain (three, geom3)', (t) => {
92
100
  pts = geom3.toPoints(obs)
93
101
 
94
102
  t.notThrows.skip(() => geom3.validate(obs))
103
+ t.is(measureArea(obs), 272.2887171436021)
104
+ t.is(measureVolume(obs), 261.96982218883045)
95
105
  t.is(pts.length, 28)
96
106
  })
@@ -1,5 +1,3 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import * as geom2 from '../../geometries/geom2/index.js'
4
2
 
5
3
  import { hullPoints2 } from './hullPoints2.js'
@@ -7,12 +5,13 @@ import { toUniquePoints } from './toUniquePoints.js'
7
5
 
8
6
  /*
9
7
  * Create a convex hull of the given geom2 geometries.
10
- * @param {...geometries} geometries - list of geom2 geometries
8
+ *
9
+ * NOTE: The given geometries must be valid geom2 geometries.
10
+ *
11
+ * @param {Geom2[]} geometries - a flat list of 2D geometries
11
12
  * @returns {Geom2} new geometry
12
13
  */
13
- export const hullGeom2 = (...geometries) => {
14
- geometries = flatten(geometries)
15
-
14
+ export const hullGeom2 = (geometries) => {
16
15
  // extract the unique points from the geometries
17
16
  const unique = toUniquePoints(geometries)
18
17
 
@@ -1,30 +1,21 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import * as geom3 from '../../geometries/geom3/index.js'
4
- import * as poly3 from '../../geometries/poly3/index.js'
5
2
 
6
- import { runner } from './quickhull/index.js'
3
+ import { hullPoints3 } from './hullPoints3.js'
7
4
  import { toUniquePoints } from './toUniquePoints.js'
8
5
 
9
6
  /*
10
- * Create a convex hull of the given geometries (geom3).
11
- * @param {...geometries} geometries - list of geom3 geometries
7
+ * Create a convex hull of the given geom3 geometries.
8
+ *
9
+ * NOTE: The given geometries must be valid geom3 geometries.
10
+ *
11
+ * @param {Geom3[]} geometries - a flat list of 3D geometries
12
12
  * @returns {Geom3} new geometry
13
13
  */
14
- export const hullGeom3 = (...geometries) => {
15
- geometries = flatten(geometries)
16
-
17
- if (geometries.length === 1) return geometries[0]
18
-
14
+ export const hullGeom3 = (geometries) => {
19
15
  // extract the unique vertices from the geometries
20
16
  const unique = toUniquePoints(geometries)
21
17
 
22
- const faces = runner(unique, { skipTriangulation: true })
23
-
24
- const polygons = faces.map((face) => {
25
- const vertices = face.map((index) => unique[index])
26
- return poly3.create(vertices)
27
- })
18
+ if (unique.length === 0) return geom3.create()
28
19
 
29
- return geom3.create(polygons)
20
+ return geom3.create(hullPoints3(unique))
30
21
  }
@@ -1,18 +1,17 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import * as path2 from '../../geometries/path2/index.js'
4
2
 
5
3
  import { hullPoints2 } from './hullPoints2.js'
6
4
  import { toUniquePoints } from './toUniquePoints.js'
7
5
 
8
6
  /*
9
- * Create a convex hull of the given geometries (path2).
10
- * @param {...geometries} geometries - list of path2 geometries
7
+ * Create a convex hull of the given path2 geometries.
8
+ *
9
+ * NOTE: The given geometries must be valid path2 geometry.
10
+ *
11
+ * @param {Path2[]} geometries - a flat list of path2 geometries
11
12
  * @returns {Path2} new geometry
12
13
  */
13
- export const hullPath2 = (...geometries) => {
14
- geometries = flatten(geometries)
15
-
14
+ export const hullPath2 = (geometries) => {
16
15
  // extract the unique points from the geometries
17
16
  const unique = toUniquePoints(geometries)
18
17
 
@@ -9,7 +9,7 @@ test('hullPath2', (t) => {
9
9
  const geometry1 = path2.fromPoints({ closed }, [[0, 0], [-4, 4], [-4, -4]])
10
10
  const geometry2 = path2.fromPoints({ closed }, [[0, 0], [4, -4], [4, 4]])
11
11
 
12
- const obs = hullPath2(geometry1, geometry2)
12
+ const obs = hullPath2([geometry1, geometry2])
13
13
  t.notThrows(() => path2.validate(obs))
14
14
  const pts = path2.toPoints(obs)
15
15
  t.is(pts.length, 4)
@@ -0,0 +1,3 @@
1
+ import type { Vec2 } from '../../maths/vec2/type.d.ts'
2
+
3
+ export function hullPoints2(uniquePoints: Array<Vec2>): Array<Vec2>
@@ -1,10 +1,12 @@
1
1
  import * as vec2 from '../../maths/vec2/index.js'
2
2
 
3
- /*
3
+ /**
4
4
  * Create a convex hull of the given set of points, where each point is an array of [x,y].
5
- * Uses https://en.wikipedia.org/wiki/Graham_scan
5
+ *
6
+ * @see https://en.wikipedia.org/wiki/Graham_scan
6
7
  * @param {Array} uniquePoints - list of UNIQUE points from which to create a hull
7
8
  * @returns {Array} a list of points that form the hull
9
+ * @alias module:modeling/hulls.hullPoints2
8
10
  */
9
11
  export const hullPoints2 = (uniquePoints) => {
10
12
  // find min point
@@ -15,28 +17,32 @@ export const hullPoints2 = (uniquePoints) => {
15
17
  }
16
18
  })
17
19
 
18
- // gather information for sorting by polar coordinates (point, angle, distSq)
19
- const points = []
20
- uniquePoints.forEach((point) => {
21
- // use faster fakeAtan2 instead of Math.atan2
22
- const angle = fakeAtan2(point[1] - min[1], point[0] - min[0])
23
- const distSq = vec2.squaredDistance(point, min)
24
- points.push({ point, angle, distSq })
25
- })
20
+ // calculations relative to min point
21
+ const squaredDistance = (point) => vec2.squaredDistance(point, min)
22
+ const polarAngle = (point) => (point[0] === min[0] && point[1] === min[1]) ? -Infinity : -(point[0] - min[0]) / (point[1] - min[1])
26
23
 
27
- // sort by polar coordinates
28
- points.sort((pt1, pt2) => pt1.angle !== pt2.angle
29
- ? pt1.angle - pt2.angle
30
- : pt1.distSq - pt2.distSq)
24
+ // points are sorted by polar angle in clockwise order
25
+ const sorted = uniquePoints
26
+ sorted.sort((pt1, pt2) => {
27
+ const pa1 = polarAngle(pt1)
28
+ const pa2 = polarAngle(pt2)
29
+ if (pa1 === pa2) {
30
+ // sort by the relative distances to min point
31
+ return squaredDistance(pt1) - squaredDistance(pt2)
32
+ }
33
+ // sort by polar angles to min point
34
+ return pa1 - pa2
35
+ })
31
36
 
32
37
  const stack = [] // start with empty stack
33
- points.forEach((point) => {
38
+ sorted.forEach((point) => {
34
39
  let cnt = stack.length
35
- while (cnt > 1 && ccw(stack[cnt - 2], stack[cnt - 1], point.point) <= Number.EPSILON) {
36
- stack.pop() // get rid of colinear and interior (clockwise) points
40
+ while (cnt > 1 && ccw(stack[cnt - 2], stack[cnt - 1], point) <= Number.EPSILON) {
41
+ // get rid of colinear and interior (clockwise) points
42
+ stack.pop()
37
43
  cnt = stack.length
38
44
  }
39
- stack.push(point.point)
45
+ stack.push(point)
40
46
  })
41
47
 
42
48
  return stack
@@ -44,15 +50,3 @@ export const hullPoints2 = (uniquePoints) => {
44
50
 
45
51
  // returns: < 0 clockwise, 0 colinear, > 0 counter-clockwise
46
52
  const ccw = (v1, v2, v3) => (v2[0] - v1[0]) * (v3[1] - v1[1]) - (v2[1] - v1[1]) * (v3[0] - v1[0])
47
-
48
- // Returned "angle" is really 1/tan (inverse of slope) made negative to increase with angle.
49
- // This function is strictly for sorting in this algorithm.
50
- const fakeAtan2 = (y, x) => {
51
- // The "if" is a special case for when the minimum vector found in loop above is present.
52
- // We need to ensure that it sorts as the minimum point. Otherwise, this becomes NaN.
53
- if (y === 0 && x === 0) {
54
- return -Infinity
55
- } else {
56
- return -x / y
57
- }
58
- }
@@ -0,0 +1,4 @@
1
+ import type { Vec3 } from '../../maths/vec3/type.d.ts'
2
+ import type { Poly3 } from '../../geometries/poly3/type.d.ts'
3
+
4
+ export function hullPoints3(uniquePoints: Array<Vec3>): Array<Poly3>
@@ -0,0 +1,21 @@
1
+ import * as poly3 from '../../geometries/poly3/index.js'
2
+
3
+ import { runner } from './quickhull/index.js'
4
+
5
+ /**
6
+ * Create a convex hull of the given set of points, where each point is an array of [x,y,z].
7
+ *
8
+ * @param {Array} uniquePoints - list of UNIQUE points from which to create a hull
9
+ * @returns {Array} a list of polygons (poly3)
10
+ * @alias module:modeling/hulls.hullPoints3
11
+ */
12
+ export const hullPoints3 = (uniquePoints) => {
13
+ const faces = runner(uniquePoints, { skipTriangulation: true })
14
+
15
+ const polygons = faces.map((face) => {
16
+ const vertices = face.map((index) => uniquePoints[index])
17
+ return poly3.create(vertices)
18
+ })
19
+
20
+ return polygons
21
+ }
@@ -1,2 +1,4 @@
1
1
  export { hull } from './hull.js'
2
2
  export { hullChain } from './hullChain.js'
3
+ export { hullPoints2 } from './hullPoints2.js'
4
+ export { hullPoints3 } from './hullPoints3.js'
@@ -4,7 +4,9 @@
4
4
  * In all cases, the function returns the results, and never changes the original shapes.
5
5
  * @module modeling/hulls
6
6
  * @example
7
- * import { hull, hullChain } from '@jscad/modeling'
7
+ * import { hull, hullChain, hullPoints2, hullPoints3 } from '@jscad/modeling'
8
8
  */
9
9
  export { hull } from './hull.js'
10
10
  export { hullChain } from './hullChain.js'
11
+ export { hullPoints2 } from './hullPoints2.js'
12
+ export { hullPoints3 } from './hullPoints3.js'
@@ -1,5 +1,3 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import { measureEpsilon } from '../../measurements/measureEpsilon.js'
4
2
 
5
3
  import * as geom2 from '../../geometries/geom2/index.js'
@@ -67,14 +65,12 @@ const generalizeGeom3 = (options, geometry) => {
67
65
  * @alias module:modeling/modifiers.generalize
68
66
  */
69
67
  export const generalize = (options, ...geometries) => {
70
- geometries = flatten(geometries)
71
- if (geometries.length === 0) throw new Error('wrong number of arguments')
72
-
73
68
  const results = geometries.map((geometry) => {
74
69
  if (path2.isA(geometry)) return generalizePath2(options, geometry)
75
70
  if (geom2.isA(geometry)) return generalizeGeom2(options, geometry)
76
71
  if (geom3.isA(geometry)) return generalizeGeom3(options, geometry)
77
- throw new Error('invalid geometry')
72
+ if (Array.isArray(geometry)) return generalize(options, ...geometry)
73
+ return geometry
78
74
  })
79
75
  return results.length === 1 ? results[0] : results
80
76
  }
@@ -3,7 +3,7 @@
3
3
  * In all cases, these functions returns the results, and never changes the original geometry.
4
4
  * @module modeling/modifiers
5
5
  * @example
6
- * import { generalize, snap } from '@jscad/modeling'
6
+ * import { generalize, snap, retessellate } from '@jscad/modeling'
7
7
  */
8
8
  export { generalize } from './generalize.js'
9
9
  export { snap } from './snap.js'
@@ -67,7 +67,6 @@ const calculateAngle = (prevVertex, midVertex, nextVertex, normal) => {
67
67
 
68
68
  // create a polygon starting from the given edge (if possible)
69
69
  const createPolygonAnd = (edge) => {
70
- let polygon
71
70
  const vertices = []
72
71
  while (edge.next) {
73
72
  const next = edge.next
@@ -81,8 +80,8 @@ const createPolygonAnd = (edge) => {
81
80
 
82
81
  edge = next
83
82
  }
84
- if (vertices.length > 0) polygon = poly3.create(vertices)
85
- return polygon
83
+ if (vertices.length > 0) return poly3.create(vertices)
84
+ return null
86
85
  }
87
86
 
88
87
  /*
@@ -31,7 +31,7 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
31
31
  // Make a list of all encountered y coordinates
32
32
  // And build a map of all polygons that have a vertex at a certain y coordinate:
33
33
  const yCoordinateBins = new Map()
34
- const yCoordinateBinningFactor = 10 / EPS
34
+ const yCoordinateBinningFactor = 10 / EPS // FIXME
35
35
  for (let polygonIndex = 0; polygonIndex < numPolygons; polygonIndex++) {
36
36
  const poly3d = sourcePolygons[polygonIndex]
37
37
  let vertices2d = []
@@ -68,7 +68,7 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
68
68
  }
69
69
  let polygonIndexes = yCoordinateToPolygonIndexes.get(y)
70
70
  if (!polygonIndexes) {
71
- polygonIndexes = {} // PERF
71
+ polygonIndexes = []
72
72
  yCoordinateToPolygonIndexes.set(y, polygonIndexes)
73
73
  }
74
74
  polygonIndexes[polygonIndex] = true
@@ -242,7 +242,7 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
242
242
  const prevOutPolygon = newOutPolygonRow[newOutPolygonRow.length - 1]
243
243
  const d1 = vec2.distance(outPolygon.topLeft, prevOutPolygon.topRight)
244
244
  const d2 = vec2.distance(outPolygon.bottomLeft, prevOutPolygon.bottomRight)
245
- if ((d1 < EPS) && (d2 < EPS)) {
245
+ if ((d1 < EPS) && (d2 < EPS)) { // FIXME
246
246
  // we can join this polygon with the one to the left:
247
247
  outPolygon.topLeft = prevOutPolygon.topLeft
248
248
  outPolygon.leftLine = prevOutPolygon.leftLine
@@ -263,8 +263,8 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
263
263
  // We have a match if the sidelines are equal or if the top coordinates
264
264
  // are on the sidelines of the previous polygon
265
265
  const prevPolygon = prevOutPolygonRow[ii]
266
- if (vec2.distance(prevPolygon.bottomLeft, thisPolygon.topLeft) < EPS) {
267
- if (vec2.distance(prevPolygon.bottomRight, thisPolygon.topRight) < EPS) {
266
+ if (vec2.distance(prevPolygon.bottomLeft, thisPolygon.topLeft) < EPS) { // FIXME
267
+ if (vec2.distance(prevPolygon.bottomRight, thisPolygon.topRight) < EPS) { // FIXME
268
268
  // Yes, the top of this polygon matches the bottom of the previous:
269
269
  matchedIndexes.add(ii)
270
270
  // Now check if the joined polygon would remain convex:
@@ -300,7 +300,7 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
300
300
  // Finish the polygon with the last point(s):
301
301
  const prevPolygon = prevOutPolygonRow[ii]
302
302
  prevPolygon.outPolygon.rightPoints.push(prevPolygon.bottomRight)
303
- if (vec2.distance(prevPolygon.bottomRight, prevPolygon.bottomLeft) > EPS) {
303
+ if (vec2.distance(prevPolygon.bottomRight, prevPolygon.bottomLeft) > EPS) { // FIXME
304
304
  // polygon ends with a horizontal line:
305
305
  prevPolygon.outPolygon.leftPoints.push(prevPolygon.bottomLeft)
306
306
  }
@@ -324,7 +324,7 @@ export const reTesselateCoplanarPolygons = (sourcePolygons) => {
324
324
  rightPoints: []
325
325
  }
326
326
  thisPolygon.outPolygon.leftPoints.push(thisPolygon.topLeft)
327
- if (vec2.distance(thisPolygon.topLeft, thisPolygon.topRight) > EPS) {
327
+ if (vec2.distance(thisPolygon.topLeft, thisPolygon.topRight) > EPS) { // FIXME
328
328
  // we have a horizontal line at the top:
329
329
  thisPolygon.outPolygon.rightPoints.push(thisPolygon.topRight)
330
330
  }
@@ -1,5 +1,3 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import * as vec2 from '../../maths/vec2/index.js'
4
2
 
5
3
  import * as geom2 from '../../geometries/geom2/index.js'
@@ -48,20 +46,18 @@ const snapGeom3 = (geometry) => {
48
46
  }
49
47
 
50
48
  /**
51
- * Snap the given geometries to the overall precision (epsilon) of the geometry.
49
+ * Snap the given geometries to the precision (calculated epsilon) of the geometry.
52
50
  * @see measurements.measureEpsilon()
53
51
  * @param {...Object} geometries - the geometries to snap
54
52
  * @return {Object|Array} the snapped geometry, or a list of snapped geometries
55
53
  * @alias module:modeling/modifiers.snap
56
54
  */
57
55
  export const snap = (...geometries) => {
58
- geometries = flatten(geometries)
59
- if (geometries.length === 0) throw new Error('wrong number of arguments')
60
-
61
56
  const results = geometries.map((geometry) => {
62
57
  if (path2.isA(geometry)) return snapPath2(geometry)
63
58
  if (geom2.isA(geometry)) return snapGeom2(geometry)
64
59
  if (geom3.isA(geometry)) return snapGeom3(geometry)
60
+ if (Array.isArray(geometry)) return snap(...geometry)
65
61
  return geometry
66
62
  })
67
63
  return results.length === 1 ? results[0] : results
@@ -1,5 +1,3 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import * as geom2 from '../../geometries/geom2/index.js'
4
2
  import * as geom3 from '../../geometries/geom3/index.js'
5
3
  import * as path2 from '../../geometries/path2/index.js'
@@ -23,13 +21,11 @@ import { offsetPath2 } from './offsetPath2.js'
23
21
  * let small = offset({ delta: -4, corners: 'chamfer' }, square({size: 40})) // contract
24
22
  */
25
23
  export const offset = (options, ...objects) => {
26
- objects = flatten(objects)
27
- if (objects.length === 0) throw new Error('wrong number of arguments')
28
-
29
24
  const results = objects.map((object) => {
30
25
  if (path2.isA(object)) return offsetPath2(options, object)
31
26
  if (geom2.isA(object)) return offsetGeom2(options, object)
32
27
  if (geom3.isA(object)) return offsetGeom3(options, object)
28
+ if (Array.isArray(object)) return offset(options, ...object)
33
29
  return object
34
30
  })
35
31
  return results.length === 1 ? results[0] : results
@@ -200,7 +200,6 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
200
200
 
201
201
  test('offset (corners: round): offset of a path2 produces expected offset path2', (t) => {
202
202
  const openline = [[-5, -5], [5, -5], [5, 5], [3, 5], [3, 0], [-3, 0], [-3, 5], [-5, 5]]
203
- const closeline = [[-5, -5], [5, -5], [5, 5], [3, 5], [3, 0], [-3, 0], [-3, 5], [-5, 5], [-5, -5]]
204
203
 
205
204
  let pts = offsetFromPoints({ delta: 1, corners: 'round', segments: 16 }, openline)
206
205
  let obs = path2.create(pts)
@@ -61,6 +61,7 @@ test('offset: offset of a geom2 produces expected changes to points', (t) => {
61
61
  [-10, -8]
62
62
  ]
63
63
  t.notThrows(() => geom2.validate(obs))
64
+ t.is(measureArea(obs), 395.3137084989848)
64
65
  t.is(pts.length, 12)
65
66
  t.true(comparePoints(pts, exp))
66
67
  })
@@ -1,5 +1,3 @@
1
- import * as geom3 from '../../geometries/geom3/index.js'
2
-
3
1
  import { union } from '../booleans/union.js'
4
2
 
5
3
  import { offsetShell } from './offsetShell.js'
@@ -3,8 +3,11 @@ import test from 'ava'
3
3
  import { comparePoints } from '../../../test/helpers/index.js'
4
4
 
5
5
  import { colorize } from '../../colors/index.js'
6
+
6
7
  import { geom3, poly3 } from '../../geometries/index.js'
7
- import { measureVolume } from '../../measurements/index.js'
8
+
9
+ import { measureArea, measureVolume } from '../../measurements/index.js'
10
+
8
11
  import { cube, sphere } from '../../primitives/index.js'
9
12
 
10
13
  import { offset } from './index.js'
@@ -13,6 +16,7 @@ test('offset: offset empty geom3', (t) => {
13
16
  const geometry = geom3.create()
14
17
  const result = offset({ }, geometry)
15
18
  t.notThrows(() => geom3.validate(result))
19
+ t.is(measureArea(result), 0)
16
20
  t.is(measureVolume(result), 0)
17
21
  t.is(geom3.toPolygons(result).length, 0)
18
22
  t.is(geom3.toPoints(result).length, 0)
@@ -50,6 +54,8 @@ test('offset: offset of a geom3 produces expected changes to polygons', (t) => {
50
54
  ]
51
55
 
52
56
  t.notThrows.skip(() => geom3.validate(obs))
57
+ t.is(measureArea(obs), 3178.8059464475555)
58
+ t.is(measureVolume(obs), 13504.574121271067)
53
59
  t.is(pts.length, 62)
54
60
  t.true(comparePoints(pts[0], exp0))
55
61
  t.true(comparePoints(pts[61], exp61))
@@ -58,6 +64,8 @@ test('offset: offset of a geom3 produces expected changes to polygons', (t) => {
58
64
  const obs2 = offset({ delta: 5 }, geometry2)
59
65
  const pts2 = geom3.toPoints(obs2)
60
66
  t.notThrows.skip(() => geom3.validate(obs2))
67
+ t.is(measureArea(obs), 3178.8059464475555)
68
+ t.is(measureVolume(obs), 13504.574121271067)
61
69
  t.is(pts2.length, 864)
62
70
  })
63
71
 
@@ -85,9 +85,9 @@ export const offsetPath2 = (options, geometry) => {
85
85
  internal: offsetFromPoints({ delta: -delta, corners, segments, closed }, points)
86
86
  }
87
87
 
88
- const output = geometry.isClosed ?
89
- createGeometryFromClosedPath(paths) :
90
- createGeometryFromOpenPath(paths, segments, corners, delta)
88
+ const output = geometry.isClosed
89
+ ? createGeometryFromClosedPath(paths)
90
+ : createGeometryFromOpenPath(paths, segments, corners, delta)
91
91
  if (geometry.color) output.color = geometry.color
92
92
  return output
93
93
  }
@@ -1,4 +1,6 @@
1
- import { flatten } from '../../utils/flatten.js'
1
+ import * as vec3 from '../../maths/vec3/index.js'
2
+
3
+ import { coalesce } from '../../utils/coalesce.js'
2
4
  import { padArrayToLength } from '../../utils/padArrayToLength.js'
3
5
 
4
6
  import { measureAggregateBoundingBox } from '../../measurements/measureAggregateBoundingBox.js'
@@ -34,9 +36,9 @@ const populateRelativeToFromBounds = (relativeTo, modes, bounds) => {
34
36
  return relativeTo
35
37
  }
36
38
 
37
- const alignGeometries = (geometry, modes, relativeTo) => {
38
- const bounds = measureAggregateBoundingBox(geometry)
39
- const translation = [0, 0, 0]
39
+ const alignGeometries = (geometries, modes, relativeTo) => {
40
+ const bounds = measureAggregateBoundingBox(geometries)
41
+ const translation = vec3.create()
40
42
  for (let i = 0; i < 3; i++) {
41
43
  if (modes[i] === 'center') {
42
44
  translation[i] = relativeTo[i] - (bounds[0][i] + bounds[1][i]) / 2
@@ -47,7 +49,7 @@ const alignGeometries = (geometry, modes, relativeTo) => {
47
49
  }
48
50
  }
49
51
 
50
- return translate(translation, geometry)
52
+ return translate(translation, geometries)
51
53
  }
52
54
 
53
55
  /**
@@ -73,14 +75,13 @@ export const align = (options, ...geometries) => {
73
75
 
74
76
  options = validateOptions(options)
75
77
  let { modes, relativeTo, grouped } = options
76
- geometries = flatten(geometries)
77
- if (geometries.length === 0) throw new Error('align(): No geometries were provided to act upon')
78
78
 
79
79
  if (relativeTo.filter((val) => val == null).length) {
80
80
  const bounds = measureAggregateBoundingBox(geometries)
81
81
  relativeTo = populateRelativeToFromBounds(relativeTo, modes, bounds)
82
82
  }
83
83
  if (grouped) {
84
+ geometries = coalesce(geometries)
84
85
  geometries = alignGeometries(geometries, modes, relativeTo)
85
86
  } else {
86
87
  geometries = geometries.map((geometry) => alignGeometries(geometry, modes, relativeTo))
@@ -53,7 +53,7 @@ test('align: multiple objects ungrouped returns geometry aligned, different mode
53
53
  cube({ size: 4, center: [10, 10, 10] }),
54
54
  cube({ size: 2, center: [4, 4, 4] })
55
55
  ]
56
- const aligned = align({ modes: ['center', 'min', 'max'], relativeTo: [30, 30, 30] }, original)
56
+ const aligned = align({ modes: ['center', 'min', 'max'], relativeTo: [30, 30, 30] }, ...original)
57
57
  const bounds = measureAggregateBoundingBox(aligned)
58
58
  const expectedBounds = [[28, 30, 26], [32, 34, 30]]
59
59
  t.notThrows(() => geom3.validate(aligned[0]))
@@ -79,7 +79,7 @@ test('align: multiple objects ungrouped, relativeTo is nulls, returns geometry a
79
79
  cube({ size: 2, center: [4, 4, 4] }),
80
80
  cube({ size: 4, center: [10, 10, 10] })
81
81
  ]
82
- const aligned = align({ modes: ['center', 'min', 'max'], relativeTo: [null, null, null], grouped: false }, original)
82
+ const aligned = align({ modes: ['center', 'min', 'max'], relativeTo: [null, null, null], grouped: false }, ...original)
83
83
  const bounds = measureAggregateBoundingBox(aligned)
84
84
  const expectedBounds = [[5.5, 3, 8], [9.5, 7, 12]]
85
85
  t.notThrows(() => geom3.validate(aligned[0]))