@jscad/modeling 3.0.3-alpha.0 → 3.0.4-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 (148) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/jscad-modeling.es.js +2 -7
  3. package/dist/jscad-modeling.min.js +2 -7
  4. package/package.json +6 -7
  5. package/rollup.config.js +8 -4
  6. package/src/curves/bezier/arcLengthToT.js +1 -1
  7. package/src/curves/bezier/create.js +1 -1
  8. package/src/curves/bezier/index.js +7 -7
  9. package/src/curves/bezier/length.js +1 -1
  10. package/src/curves/bezier/lengths.js +2 -1
  11. package/src/curves/bezier/tangentAt.js +1 -1
  12. package/src/curves/bezier/valueAt.js +1 -1
  13. package/src/curves/index.js +3 -3
  14. package/src/geometries/geom2/applyTransforms.js +3 -1
  15. package/src/geometries/geom2/clone.js +5 -1
  16. package/src/geometries/geom2/create.js +4 -14
  17. package/src/geometries/geom2/fromSides.js +4 -2
  18. package/src/geometries/geom2/index.js +21 -5
  19. package/src/geometries/geom2/isA.js +5 -1
  20. package/src/geometries/geom2/reverse.js +4 -2
  21. package/src/geometries/geom2/toOutlines.js +2 -1
  22. package/src/geometries/geom2/toPoints.js +5 -2
  23. package/src/geometries/geom2/toSides.js +4 -3
  24. package/src/geometries/geom2/toString.js +3 -2
  25. package/src/geometries/geom2/transform.js +4 -2
  26. package/src/geometries/geom2/validate.js +6 -2
  27. package/src/geometries/geom3/clone.js +5 -1
  28. package/src/geometries/geom3/create.js +5 -19
  29. package/src/geometries/geom3/fromVertices.js +13 -1
  30. package/src/geometries/geom3/fromVerticesConvex.js +1 -1
  31. package/src/geometries/geom3/index.d.ts +1 -0
  32. package/src/geometries/geom3/index.js +26 -4
  33. package/src/geometries/geom3/invert.js +5 -1
  34. package/src/geometries/geom3/isA.js +5 -1
  35. package/src/geometries/geom3/isConvex.d.ts +3 -0
  36. package/src/geometries/geom3/isConvex.js +65 -0
  37. package/src/geometries/geom3/isConvex.test.js +44 -0
  38. package/src/geometries/geom3/toPolygons.js +4 -2
  39. package/src/geometries/geom3/toString.js +3 -2
  40. package/src/geometries/geom3/toVertices.js +8 -4
  41. package/src/geometries/geom3/transform.js +5 -2
  42. package/src/geometries/geom3/validate.js +6 -2
  43. package/src/geometries/index.js +9 -7
  44. package/src/geometries/path2/appendArc.js +7 -5
  45. package/src/geometries/path2/appendArc.test.js +11 -15
  46. package/src/geometries/path2/appendBezier.js +6 -4
  47. package/src/geometries/path2/appendPoints.js +4 -2
  48. package/src/geometries/path2/applyTransforms.js +3 -0
  49. package/src/geometries/path2/clone.js +5 -1
  50. package/src/geometries/path2/close.js +5 -1
  51. package/src/geometries/path2/concat.js +3 -2
  52. package/src/geometries/path2/create.js +4 -15
  53. package/src/geometries/path2/equals.js +12 -7
  54. package/src/geometries/path2/fromPoints.js +5 -3
  55. package/src/geometries/path2/index.js +21 -4
  56. package/src/geometries/path2/isA.js +5 -1
  57. package/src/geometries/path2/reverse.js +4 -2
  58. package/src/geometries/path2/toPoints.js +5 -3
  59. package/src/geometries/path2/toString.js +3 -2
  60. package/src/geometries/path2/transform.js +4 -2
  61. package/src/geometries/path2/validate.js +5 -1
  62. package/src/geometries/path3/applyTransforms.js +1 -1
  63. package/src/geometries/path3/close.js +4 -2
  64. package/src/geometries/path3/concat.js +2 -3
  65. package/src/geometries/path3/create.js +4 -20
  66. package/src/geometries/path3/equals.js +4 -2
  67. package/src/geometries/path3/fromVertices.js +2 -3
  68. package/src/geometries/path3/index.js +17 -1
  69. package/src/geometries/path3/isA.js +4 -2
  70. package/src/geometries/path3/reverse.js +2 -3
  71. package/src/geometries/path3/toString.js +2 -3
  72. package/src/geometries/path3/toVertices.js +2 -3
  73. package/src/geometries/path3/transform.js +2 -3
  74. package/src/geometries/path3/validate.js +6 -3
  75. package/src/geometries/poly2/arePointsInside.js +4 -1
  76. package/src/geometries/poly2/clone.js +4 -1
  77. package/src/geometries/poly2/create.js +2 -9
  78. package/src/geometries/poly2/index.js +16 -4
  79. package/src/geometries/poly2/isA.js +5 -1
  80. package/src/geometries/poly2/isConvex.js +5 -1
  81. package/src/geometries/poly2/isSimple.js +5 -1
  82. package/src/geometries/poly2/measureArea.js +4 -1
  83. package/src/geometries/poly2/measureBoundingBox.js +6 -1
  84. package/src/geometries/poly2/reverse.js +4 -1
  85. package/src/geometries/poly2/toPoints.js +6 -1
  86. package/src/geometries/poly2/toString.js +5 -1
  87. package/src/geometries/poly2/transform.js +5 -1
  88. package/src/geometries/poly2/validate.js +6 -2
  89. package/src/geometries/poly3/clone.js +4 -1
  90. package/src/geometries/poly3/create.js +3 -11
  91. package/src/geometries/poly3/fromVerticesAndPlane.js +3 -1
  92. package/src/geometries/poly3/index.js +19 -4
  93. package/src/geometries/poly3/invert.js +4 -1
  94. package/src/geometries/poly3/isA.js +5 -1
  95. package/src/geometries/poly3/isConvex.js +5 -1
  96. package/src/geometries/poly3/measureArea.js +5 -1
  97. package/src/geometries/poly3/measureBoundingBox.js +4 -1
  98. package/src/geometries/poly3/measureBoundingSphere.js +4 -3
  99. package/src/geometries/poly3/measureSignedVolume.js +6 -1
  100. package/src/geometries/poly3/plane.js +6 -0
  101. package/src/geometries/poly3/toString.js +5 -1
  102. package/src/geometries/poly3/toVertices.js +6 -1
  103. package/src/geometries/poly3/transform.js +5 -1
  104. package/src/geometries/poly3/validate.js +6 -2
  105. package/src/geometries/slice/calculatePlane.js +3 -3
  106. package/src/geometries/slice/clone.js +4 -1
  107. package/src/geometries/slice/create.js +5 -10
  108. package/src/geometries/slice/equals.js +5 -1
  109. package/src/geometries/slice/fromGeom2.js +1 -1
  110. package/src/geometries/slice/fromVertices.js +3 -3
  111. package/src/geometries/slice/index.js +19 -4
  112. package/src/geometries/slice/isA.js +5 -1
  113. package/src/geometries/slice/reverse.js +5 -2
  114. package/src/geometries/slice/toEdges.js +5 -3
  115. package/src/geometries/slice/toPolygons.js +5 -1
  116. package/src/geometries/slice/toString.js +5 -1
  117. package/src/geometries/slice/toVertices.js +5 -3
  118. package/src/geometries/slice/transform.js +4 -3
  119. package/src/geometries/slice/validate.js +3 -2
  120. package/src/index.d.ts +1 -0
  121. package/src/index.js +4 -0
  122. package/src/maths/constants.js +11 -7
  123. package/src/maths/index.js +2 -1
  124. package/src/maths/mat4/isOnlyTransformScale.js +1 -1
  125. package/src/operations/booleans/index.js +2 -0
  126. package/src/operations/booleans/intersect.js +0 -1
  127. package/src/operations/booleans/scission.js +0 -1
  128. package/src/operations/booleans/trees/splitLineSegmentByPlane.js +1 -4
  129. package/src/operations/booleans/trees/splitPolygonByPlane.test.js +138 -0
  130. package/src/operations/booleans/unionGeom3.test.js +35 -0
  131. package/src/operations/extrusions/extrudeFromSlices.js +15 -5
  132. package/src/operations/extrusions/extrudeRotate.js +2 -1
  133. package/src/operations/extrusions/extrudeRotate.test.js +34 -0
  134. package/src/operations/extrusions/extrudeWalls.test.js +60 -0
  135. package/src/operations/minkowski/index.d.ts +1 -0
  136. package/src/operations/minkowski/index.js +15 -0
  137. package/src/operations/minkowski/minkowskiSum.d.ts +4 -0
  138. package/src/operations/minkowski/minkowskiSum.js +223 -0
  139. package/src/operations/minkowski/minkowskiSum.test.js +199 -0
  140. package/src/operations/modifiers/reTesselateCoplanarPolygons.js +10 -3
  141. package/src/operations/modifiers/reTesselateCoplanarPolygons.test.js +36 -1
  142. package/src/operations/modifiers/retessellate.js +4 -2
  143. package/src/operations/modifiers/snap.test.js +24 -15
  144. package/src/operations/offsets/offsetGeom3.test.js +5 -7
  145. package/src/primitives/arc.js +2 -2
  146. package/src/primitives/arc.test.js +104 -113
  147. package/src/utils/flatten.js +1 -1
  148. package/src/utils/flatten.test.js +94 -0
@@ -0,0 +1,65 @@
1
+ import { EPS } from '../../maths/constants.js'
2
+ import { dot } from '../../maths/vec3/dot.js'
3
+ import { plane } from '../poly3/plane.js'
4
+
5
+ import { isA } from './isA.js'
6
+ import { toPolygons } from './toPolygons.js'
7
+
8
+ /**
9
+ * Test if a 3D geometry is convex.
10
+ *
11
+ * A polyhedron is convex if every vertex lies on or behind every face plane
12
+ * (i.e., on the interior side of the plane).
13
+ *
14
+ * @param {geom3} geometry - the geometry to test
15
+ * @returns {boolean} true if the geometry is convex
16
+ * @alias module:modeling/geom3.isConvex
17
+ *
18
+ * @example
19
+ * const cube = cuboid()
20
+ * console.log(geom3.isConvex(cube)) // true
21
+ */
22
+ export const isConvex = (geometry) => {
23
+ if (!isA(geometry)) {
24
+ throw new Error('isConvex requires a geom3 geometry')
25
+ }
26
+
27
+ const polygons = toPolygons(geometry)
28
+
29
+ if (polygons.length === 0) {
30
+ return true // Empty geometry is trivially convex
31
+ }
32
+
33
+ // Collect all unique vertices
34
+ const vertices = []
35
+ const found = new Set()
36
+ for (let i = 0; i < polygons.length; i++) {
37
+ const verts = polygons[i].vertices
38
+ for (let j = 0; j < verts.length; j++) {
39
+ const v = verts[j]
40
+ const key = `${v[0]},${v[1]},${v[2]}`
41
+ if (!found.has(key)) {
42
+ found.add(key)
43
+ vertices.push(v)
44
+ }
45
+ }
46
+ }
47
+
48
+ // For each face plane, check that all vertices are on or behind it
49
+ for (let i = 0; i < polygons.length; i++) {
50
+ const p = plane(polygons[i])
51
+
52
+ for (let j = 0; j < vertices.length; j++) {
53
+ const v = vertices[j]
54
+ // Distance from point to plane: dot(normal, point) - w
55
+ const distance = dot(p, v) - p[3]
56
+
57
+ // If any vertex is in front of any face (positive distance), not convex
58
+ if (distance > EPS) {
59
+ return false
60
+ }
61
+ }
62
+ }
63
+
64
+ return true
65
+ }
@@ -0,0 +1,44 @@
1
+ import test from 'ava'
2
+
3
+ import { geom3, cylinderElliptic, sphere, cuboid, subtract } from '../../index.js'
4
+
5
+ test('isConvex: throws for non-geom3 input', (t) => {
6
+ t.throws(() => geom3.isConvex('invalid'), { message: /requires a geom3/ })
7
+ t.throws(() => geom3.isConvex(null), { message: /requires a geom3/ })
8
+ })
9
+
10
+ test('isConvex: empty geometry is convex', (t) => {
11
+ const empty = geom3.create()
12
+ t.true(geom3.isConvex(empty))
13
+ })
14
+
15
+ test('isConvex: cuboid is convex', (t) => {
16
+ const cube = cuboid({ size: [10, 10, 10] })
17
+ t.true(geom3.isConvex(cube))
18
+ })
19
+
20
+ test('isConvex: sphere is convex', (t) => {
21
+ const sph = sphere({ radius: 5, segments: 16 })
22
+ t.true(geom3.isConvex(sph))
23
+ })
24
+
25
+ test('isConvex: cylinder is convex', (t) => {
26
+ const cyl = cylinderElliptic({ height: 10, startRadius: [3, 3], endRadius: [3, 3], segments: 16 })
27
+ t.true(geom3.isConvex(cyl))
28
+ })
29
+
30
+ test('isConvex: cube with hole is not convex', (t) => {
31
+ const cube = cuboid({ size: [10, 10, 10] })
32
+ const hole = cuboid({ size: [4, 4, 20] }) // Hole through the cube
33
+
34
+ const withHole = subtract(cube, hole)
35
+ t.false(geom3.isConvex(withHole))
36
+ })
37
+
38
+ test('isConvex: L-shaped solid is not convex', (t) => {
39
+ const big = cuboid({ size: [10, 10, 10], center: [0, 0, 0] })
40
+ const corner = cuboid({ size: [6, 6, 12], center: [3, 3, 0] })
41
+
42
+ const lShape = subtract(big, corner)
43
+ t.false(geom3.isConvex(lShape))
44
+ })
@@ -2,12 +2,14 @@ import { applyTransforms } from './applyTransforms.js'
2
2
 
3
3
  /**
4
4
  * Produces an array of polygons from the given geometry, after applying transforms.
5
+ *
5
6
  * The returned array should not be modified as the polygons are shared with the geometry.
7
+ *
6
8
  * @param {Geom3} geometry - the geometry
7
9
  * @returns {Array} an array of polygons
8
- * @alias module:modeling/geometries/geom3.toPolygons
10
+ * @alias module:modeling/geom3.toPolygons
9
11
  *
10
12
  * @example
11
- * let sharedPolygons = toPolygons(geometry)
13
+ * let sharedPolygons = geom3.toPolygons(geometry)
12
14
  */
13
15
  export const toPolygons = (geometry) => applyTransforms(geometry).polygons
@@ -4,12 +4,13 @@ import { toPolygons } from './toPolygons.js'
4
4
 
5
5
  /**
6
6
  * Create a string representing the contents of the given geometry.
7
+ *
7
8
  * @param {Geom3} geometry - the geometry
8
9
  * @returns {string} a representative string
9
- * @alias module:modeling/geometries/geom3.toString
10
+ * @alias module:modeling/geom3.toString
10
11
  *
11
12
  * @example
12
- * console.out(toString(geometry))
13
+ * console.out(geom3.toString(geometry))
13
14
  */
14
15
  export const toString = (geometry) => {
15
16
  const polygons = toPolygons(geometry)
@@ -3,12 +3,16 @@ import * as poly3 from '../poly3/index.js'
3
3
  import { toPolygons } from './toPolygons.js'
4
4
 
5
5
  /**
6
- * Return the given geometry as a list of points, after applying transforms.
6
+ * Return the given geometry as a list of vertices, after applying transforms.
7
+ *
8
+ * The returned array should not be modified as the vertices are shared with the geometry.
7
9
  *
8
- * The returned array should not be modified as the points are shared with the geometry.
9
10
  * @param {Geom3} geometry - the geometry
10
- * @return {Array} list of points, where each sub-array represents a polygon
11
- * @alias module:modeling/geometries/geom3.toVertices
11
+ * @return {Array} list of vertices, where each sub-array represents a polygon
12
+ * @alias module:modeling/geom3.toVertices
13
+ *
14
+ * @example
15
+ * let sharedVertices = geom3.toVertices(geometry)
12
16
  */
13
17
  export const toVertices = (geometry) => {
14
18
  const polygons = toPolygons(geometry)
@@ -2,15 +2,18 @@ import * as mat4 from '../../maths/mat4/index.js'
2
2
 
3
3
  /**
4
4
  * Transform the given geometry using the given matrix.
5
+ *
5
6
  * This is a lazy transform of the polygons, as this function only adjusts the transforms.
7
+ *
6
8
  * See applyTransforms() for the actual application of the transforms to the polygons.
9
+ *
7
10
  * @param {Mat4} matrix - the matrix to transform with
8
11
  * @param {Geom3} geometry - the geometry to transform
9
12
  * @returns {Geom3} a new geometry
10
- * @alias module:modeling/geometries/geom3.transform
13
+ * @alias module:modeling/geom3.transform
11
14
  *
12
15
  * @example
13
- * let newGeometry = transform(fromXRotation(TAU / 4), geometry)
16
+ * let newGeometry = geom3.transform(fromXRotation(TAU / 4), geometry)
14
17
  */
15
18
  export const transform = (matrix, geometry) => {
16
19
  const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
@@ -4,13 +4,17 @@ import { isA } from './isA.js'
4
4
 
5
5
  /**
6
6
  * Determine if the given object is a valid 3D geometry.
7
+ *
7
8
  * Checks for valid data structure, convex polygon faces, and manifold edges.
8
9
  *
9
- * **If the geometry is not valid, an exception will be thrown with details of the geometry error.**
10
+ * **NOTE: If the geometry is not valid, an exception will be thrown with details of the geometry error.**
10
11
  *
11
12
  * @param {object} object - the object to interrogate
12
13
  * @throws {Error} error if the geometry is not valid
13
- * @alias module:modeling/geometries/geom3.validate
14
+ * @alias module:modeling/geom3.validate
15
+ *
16
+ * @example
17
+ * if (geom3.validate(geometry)) { ... }
14
18
  */
15
19
  export const validate = (object) => {
16
20
  if (!isA(object)) {
@@ -1,15 +1,17 @@
1
1
  /**
2
2
  * Geometries are objects that represent the contents of primitives or the results of operations.
3
+ *
3
4
  * Note: Geometries are considered immutable, so never change the contents directly.
4
5
  *
5
- * @see {@link geom2} - 2D geometry consisting of 2D outlines
6
- * @see {@link geom3} - 3D geometry consisting of polygons
7
- * @see {@link path2} - 2D geometry consisting of ordered points
8
- * @see {@link poly2} - 2D polygon consisting of ordered points
9
- * @see {@link poly3} - 3D polygon consisting of ordered vertices
10
- * @see {@link slice} - 3D geometry consisting of 3D contours
6
+ * @see [Geom2]{@link module:modeling/geom2} - 2D geometry consisting of 2D outlines
7
+ * @see [Geom3]{@link module:modeling/geom3} - 3D geometry consisting of polygons
8
+ * @see [Path2]{@link module:modeling/path2} - 2D geometry consisting of ordered 2D points
9
+ * @see [Path3]{@link module:modeling/path3} - 3D geometry consisting of ordered 3D vertices
10
+ * @see [Poly2]{@link module:modeling/poly2} - 2D polygon consisting of 2D points
11
+ * @see [Poly3]{@link module:modeling/poly3} - 3D polygon consisting of 3D vertices
12
+ * @see [Slice]{@link module:modeling/slice} - 3D geometry consisting of 3D contours
13
+ * @alias module:modeling.geometry
11
14
  *
12
- * @module modeling/geometries
13
15
  * @example
14
16
  * import { geom2, geom3, path2, poly2, poly3, slice } from '@jscad/modeling'
15
17
  */
@@ -6,8 +6,10 @@ import { toPoints } from './toPoints.js'
6
6
 
7
7
  /**
8
8
  * Append a series of points to the given geometry that represent an arc.
9
+ *
9
10
  * This implementation follows the SVG specifications.
10
11
  * @see http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
12
+ *
11
13
  * @param {object} options - options for construction
12
14
  * @param {Vec2} options.endpoint - end point of arc (REQUIRED)
13
15
  * @param {Vec2} [options.radius=[0,0]] - radius of arc (X and Y)
@@ -17,12 +19,12 @@ import { toPoints } from './toPoints.js'
17
19
  * @param {number} [options.segments=16] - number of segments per full rotation
18
20
  * @param {Path2} geometry - the path of which to append the arc
19
21
  * @returns {Path2} a new path with the appended points
20
- * @alias module:modeling/geometries/path2.appendArc
22
+ * @alias module:modeling/path2.appendArc
21
23
  *
22
24
  * @example
23
- * let myShape = fromPoints({}, [[27.5,-22.96875]]);
24
- * myShape = appendPoints([[27.5,-3.28125]], myShape);
25
- * myShape = appendArc({endpoint: [12.5, -22.96875], radius: [15, -19.6875]}, myShape);
25
+ * let myShape = path2.fromPoints({}, [[27.5,-22.96875]]);
26
+ * myShape = path2.appendPoints([[27.5,-3.28125]], myShape);
27
+ * myShape = path2.appendArc({endpoint: [12.5, -22.96875], radius: [15, -19.6875]}, myShape);
26
28
  */
27
29
  export const appendArc = (options, geometry) => {
28
30
  const defaults = {
@@ -120,7 +122,7 @@ export const appendArc = (options, geometry) => {
120
122
  }
121
123
 
122
124
  // Ok, we have the center point and angle range (from theta1, deltatheta radians) so we can create the ellipse
123
- let numSteps = Math.ceil(Math.abs(deltatheta) / TAU * segments) + 1
125
+ let numSteps = Math.floor(segments * (Math.abs(deltatheta) / TAU))
124
126
  if (numSteps < 1) numSteps = 1
125
127
  for (let step = 1; step < numSteps; step++) {
126
128
  const theta = theta1 + step / numSteps * deltatheta
@@ -23,12 +23,12 @@ test('appendArc: appending to a path produces a new path', (t) => {
23
23
  const p2 = fromPoints({}, [[27, -22], [27, -3]])
24
24
  obs = appendArc({ endpoint: [12, -22], radius: [15, -20] }, p2)
25
25
  pts = toPoints(obs)
26
- t.is(pts.length, 7)
26
+ t.is(pts.length, 5)
27
27
 
28
28
  // test segments
29
29
  obs = appendArc({ endpoint: [12, -22], radius: [15, -20], segments: 64 }, p2)
30
30
  pts = toPoints(obs)
31
- t.is(pts.length, 19)
31
+ t.is(pts.length, 17)
32
32
 
33
33
  // test clockwise
34
34
  obs = appendArc({ endpoint: [12, -22], radius: [15, -20], clockwise: true }, p2)
@@ -36,19 +36,17 @@ test('appendArc: appending to a path produces a new path', (t) => {
36
36
  let exp = [
37
37
  [27, -22],
38
38
  [27, -3],
39
- [26.086451657912605, -8.941047736250177],
40
- [23.87938869625451, -14.243872270248309],
41
- [20.58174906029909, -18.420882475791835],
42
- [16.49674848226545, -21.0880050920699],
43
- [11.999999999999998, -22]
39
+ [24.7485593841743, -12.579008396887021],
40
+ [19.29019838402471, -19.492932330409836],
41
+ [12, -22]
44
42
  ]
45
- t.is(pts.length, 7)
43
+ t.is(pts.length, 5)
46
44
  t.true(comparePoints(pts, exp))
47
45
 
48
46
  // test large
49
47
  obs = appendArc({ endpoint: [12, -22], radius: [15, -20], large: true }, p2)
50
48
  pts = toPoints(obs)
51
- t.is(pts.length, 16)
49
+ t.is(pts.length, 14)
52
50
 
53
51
  // test xaxisRotation
54
52
  obs = appendArc({ endpoint: [12, -22], radius: [15, -20], xaxisRotation: TAU / 4 }, p2)
@@ -56,14 +54,12 @@ test('appendArc: appending to a path produces a new path', (t) => {
56
54
  exp = [
57
55
  [27, -22],
58
56
  [27, -3],
59
- [21.830323320631795, -4.401628923214028],
60
- [17.364704977487236, -6.805886946199115],
57
+ [19.486852090983938, -5.488140907400943],
61
58
  [13.940501387124588, -10.031143708098092],
62
- [11.816394990371812, -13.833746263211978],
63
- [11.15285201325494, -17.926425912558045],
64
- [12, -22.000000000000004]
59
+ [11.296247566821858, -15.862906638006239],
60
+ [12, -22]
65
61
  ]
66
- t.is(pts.length, 8)
62
+ t.is(pts.length, 6)
67
63
  t.true(comparePoints(pts, exp))
68
64
 
69
65
  // test small arc between far points
@@ -7,22 +7,24 @@ import { toPoints } from './toPoints.js'
7
7
 
8
8
  /**
9
9
  * Append a series of points to the given geometry that represent a Bézier curve.
10
+ *
10
11
  * The Bézier curve starts at the last point in the given geometry, and ends at the last control point.
11
12
  * The other control points are intermediate control points to transition the curve from start to end points.
12
13
  * The first control point may be null to ensure a smooth transition occurs. In this case,
13
14
  * the second to last point of the given geometry is mirrored into the control points of the Bézier curve.
14
15
  * In other words, the trailing gradient of the geometry matches the new gradient of the curve.
16
+ *
15
17
  * @param {object} options - options for construction
16
18
  * @param {Array} options.controlPoints - list of control points (2D) for the Bézier curve
17
19
  * @param {number} [options.segments=16] - number of segments per 360 rotation
18
20
  * @param {Path2} geometry - the path of which to append points
19
21
  * @returns {Path2} a new path with the appended points
20
- * @alias module:modeling/geometries/path2.appendBezier
22
+ * @alias module:modeling/path2.appendBezier
21
23
  *
22
24
  * @example
23
- * let myShape = fromPoints({}, [[10,-20]])
24
- * myShape = appendBezier({controlPoints: [[10,-10],[25,-10],[25,-20]]}, myShape);
25
- * myShape = appendBezier({controlPoints: [null, [25,-30],[40,-30],[40,-20]]}, myShape)
25
+ * let myShape = path2.fromPoints({}, [[10,-20]])
26
+ * myShape = path2.appendBezier({controlPoints: [[10,-10],[25,-10],[25,-20]]}, myShape);
27
+ * myShape = path2.appendBezier({controlPoints: [null, [25,-30],[40,-30],[40,-20]]}, myShape)
26
28
  */
27
29
  export const appendBezier = (options, geometry) => {
28
30
  const defaults = {
@@ -3,11 +3,13 @@ import { create } from './create.js'
3
3
 
4
4
  /**
5
5
  * Append the given list of points to the end of the given geometry.
6
+ *
6
7
  * @param {Array} points - the points (2D) to append to the given path
7
8
  * @param {Path2} geometry - the given path
8
9
  * @returns {Path2} a new path with the appended points
9
- * @alias module:modeling/geometries/path2.appendPoints
10
+ * @alias module:modeling/path2.appendPoints
11
+ *
10
12
  * @example
11
- * let newPath = appendPoints([[3, 4], [4, 5]], oldPath)
13
+ * let newPath = path2.appendPoints([[3, 4], [4, 5]], oldPath)
12
14
  */
13
15
  export const appendPoints = (points, geometry) => concat(geometry, create(points))
@@ -3,9 +3,12 @@ import * as vec2 from '../../maths/vec2/index.js'
3
3
 
4
4
  /*
5
5
  * Apply the transforms of the given geometry.
6
+ *
6
7
  * NOTE: This function must be called BEFORE exposing any data. See toPoints.
8
+ *
7
9
  * @param {path} geometry - the geometry to transform
8
10
  * @returns {path} the given geometry
11
+ *
9
12
  * @example
10
13
  * geometry = applyTransforms(geometry)
11
14
  */
@@ -1,7 +1,11 @@
1
1
  /**
2
2
  * Performs a shallow clone of the give geometry.
3
+ *
3
4
  * @param {Path2} geometry - the geometry to clone
4
5
  * @returns {Path2} a new path
5
- * @alias module:modeling/geometries/path2.clone
6
+ * @alias module:modeling/path2.clone
7
+ *
8
+ * @example
9
+ * let newPath = path2.clone(oldPath)
6
10
  */
7
11
  export const clone = (geometry) => Object.assign({}, geometry)
@@ -6,9 +6,13 @@ import { clone } from './clone.js'
6
6
 
7
7
  /**
8
8
  * Close the given geometry.
9
+ *
9
10
  * @param {Path2} geometry - the path to close
10
11
  * @returns {Path2} a new path
11
- * @alias module:modeling/geometries/path2.close
12
+ * @alias module:modeling/path2.close
13
+ *
14
+ * @example
15
+ * let newPath = path2.close(oldPath)
12
16
  */
13
17
  export const close = (geometry) => {
14
18
  if (geometry.isClosed) return geometry
@@ -10,12 +10,13 @@ import { toPoints } from './toPoints.js'
10
10
  * A concatenation of zero paths is an empty, open path.
11
11
  * A concatenation of one closed path to a series of open paths produces a closed path.
12
12
  * A concatenation of a path to a closed path is an error.
13
+ *
13
14
  * @param {...Path2} paths - the paths to concatenate
14
15
  * @returns {Path2} a new path
15
- * @alias module:modeling/geometries/path2.concat
16
+ * @alias module:modeling/path2.concat
16
17
  *
17
18
  * @example
18
- * let newPath = concat(fromPoints({}, [[1, 2]]), fromPoints({}, [[3, 4]]))
19
+ * let newPath = path2.concat(path2.fromPoints({}, [[1, 2]]), path2.fromPoints({}, [[3, 4]]))
19
20
  */
20
21
  export const concat = (...paths) => {
21
22
  // Only the last path can be closed, producing a closed path.
@@ -1,24 +1,13 @@
1
1
  import * as mat4 from '../../maths/mat4/index.js'
2
2
 
3
- /**
4
- * Represents a 2D geometry consisting of a list of ordered points.
5
- * @property {Array} points - list of ordered points
6
- * @property {boolean} isClosed - true if the path is closed where start and end points are the same
7
- * @property {Mat4} transforms - transforms to apply to the points, see transform()
8
- * @example
9
- * {
10
- * "points": [[0,0], [4,0], [4,3]],
11
- * "isClosed": true,
12
- * "transforms": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
13
- * }
14
- */
15
-
16
3
  /**
17
4
  * Create an empty, open path.
5
+ *
6
+ * @param {Array} [points] - a list of points of which to create the path
18
7
  * @returns {Path2} a new path
19
- * @alias module:modeling/geometries/path2.create
8
+ * @alias module:modeling/path2.create
20
9
  *
21
10
  * @example
22
- * let newPath = create()
11
+ * let newPath = path2.create()
23
12
  */
24
13
  export const create = (points = []) => ({ points: points, isClosed: false, transforms: mat4.create() })
@@ -3,13 +3,18 @@ import * as vec2 from '../../maths/vec2/index.js'
3
3
  import { toPoints } from './toPoints.js'
4
4
 
5
5
  /**
6
- * Determine if the given paths are equal.
7
- * For closed paths, this includes equality under point order rotation.
8
- * @param {Path2} a - the first path to compare
9
- * @param {Path2} b - the second path to compare
10
- * @returns {Boolean}
11
- * @alias module:modeling/geometries/path2.equals
12
- */
6
+ * Determine if the given paths are equal.
7
+ *
8
+ * For closed paths, this includes equality under point order rotation.
9
+ *
10
+ * @param {Path2} a - the first path to compare
11
+ * @param {Path2} b - the second path to compare
12
+ * @returns {Boolean}
13
+ * @alias module:modeling/path2.equals
14
+ *
15
+ * @example
16
+ * if (path2.equals(pathA, pathB)) { ... }
17
+ */
13
18
  export const equals = (a, b) => {
14
19
  if (a.isClosed !== b.isClosed) {
15
20
  return false
@@ -7,16 +7,18 @@ import { create } from './create.js'
7
7
 
8
8
  /**
9
9
  * Create a new path from the given points.
10
+ *
10
11
  * The points must be provided an array of points,
11
12
  * where each point is an array of two numbers.
13
+ *
12
14
  * @param {object} options - options for construction
13
15
  * @param {boolean} [options.closed=false] - if the path should be open or closed
14
16
  * @param {Array} points - array of points (2D) from which to create the path
15
17
  * @returns {Path2} a new path
16
- * @alias module:modeling/geometries/path2.fromPoints
18
+ * @alias module:modeling/path2.fromPoints
17
19
  *
18
- * @example:
19
- * my newPath = fromPoints({closed: true}, [[10, 10], [-10, 10]])
20
+ * @example
21
+ * my newPath = path2.fromPoints({closed: true}, [[10, 10], [-10, 10]])
20
22
  */
21
23
  export const fromPoints = (options, points) => {
22
24
  const defaults = { closed: false }
@@ -1,11 +1,28 @@
1
1
  /**
2
2
  * Represents a 2D geometry consisting of a list of ordered points.
3
- * @see {@link path2} for data structure information.
4
- * @module modeling/geometries/path2
3
+ *
4
+ * @see {@link Path2} for data structure information.
5
+ * @module modeling/path2
6
+ *
7
+ * @example
8
+ * import { path2 } from '@jscad/modeling'
9
+ * let myShape = path2.fromPoints({ closed: true }, [[0,0], [4,0], [4,3]])
10
+ */
11
+
12
+ /**
13
+ * @typedef Path2
14
+ * @type {Object}
15
+ * @property {Array} points - list of ordered points
16
+ * @property {boolean} isClosed - true if the path is closed where start and end points are the same
17
+ * @property {Mat4} transforms - transforms to apply to the points, see transform()
5
18
  *
6
19
  * @example
7
- * import { geometries } from '@jscad/modeling'
8
- * let myShape = geometries.path2.fromPoints({ closed: true }, [[0,0], [4,0], [4,3]])
20
+ * // data structure
21
+ * {
22
+ * "points": [[0,0], [4,0], [4,3]],
23
+ * "isClosed": true,
24
+ * "transforms": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
25
+ * }
9
26
  */
10
27
  export { appendArc } from './appendArc.js'
11
28
  export { appendBezier } from './appendBezier.js'
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Determine if the given object is a path2 geometry.
3
+ *
3
4
  * @param {object} object - the object to interrogate
4
5
  * @returns {Boolean} true if the object matches a path2
5
- * @alias module:modeling/geometries/path2.isA
6
+ * @alias module:modeling/path2.isA
7
+ *
8
+ * @example
9
+ * if (path2.isA(geometry)) { ... }
6
10
  */
7
11
  export const isA = (object) => {
8
12
  if (object && typeof object === 'object') {
@@ -2,13 +2,15 @@ import { clone } from './clone.js'
2
2
 
3
3
  /**
4
4
  * Reverses the path so that the points are in the opposite order.
5
+ *
5
6
  * This swaps the left (interior) and right (exterior) edges.
7
+ *
6
8
  * @param {Path2} geometry - the path to reverse
7
9
  * @returns {Path2} a new path
8
- * @alias module:modeling/geometries/path2.reverse
10
+ * @alias module:modeling/path2.reverse
9
11
  *
10
12
  * @example
11
- * let newPath = reverse(myPath)
13
+ * let newPath = path2.reverse(oldPath)
12
14
  */
13
15
  export const reverse = (geometry) => {
14
16
  // NOTE: this only updates the order of the points
@@ -2,12 +2,14 @@ import { applyTransforms } from './applyTransforms.js'
2
2
 
3
3
  /**
4
4
  * Produces an array of points from the given geometry.
5
- * The returned array should not be modified as the data is shared with the geometry.
5
+ *
6
+ * NOTE: The returned array should not be modified as the data is shared with the geometry.
7
+ *
6
8
  * @param {Path2} geometry - the geometry
7
9
  * @returns {Array} an array of points
8
- * @alias module:modeling/geometries/path2.toPoints
10
+ * @alias module:modeling/path2.toPoints
9
11
  *
10
12
  * @example
11
- * let sharedPoints = toPoints(geometry)
13
+ * let sharedPoints = path2.toPoints(geometry)
12
14
  */
13
15
  export const toPoints = (geometry) => applyTransforms(geometry).points
@@ -4,12 +4,13 @@ import { toPoints } from './toPoints.js'
4
4
 
5
5
  /**
6
6
  * Create a string representing the contents of the given path.
7
+ *
7
8
  * @param {Path2} geometry - the path
8
9
  * @returns {string} a representative string
9
- * @alias module:modeling/geometries/path2.toString
10
+ * @alias module:modeling/path2.toString
10
11
  *
11
12
  * @example
12
- * console.out(toString(path))
13
+ * console.out(path2.toString(path))
13
14
  */
14
15
  export const toString = (geometry) => {
15
16
  const points = toPoints(geometry)
@@ -2,15 +2,17 @@ import * as mat4 from '../../maths/mat4/index.js'
2
2
 
3
3
  /**
4
4
  * Transform the given geometry using the given matrix.
5
+ *
5
6
  * This is a lazy transform of the points, as this function only adjusts the transforms.
6
7
  * The transforms are applied when accessing the points via toPoints().
8
+ *
7
9
  * @param {Mat4} matrix - the matrix to transform with
8
10
  * @param {Path2} geometry - the geometry to transform
9
11
  * @returns {Path2} a new path
10
- * @alias module:modeling/geometries/path2.transform
12
+ * @alias module:modeling/path2.transform
11
13
  *
12
14
  * @example
13
- * let newPath = transform(fromZRotation(TAU / 8), path)
15
+ * let newPath = path2.transform(mat4.fromZRotation(TAU / 8), path)
14
16
  */
15
17
  export const transform = (matrix, geometry) => {
16
18
  const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)