@jscad/modeling 3.0.1-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.
- package/CHANGELOG.md +22 -0
- package/dist/jscad-modeling.es.js +2 -2
- package/dist/jscad-modeling.min.js +2 -2
- package/package.json +2 -2
- package/src/colors/colorize.test.js +1 -1
- package/src/geometries/geom2/index.d.ts +0 -2
- package/src/geometries/geom2/index.js +0 -2
- package/src/geometries/geom3/applyTransforms.test.js +2 -2
- package/src/geometries/geom3/clone.test.js +2 -2
- package/src/geometries/geom3/create.js +1 -9
- package/src/geometries/geom3/{fromPoints.d.ts → fromVertices.d.ts} +1 -1
- package/src/geometries/geom3/{fromPoints.js → fromVertices.js} +3 -2
- package/src/geometries/geom3/{fromPoints.test.js → fromVertices.test.js} +6 -6
- package/src/geometries/geom3/{fromPointsConvex.d.ts → fromVerticesConvex.d.ts} +1 -1
- package/src/geometries/geom3/fromVerticesConvex.js +25 -0
- package/src/geometries/geom3/{fromPointsConvex.test.js → fromVerticesConvex.test.js} +3 -3
- package/src/geometries/geom3/index.d.ts +3 -5
- package/src/geometries/geom3/index.js +4 -6
- package/src/geometries/geom3/invert.test.js +2 -2
- package/src/geometries/geom3/isA.test.js +2 -2
- package/src/geometries/geom3/toString.test.js +2 -2
- package/src/geometries/geom3/{toPoints.d.ts → toVertices.d.ts} +1 -1
- package/src/geometries/geom3/{toPoints.js → toVertices.js} +3 -2
- package/src/geometries/geom3/{toPoints.test.js → toVertices.test.js} +4 -4
- package/src/geometries/geom3/transform.test.js +2 -2
- package/src/geometries/geom3/validate.test.js +4 -4
- package/src/geometries/index.d.ts +1 -0
- package/src/geometries/index.js +1 -0
- package/src/geometries/path2/appendBezier.js +1 -1
- package/src/geometries/path2/create.js +1 -10
- package/src/geometries/path2/index.d.ts +0 -2
- package/src/geometries/path2/index.js +0 -2
- package/src/geometries/path3/applyTransforms.js +22 -0
- package/src/geometries/path3/applyTransforms.test.js +28 -0
- package/src/geometries/path3/close.d.ts +3 -0
- package/src/geometries/path3/close.js +31 -0
- package/src/geometries/path3/close.test.js +43 -0
- package/src/geometries/path3/concat.d.ts +3 -0
- package/src/geometries/path3/concat.js +36 -0
- package/src/geometries/path3/concat.test.js +35 -0
- package/src/geometries/path3/create.d.ts +4 -0
- package/src/geometries/path3/create.js +30 -0
- package/src/geometries/path3/create.test.js +8 -0
- package/src/geometries/path3/equals.d.ts +3 -0
- package/src/geometries/path3/equals.js +48 -0
- package/src/geometries/path3/equals.test.js +38 -0
- package/src/geometries/path3/fromVertices.d.ts +8 -0
- package/src/geometries/path3/fromVertices.js +45 -0
- package/src/geometries/path3/fromVertices.test.js +33 -0
- package/src/geometries/path3/index.d.ts +13 -0
- package/src/geometries/path3/index.js +21 -0
- package/src/geometries/path3/isA.d.ts +3 -0
- package/src/geometries/path3/isA.js +20 -0
- package/src/geometries/path3/isA.test.js +19 -0
- package/src/geometries/path3/reverse.d.ts +3 -0
- package/src/geometries/path3/reverse.js +19 -0
- package/src/geometries/path3/reverse.test.js +9 -0
- package/src/geometries/path3/toString.d.ts +3 -0
- package/src/geometries/path3/toString.js +24 -0
- package/src/geometries/path3/toVertices.d.ts +4 -0
- package/src/geometries/path3/toVertices.js +16 -0
- package/src/geometries/path3/toVertices.test.js +13 -0
- package/src/geometries/path3/transform.d.ts +4 -0
- package/src/geometries/path3/transform.js +21 -0
- package/src/geometries/path3/transform.test.js +50 -0
- package/src/geometries/path3/type.d.ts +10 -0
- package/src/geometries/path3/validate.d.ts +1 -0
- package/src/geometries/path3/validate.js +41 -0
- package/src/geometries/poly2/create.js +1 -6
- package/src/geometries/poly3/create.js +1 -6
- package/src/geometries/poly3/index.js +1 -1
- package/src/geometries/poly3/measureBoundingBox.js +2 -0
- package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -1
- package/src/geometries/poly3/measureBoundingSphere.js +25 -8
- package/src/geometries/poly3/measureBoundingSphere.test.js +12 -8
- package/src/index.js +41 -0
- package/src/measurements/measureBoundingSphere.js +2 -6
- package/src/operations/booleans/intersectGeom3.test.js +4 -4
- package/src/operations/booleans/martinez/compareEvents.js +2 -7
- package/src/operations/booleans/martinez/connectEdges.js +30 -41
- package/src/operations/booleans/martinez/contour.js +1 -1
- package/src/operations/booleans/martinez/divideSegment.js +12 -11
- package/src/operations/booleans/martinez/fillQueue.js +24 -28
- package/src/operations/booleans/martinez/index.js +2 -1
- package/src/operations/booleans/martinez/possibleIntersection.js +41 -30
- package/src/operations/booleans/martinez/segmentIntersection.js +7 -9
- package/src/operations/booleans/martinez/splaytree.js +59 -457
- package/src/operations/booleans/martinez/subdivideSegments.js +4 -4
- package/src/operations/booleans/martinez/sweepEvent.js +3 -17
- package/src/operations/booleans/subtractGeom3.test.js +4 -4
- package/src/operations/booleans/trees/Node.js +25 -27
- package/src/operations/booleans/trees/PolygonTreeNode.js +153 -106
- package/src/operations/booleans/trees/Tree.js +9 -4
- package/src/operations/booleans/trees/splitLineSegmentByPlane.js +5 -3
- package/src/operations/booleans/trees/splitPolygonByPlane.js +39 -34
- package/src/operations/booleans/unionGeom3.test.js +5 -5
- package/src/operations/extrusions/extrudeFromSlices.test.js +6 -6
- package/src/operations/extrusions/extrudeLinear.test.js +8 -8
- package/src/operations/extrusions/extrudeRotate.test.js +12 -12
- package/src/operations/extrusions/extrudeWalls.js +3 -1
- package/src/operations/hulls/hull.test.js +5 -5
- package/src/operations/hulls/hullChain.test.js +5 -5
- package/src/operations/hulls/hullPoints2.js +20 -28
- package/src/operations/hulls/toUniquePoints.js +2 -2
- package/src/operations/modifiers/generalize.test.js +6 -6
- package/src/operations/modifiers/insertTjunctions.test.js +2 -2
- package/src/operations/modifiers/mergePolygons.js +2 -3
- package/src/operations/modifiers/reTesselateCoplanarPolygons.js +7 -7
- package/src/operations/modifiers/retessellate.test.js +10 -10
- package/src/operations/modifiers/snap.test.js +4 -4
- package/src/operations/offsets/offsetGeom3.test.js +4 -4
- package/src/operations/transforms/center.test.js +7 -7
- package/src/operations/transforms/mirror.test.js +7 -7
- package/src/operations/transforms/rotate.test.js +7 -7
- package/src/operations/transforms/scale.test.js +7 -7
- package/src/operations/transforms/transform.test.js +2 -2
- package/src/operations/transforms/translate.test.js +7 -7
- package/src/primitives/cube.test.js +4 -4
- package/src/primitives/cuboid.test.js +4 -4
- package/src/primitives/cylinder.test.js +5 -5
- package/src/primitives/cylinderElliptic.test.js +9 -9
- package/src/primitives/ellipsoid.test.js +5 -5
- package/src/primitives/geodesicSphere.test.js +4 -4
- package/src/primitives/polyhedron.test.js +2 -2
- package/src/primitives/roundedCuboid.test.js +7 -7
- package/src/primitives/roundedCylinder.test.js +9 -9
- package/src/primitives/sphere.test.js +5 -5
- package/src/primitives/torus.test.js +4 -4
- package/src/geometries/geom2/fromCompactBinary.d.ts +0 -3
- package/src/geometries/geom2/fromCompactBinary.js +0 -40
- package/src/geometries/geom2/fromToCompactBinary.test.js +0 -100
- package/src/geometries/geom2/toCompactBinary.d.ts +0 -3
- package/src/geometries/geom2/toCompactBinary.js +0 -56
- package/src/geometries/geom3/fromCompactBinary.d.ts +0 -3
- package/src/geometries/geom3/fromCompactBinary.js +0 -42
- package/src/geometries/geom3/fromPointsConvex.js +0 -25
- package/src/geometries/geom3/fromToCompactBinary.test.js +0 -139
- package/src/geometries/geom3/toCompactBinary.d.ts +0 -3
- package/src/geometries/geom3/toCompactBinary.js +0 -66
- package/src/geometries/path2/fromCompactBinary.d.ts +0 -3
- package/src/geometries/path2/fromCompactBinary.js +0 -31
- package/src/geometries/path2/fromToCompactBinary.test.js +0 -114
- package/src/geometries/path2/toCompactBinary.d.ts +0 -3
- package/src/geometries/path2/toCompactBinary.js +0 -50
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { equals } from '../../maths/vec3/index.js'
|
|
2
|
+
|
|
3
|
+
import { fromVertices } from './fromVertices.js'
|
|
4
|
+
import { toVertices } from './toVertices.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Concatenate the given paths.
|
|
8
|
+
*
|
|
9
|
+
* If both contain the same vertex at the junction, merge it into one.
|
|
10
|
+
* A concatenation of zero paths is an empty, open path.
|
|
11
|
+
* A concatenation of one closed path to a series of open paths produces a closed path.
|
|
12
|
+
* A concatenation of a path to a closed path is an error.
|
|
13
|
+
*
|
|
14
|
+
* @param {...Path3} paths - the paths to concatenate
|
|
15
|
+
* @returns {Path3} a new path
|
|
16
|
+
* @function
|
|
17
|
+
* @alias module:modeling/geometries/path3.concat
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* let newPath = concat(fromVertices({}, [[1, 2, 3]]), fromVertices({}, [[4, 5, 6]]))
|
|
21
|
+
*/
|
|
22
|
+
export const concat = (...paths) => {
|
|
23
|
+
// Only the last path can be closed, producing a closed path.
|
|
24
|
+
let isClosed = false
|
|
25
|
+
let vertices = []
|
|
26
|
+
paths.forEach((path, i) => {
|
|
27
|
+
const tmp = toVertices(path).slice()
|
|
28
|
+
if (vertices.length > 0 && tmp.length > 0 && equals(tmp[0], vertices[vertices.length - 1])) tmp.shift()
|
|
29
|
+
if (tmp.length > 0 && isClosed) {
|
|
30
|
+
throw new Error(`Cannot concatenate to a closed path; check the ${i}th path`)
|
|
31
|
+
}
|
|
32
|
+
isClosed = path.isClosed
|
|
33
|
+
vertices = vertices.concat(tmp)
|
|
34
|
+
})
|
|
35
|
+
return fromVertices({ closed: isClosed }, vertices)
|
|
36
|
+
}
|
|
@@ -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,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,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,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,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,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,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,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,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 })
|
|
@@ -15,7 +15,7 @@ export { isA } from './isA.js'
|
|
|
15
15
|
export { isConvex } from './isConvex.js'
|
|
16
16
|
export { measureArea } from './measureArea.js'
|
|
17
17
|
export { measureBoundingBox } from './measureBoundingBox.js'
|
|
18
|
-
export { measureBoundingSphere } from './measureBoundingSphere.js'
|
|
18
|
+
export { measureBoundingSphere, measureBoundingSphereAndCache } from './measureBoundingSphere.js'
|
|
19
19
|
export { measureSignedVolume } from './measureSignedVolume.js'
|
|
20
20
|
export { plane } from './plane.js'
|
|
21
21
|
export { toVertices } from './toVertices.js'
|