@jscad/modeling 3.0.2-alpha.0 → 3.0.3-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 (116) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/jscad-modeling.es.js +2 -2
  3. package/dist/jscad-modeling.min.js +2 -2
  4. package/package.json +2 -2
  5. package/src/colors/colorize.test.js +1 -1
  6. package/src/geometries/geom2/index.d.ts +0 -2
  7. package/src/geometries/geom2/index.js +0 -2
  8. package/src/geometries/geom3/applyTransforms.test.js +2 -2
  9. package/src/geometries/geom3/clone.test.js +2 -2
  10. package/src/geometries/geom3/create.js +1 -9
  11. package/src/geometries/geom3/{fromPoints.d.ts → fromVertices.d.ts} +1 -1
  12. package/src/geometries/geom3/{fromPoints.js → fromVertices.js} +3 -2
  13. package/src/geometries/geom3/{fromPoints.test.js → fromVertices.test.js} +6 -6
  14. package/src/geometries/geom3/{fromPointsConvex.d.ts → fromVerticesConvex.d.ts} +1 -1
  15. package/src/geometries/geom3/fromVerticesConvex.js +25 -0
  16. package/src/geometries/geom3/{fromPointsConvex.test.js → fromVerticesConvex.test.js} +3 -3
  17. package/src/geometries/geom3/index.d.ts +3 -5
  18. package/src/geometries/geom3/index.js +4 -6
  19. package/src/geometries/geom3/invert.test.js +2 -2
  20. package/src/geometries/geom3/isA.test.js +2 -2
  21. package/src/geometries/geom3/toString.test.js +2 -2
  22. package/src/geometries/geom3/{toPoints.d.ts → toVertices.d.ts} +1 -1
  23. package/src/geometries/geom3/{toPoints.js → toVertices.js} +3 -2
  24. package/src/geometries/geom3/{toPoints.test.js → toVertices.test.js} +4 -4
  25. package/src/geometries/geom3/transform.test.js +2 -2
  26. package/src/geometries/geom3/validate.test.js +4 -4
  27. package/src/geometries/index.d.ts +1 -0
  28. package/src/geometries/index.js +1 -0
  29. package/src/geometries/path2/create.js +1 -10
  30. package/src/geometries/path2/index.d.ts +0 -2
  31. package/src/geometries/path2/index.js +0 -2
  32. package/src/geometries/path3/applyTransforms.js +22 -0
  33. package/src/geometries/path3/applyTransforms.test.js +28 -0
  34. package/src/geometries/path3/close.d.ts +3 -0
  35. package/src/geometries/path3/close.js +31 -0
  36. package/src/geometries/path3/close.test.js +43 -0
  37. package/src/geometries/path3/concat.d.ts +3 -0
  38. package/src/geometries/path3/concat.js +36 -0
  39. package/src/geometries/path3/concat.test.js +35 -0
  40. package/src/geometries/path3/create.d.ts +4 -0
  41. package/src/geometries/path3/create.js +30 -0
  42. package/src/geometries/path3/create.test.js +8 -0
  43. package/src/geometries/path3/equals.d.ts +3 -0
  44. package/src/geometries/path3/equals.js +48 -0
  45. package/src/geometries/path3/equals.test.js +38 -0
  46. package/src/geometries/path3/fromVertices.d.ts +8 -0
  47. package/src/geometries/path3/fromVertices.js +45 -0
  48. package/src/geometries/path3/fromVertices.test.js +33 -0
  49. package/src/geometries/path3/index.d.ts +13 -0
  50. package/src/geometries/path3/index.js +21 -0
  51. package/src/geometries/path3/isA.d.ts +3 -0
  52. package/src/geometries/path3/isA.js +20 -0
  53. package/src/geometries/path3/isA.test.js +19 -0
  54. package/src/geometries/path3/reverse.d.ts +3 -0
  55. package/src/geometries/path3/reverse.js +19 -0
  56. package/src/geometries/path3/reverse.test.js +9 -0
  57. package/src/geometries/path3/toString.d.ts +3 -0
  58. package/src/geometries/path3/toString.js +24 -0
  59. package/src/geometries/path3/toVertices.d.ts +4 -0
  60. package/src/geometries/path3/toVertices.js +16 -0
  61. package/src/geometries/path3/toVertices.test.js +13 -0
  62. package/src/geometries/path3/transform.d.ts +4 -0
  63. package/src/geometries/path3/transform.js +21 -0
  64. package/src/geometries/path3/transform.test.js +50 -0
  65. package/src/geometries/path3/type.d.ts +10 -0
  66. package/src/geometries/path3/validate.d.ts +1 -0
  67. package/src/geometries/path3/validate.js +41 -0
  68. package/src/geometries/poly2/create.js +1 -6
  69. package/src/geometries/poly3/create.js +1 -6
  70. package/src/operations/booleans/intersectGeom3.test.js +4 -4
  71. package/src/operations/booleans/subtractGeom3.test.js +4 -4
  72. package/src/operations/booleans/unionGeom3.test.js +5 -5
  73. package/src/operations/extrusions/extrudeFromSlices.test.js +6 -6
  74. package/src/operations/extrusions/extrudeLinear.test.js +8 -8
  75. package/src/operations/extrusions/extrudeRotate.test.js +12 -12
  76. package/src/operations/hulls/hull.test.js +5 -5
  77. package/src/operations/hulls/hullChain.test.js +5 -5
  78. package/src/operations/hulls/toUniquePoints.js +2 -2
  79. package/src/operations/modifiers/generalize.test.js +6 -6
  80. package/src/operations/modifiers/insertTjunctions.test.js +2 -2
  81. package/src/operations/modifiers/retessellate.test.js +10 -10
  82. package/src/operations/modifiers/snap.test.js +4 -4
  83. package/src/operations/offsets/offsetGeom3.test.js +4 -4
  84. package/src/operations/transforms/center.test.js +7 -7
  85. package/src/operations/transforms/mirror.test.js +7 -7
  86. package/src/operations/transforms/rotate.test.js +7 -7
  87. package/src/operations/transforms/scale.test.js +7 -7
  88. package/src/operations/transforms/transform.test.js +2 -2
  89. package/src/operations/transforms/translate.test.js +7 -7
  90. package/src/primitives/cube.test.js +4 -4
  91. package/src/primitives/cuboid.test.js +4 -4
  92. package/src/primitives/cylinder.test.js +5 -5
  93. package/src/primitives/cylinderElliptic.test.js +9 -9
  94. package/src/primitives/ellipsoid.test.js +5 -5
  95. package/src/primitives/geodesicSphere.test.js +4 -4
  96. package/src/primitives/polyhedron.test.js +2 -2
  97. package/src/primitives/roundedCuboid.test.js +7 -7
  98. package/src/primitives/roundedCylinder.test.js +9 -9
  99. package/src/primitives/sphere.test.js +5 -5
  100. package/src/primitives/torus.test.js +4 -4
  101. package/src/geometries/geom2/fromCompactBinary.d.ts +0 -3
  102. package/src/geometries/geom2/fromCompactBinary.js +0 -40
  103. package/src/geometries/geom2/fromToCompactBinary.test.js +0 -100
  104. package/src/geometries/geom2/toCompactBinary.d.ts +0 -3
  105. package/src/geometries/geom2/toCompactBinary.js +0 -56
  106. package/src/geometries/geom3/fromCompactBinary.d.ts +0 -3
  107. package/src/geometries/geom3/fromCompactBinary.js +0 -42
  108. package/src/geometries/geom3/fromPointsConvex.js +0 -25
  109. package/src/geometries/geom3/fromToCompactBinary.test.js +0 -139
  110. package/src/geometries/geom3/toCompactBinary.d.ts +0 -3
  111. package/src/geometries/geom3/toCompactBinary.js +0 -66
  112. package/src/geometries/path2/fromCompactBinary.d.ts +0 -3
  113. package/src/geometries/path2/fromCompactBinary.js +0 -31
  114. package/src/geometries/path2/fromToCompactBinary.test.js +0 -114
  115. package/src/geometries/path2/toCompactBinary.d.ts +0 -3
  116. package/src/geometries/path2/toCompactBinary.js +0 -50
@@ -0,0 +1,35 @@
1
+ import test from 'ava'
2
+
3
+ import { concat, equals, fromVertices } from './index.js'
4
+
5
+ test('concat: no paths produces an empty open path', (t) => {
6
+ t.true(equals(concat(), fromVertices({ closed: false }, [])))
7
+ })
8
+
9
+ test('concat: empty paths produces an empty open path', (t) => {
10
+ t.true(equals(concat(fromVertices({}, []), fromVertices({}, [])), fromVertices({ closed: false }, [])))
11
+ })
12
+
13
+ test('concat: many open paths produces a open path', (t) => {
14
+ const p1 = fromVertices({ closed: false }, [[0, 0, 0]])
15
+ const p2 = fromVertices({ closed: false }, [[1, 1, 0]])
16
+ const p3 = fromVertices({ closed: false }, [[1, 1, 0], [3, 3, 0]])
17
+
18
+ const result = concat(p1, p2, p3)
19
+ t.true(equals(result, fromVertices({}, [[0, 0, 0], [1, 1, 0], [3, 3, 0]])))
20
+ t.is(p1.vertices.length, 1)
21
+ t.is(p2.vertices.length, 1)
22
+ t.is(p3.vertices.length, 2)
23
+ })
24
+
25
+ test('concat: an open path and a closed path produces a closed path', (t) => {
26
+ t.true(equals(concat(fromVertices({ closed: false }, [[0, 0, 0]]),
27
+ fromVertices({ closed: true }, [[1, 1, 0]])),
28
+ fromVertices({ closed: true }, [[0, 0, 0], [1, 1, 0]])))
29
+ })
30
+
31
+ test('concat: a closed path and an open path throws an error', (t) => {
32
+ const p1 = fromVertices({ closed: true }, [[0, 0, 0]])
33
+ const p2 = fromVertices({ closed: false }, [[1, 1, 0]])
34
+ t.throws(() => concat(p1, p2), { message: 'Cannot concatenate to a closed path; check the 1th path' })
35
+ })
@@ -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 create(vertices?: Array<Vec3>): Path3
@@ -0,0 +1,30 @@
1
+ import * as mat4 from '../../maths/mat4/index.js'
2
+
3
+ /**
4
+ * Represents a 3D geometry consisting of a list of ordered vertices.
5
+ *
6
+ * @typedef {Object} Path3
7
+ * @property {Array} vertices - list of ordered vertices
8
+ * @property {boolean} isClosed - true if the path is closed where start and end vertices are the same
9
+ * @property {Mat4} transforms - transforms to apply to the vertices, see transform()
10
+ *
11
+ * @example
12
+ * {
13
+ * vertices: [[0,0,0], [4,0,0], [4,3,0]],
14
+ * isClosed: true,
15
+ * transforms: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
16
+ * }
17
+ */
18
+
19
+ /**
20
+ * Create an empty, open path.
21
+ *
22
+ * @returns {Path3} a new path
23
+ * @function
24
+ * @alias module:modeling/geometries/path3.create
25
+ *
26
+ * @example
27
+ * let pathA = create()
28
+ * let pathB = create([[0,0,0], [4,0,0], [4,3,0]])
29
+ */
30
+ export const create = (vertices = []) => ({ vertices: vertices, isClosed: false, transforms: mat4.create() })
@@ -0,0 +1,8 @@
1
+ import test from 'ava'
2
+
3
+ import { create, equals, fromVertices } from './index.js'
4
+
5
+ test('create: Creates an empty path', (t) => {
6
+ t.true(equals(create(), fromVertices({ closed: false }, [])))
7
+ t.true(equals(create([[0, 0, 0], [1, 1, 1]]), fromVertices({ closed: false }, [[0, 0, 0], [1, 1, 1]])))
8
+ })
@@ -0,0 +1,3 @@
1
+ import type { Path3 } from './type.d.ts'
2
+
3
+ export function equals(a: Path3, b: Path3): boolean
@@ -0,0 +1,48 @@
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
+ * @function
14
+ * @alias module:modeling/geometries/path3.equals
15
+ */
16
+ export const equals = (a, b) => {
17
+ if (a.isClosed !== b.isClosed) {
18
+ return false
19
+ }
20
+ if (a.vertices.length !== b.vertices.length) {
21
+ return false
22
+ }
23
+
24
+ const aVertices = toVertices(a)
25
+ const bVertices = toVertices(b)
26
+
27
+ // closed paths might be equal under graph rotation
28
+ // so try comparison by rotating across all vertices
29
+ const length = aVertices.length
30
+ let offset = 0
31
+ do {
32
+ let unequal = false
33
+ for (let i = 0; i < length; i++) {
34
+ if (!vec3.equals(aVertices[i], bVertices[(i + offset) % length])) {
35
+ unequal = true
36
+ break
37
+ }
38
+ }
39
+ if (unequal === false) {
40
+ return true
41
+ }
42
+ // unequal open paths should only be compared once, never rotated
43
+ if (!a.isClosed) {
44
+ return false
45
+ }
46
+ } while (++offset < length)
47
+ return false
48
+ }
@@ -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,45 @@
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
+ * @function
19
+ * @alias module:modeling/geometries/path3.fromVertices
20
+ *
21
+ * @example
22
+ * my newPath = fromVertices({closed: true}, [[10, 10, 10], [-10, 10, -10]])
23
+ */
24
+ export const fromVertices = (options, vertices) => {
25
+ const defaults = { closed: false }
26
+ let { closed } = Object.assign({}, defaults, options)
27
+
28
+ let created = create()
29
+ // FIXME why clone vertices?
30
+ created.vertices = vertices.map((vertex) => vec3.clone(vertex))
31
+
32
+ // check if first and last vertices are equal
33
+ if (created.vertices.length > 1) {
34
+ const p0 = created.vertices[0]
35
+ const pn = created.vertices[created.vertices.length - 1]
36
+ // FIXME correct EPS
37
+ if (vec3.distance(p0, pn) < (EPS * EPS)) {
38
+ // and close automatically
39
+ closed = true
40
+ }
41
+ }
42
+ if (closed === true) created = close(created)
43
+
44
+ return created
45
+ }
@@ -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,21 @@
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/geometries/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
+ export { close } from './close.js'
12
+ export { concat } from './concat.js'
13
+ export { create } from './create.js'
14
+ export { equals } from './equals.js'
15
+ export { fromVertices } from './fromVertices.js'
16
+ export { isA } from './isA.js'
17
+ export { reverse } from './reverse.js'
18
+ export { toString } from './toString.js'
19
+ export { toVertices } from './toVertices.js'
20
+ export { transform } from './transform.js'
21
+ 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,20 @@
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
+ * @function
7
+ * @alias module:modeling/geometries/path3.isA
8
+ */
9
+ export const isA = (object) => {
10
+ if (object && typeof object === 'object') {
11
+ // see create for the required attributes and types
12
+ if ('vertices' in object && 'transforms' in object && 'isClosed' in object) {
13
+ // NOTE: transforms should be a TypedArray, which has a read-only length
14
+ if (Array.isArray(object.vertices) && 'length' in object.transforms) {
15
+ return true
16
+ }
17
+ }
18
+ }
19
+ return false
20
+ }
@@ -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,19 @@
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
+ * @function
9
+ * @alias module:modeling/geometries/path3.reverse
10
+ *
11
+ * @example
12
+ * let newPath = reverse(path)
13
+ */
14
+ export const reverse = (geometry) => {
15
+ // NOTE: this only updates the order of the vertices
16
+ const cloned = Object.assign({}, geometry)
17
+ cloned.vertices = geometry.vertices.slice().reverse()
18
+ return cloned
19
+ }
@@ -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,24 @@
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
+ * @function
11
+ * @alias module:modeling/geometries/path3.toString
12
+ *
13
+ * @example
14
+ * console.out(toString(path))
15
+ */
16
+ export const toString = (geometry) => {
17
+ const vertices = toVertices(geometry)
18
+ let result = 'path (' + vertices.length + ' vertices, ' + geometry.isClosed + '):\n[\n'
19
+ vertices.forEach((vertex) => {
20
+ result += ' ' + vec3.toString(vertex) + ',\n'
21
+ })
22
+ result += ']\n'
23
+ return result
24
+ }
@@ -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,16 @@
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
+ * @function
11
+ * @alias module:modeling/geometries/path3.toVertices
12
+ *
13
+ * @example
14
+ * let sharedVertices = toVertices(path)
15
+ */
16
+ 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,21 @@
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
+ * @function
13
+ * @alias module:modeling/geometries/path3.transform
14
+ *
15
+ * @example
16
+ * let newPath = transform(fromZRotation(TAU / 8), path)
17
+ */
18
+ export const transform = (matrix, geometry) => {
19
+ const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
20
+ return Object.assign({}, geometry, { transforms })
21
+ }
@@ -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,41 @@
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
+ * Checks for valid vertices, and duplicate vertices.
8
+ *
9
+ * **If the geometry is not valid, an exception will be thrown with details of the geometry error.**
10
+ *
11
+ * @param {Object} object - the object to interrogate
12
+ * @throws {Error} error if the geometry is not valid
13
+ * @function
14
+ * @alias module:modeling/geometries/path3.validate
15
+ */
16
+ export const validate = (object) => {
17
+ if (!isA(object)) {
18
+ throw new Error('invalid path3 structure')
19
+ }
20
+
21
+ // check for duplicate vertices
22
+ if (object.vertices.length > 1) {
23
+ for (let i = 0; i < object.vertices.length; i++) {
24
+ if (vec3.equals(object.vertices[i], object.vertices[(i + 1) % object.vertices.length])) {
25
+ throw new Error(`path3 has duplicate vertex ${object.vertices[i]}`)
26
+ }
27
+ }
28
+ }
29
+
30
+ // check for infinity, nan
31
+ object.vertices.forEach((vertex) => {
32
+ if (!vertex.every(Number.isFinite)) {
33
+ throw new Error(`path3 invalid non-finite vertex ${vertex}`)
34
+ }
35
+ })
36
+
37
+ // check transforms
38
+ if (!object.transforms.every(Number.isFinite)) {
39
+ throw new Error(`path3 invalid transforms ${object.transforms}`)
40
+ }
41
+ }
@@ -15,9 +15,4 @@
15
15
  * @example
16
16
  * let polygon = create([[0,0], [4,0], [4,3]])
17
17
  */
18
- export const create = (points) => {
19
- if (points === undefined || points.length < 3) {
20
- points = [] // empty contents
21
- }
22
- return { points }
23
- }
18
+ export const create = (points = []) => ({ points })
@@ -16,9 +16,4 @@
16
16
  * @example
17
17
  * const polygon = create([[1, 0], [0, 1], [0, 0]])
18
18
  */
19
- export const create = (vertices) => {
20
- if (vertices === undefined || vertices.length < 3) {
21
- vertices = [] // empty contents
22
- }
23
- return { vertices }
24
- }
19
+ export const create = (vertices = []) => ({ vertices })
@@ -17,7 +17,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
17
17
 
18
18
  // intersect of one object
19
19
  const result1 = intersect(geometry1)
20
- let obs = geom3.toPoints(result1)
20
+ let obs = geom3.toVertices(result1)
21
21
  let exp = [
22
22
  [[2, 0, 0], [1.4142135623730951, -1.414213562373095, 0],
23
23
  [1.0000000000000002, -1, -1.414213562373095], [1.4142135623730951, 0, -1.414213562373095]],
@@ -78,7 +78,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
78
78
  const geometry2 = center({ relativeTo: [10, 10, 10] }, cuboid({ size: [4, 4, 4] }))
79
79
 
80
80
  const result2 = intersect(geometry1, geometry2)
81
- obs = geom3.toPoints(result2)
81
+ obs = geom3.toVertices(result2)
82
82
  t.notThrows(() => geom3.validate(result2))
83
83
  t.is(measureArea(result2), 0)
84
84
  t.is(measureVolume(result2), 0)
@@ -88,7 +88,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
88
88
  const geometry3 = cuboid({ size: [18, 18, 18] })
89
89
 
90
90
  const result3 = intersect(geometry2, geometry3)
91
- obs = geom3.toPoints(result3)
91
+ obs = geom3.toVertices(result3)
92
92
 
93
93
  // the order changes based on the best plane chosen in Node.js
94
94
  exp = [
@@ -108,7 +108,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
108
108
 
109
109
  // intersect of two completely overlapping objects
110
110
  const result4 = intersect(geometry1, geometry3)
111
- obs = geom3.toPoints(result4)
111
+ obs = geom3.toVertices(result4)
112
112
  t.notThrows.skip(() => geom3.validate(result4))
113
113
  t.is(measureArea(result4), 44.053756306589825)
114
114
  t.is(measureVolume(result4), 25.751611331979678)