@jscad/modeling 3.0.4-alpha.0 → 3.0.6-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 +12 -0
- package/dist/jscad-modeling.es.js +2 -2
- package/dist/jscad-modeling.min.js +2 -2
- package/package.json +4 -4
- package/rollup.config.js +1 -1
- package/src/colors/colorize.js +17 -1
- package/src/colors/hsvToRgb.js +1 -0
- package/src/colors/rgbToHsl.js +3 -1
- package/src/colors/rgbToHsv.js +1 -0
- package/src/curves/bezier/create.js +1 -1
- package/src/geometries/geom2/fromPoints.d.ts +4 -0
- package/src/geometries/geom2/fromPoints.js +28 -0
- package/src/geometries/geom2/fromPoints.test.js +22 -0
- package/src/geometries/geom2/index.d.ts +1 -0
- package/src/geometries/geom2/index.js +1 -0
- package/src/geometries/geom2/transform.js +1 -0
- package/src/geometries/geom3/transform.js +1 -0
- package/src/geometries/geom3/validate.js +11 -1
- package/src/geometries/path2/appendArc.js +19 -25
- package/src/geometries/path2/appendBezier.js +8 -11
- package/src/geometries/path2/appendPoints.js +21 -3
- package/src/geometries/path2/appendPoints.test.js +16 -4
- package/src/geometries/path2/close.js +7 -10
- package/src/geometries/path2/concat.js +2 -2
- package/src/geometries/path2/fromPoints.js +7 -7
- package/src/geometries/path2/reverse.js +3 -3
- package/src/geometries/path2/transform.js +1 -0
- package/src/geometries/path2/validate.js +2 -2
- package/src/geometries/path3/clone.d.ts +3 -0
- package/src/geometries/path3/clone.js +11 -0
- package/src/geometries/path3/close.js +7 -8
- package/src/geometries/path3/index.d.ts +1 -0
- package/src/geometries/path3/index.js +1 -0
- package/src/geometries/path3/transform.js +1 -0
- package/src/geometries/poly2/type.d.ts +1 -5
- package/src/geometries/slice/earcut/index.js +4 -2
- package/src/geometries/slice/earcut/linkedPolygon.js +4 -2
- package/src/geometries/slice/fromOutlines.d.ts +5 -0
- package/src/geometries/slice/fromOutlines.js +16 -0
- package/src/geometries/slice/fromOutlines.test.js +17 -0
- package/src/geometries/slice/index.d.ts +1 -1
- package/src/geometries/slice/index.js +1 -1
- package/src/geometries/slice/transform.js +2 -1
- package/src/maths/line2/intersectPointOfLines.js +1 -4
- package/src/maths/plane/fromNormalAndPoint.js +4 -6
- package/src/maths/plane/fromPoints.js +8 -7
- package/src/maths/plane/fromPointsRandom.js +13 -13
- package/src/measurements/measureAggregateEpsilon.js +3 -1
- package/src/measurements/measureAggregateEpsilon.test.js +1 -1
- package/src/measurements/measureArea.js +6 -4
- package/src/measurements/measureArea.test.js +4 -1
- package/src/measurements/measureBoundingBox.js +16 -2
- package/src/measurements/measureBoundingBox.test.js +4 -1
- package/src/measurements/measureBoundingSphere.js +38 -29
- package/src/measurements/measureBoundingSphere.test.js +4 -1
- package/src/measurements/measureCenterOfMass.js +3 -2
- package/src/measurements/measureEpsilon.js +4 -2
- package/src/operations/booleans/martinez/computeFields.js +6 -0
- package/src/operations/booleans/martinez/divideSegment.js +2 -1
- package/src/operations/booleans/martinez/fillQueue.js +3 -1
- package/src/operations/booleans/martinez/subdivideSegments.js +5 -4
- package/src/operations/booleans/subtractGeom3Sub.js +1 -1
- package/src/operations/booleans/trees/PolygonTreeNode.js +3 -0
- package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +1 -3
- package/src/operations/booleans/union.js +1 -1
- package/src/operations/extrusions/extrudeFromSlices.js +1 -1
- package/src/operations/extrusions/extrudeFromSlices.test.js +1 -1
- package/src/operations/extrusions/extrudeHelical.js +2 -1
- package/src/operations/extrusions/extrudeLinear.js +1 -1
- package/src/operations/extrusions/extrudeLinearGeom2.js +12 -8
- package/src/operations/extrusions/extrudeRotate.js +1 -1
- package/src/operations/hulls/hull.js +3 -2
- package/src/operations/hulls/quickhull/Face.js +8 -3
- package/src/operations/hulls/quickhull/QuickHull.js +10 -5
- package/src/operations/hulls/quickhull/VertexList.js +2 -1
- package/src/operations/hulls/toUniquePoints.js +3 -0
- package/src/operations/modifiers/generalize.js +9 -2
- package/src/operations/modifiers/reTesselateCoplanarPolygons.js +2 -2
- package/src/operations/modifiers/reTesselateCoplanarPolygons.test.js +29 -3
- package/src/operations/modifiers/retessellate.js +1 -0
- package/src/operations/modifiers/snap.js +22 -3
- package/src/operations/modifiers/snap.test.js +12 -2
- package/src/operations/offsets/offsetGeom3.test.js +2 -2
- package/src/operations/transforms/align.js +2 -1
- package/src/operations/transforms/align.test.js +1 -1
- package/src/operations/transforms/mirror.js +6 -2
- package/src/operations/transforms/rotate.js +6 -2
- package/src/operations/transforms/scale.js +6 -2
- package/src/operations/transforms/transform.js +6 -2
- package/src/operations/transforms/transform.test.js +16 -5
- package/src/operations/transforms/translate.js +12 -4
- package/src/primitives/arc.js +14 -14
- package/src/primitives/circle.js +10 -9
- package/src/primitives/cube.js +5 -6
- package/src/primitives/cuboid.js +6 -6
- package/src/primitives/cylinder.js +8 -8
- package/src/primitives/cylinderElliptic.js +11 -11
- package/src/primitives/ellipse.js +12 -12
- package/src/primitives/ellipsoid.js +8 -8
- package/src/primitives/geodesicSphere.js +6 -6
- package/src/primitives/line.js +2 -0
- package/src/primitives/polygon.js +6 -7
- package/src/primitives/polyhedron.js +7 -8
- package/src/primitives/rectangle.js +6 -6
- package/src/primitives/roundedCuboid.js +8 -8
- package/src/primitives/roundedCylinder.js +9 -9
- package/src/primitives/roundedRectangle.js +8 -8
- package/src/primitives/sphere.js +7 -8
- package/src/primitives/square.js +6 -6
- package/src/primitives/star.js +12 -14
- package/src/primitives/torus.js +11 -11
- package/src/primitives/triangle.js +7 -6
- package/src/utils/areAllShapesTheSameType.js +4 -0
- package/test/helpers/nearlyEqual.js +1 -0
- package/src/geometries/slice/fromGeom2.d.ts +0 -5
- package/src/geometries/slice/fromGeom2.js +0 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jscad/modeling",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.6-alpha.0",
|
|
4
4
|
"description": "Constructive Solid Geometry (CSG) Library for JSCAD",
|
|
5
5
|
"homepage": "https://openjscad.xyz/",
|
|
6
6
|
"repository": "https://github.com/jscad/OpenJSCAD.org",
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
"module": "dist/jscad-modeling.es.js",
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "rollup --config",
|
|
14
|
-
"coverage": "c8 --all --reporter=html --reporter=text
|
|
14
|
+
"coverage": "c8 --all --reporter=html --reporter=text npm test",
|
|
15
15
|
"test": "ava 'src/**/*.test.js' --verbose --timeout 2m",
|
|
16
16
|
"test:tsd": "tsd",
|
|
17
|
-
"version": "
|
|
17
|
+
"version": "npm run build && git add dist"
|
|
18
18
|
},
|
|
19
19
|
"contributors": [
|
|
20
20
|
{
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"c8": "^10.1.0",
|
|
64
64
|
"rollup": "^4.52.0"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "201d3987b309fd1f383b10b5555dd0a2765cad43"
|
|
67
67
|
}
|
package/rollup.config.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from 'fs'
|
|
|
2
2
|
|
|
3
3
|
import terser from '@rollup/plugin-terser'
|
|
4
4
|
|
|
5
|
-
const {name, version, license} = JSON.parse(fs.readFileSync('package.json'))
|
|
5
|
+
const { name, version, license } = JSON.parse(fs.readFileSync('package.json'))
|
|
6
6
|
|
|
7
7
|
export default {
|
|
8
8
|
input: 'src/index.js',
|
package/src/colors/colorize.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as geom2 from '../geometries/geom2/index.js'
|
|
2
2
|
import * as geom3 from '../geometries/geom3/index.js'
|
|
3
3
|
import * as path2 from '../geometries/path2/index.js'
|
|
4
|
+
import * as path3 from '../geometries/path3/index.js'
|
|
5
|
+
import * as slice from '../geometries/slice/index.js'
|
|
4
6
|
import * as poly3 from '../geometries/poly3/index.js'
|
|
5
7
|
|
|
6
8
|
const colorGeom2 = (color, object) => {
|
|
@@ -21,6 +23,18 @@ const colorPath2 = (color, object) => {
|
|
|
21
23
|
return newPath2
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
const colorPath3 = (color, object) => {
|
|
27
|
+
const newPath3 = path3.clone(object)
|
|
28
|
+
newPath3.color = color
|
|
29
|
+
return newPath3
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const colorSlice = (color, object) => {
|
|
33
|
+
const newSlice = slice.clone(object)
|
|
34
|
+
newSlice.color = color
|
|
35
|
+
return newSlice
|
|
36
|
+
}
|
|
37
|
+
|
|
24
38
|
const colorPoly3 = (color, object) => {
|
|
25
39
|
const newPoly = poly3.clone(object)
|
|
26
40
|
newPoly.color = color
|
|
@@ -46,9 +60,11 @@ export const colorize = (color, ...objects) => {
|
|
|
46
60
|
if (color.length === 3) color = [color[0], color[1], color[2], 1.0] // add alpha
|
|
47
61
|
|
|
48
62
|
const results = objects.map((object) => {
|
|
49
|
-
if (geom2.isA(object)) return colorGeom2(color, object)
|
|
50
63
|
if (geom3.isA(object)) return colorGeom3(color, object)
|
|
64
|
+
if (geom2.isA(object)) return colorGeom2(color, object)
|
|
51
65
|
if (path2.isA(object)) return colorPath2(color, object)
|
|
66
|
+
if (path3.isA(object)) return colorPath3(color, object)
|
|
67
|
+
if (slice.isA(object)) return colorSlice(color, object)
|
|
52
68
|
if (poly3.isA(object)) return colorPoly3(color, object)
|
|
53
69
|
if (Array.isArray(object)) return colorize(color, ...object)
|
|
54
70
|
|
package/src/colors/hsvToRgb.js
CHANGED
package/src/colors/rgbToHsl.js
CHANGED
|
@@ -24,7 +24,8 @@ export const rgbToHsl = (...values) => {
|
|
|
24
24
|
const l = (max + min) / 2
|
|
25
25
|
|
|
26
26
|
if (max === min) {
|
|
27
|
-
h =
|
|
27
|
+
h = 0
|
|
28
|
+
s = 0 // achromatic
|
|
28
29
|
} else {
|
|
29
30
|
const d = max - min
|
|
30
31
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
|
@@ -36,6 +37,7 @@ export const rgbToHsl = (...values) => {
|
|
|
36
37
|
h = (b - r) / d + 2
|
|
37
38
|
break
|
|
38
39
|
case b:
|
|
40
|
+
default:
|
|
39
41
|
h = (r - g) / d + 4
|
|
40
42
|
break
|
|
41
43
|
}
|
package/src/colors/rgbToHsv.js
CHANGED
|
@@ -51,7 +51,7 @@ const getPointType = function (points) {
|
|
|
51
51
|
})
|
|
52
52
|
pType = 'float_' + point.length
|
|
53
53
|
} else throw new Error('Bezier points must all be numbers or arrays of number.')
|
|
54
|
-
if (firstPointType
|
|
54
|
+
if (firstPointType === null) {
|
|
55
55
|
firstPointType = pType
|
|
56
56
|
} else {
|
|
57
57
|
if (firstPointType !== pType) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as vec2 from '../../maths/vec2/index.js'
|
|
2
|
+
|
|
3
|
+
import { create } from './create.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a new 2D geometry from the given points.
|
|
7
|
+
*
|
|
8
|
+
* The direction (rotation) of the points is not relevant,
|
|
9
|
+
* as the points can define a convex or a concave polygon.
|
|
10
|
+
*
|
|
11
|
+
* The geometry must not self intersect, i.e. the sides cannot cross.
|
|
12
|
+
* @param {Array} points - list of points in 2D space
|
|
13
|
+
* @returns {geom2} a new geometry
|
|
14
|
+
* @alias module:modeling/geom2.fromPoints
|
|
15
|
+
*/
|
|
16
|
+
export const fromPoints = (points) => {
|
|
17
|
+
if (!Array.isArray(points)) {
|
|
18
|
+
throw new Error('the given points must be an array')
|
|
19
|
+
}
|
|
20
|
+
const length = points.length
|
|
21
|
+
if (length < 3) {
|
|
22
|
+
throw new Error('the given points must define a closed geometry with three or more points')
|
|
23
|
+
}
|
|
24
|
+
// adjust length if the given points are closed by the same point
|
|
25
|
+
if (vec2.equals(points[0], points[length - 1])) points = points.slice(0, -1)
|
|
26
|
+
|
|
27
|
+
return create([points])
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import test from 'ava'
|
|
2
|
+
|
|
3
|
+
import { fromPoints } from './index.js'
|
|
4
|
+
|
|
5
|
+
test('fromPoints: creates populated geom2', (t) => {
|
|
6
|
+
const points = [[0, 0], [1, 0], [0, 1]]
|
|
7
|
+
const expected = {
|
|
8
|
+
outlines: [[[0, 0], [1, 0], [0, 1]]],
|
|
9
|
+
transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
|
|
10
|
+
}
|
|
11
|
+
t.deepEqual(fromPoints(points), expected)
|
|
12
|
+
|
|
13
|
+
const points2 = [[0, 0], [1, 0], [0, 1], [0, 0]]
|
|
14
|
+
t.deepEqual(fromPoints(points2), expected)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('fromPoints: throws for improper points', (t) => {
|
|
18
|
+
t.throws(() => fromPoints(), { instanceOf: Error })
|
|
19
|
+
t.throws(() => fromPoints(0, 0), { instanceOf: Error })
|
|
20
|
+
t.throws(() => fromPoints([]), { instanceOf: Error })
|
|
21
|
+
t.throws(() => fromPoints([[0, 0]]), { instanceOf: Error })
|
|
22
|
+
})
|
|
@@ -18,6 +18,7 @@ import { reverse } from './reverse.js'
|
|
|
18
18
|
*/
|
|
19
19
|
export const transform = (matrix, geometry) => {
|
|
20
20
|
const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
|
|
21
|
+
// use Object.assign in order to assign EXTRA attributes like color, name, etc
|
|
21
22
|
const transformed = Object.assign({}, geometry, { transforms })
|
|
22
23
|
// determine if the transform is mirroring in 2D
|
|
23
24
|
if (matrix[0] * matrix[5] - matrix[4] * matrix[1] < 0) {
|
|
@@ -17,5 +17,6 @@ import * as mat4 from '../../maths/mat4/index.js'
|
|
|
17
17
|
*/
|
|
18
18
|
export const transform = (matrix, geometry) => {
|
|
19
19
|
const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
|
|
20
|
+
// use Object.assign in order to assign EXTRA attributes like color, name, etc
|
|
20
21
|
return Object.assign({}, geometry, { transforms })
|
|
21
22
|
}
|
|
@@ -22,7 +22,17 @@ export const validate = (object) => {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// check polygons
|
|
25
|
-
object.polygons.forEach(
|
|
25
|
+
object.polygons.forEach((p, i) => {
|
|
26
|
+
if (p.vertices.length < 3) {
|
|
27
|
+
throw new Error(`invalid polygon ${i}: only ${p.vertices.length} vertices`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
poly3.validate(p)
|
|
32
|
+
} catch (e) {
|
|
33
|
+
throw new Error(`invalid polygon ${i}: ${e}`)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
26
36
|
validateManifold(object)
|
|
27
37
|
|
|
28
38
|
// check transforms
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TAU } from '../../maths/constants.js'
|
|
2
2
|
import * as vec2 from '../../maths/vec2/index.js'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { appendPoints } from './appendPoints.js'
|
|
5
5
|
import { toPoints } from './toPoints.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -27,19 +27,19 @@ import { toPoints } from './toPoints.js'
|
|
|
27
27
|
* myShape = path2.appendArc({endpoint: [12.5, -22.96875], radius: [15, -19.6875]}, myShape);
|
|
28
28
|
*/
|
|
29
29
|
export const appendArc = (options, geometry) => {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
const {
|
|
31
|
+
endpoint = null,
|
|
32
|
+
radius = [0, 0], // X and Y radius
|
|
33
|
+
xaxisRotation = 0,
|
|
34
|
+
clockwise = false,
|
|
35
|
+
large = false,
|
|
36
|
+
segments = 16
|
|
37
|
+
} = options
|
|
38
38
|
|
|
39
39
|
// validate the given options
|
|
40
40
|
if (!Array.isArray(endpoint)) throw new Error('endpoint must be an array of X and Y values')
|
|
41
|
-
if (endpoint.length
|
|
42
|
-
|
|
41
|
+
if (endpoint.length !== 2) throw new Error('endpoint must contain X and Y values')
|
|
42
|
+
let xendPoint = vec2.clone(endpoint)
|
|
43
43
|
|
|
44
44
|
if (!Array.isArray(radius)) throw new Error('radius must be an array of X and Y values')
|
|
45
45
|
if (radius.length < 2) throw new Error('radius must contain X and Y values')
|
|
@@ -48,11 +48,6 @@ export const appendArc = (options, geometry) => {
|
|
|
48
48
|
|
|
49
49
|
const decimals = 100000
|
|
50
50
|
|
|
51
|
-
// validate the given geometry
|
|
52
|
-
if (geometry.isClosed) {
|
|
53
|
-
throw new Error('the given path cannot be closed')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
51
|
const points = toPoints(geometry)
|
|
57
52
|
if (points.length < 1) {
|
|
58
53
|
throw new Error('the given path must contain one or more points (as the starting point for the arc)')
|
|
@@ -65,14 +60,14 @@ export const appendArc = (options, geometry) => {
|
|
|
65
60
|
// round to precision in order to have determinate calculations
|
|
66
61
|
xRadius = Math.round(xRadius * decimals) / decimals
|
|
67
62
|
yRadius = Math.round(yRadius * decimals) / decimals
|
|
68
|
-
|
|
63
|
+
xendPoint = vec2.fromValues(Math.round(xendPoint[0] * decimals) / decimals, Math.round(xendPoint[1] * decimals) / decimals)
|
|
69
64
|
|
|
70
65
|
const sweepFlag = !clockwise
|
|
71
|
-
|
|
66
|
+
const newPoints = []
|
|
72
67
|
if ((xRadius === 0) || (yRadius === 0)) {
|
|
73
68
|
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes:
|
|
74
69
|
// If rx = 0 or ry = 0, then treat this as a straight line from (x1, y1) to (x2, y2) and stop
|
|
75
|
-
newPoints.push(
|
|
70
|
+
newPoints.push(xendPoint)
|
|
76
71
|
} else {
|
|
77
72
|
xRadius = Math.abs(xRadius)
|
|
78
73
|
yRadius = Math.abs(yRadius)
|
|
@@ -81,7 +76,7 @@ export const appendArc = (options, geometry) => {
|
|
|
81
76
|
const phi = xaxisRotation
|
|
82
77
|
const cosPhi = Math.cos(phi)
|
|
83
78
|
const sinPhi = Math.sin(phi)
|
|
84
|
-
const minusHalfDistance = vec2.subtract(vec2.create(), startpoint,
|
|
79
|
+
const minusHalfDistance = vec2.subtract(vec2.create(), startpoint, xendPoint)
|
|
85
80
|
vec2.scale(minusHalfDistance, minusHalfDistance, 0.5)
|
|
86
81
|
// F.6.5.1:
|
|
87
82
|
// round to precision in order to have determinate calculations
|
|
@@ -106,7 +101,7 @@ export const appendArc = (options, geometry) => {
|
|
|
106
101
|
vec2.scale(centerTranslated, centerTranslated, multiplier1)
|
|
107
102
|
// F.6.5.3:
|
|
108
103
|
let center = vec2.fromValues(cosPhi * centerTranslated[0] - sinPhi * centerTranslated[1], sinPhi * centerTranslated[0] + cosPhi * centerTranslated[1])
|
|
109
|
-
center = vec2.add(center, center, vec2.scale(vec2.create(), vec2.add(vec2.create(), startpoint,
|
|
104
|
+
center = vec2.add(center, center, vec2.scale(vec2.create(), vec2.add(vec2.create(), startpoint, xendPoint), 0.5))
|
|
110
105
|
|
|
111
106
|
// F.6.5.5:
|
|
112
107
|
const vector1 = vec2.fromValues((startTranslated[0] - centerTranslated[0]) / xRadius, (startTranslated[1] - centerTranslated[1]) / yRadius)
|
|
@@ -134,9 +129,8 @@ export const appendArc = (options, geometry) => {
|
|
|
134
129
|
newPoints.push(point)
|
|
135
130
|
}
|
|
136
131
|
// ensure end point is precisely what user gave as parameter
|
|
137
|
-
if (numSteps) newPoints.push(
|
|
132
|
+
if (numSteps) newPoints.push(endpoint)
|
|
138
133
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return result
|
|
134
|
+
|
|
135
|
+
return appendPoints(newPoints, geometry)
|
|
142
136
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TAU } from '../../maths/constants.js'
|
|
2
2
|
import * as vec2 from '../../maths/vec2/index.js'
|
|
3
|
-
import * as vec3 from '../../maths/vec3/index.js' //
|
|
3
|
+
import * as vec3 from '../../maths/vec3/index.js' // required due to vec2.cross()
|
|
4
4
|
|
|
5
5
|
import { appendPoints } from './appendPoints.js'
|
|
6
6
|
import { toPoints } from './toPoints.js'
|
|
@@ -27,10 +27,10 @@ import { toPoints } from './toPoints.js'
|
|
|
27
27
|
* myShape = path2.appendBezier({controlPoints: [null, [25,-30],[40,-30],[40,-20]]}, myShape)
|
|
28
28
|
*/
|
|
29
29
|
export const appendBezier = (options, geometry) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
let {
|
|
31
|
+
controlPoints = null,
|
|
32
|
+
segments = 16
|
|
33
|
+
} = options
|
|
34
34
|
|
|
35
35
|
// validate the given options
|
|
36
36
|
if (!Array.isArray(controlPoints)) throw new Error('controlPoints must be an array of one or more points')
|
|
@@ -39,10 +39,6 @@ export const appendBezier = (options, geometry) => {
|
|
|
39
39
|
if (segments < 4) throw new Error('segments must be four or more')
|
|
40
40
|
|
|
41
41
|
// validate the given geometry
|
|
42
|
-
if (geometry.isClosed) {
|
|
43
|
-
throw new Error('the given geometry cannot be closed')
|
|
44
|
-
}
|
|
45
|
-
|
|
46
42
|
const points = toPoints(geometry)
|
|
47
43
|
if (points.length < 1) {
|
|
48
44
|
throw new Error('the given path must contain one or more points (as the starting point for the bezier curve)')
|
|
@@ -148,9 +144,10 @@ export const appendBezier = (options, geometry) => {
|
|
|
148
144
|
}
|
|
149
145
|
|
|
150
146
|
// append to the new points to the given path
|
|
151
|
-
// but skip the first new point because it is identical to the last point in the given path
|
|
152
|
-
newPoints.shift()
|
|
153
147
|
const result = appendPoints(newPoints, geometry)
|
|
148
|
+
|
|
149
|
+
// add a hint for the next bezier
|
|
154
150
|
result.lastBezierControlPoint = controlPoints[controlPoints.length - 2]
|
|
151
|
+
|
|
155
152
|
return result
|
|
156
153
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { equals } from '../../maths/vec2/equals.js'
|
|
2
|
+
|
|
3
|
+
import { fromPoints } from './fromPoints.js'
|
|
4
|
+
import { toPoints } from './toPoints.js'
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Append the given list of points to the end of the given geometry.
|
|
@@ -12,4 +14,20 @@ import { create } from './create.js'
|
|
|
12
14
|
* @example
|
|
13
15
|
* let newPath = path2.appendPoints([[3, 4], [4, 5]], oldPath)
|
|
14
16
|
*/
|
|
15
|
-
export const appendPoints = (points, geometry) =>
|
|
17
|
+
export const appendPoints = (points, geometry) => {
|
|
18
|
+
let originalPoints = toPoints(geometry)
|
|
19
|
+
|
|
20
|
+
if (geometry.isClosed && originalPoints.length > 1) {
|
|
21
|
+
// add the closing point to the originalPoints
|
|
22
|
+
originalPoints = originalPoints.slice()
|
|
23
|
+
originalPoints.push(originalPoints[0])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (originalPoints.length > 0) {
|
|
27
|
+
// remove redundant points
|
|
28
|
+
const lastPoint = originalPoints[originalPoints.length - 1]
|
|
29
|
+
while (points.length > 0 && equals(points[0], lastPoint)) points.shift()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return fromPoints({}, originalPoints.concat(points))
|
|
33
|
+
}
|
|
@@ -12,10 +12,16 @@ test('appendPoints: appending to an empty path produces a new path with expected
|
|
|
12
12
|
|
|
13
13
|
test('appendPoints: appending to a path produces a new path with expected points', (t) => {
|
|
14
14
|
const p1 = fromPoints({}, [[1, 1], [2, 2]])
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
let obs = appendPoints([[3, 3], [4, 4]], p1)
|
|
16
|
+
let pts = toPoints(obs)
|
|
17
17
|
t.not(p1, obs)
|
|
18
18
|
t.is(pts.length, 4)
|
|
19
|
+
|
|
20
|
+
const p2 = fromPoints({ closed: true }, [[0, 0], [1, 0], [0, 1]])
|
|
21
|
+
obs = appendPoints([[0, -1], [0, -2]], p2)
|
|
22
|
+
pts = toPoints(obs)
|
|
23
|
+
t.not(p2, obs)
|
|
24
|
+
t.is(pts.length, 6)
|
|
19
25
|
})
|
|
20
26
|
|
|
21
27
|
test('appendPoints: appending empty points to a path produces a new path with expected points', (t) => {
|
|
@@ -28,8 +34,14 @@ test('appendPoints: appending empty points to a path produces a new path with ex
|
|
|
28
34
|
|
|
29
35
|
test('appendPoints: appending same points to a path produces a new path with expected points', (t) => {
|
|
30
36
|
const p1 = fromPoints({}, [[1, 1], [2, 2]])
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
let obs = appendPoints([[2, 2], [3, 3]], p1)
|
|
38
|
+
let pts = toPoints(obs)
|
|
33
39
|
t.not(p1, obs)
|
|
34
40
|
t.is(pts.length, 3)
|
|
41
|
+
|
|
42
|
+
const p2 = fromPoints({ closed: true }, [[0, 0], [1, 0], [0, 1]])
|
|
43
|
+
obs = appendPoints([[0, 0], [0, 0], [0, -1]], p2)
|
|
44
|
+
pts = toPoints(obs)
|
|
45
|
+
t.not(p2, obs)
|
|
46
|
+
t.is(pts.length, 5)
|
|
35
47
|
})
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { EPS } from '../../maths/constants.js'
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import { clone } from './clone.js'
|
|
3
|
+
import { distance } from '../../maths/vec2/distance.js'
|
|
6
4
|
|
|
7
5
|
/**
|
|
8
6
|
* Close the given geometry.
|
|
@@ -17,19 +15,18 @@ import { clone } from './clone.js'
|
|
|
17
15
|
export const close = (geometry) => {
|
|
18
16
|
if (geometry.isClosed) return geometry
|
|
19
17
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (cloned.points.length > 1) {
|
|
18
|
+
const isClosed = true
|
|
19
|
+
const points = geometry.points.slice()
|
|
20
|
+
if (points.length > 1) {
|
|
24
21
|
// make sure the paths are formed properly
|
|
25
|
-
const points = cloned.points
|
|
26
22
|
const p0 = points[0]
|
|
27
23
|
let pn = points[points.length - 1]
|
|
28
|
-
while (
|
|
24
|
+
while (distance(p0, pn) < EPS) {
|
|
29
25
|
points.pop()
|
|
30
26
|
if (points.length === 1) break
|
|
31
27
|
pn = points[points.length - 1]
|
|
32
28
|
}
|
|
33
29
|
}
|
|
34
|
-
|
|
30
|
+
// use Object.assign in order to assign EXTRA attributes like color, name, etc
|
|
31
|
+
return Object.assign({}, geometry, { isClosed, points })
|
|
35
32
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { equals } from '../../maths/vec2/
|
|
1
|
+
import { equals } from '../../maths/vec2/equals.js'
|
|
2
2
|
|
|
3
3
|
import { fromPoints } from './fromPoints.js'
|
|
4
4
|
import { toPoints } from './toPoints.js'
|
|
@@ -6,7 +6,7 @@ import { toPoints } from './toPoints.js'
|
|
|
6
6
|
/**
|
|
7
7
|
* Concatenate the given paths.
|
|
8
8
|
*
|
|
9
|
-
* If
|
|
9
|
+
* If paths contain the same junction point, merge it into one.
|
|
10
10
|
* A concatenation of zero paths is an empty, open path.
|
|
11
11
|
* A concatenation of one closed path to a series of open paths produces a closed path.
|
|
12
12
|
* A concatenation of a path to a closed path is an error.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EPS } from '../../maths/constants.js'
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { distance } from '../../maths/vec2/distance.js'
|
|
4
4
|
|
|
5
5
|
import { close } from './close.js'
|
|
6
6
|
import { create } from './create.js'
|
|
@@ -18,20 +18,20 @@ import { create } from './create.js'
|
|
|
18
18
|
* @alias module:modeling/path2.fromPoints
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
|
-
*
|
|
21
|
+
* let newPath = path2.fromPoints({closed: true}, [[10, 10], [-10, 10]])
|
|
22
22
|
*/
|
|
23
23
|
export const fromPoints = (options, points) => {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
let {
|
|
25
|
+
closed = false
|
|
26
|
+
} = options
|
|
26
27
|
|
|
27
|
-
let created = create()
|
|
28
|
-
created.points = points.map((point) => vec2.clone(point))
|
|
28
|
+
let created = create(points.slice())
|
|
29
29
|
|
|
30
30
|
// check if first and last points are equal
|
|
31
31
|
if (created.points.length > 1) {
|
|
32
32
|
const p0 = created.points[0]
|
|
33
33
|
const pn = created.points[created.points.length - 1]
|
|
34
|
-
if (
|
|
34
|
+
if (distance(p0, pn) < EPS) {
|
|
35
35
|
// and close automatically
|
|
36
36
|
closed = true
|
|
37
37
|
}
|
|
@@ -14,7 +14,7 @@ import { clone } from './clone.js'
|
|
|
14
14
|
*/
|
|
15
15
|
export const reverse = (geometry) => {
|
|
16
16
|
// NOTE: this only updates the order of the points
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
return
|
|
17
|
+
const points = geometry.points.slice().reverse()
|
|
18
|
+
// use Object.assign in order to assign EXTRA attributes like color, name, etc
|
|
19
|
+
return Object.assign({}, geometry, { points })
|
|
20
20
|
}
|
|
@@ -16,5 +16,6 @@ import * as mat4 from '../../maths/mat4/index.js'
|
|
|
16
16
|
*/
|
|
17
17
|
export const transform = (matrix, geometry) => {
|
|
18
18
|
const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
|
|
19
|
+
// use Object.assign in order to assign EXTRA attributes like color, name, etc
|
|
19
20
|
return Object.assign({}, geometry, { transforms })
|
|
20
21
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { equals } from '../../maths/vec2/equals.js'
|
|
2
2
|
|
|
3
3
|
import { isA } from './isA.js'
|
|
4
4
|
|
|
@@ -24,7 +24,7 @@ export const validate = (object) => {
|
|
|
24
24
|
// check for duplicate points
|
|
25
25
|
if (object.points.length > 1) {
|
|
26
26
|
for (let i = 0; i < object.points.length; i++) {
|
|
27
|
-
if (
|
|
27
|
+
if (equals(object.points[i], object.points[(i + 1) % object.points.length])) {
|
|
28
28
|
throw new Error(`path2 has duplicate point ${object.points[i]}`)
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performs a shallow clone of the give geometry.
|
|
3
|
+
*
|
|
4
|
+
* @param {Path3} geometry - the geometry to clone
|
|
5
|
+
* @returns {Path3} a new path
|
|
6
|
+
* @alias module:modeling/path3.clone
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* let newPath = path3.clone(oldPath)
|
|
10
|
+
*/
|
|
11
|
+
export const clone = (geometry) => Object.assign({}, geometry)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EPS } from '../../maths/constants.js'
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { distance } from '../../maths/vec3/distance.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Close the given geometry.
|
|
@@ -15,19 +15,18 @@ import * as vec3 from '../../maths/vec3/index.js'
|
|
|
15
15
|
export const close = (geometry) => {
|
|
16
16
|
if (geometry.isClosed) return geometry
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (cloned.vertices.length > 1) {
|
|
18
|
+
const isClosed = true
|
|
19
|
+
const vertices = geometry.vertices.slice()
|
|
20
|
+
if (vertices.length > 1) {
|
|
22
21
|
// make sure the paths are formed properly
|
|
23
|
-
const vertices = cloned.vertices
|
|
24
22
|
const p0 = vertices[0]
|
|
25
23
|
let pn = vertices[vertices.length - 1]
|
|
26
|
-
while (
|
|
24
|
+
while (distance(p0, pn) < EPS) {
|
|
27
25
|
vertices.pop()
|
|
28
26
|
if (vertices.length === 1) break
|
|
29
27
|
pn = vertices[vertices.length - 1]
|
|
30
28
|
}
|
|
31
29
|
}
|
|
32
|
-
|
|
30
|
+
// use Object.assign in order to assign EXTRA attributes like color, name, etc
|
|
31
|
+
return Object.assign({}, geometry, { isClosed, vertices })
|
|
33
32
|
}
|
|
@@ -16,5 +16,6 @@ import * as mat4 from '../../maths/mat4/index.js'
|
|
|
16
16
|
*/
|
|
17
17
|
export const transform = (matrix, geometry) => {
|
|
18
18
|
const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
|
|
19
|
+
// use Object.assign in order to assign EXTRA attributes like color, name, etc
|
|
19
20
|
return Object.assign({}, geometry, { transforms })
|
|
20
21
|
}
|