@jscad/modeling 3.0.2-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 (232) hide show
  1. package/CHANGELOG.md +25 -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/colors/colorize.test.js +1 -1
  7. package/src/curves/bezier/arcLengthToT.js +1 -1
  8. package/src/curves/bezier/create.js +1 -1
  9. package/src/curves/bezier/index.js +7 -7
  10. package/src/curves/bezier/length.js +1 -1
  11. package/src/curves/bezier/lengths.js +2 -1
  12. package/src/curves/bezier/tangentAt.js +1 -1
  13. package/src/curves/bezier/valueAt.js +1 -1
  14. package/src/curves/index.js +3 -3
  15. package/src/geometries/geom2/applyTransforms.js +3 -1
  16. package/src/geometries/geom2/clone.js +5 -1
  17. package/src/geometries/geom2/create.js +4 -14
  18. package/src/geometries/geom2/fromSides.js +4 -2
  19. package/src/geometries/geom2/index.d.ts +0 -2
  20. package/src/geometries/geom2/index.js +21 -7
  21. package/src/geometries/geom2/isA.js +5 -1
  22. package/src/geometries/geom2/reverse.js +4 -2
  23. package/src/geometries/geom2/toOutlines.js +2 -1
  24. package/src/geometries/geom2/toPoints.js +5 -2
  25. package/src/geometries/geom2/toSides.js +4 -3
  26. package/src/geometries/geom2/toString.js +3 -2
  27. package/src/geometries/geom2/transform.js +4 -2
  28. package/src/geometries/geom2/validate.js +6 -2
  29. package/src/geometries/geom3/applyTransforms.test.js +2 -2
  30. package/src/geometries/geom3/clone.js +5 -1
  31. package/src/geometries/geom3/clone.test.js +2 -2
  32. package/src/geometries/geom3/create.js +6 -28
  33. package/src/geometries/geom3/{fromPoints.d.ts → fromVertices.d.ts} +1 -1
  34. package/src/geometries/geom3/{fromPoints.js → fromVertices.js} +15 -2
  35. package/src/geometries/geom3/{fromPoints.test.js → fromVertices.test.js} +6 -6
  36. package/src/geometries/geom3/{fromPointsConvex.d.ts → fromVerticesConvex.d.ts} +1 -1
  37. package/src/geometries/geom3/fromVerticesConvex.js +25 -0
  38. package/src/geometries/geom3/{fromPointsConvex.test.js → fromVerticesConvex.test.js} +3 -3
  39. package/src/geometries/geom3/index.d.ts +4 -5
  40. package/src/geometries/geom3/index.js +29 -9
  41. package/src/geometries/geom3/invert.js +5 -1
  42. package/src/geometries/geom3/invert.test.js +2 -2
  43. package/src/geometries/geom3/isA.js +5 -1
  44. package/src/geometries/geom3/isA.test.js +2 -2
  45. package/src/geometries/geom3/isConvex.d.ts +3 -0
  46. package/src/geometries/geom3/isConvex.js +65 -0
  47. package/src/geometries/geom3/isConvex.test.js +44 -0
  48. package/src/geometries/geom3/toPolygons.js +4 -2
  49. package/src/geometries/geom3/toString.js +3 -2
  50. package/src/geometries/geom3/toString.test.js +2 -2
  51. package/src/geometries/geom3/{toPoints.d.ts → toVertices.d.ts} +1 -1
  52. package/src/geometries/geom3/toVertices.js +20 -0
  53. package/src/geometries/geom3/{toPoints.test.js → toVertices.test.js} +4 -4
  54. package/src/geometries/geom3/transform.js +5 -2
  55. package/src/geometries/geom3/transform.test.js +2 -2
  56. package/src/geometries/geom3/validate.js +6 -2
  57. package/src/geometries/geom3/validate.test.js +4 -4
  58. package/src/geometries/index.d.ts +1 -0
  59. package/src/geometries/index.js +10 -7
  60. package/src/geometries/path2/appendArc.js +7 -5
  61. package/src/geometries/path2/appendArc.test.js +11 -15
  62. package/src/geometries/path2/appendBezier.js +6 -4
  63. package/src/geometries/path2/appendPoints.js +4 -2
  64. package/src/geometries/path2/applyTransforms.js +3 -0
  65. package/src/geometries/path2/clone.js +5 -1
  66. package/src/geometries/path2/close.js +5 -1
  67. package/src/geometries/path2/concat.js +3 -2
  68. package/src/geometries/path2/create.js +5 -25
  69. package/src/geometries/path2/equals.js +12 -7
  70. package/src/geometries/path2/fromPoints.js +5 -3
  71. package/src/geometries/path2/index.d.ts +0 -2
  72. package/src/geometries/path2/index.js +21 -6
  73. package/src/geometries/path2/isA.js +5 -1
  74. package/src/geometries/path2/reverse.js +4 -2
  75. package/src/geometries/path2/toPoints.js +5 -3
  76. package/src/geometries/path2/toString.js +3 -2
  77. package/src/geometries/path2/transform.js +4 -2
  78. package/src/geometries/path2/validate.js +5 -1
  79. package/src/geometries/path3/applyTransforms.js +22 -0
  80. package/src/geometries/path3/applyTransforms.test.js +28 -0
  81. package/src/geometries/path3/close.d.ts +3 -0
  82. package/src/geometries/path3/close.js +33 -0
  83. package/src/geometries/path3/close.test.js +43 -0
  84. package/src/geometries/path3/concat.d.ts +3 -0
  85. package/src/geometries/path3/concat.js +35 -0
  86. package/src/geometries/path3/concat.test.js +35 -0
  87. package/src/geometries/path3/create.d.ts +4 -0
  88. package/src/geometries/path3/create.js +14 -0
  89. package/src/geometries/path3/create.test.js +8 -0
  90. package/src/geometries/path3/equals.d.ts +3 -0
  91. package/src/geometries/path3/equals.js +50 -0
  92. package/src/geometries/path3/equals.test.js +38 -0
  93. package/src/geometries/path3/fromVertices.d.ts +8 -0
  94. package/src/geometries/path3/fromVertices.js +44 -0
  95. package/src/geometries/path3/fromVertices.test.js +33 -0
  96. package/src/geometries/path3/index.d.ts +13 -0
  97. package/src/geometries/path3/index.js +37 -0
  98. package/src/geometries/path3/isA.d.ts +3 -0
  99. package/src/geometries/path3/isA.js +22 -0
  100. package/src/geometries/path3/isA.test.js +19 -0
  101. package/src/geometries/path3/reverse.d.ts +3 -0
  102. package/src/geometries/path3/reverse.js +18 -0
  103. package/src/geometries/path3/reverse.test.js +9 -0
  104. package/src/geometries/path3/toString.d.ts +3 -0
  105. package/src/geometries/path3/toString.js +23 -0
  106. package/src/geometries/path3/toVertices.d.ts +4 -0
  107. package/src/geometries/path3/toVertices.js +15 -0
  108. package/src/geometries/path3/toVertices.test.js +13 -0
  109. package/src/geometries/path3/transform.d.ts +4 -0
  110. package/src/geometries/path3/transform.js +20 -0
  111. package/src/geometries/path3/transform.test.js +50 -0
  112. package/src/geometries/path3/type.d.ts +10 -0
  113. package/src/geometries/path3/validate.d.ts +1 -0
  114. package/src/geometries/path3/validate.js +44 -0
  115. package/src/geometries/poly2/arePointsInside.js +4 -1
  116. package/src/geometries/poly2/clone.js +4 -1
  117. package/src/geometries/poly2/create.js +3 -15
  118. package/src/geometries/poly2/index.js +16 -4
  119. package/src/geometries/poly2/isA.js +5 -1
  120. package/src/geometries/poly2/isConvex.js +5 -1
  121. package/src/geometries/poly2/isSimple.js +5 -1
  122. package/src/geometries/poly2/measureArea.js +4 -1
  123. package/src/geometries/poly2/measureBoundingBox.js +6 -1
  124. package/src/geometries/poly2/reverse.js +4 -1
  125. package/src/geometries/poly2/toPoints.js +6 -1
  126. package/src/geometries/poly2/toString.js +5 -1
  127. package/src/geometries/poly2/transform.js +5 -1
  128. package/src/geometries/poly2/validate.js +6 -2
  129. package/src/geometries/poly3/clone.js +4 -1
  130. package/src/geometries/poly3/create.js +4 -17
  131. package/src/geometries/poly3/fromVerticesAndPlane.js +3 -1
  132. package/src/geometries/poly3/index.js +19 -4
  133. package/src/geometries/poly3/invert.js +4 -1
  134. package/src/geometries/poly3/isA.js +5 -1
  135. package/src/geometries/poly3/isConvex.js +5 -1
  136. package/src/geometries/poly3/measureArea.js +5 -1
  137. package/src/geometries/poly3/measureBoundingBox.js +4 -1
  138. package/src/geometries/poly3/measureBoundingSphere.js +4 -3
  139. package/src/geometries/poly3/measureSignedVolume.js +6 -1
  140. package/src/geometries/poly3/plane.js +6 -0
  141. package/src/geometries/poly3/toString.js +5 -1
  142. package/src/geometries/poly3/toVertices.js +6 -1
  143. package/src/geometries/poly3/transform.js +5 -1
  144. package/src/geometries/poly3/validate.js +6 -2
  145. package/src/geometries/slice/calculatePlane.js +3 -3
  146. package/src/geometries/slice/clone.js +4 -1
  147. package/src/geometries/slice/create.js +5 -10
  148. package/src/geometries/slice/equals.js +5 -1
  149. package/src/geometries/slice/fromGeom2.js +1 -1
  150. package/src/geometries/slice/fromVertices.js +3 -3
  151. package/src/geometries/slice/index.js +19 -4
  152. package/src/geometries/slice/isA.js +5 -1
  153. package/src/geometries/slice/reverse.js +5 -2
  154. package/src/geometries/slice/toEdges.js +5 -3
  155. package/src/geometries/slice/toPolygons.js +5 -1
  156. package/src/geometries/slice/toString.js +5 -1
  157. package/src/geometries/slice/toVertices.js +5 -3
  158. package/src/geometries/slice/transform.js +4 -3
  159. package/src/geometries/slice/validate.js +3 -2
  160. package/src/index.d.ts +1 -0
  161. package/src/index.js +4 -0
  162. package/src/maths/constants.js +11 -7
  163. package/src/maths/index.js +2 -1
  164. package/src/maths/mat4/isOnlyTransformScale.js +1 -1
  165. package/src/operations/booleans/index.js +2 -0
  166. package/src/operations/booleans/intersect.js +0 -1
  167. package/src/operations/booleans/intersectGeom3.test.js +4 -4
  168. package/src/operations/booleans/scission.js +0 -1
  169. package/src/operations/booleans/subtractGeom3.test.js +4 -4
  170. package/src/operations/booleans/trees/splitLineSegmentByPlane.js +1 -4
  171. package/src/operations/booleans/trees/splitPolygonByPlane.test.js +138 -0
  172. package/src/operations/booleans/unionGeom3.test.js +40 -5
  173. package/src/operations/extrusions/extrudeFromSlices.js +15 -5
  174. package/src/operations/extrusions/extrudeFromSlices.test.js +6 -6
  175. package/src/operations/extrusions/extrudeLinear.test.js +8 -8
  176. package/src/operations/extrusions/extrudeRotate.js +2 -1
  177. package/src/operations/extrusions/extrudeRotate.test.js +46 -12
  178. package/src/operations/extrusions/extrudeWalls.test.js +60 -0
  179. package/src/operations/hulls/hull.test.js +5 -5
  180. package/src/operations/hulls/hullChain.test.js +5 -5
  181. package/src/operations/hulls/toUniquePoints.js +2 -2
  182. package/src/operations/minkowski/index.d.ts +1 -0
  183. package/src/operations/minkowski/index.js +15 -0
  184. package/src/operations/minkowski/minkowskiSum.d.ts +4 -0
  185. package/src/operations/minkowski/minkowskiSum.js +223 -0
  186. package/src/operations/minkowski/minkowskiSum.test.js +199 -0
  187. package/src/operations/modifiers/generalize.test.js +6 -6
  188. package/src/operations/modifiers/insertTjunctions.test.js +2 -2
  189. package/src/operations/modifiers/reTesselateCoplanarPolygons.js +10 -3
  190. package/src/operations/modifiers/reTesselateCoplanarPolygons.test.js +36 -1
  191. package/src/operations/modifiers/retessellate.js +4 -2
  192. package/src/operations/modifiers/retessellate.test.js +10 -10
  193. package/src/operations/modifiers/snap.test.js +28 -19
  194. package/src/operations/offsets/offsetGeom3.test.js +9 -11
  195. package/src/operations/transforms/center.test.js +7 -7
  196. package/src/operations/transforms/mirror.test.js +7 -7
  197. package/src/operations/transforms/rotate.test.js +7 -7
  198. package/src/operations/transforms/scale.test.js +7 -7
  199. package/src/operations/transforms/transform.test.js +2 -2
  200. package/src/operations/transforms/translate.test.js +7 -7
  201. package/src/primitives/arc.js +2 -2
  202. package/src/primitives/arc.test.js +104 -113
  203. package/src/primitives/cube.test.js +4 -4
  204. package/src/primitives/cuboid.test.js +4 -4
  205. package/src/primitives/cylinder.test.js +5 -5
  206. package/src/primitives/cylinderElliptic.test.js +9 -9
  207. package/src/primitives/ellipsoid.test.js +5 -5
  208. package/src/primitives/geodesicSphere.test.js +4 -4
  209. package/src/primitives/polyhedron.test.js +2 -2
  210. package/src/primitives/roundedCuboid.test.js +7 -7
  211. package/src/primitives/roundedCylinder.test.js +9 -9
  212. package/src/primitives/sphere.test.js +5 -5
  213. package/src/primitives/torus.test.js +4 -4
  214. package/src/utils/flatten.js +1 -1
  215. package/src/utils/flatten.test.js +94 -0
  216. package/src/geometries/geom2/fromCompactBinary.d.ts +0 -3
  217. package/src/geometries/geom2/fromCompactBinary.js +0 -40
  218. package/src/geometries/geom2/fromToCompactBinary.test.js +0 -100
  219. package/src/geometries/geom2/toCompactBinary.d.ts +0 -3
  220. package/src/geometries/geom2/toCompactBinary.js +0 -56
  221. package/src/geometries/geom3/fromCompactBinary.d.ts +0 -3
  222. package/src/geometries/geom3/fromCompactBinary.js +0 -42
  223. package/src/geometries/geom3/fromPointsConvex.js +0 -25
  224. package/src/geometries/geom3/fromToCompactBinary.test.js +0 -139
  225. package/src/geometries/geom3/toCompactBinary.d.ts +0 -3
  226. package/src/geometries/geom3/toCompactBinary.js +0 -66
  227. package/src/geometries/geom3/toPoints.js +0 -15
  228. package/src/geometries/path2/fromCompactBinary.d.ts +0 -3
  229. package/src/geometries/path2/fromCompactBinary.js +0 -31
  230. package/src/geometries/path2/fromToCompactBinary.test.js +0 -114
  231. package/src/geometries/path2/toCompactBinary.d.ts +0 -3
  232. package/src/geometries/path2/toCompactBinary.js +0 -50
@@ -0,0 +1,50 @@
1
+ import * as vec3 from '../../maths/vec3/index.js'
2
+
3
+ import { toVertices } from './toVertices.js'
4
+
5
+ /**
6
+ * Determine if the given paths are equal.
7
+ *
8
+ * For closed paths, this includes equality by vertex order rotation.
9
+ *
10
+ * @param {Path3} a - the first path to compare
11
+ * @param {Path3} b - the second path to compare
12
+ * @returns {boolean}
13
+ * @alias module:modeling/path3.equals
14
+ *
15
+ * @example
16
+ * if (path3.equals(pathA, pathB)) { ... }
17
+ */
18
+ export const equals = (a, b) => {
19
+ if (a.isClosed !== b.isClosed) {
20
+ return false
21
+ }
22
+ if (a.vertices.length !== b.vertices.length) {
23
+ return false
24
+ }
25
+
26
+ const aVertices = toVertices(a)
27
+ const bVertices = toVertices(b)
28
+
29
+ // closed paths might be equal under graph rotation
30
+ // so try comparison by rotating across all vertices
31
+ const length = aVertices.length
32
+ let offset = 0
33
+ do {
34
+ let unequal = false
35
+ for (let i = 0; i < length; i++) {
36
+ if (!vec3.equals(aVertices[i], bVertices[(i + offset) % length])) {
37
+ unequal = true
38
+ break
39
+ }
40
+ }
41
+ if (unequal === false) {
42
+ return true
43
+ }
44
+ // unequal open paths should only be compared once, never rotated
45
+ if (!a.isClosed) {
46
+ return false
47
+ }
48
+ } while (++offset < length)
49
+ return false
50
+ }
@@ -0,0 +1,38 @@
1
+ import test from 'ava'
2
+
3
+ import { equals, fromVertices } from './index.js'
4
+
5
+ test('equals: two paths with different points are not equal', (t) => {
6
+ const p1 = fromVertices({ closed: false }, [[0, 0, 0], [2, 0, 0], [2, 1, 0]])
7
+ const p2 = fromVertices({ closed: false }, [[0, 0, 0], [2, 0, 0], [2, 1, 0], [0, 1, 0]])
8
+ t.false(equals(p1, p2))
9
+
10
+ const p3 = fromVertices({ closed: true }, [[2, 0, 0], [2, 1, 0], [0, 1, 0], [1, 0, 0]])
11
+ const p4 = fromVertices({ closed: true }, [[0, 0, 0], [2, 0, 0], [2, 1, 0], [0, 1, 0]])
12
+ t.false(equals(p3, p4))
13
+ })
14
+
15
+ test('equals: two open paths with the same points are equal', (t) => {
16
+ const p1 = fromVertices({ closed: false }, [[0, 0, 0], [2, 0, 0], [2, 1, 0], [0, 1, 0]])
17
+ const p2 = fromVertices({ closed: false }, [[0, 0, 0], [2, 0, 0], [2, 1, 0], [0, 1, 0]])
18
+ t.true(equals(p1, p2))
19
+ })
20
+
21
+ test('equals: two open paths with the same points rotated are unequal', (t) => {
22
+ t.false(equals(fromVertices({ closed: false }, [[0, 0, 0], [2, 0, 0], [2, 1, 0], [0, 1, 0]]),
23
+ fromVertices({ closed: false }, [[2, 0, 0], [2, 1, 0], [0, 1, 0], [0, 0, 0]])))
24
+ })
25
+
26
+ test('equals: two closed paths with the same points are equal', (t) => {
27
+ t.true(equals(fromVertices({ closed: true }, [[0, 0, 0], [2, 0, 0], [2, 1, 0], [0, 1, 0]]),
28
+ fromVertices({ closed: true }, [[0, 0, 0], [2, 0, 0], [2, 1, 0], [0, 1, 0]])))
29
+
30
+ // rotated
31
+ t.true(equals(fromVertices({ closed: true }, [[0, 0, 0], [2, 0, 0], [2, 1, 0], [0, 1, 0]]),
32
+ fromVertices({ closed: true }, [[2, 0, 0], [2, 1, 0], [0, 1, 0], [0, 0, 0]])))
33
+ })
34
+
35
+ test('equals: closed path and open path with the same points are unequal', (t) => {
36
+ t.false(equals(fromVertices({ closed: true }, [[0, 0, 0], [2, 0, 0], [2, 1, 0], [0, 1, 0]]),
37
+ fromVertices({ closed: false }, [[0, 0, 0], [2, 0, 0], [2, 1, 0], [0, 1, 0]])))
38
+ })
@@ -0,0 +1,8 @@
1
+ import type { Path3 } from './type.d.ts'
2
+ import type { Vec3 } from '../../maths/vec3/type.d.ts'
3
+
4
+ export interface FromVerticesOptions {
5
+ closed?: boolean
6
+ }
7
+
8
+ export function fromVertices(options: FromVerticesOptions, vertices: Array<Vec3>): Path3
@@ -0,0 +1,44 @@
1
+ import { EPS } from '../../maths/constants.js'
2
+
3
+ import * as vec3 from '../../maths/vec3/index.js'
4
+
5
+ import { close } from './close.js'
6
+ import { create } from './create.js'
7
+
8
+ /**
9
+ * Create a new path from the given vertices.
10
+ *
11
+ * The vertices must be provided as an array of vertices,
12
+ * where each vertex is an array of three numbers.
13
+ *
14
+ * @param {Object} options - options for construction
15
+ * @param {boolean} [options.closed=false] - if the path should be open or closed
16
+ * @param {Array} vertices - array of vertices (3D) from which to create the path
17
+ * @returns {Path3} a new path
18
+ * @alias module:modeling/path3.fromVertices
19
+ *
20
+ * @example
21
+ * my newPath = path3.fromVertices({closed: true}, [[10, 10, 10], [-10, 10, -10]])
22
+ */
23
+ export const fromVertices = (options, vertices) => {
24
+ const defaults = { closed: false }
25
+ let { closed } = Object.assign({}, defaults, options)
26
+
27
+ let created = create()
28
+ // FIXME why clone vertices?
29
+ created.vertices = vertices.map((vertex) => vec3.clone(vertex))
30
+
31
+ // check if first and last vertices are equal
32
+ if (created.vertices.length > 1) {
33
+ const p0 = created.vertices[0]
34
+ const pn = created.vertices[created.vertices.length - 1]
35
+ // FIXME correct EPS
36
+ if (vec3.distance(p0, pn) < (EPS * EPS)) {
37
+ // and close automatically
38
+ closed = true
39
+ }
40
+ }
41
+ if (closed === true) created = close(created)
42
+
43
+ return created
44
+ }
@@ -0,0 +1,33 @@
1
+ import test from 'ava'
2
+
3
+ import { vec3 } from '../../maths/index.js'
4
+
5
+ import { fromVertices, toVertices, toString } from './index.js'
6
+
7
+ test('fromVertices: creating a path from no vertices produces an open, empty non-canonical path', (t) => {
8
+ const created = fromVertices({}, [])
9
+ t.false(created.isClosed)
10
+ t.deepEqual(toVertices(created), [])
11
+ })
12
+
13
+ test('fromVertices: creating a path from one point produces a open, non-canonical path', (t) => {
14
+ const created = fromVertices({}, [[1, 1, 0]])
15
+ toString(created)
16
+
17
+ t.false(created.isClosed)
18
+ t.deepEqual(toVertices(created), [vec3.fromValues(1, 1, 0)])
19
+ })
20
+
21
+ test('fromVertices: creating a closed path from one point produces a closed, non-canonical path', (t) => {
22
+ const created = fromVertices({ closed: true }, [[1, 1, 0]])
23
+ t.true(created.isClosed)
24
+ t.deepEqual(toVertices(created), [vec3.fromValues(1, 1, 0)])
25
+
26
+ toString(created)
27
+ })
28
+
29
+ test('fromVertices: creating a path from a closed set of vertices creates a closed, non-canonical path', (t) => {
30
+ const created = fromVertices({ closed: false }, [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 0, 0]])
31
+ t.true(created.isClosed)
32
+ t.is(3, created.vertices.length) // the last given point is dropped
33
+ })
@@ -0,0 +1,13 @@
1
+ export { close } from './close.js'
2
+ export { concat } from './concat.js'
3
+ export { create } from './create.js'
4
+ export { equals } from './equals.js'
5
+ export { fromVertices, FromVerticesOptions } from './fromVertices.js'
6
+ export { isA } from './isA.js'
7
+ export { reverse } from './reverse.js'
8
+ export { toString } from './toString.js'
9
+ export { toVertices } from './toVertices.js'
10
+ export { transform } from './transform.js'
11
+ export { validate } from './validate.js'
12
+
13
+ export type { Path3 } from './type.d.ts'
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Represents a 3D geometry consisting of a list of ordered vertices.
3
+ *
4
+ * @see {@link Path3} for data structure information.
5
+ * @module modeling/path3
6
+ *
7
+ * @example
8
+ * import { path3 } from '@jscad/modeling'
9
+ * let myShape = path3.fromVertices({ closed: true }, [[0,0,0], [4,0,0], [4,3,0]])
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} Path3
14
+ * @property {Array} vertices - list of ordered vertices
15
+ * @property {boolean} isClosed - true if the path is closed where start and end vertices are the same
16
+ * @property {Mat4} transforms - transforms to apply to the vertices, see transform()
17
+ *
18
+ * @example
19
+ * // data structure
20
+ * {
21
+ * vertices: [[0,0,0], [4,0,0], [4,3,0]],
22
+ * isClosed: true,
23
+ * transforms: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
24
+ * }
25
+ */
26
+
27
+ export { close } from './close.js'
28
+ export { concat } from './concat.js'
29
+ export { create } from './create.js'
30
+ export { equals } from './equals.js'
31
+ export { fromVertices } from './fromVertices.js'
32
+ export { isA } from './isA.js'
33
+ export { reverse } from './reverse.js'
34
+ export { toString } from './toString.js'
35
+ export { toVertices } from './toVertices.js'
36
+ export { transform } from './transform.js'
37
+ export { validate } from './validate.js'
@@ -0,0 +1,3 @@
1
+ import type { Path3 } from './type.d.ts'
2
+
3
+ export function isA(object: any): object is Path3
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Determine if the given object is a path3 geometry.
3
+ *
4
+ * @param {Object} object - the object to interrogate
5
+ * @returns {boolean} true if the object matches a path3
6
+ * @alias module:modeling/path3.isA
7
+ *
8
+ * @example
9
+ * if (path3.isA(geometry)) { ... }
10
+ */
11
+ export const isA = (object) => {
12
+ if (object && typeof object === 'object') {
13
+ // see create for the required attributes and types
14
+ if ('vertices' in object && 'transforms' in object && 'isClosed' in object) {
15
+ // NOTE: transforms should be a TypedArray, which has a read-only length
16
+ if (Array.isArray(object.vertices) && 'length' in object.transforms) {
17
+ return true
18
+ }
19
+ }
20
+ }
21
+ return false
22
+ }
@@ -0,0 +1,19 @@
1
+ import test from 'ava'
2
+
3
+ import { isA, create, fromVertices } from './index.js'
4
+
5
+ test('isA: identifies created paths', (t) => {
6
+ const p1 = create()
7
+ const p2 = fromVertices({}, [[0, 0, 0]])
8
+ t.true(isA(p1))
9
+ t.true(isA(p2))
10
+ })
11
+
12
+ test('isA: identifies non paths', (t) => {
13
+ const p1 = null
14
+ const p2 = {}
15
+ const p3 = { vertices: 1, transforms: 1, isClosed: 1 }
16
+ t.false(isA(p1))
17
+ t.false(isA(p2))
18
+ t.false(isA(p3))
19
+ })
@@ -0,0 +1,3 @@
1
+ import type { Path3 } from './type.d.ts'
2
+
3
+ export function reverse(path: Path3): Path3
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Reverses the path so that the vertices are in the opposite order.
3
+ *
4
+ * This swaps the left (interior) and right (exterior) edges.
5
+ *
6
+ * @param {Path3} geometry - the path to reverse
7
+ * @returns {Path3} a new path
8
+ * @alias module:modeling/path3.reverse
9
+ *
10
+ * @example
11
+ * let newPath = path3.reverse(oldPath)
12
+ */
13
+ export const reverse = (geometry) => {
14
+ // NOTE: this only updates the order of the vertices
15
+ const cloned = Object.assign({}, geometry)
16
+ cloned.vertices = geometry.vertices.slice().reverse()
17
+ return cloned
18
+ }
@@ -0,0 +1,9 @@
1
+ import test from 'ava'
2
+
3
+ import { reverse, equals, fromVertices } from './index.js'
4
+
5
+ test('reverse: The reverse of a path has reversed points', (t) => {
6
+ const vertices = [[0, 0], [1, 1, 0]]
7
+ t.false(equals(reverse(fromVertices({}, vertices)),
8
+ fromVertices({}, vertices)))
9
+ })
@@ -0,0 +1,3 @@
1
+ import type { Path3 } from './type.d.ts'
2
+
3
+ export function toString(geometry: Path3): string
@@ -0,0 +1,23 @@
1
+ import * as vec3 from '../../maths/vec3/index.js'
2
+
3
+ import { toVertices } from './toVertices.js'
4
+
5
+ /**
6
+ * Create a string representing the contents of the given path.
7
+ *
8
+ * @param {path} geometry - the path
9
+ * @returns {string} a representative string
10
+ * @alias module:modeling/path3.toString
11
+ *
12
+ * @example
13
+ * console.out(path3.toString(geometry))
14
+ */
15
+ export const toString = (geometry) => {
16
+ const vertices = toVertices(geometry)
17
+ let result = 'path (' + vertices.length + ' vertices, ' + geometry.isClosed + '):\n[\n'
18
+ vertices.forEach((vertex) => {
19
+ result += ' ' + vec3.toString(vertex) + ',\n'
20
+ })
21
+ result += ']\n'
22
+ return result
23
+ }
@@ -0,0 +1,4 @@
1
+ import type { Path3 } from './type.d.ts'
2
+ import type { Vec3 } from '../../maths/vec3/type.d.ts'
3
+
4
+ export function toVertices(geometry: Path3): Array<Vec3>
@@ -0,0 +1,15 @@
1
+ import { applyTransforms } from './applyTransforms.js'
2
+
3
+ /**
4
+ * Produces an array of vertices from the given geometry.
5
+ *
6
+ * The returned array should not be modified as the data is shared with the geometry.
7
+ *
8
+ * @param {Path3} geometry - the geometry
9
+ * @returns {Array} an array of vertices
10
+ * @alias module:modeling/path3.toVertices
11
+ *
12
+ * @example
13
+ * let sharedVertices = path3.toVertices(geometry)
14
+ */
15
+ export const toVertices = (geometry) => applyTransforms(geometry).vertices
@@ -0,0 +1,13 @@
1
+ import test from 'ava'
2
+
3
+ import { vec3 } from '../../maths/index.js'
4
+
5
+ import { toVertices, fromVertices } from './index.js'
6
+
7
+ test('toVertices: An empty path produces an empty point array', (t) => {
8
+ t.deepEqual(toVertices(fromVertices({}, [])), [])
9
+ })
10
+
11
+ test('toVertices: An non-empty open path produces a matching point array', (t) => {
12
+ t.deepEqual(toVertices(fromVertices({}, [[1, 1, 0]])), [vec3.fromValues(1, 1, 0)])
13
+ })
@@ -0,0 +1,4 @@
1
+ import type { Path3 } from './type.d.ts'
2
+ import type { Mat4 } from '../../maths/mat4/type.d.ts'
3
+
4
+ export function transform(matrix: Mat4, geometry: Path3): Path3
@@ -0,0 +1,20 @@
1
+ import * as mat4 from '../../maths/mat4/index.js'
2
+
3
+ /**
4
+ * Transform the given geometry using the given matrix.
5
+ *
6
+ * This is a lazy transform of the vertices, as this function only adjusts the transforms.
7
+ * The transforms are applied when accessing the vertices via toVertices().
8
+ *
9
+ * @param {Mat4} matrix - the matrix to transform with
10
+ * @param {Path3} geometry - the geometry to transform
11
+ * @returns {Path3} a new path
12
+ * @alias module:modeling/path3.transform
13
+ *
14
+ * @example
15
+ * let newPath = path3.transform(mat4.fromZRotation(TAU / 8), oldPath)
16
+ */
17
+ export const transform = (matrix, geometry) => {
18
+ const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
19
+ return Object.assign({}, geometry, { transforms })
20
+ }
@@ -0,0 +1,50 @@
1
+ import test from 'ava'
2
+
3
+ import { mat4 } from '../../maths/index.js'
4
+
5
+ import { transform, fromVertices, toVertices } from './index.js'
6
+
7
+ import { comparePoints, compareVectors } from '../../../test/helpers/index.js'
8
+
9
+ test('transform: adjusts the transforms of path', (t) => {
10
+ const vertices = [[0, 0, 0], [1, 0, 0], [0, 1, 0]]
11
+ const rotation = 90 * 0.017453292519943295
12
+ const rotate90 = mat4.fromZRotation(mat4.create(), rotation)
13
+
14
+ // continue with typical user scenario, several iterations of transforms and access
15
+
16
+ // expect lazy transform, i.e. only the transforms change
17
+ const expected = {
18
+ vertices: [[0, 0, 0], [1, 0, 0], [0, 1, 0]],
19
+ isClosed: false,
20
+ transforms: [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
21
+ }
22
+ const geometry = fromVertices({}, vertices)
23
+ let another = transform(rotate90, geometry)
24
+ t.not(geometry, another)
25
+ t.true(comparePoints(another.vertices, expected.vertices))
26
+ t.false(another.isClosed)
27
+ t.true(compareVectors(another.transforms, expected.transforms))
28
+
29
+ // expect lazy transform, i.e. only the transforms change
30
+ expected.transforms = [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 5, 10, 15, 1]
31
+ another = transform(mat4.fromTranslation(mat4.create(), [5, 10, 15]), another)
32
+ t.true(comparePoints(another.vertices, expected.vertices))
33
+ t.false(another.isClosed)
34
+ t.true(compareVectors(another.transforms, expected.transforms))
35
+
36
+ // expect application of the transforms to the vertices
37
+ expected.vertices = [[5, 10, 15], [5, 11, 15], [4, 10, 15]]
38
+ expected.transforms = mat4.create()
39
+ toVertices(another)
40
+ t.true(comparePoints(another.vertices, expected.vertices))
41
+ t.false(another.isClosed)
42
+ t.true(compareVectors(another.transforms, expected.transforms))
43
+
44
+ // expect lazy transform, i.e. only the transforms change
45
+ expected.transforms = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 10, 15, 1]
46
+ another = transform(mat4.fromTranslation(mat4.create(), [5, 10, 15]), another)
47
+ t.true(comparePoints(another.vertices, expected.vertices))
48
+ t.false(another.isClosed)
49
+ t.true(compareVectors(another.transforms, expected.transforms))
50
+ })
@@ -0,0 +1,10 @@
1
+ import type { Vec3 } from '../../maths/vec3/type.d.ts'
2
+ import type { Mat4 } from '../../maths/mat4/type.d.ts'
3
+ import type { Color } from '../types.d.ts'
4
+
5
+ export interface Path3 {
6
+ vertices: Array<Vec3>
7
+ isClosed: boolean
8
+ transforms: Mat4
9
+ color?: Color
10
+ }
@@ -0,0 +1 @@
1
+ export function validate(object: any): void
@@ -0,0 +1,44 @@
1
+ import * as vec3 from '../../maths/vec3/index.js'
2
+
3
+ import { isA } from './isA.js'
4
+
5
+ /**
6
+ * Determine if the given object is a valid path3.
7
+ *
8
+ * Checks for valid vertices, and duplicate vertices.
9
+ *
10
+ * **NOTE: If the geometry is not valid, an exception will be thrown with details of the geometry error.**
11
+ *
12
+ * @param {Object} object - the object to interrogate
13
+ * @throws {Error} error if the geometry is not valid
14
+ * @alias module:modeling/path3.validate
15
+ *
16
+ * @example
17
+ * path3.vaidate(geometry)
18
+ */
19
+ export const validate = (object) => {
20
+ if (!isA(object)) {
21
+ throw new Error('invalid path3 structure')
22
+ }
23
+
24
+ // check for duplicate vertices
25
+ if (object.vertices.length > 1) {
26
+ for (let i = 0; i < object.vertices.length; i++) {
27
+ if (vec3.equals(object.vertices[i], object.vertices[(i + 1) % object.vertices.length])) {
28
+ throw new Error(`path3 has duplicate vertex ${object.vertices[i]}`)
29
+ }
30
+ }
31
+ }
32
+
33
+ // check for infinity, nan
34
+ object.vertices.forEach((vertex) => {
35
+ if (!vertex.every(Number.isFinite)) {
36
+ throw new Error(`path3 invalid non-finite vertex ${vertex}`)
37
+ }
38
+ })
39
+
40
+ // check transforms
41
+ if (!object.transforms.every(Number.isFinite)) {
42
+ throw new Error(`path3 invalid transforms ${object.transforms}`)
43
+ }
44
+ }
@@ -7,7 +7,10 @@ import { reverse } from './reverse.js'
7
7
  * @param {Array} points - a list of points, where each point is an array with X and Y values
8
8
  * @param {Poly2} polygon - a 2D polygon
9
9
  * @return {number} 1 if all points are inside, 0 if some or none are inside
10
- * @alias module:modeling/geometries/poly2.arePointsInside
10
+ * @alias module:modeling/poly2.arePointsInside
11
+ *
12
+ * @example
13
+ * if (poly2.arePointsInside([[0,0], [4,0], [4,3]], geometry) { ... }
11
14
  */
12
15
  export const arePointsInside = (points, polygon) => {
13
16
  if (points.length === 0) return 0 // nothing to check
@@ -3,6 +3,9 @@
3
3
  *
4
4
  * @param {Poly2} polygon - polygon to clone
5
5
  * @returns {Poly2} a new polygon
6
- * @alias module:modeling/geometries/poly2.clone
6
+ * @alias module:modeling/poly2.clone
7
+ *
8
+ * @example
9
+ * const newPoly = poly2.clone(oldPoly)
7
10
  */
8
11
  export const clone = (polygon) => Object.assign({}, polygon)
@@ -1,23 +1,11 @@
1
- /**
2
- * Represents a 2D polygon consisting of a list of ordered points
3
- * which is closed between start and end points.
4
- * @see https://en.wikipedia.org/wiki/Polygon
5
- * @property {Array} points - list of ordered points (2D)
6
- */
7
-
8
1
  /**
9
2
  * Creates a new polygon with initial values.
10
3
  *
11
4
  * @param {Array} [points] - list of points (2D)
12
5
  * @returns {Poly2} a new polygon
13
- * @alias module:modeling/geometries/poly2.create
6
+ * @alias module:modeling/poly2.create
14
7
  *
15
8
  * @example
16
- * let polygon = create([[0,0], [4,0], [4,3]])
9
+ * let polygon = poly2.create([[0,0], [4,0], [4,3]])
17
10
  */
18
- export const create = (points) => {
19
- if (points === undefined || points.length < 3) {
20
- points = [] // empty contents
21
- }
22
- return { points }
23
- }
11
+ export const create = (points = []) => ({ points })
@@ -1,12 +1,24 @@
1
1
  /**
2
2
  * Represents a 2D polygon consisting of a list of ordered points.
3
- * @see {@link poly2} for data structure information.
4
- * @module modeling/geometries/poly2
3
+ * @see {@link Poly2} for data structure information.
4
+ * @module modeling/poly2
5
5
  *
6
6
  * @example
7
- * import { geometries } from '@jscad/modeling'
8
- * const p1 = geometries.poly2.create([[0,0], [4,0], [4,3]])
7
+ * import { poly2 } from '@jscad/modeling'
8
+ * const p1 = poly2.create([[0,0], [4,0], [4,3]])
9
9
  */
10
+
11
+ /**
12
+ * @typedef {Object} Poly2
13
+ * @property {Array} points - list of ordered points (2D)
14
+ *
15
+ * @example
16
+ * // data structure
17
+ * {
18
+ * points: [[0,0], [4,0], [4,3]],
19
+ * }
20
+ */
21
+
10
22
  export { arePointsInside } from './arePointsInside.js'
11
23
  export { clone } from './clone.js'
12
24
  export { create } from './create.js'
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Determine if the given object is a 2D polygon.
3
+ *
3
4
  * @param {object} object - the object to interrogate
4
5
  * @returns {Boolean} true if the object matches a poly2
5
- * @alias module:modeling/geometries/poly2.isA
6
+ * @alias module:modeling/poly2.isA
7
+ *
8
+ * @example
9
+ * if (poly2.isA(geometry)) { ... }
6
10
  */
7
11
  export const isA = (object) => {
8
12
  if (object && typeof object === 'object') {
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Check whether the given polygon is convex.
3
+ *
3
4
  * @param {Poly2} polygon - the polygon to interrogate
4
5
  * @returns {Boolean} true if convex
5
- * @alias module:modeling/geometries/poly2.isConvex
6
+ * @alias module:modeling/poly2.isConvex
7
+ *
8
+ * @example
9
+ * if (poly2.isConvex(geometry)) { ... }
6
10
  */
7
11
  export const isConvex = (polygon) => {
8
12
  const numPoints = polygon.points.length
@@ -3,9 +3,13 @@ import { intersect } from '../../maths/utils/intersect.js'
3
3
  /**
4
4
  * Check whether the given polygon is simple, i.e. does not intersect itself.
5
5
  * @see https://en.wikipedia.org/wiki/Simple_polygon
6
+ *
6
7
  * @param {Poly2} polygon - the polygon to interrogate
7
8
  * @returns {Boolean} true if simple
8
- * @alias module:modeling/geometries/poly2.isSimple
9
+ * @alias module:modeling/poly2.isSimple
10
+ *
11
+ * @example
12
+ * if (poly2.isSimple(geometry)) { ... }
9
13
  */
10
14
  export const isSimple = (polygon) => {
11
15
  const numPoints = polygon.points.length
@@ -5,6 +5,9 @@ import { area } from '../../maths/utils/area.js'
5
5
  *
6
6
  * @param {Poly2} polygon - the polygon to measure
7
7
  * @return {number} the area of the polygon
8
- * @alias module:modeling/geometries/poly2.measureArea
8
+ * @alias module:modeling/poly2.measureArea
9
+ *
10
+ * @example
11
+ * const area = poly2.measureArea(geometry)
9
12
  */
10
13
  export const measureArea = (polygon) => area(polygon.points)