@jscad/modeling 2.12.0 → 2.12.2
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 +28 -0
- package/dist/jscad-modeling.min.js +394 -388
- package/package.json +2 -2
- package/src/geometries/geom2/transform.js +9 -1
- package/src/geometries/geom2/transform.test.js +58 -1
- package/src/geometries/geom3/fromPointsConvex.d.ts +6 -0
- package/src/geometries/geom3/fromPointsConvex.js +26 -0
- package/src/geometries/geom3/fromPointsConvex.test.js +26 -0
- package/src/geometries/geom3/index.d.ts +1 -0
- package/src/geometries/geom3/index.js +1 -0
- package/src/maths/mat4/index.d.ts +1 -0
- package/src/maths/mat4/isIdentity.d.ts +5 -0
- package/src/maths/plane/fromNoisyPoints.d.ts +6 -0
- package/src/maths/plane/fromNoisyPoints.js +106 -0
- package/src/maths/plane/fromNoisyPoints.test.js +24 -0
- package/src/maths/plane/index.d.ts +1 -0
- package/src/maths/plane/index.js +1 -0
- package/src/operations/booleans/index.d.ts +1 -0
- package/src/operations/booleans/scission.d.ts +7 -0
- package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +33 -0
- package/src/operations/extrusions/extrudeRotate.js +1 -1
- package/src/operations/transforms/mirror.test.js +9 -3
- package/src/primitives/polygon.d.ts +1 -0
- package/src/primitives/polygon.js +15 -4
- package/src/primitives/polygon.test.js +10 -0
- package/src/primitives/roundedCuboid.js +1 -1
- package/src/primitives/roundedCylinder.js +1 -1
- package/src/primitives/roundedRectangle.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jscad/modeling",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.2",
|
|
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",
|
|
@@ -61,5 +61,5 @@
|
|
|
61
61
|
"nyc": "15.1.0",
|
|
62
62
|
"uglifyify": "5.0.2"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "5fbb5fea458c28b0d96450dde9231e1d4d8ce6a4"
|
|
65
65
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const mat4 = require('../../maths/mat4')
|
|
2
2
|
|
|
3
|
+
const reverse = require('./reverse.js')
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Transform the given geometry using the given matrix.
|
|
5
7
|
* This is a lazy transform of the sides, as this function only adjusts the transforms.
|
|
@@ -14,7 +16,13 @@ const mat4 = require('../../maths/mat4')
|
|
|
14
16
|
*/
|
|
15
17
|
const transform = (matrix, geometry) => {
|
|
16
18
|
const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
|
|
17
|
-
|
|
19
|
+
const transformed = Object.assign({}, geometry, { transforms })
|
|
20
|
+
// determine if the transform is mirroring in 2D
|
|
21
|
+
if (matrix[0] * matrix[5] - matrix[4] * matrix[1] < 0) {
|
|
22
|
+
// reverse the order to preserve the orientation
|
|
23
|
+
return reverse(transformed)
|
|
24
|
+
}
|
|
25
|
+
return transformed
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
module.exports = transform
|
|
@@ -2,7 +2,13 @@ const test = require('ava')
|
|
|
2
2
|
|
|
3
3
|
const mat4 = require('../../maths/mat4')
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { measureArea } = require('../../measurements/index.js')
|
|
6
|
+
|
|
7
|
+
const { mirrorX, mirrorY, mirrorZ } = require('../../operations/transforms/index.js')
|
|
8
|
+
|
|
9
|
+
const { square } = require('../../primitives/index.js')
|
|
10
|
+
|
|
11
|
+
const { fromPoints, transform, toOutlines, toSides } = require('./index.js')
|
|
6
12
|
|
|
7
13
|
const { comparePoints, compareVectors } = require('../../../test/helpers/')
|
|
8
14
|
|
|
@@ -51,3 +57,54 @@ test('transform: adjusts the transforms of geom2', (t) => {
|
|
|
51
57
|
t.true(comparePoints(another.sides[2], expected.sides[2]))
|
|
52
58
|
t.true(compareVectors(another.transforms, expected.transforms))
|
|
53
59
|
})
|
|
60
|
+
|
|
61
|
+
test('transform: geom2 mirrorX', (t) => {
|
|
62
|
+
const geometry = square()
|
|
63
|
+
const transformed = mirrorX(geometry)
|
|
64
|
+
t.is(measureArea(geometry), 4)
|
|
65
|
+
// area will be negative unless we reversed the points
|
|
66
|
+
t.is(measureArea(transformed), 4)
|
|
67
|
+
const pts = toOutlines(transformed)[0]
|
|
68
|
+
const exp = [[-1, 1], [-1, -1], [1, -1], [1, 1]]
|
|
69
|
+
t.true(comparePoints(pts, exp))
|
|
70
|
+
t.deepEqual(toSides(transformed), [
|
|
71
|
+
[[1, 1], [-1, 1]],
|
|
72
|
+
[[-1, 1], [-1, -1]],
|
|
73
|
+
[[-1, -1], [1, -1]],
|
|
74
|
+
[[1, -1], [1, 1]]
|
|
75
|
+
])
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('transform: geom2 mirrorY', (t) => {
|
|
79
|
+
const geometry = square()
|
|
80
|
+
const transformed = mirrorY(geometry)
|
|
81
|
+
t.is(measureArea(geometry), 4)
|
|
82
|
+
// area will be negative unless we reversed the points
|
|
83
|
+
t.is(measureArea(transformed), 4)
|
|
84
|
+
const pts = toOutlines(transformed)[0]
|
|
85
|
+
const exp = [[1, -1], [1, 1], [-1, 1], [-1, -1]]
|
|
86
|
+
t.true(comparePoints(pts, exp))
|
|
87
|
+
t.deepEqual(toSides(transformed), [
|
|
88
|
+
[[-1, -1], [1, -1]],
|
|
89
|
+
[[1, -1], [1, 1]],
|
|
90
|
+
[[1, 1], [-1, 1]],
|
|
91
|
+
[[-1, 1], [-1, -1]]
|
|
92
|
+
])
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('transform: geom2 mirrorZ', (t) => {
|
|
96
|
+
const geometry = square()
|
|
97
|
+
const transformed = mirrorZ(geometry)
|
|
98
|
+
t.is(measureArea(geometry), 4)
|
|
99
|
+
// area will be negative unless we DIDN'T reverse the points
|
|
100
|
+
t.is(measureArea(transformed), 4)
|
|
101
|
+
const pts = toOutlines(transformed)[0]
|
|
102
|
+
const exp = [[-1, -1], [1, -1], [1, 1], [-1, 1]]
|
|
103
|
+
t.true(comparePoints(pts, exp))
|
|
104
|
+
t.deepEqual(toSides(transformed), [
|
|
105
|
+
[[-1, 1], [-1, -1]],
|
|
106
|
+
[[-1, -1], [1, -1]],
|
|
107
|
+
[[1, -1], [1, 1]],
|
|
108
|
+
[[1, 1], [-1, 1]]
|
|
109
|
+
])
|
|
110
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const quickhull = require('../../operations/hulls/quickhull')
|
|
2
|
+
const create = require('./create')
|
|
3
|
+
const poly3 = require('../poly3')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Construct a new convex 3D geometry from a list of unique points.
|
|
7
|
+
* @param {Array} uniquePoints - list of points to construct convex 3D geometry
|
|
8
|
+
* @returns {geom3} a new geometry
|
|
9
|
+
* @alias module:modeling/geometries/geom3.fromPointsConvex
|
|
10
|
+
*/
|
|
11
|
+
const fromPointsConvex = (uniquePoints) => {
|
|
12
|
+
if (!Array.isArray(uniquePoints)) {
|
|
13
|
+
throw new Error('the given points must be an array')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const faces = quickhull(uniquePoints, { skipTriangulation: true })
|
|
17
|
+
|
|
18
|
+
const polygons = faces.map((face) => {
|
|
19
|
+
const vertices = face.map((index) => uniquePoints[index])
|
|
20
|
+
return poly3.create(vertices)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return create(polygons)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = fromPointsConvex
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { fromPointsConvex, validate } = require('./index')
|
|
4
|
+
|
|
5
|
+
test('fromPointsConvex (uniquePoints)', (t) => {
|
|
6
|
+
let out = []
|
|
7
|
+
for(x=-9;x<=9;++x)
|
|
8
|
+
for(y=-9;y<=9;++y)
|
|
9
|
+
for(z=-9;z<=9;++z)
|
|
10
|
+
if (x*x+y*y+z*z <= 96)
|
|
11
|
+
out.push([x,y,z])
|
|
12
|
+
|
|
13
|
+
let obs = fromPointsConvex(out)
|
|
14
|
+
validate(obs)
|
|
15
|
+
t.is(obs.polygons.length, 170)
|
|
16
|
+
t.true(obs.polygons.every((f) => ([3,4,8,9].indexOf(f.vertices.length) !== -1)))
|
|
17
|
+
let c = [0,0,0,0,0,0,0,0,0,0]
|
|
18
|
+
obs.polygons.forEach((f) => c[f.vertices.length]++)
|
|
19
|
+
t.is(c[3], 120);
|
|
20
|
+
t.is(c[4], 24);
|
|
21
|
+
t.is(c[8], 18);
|
|
22
|
+
t.is(c[9], 8);
|
|
23
|
+
let edges2 = 336*2
|
|
24
|
+
obs.polygons.forEach((f) => edges2 -= f.vertices.length)
|
|
25
|
+
t.is(edges2, 0);
|
|
26
|
+
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as clone } from './clone'
|
|
2
2
|
export { default as create } from './create'
|
|
3
|
+
export { default as fromPointsConvex } from './fromPointsConvex'
|
|
3
4
|
export { default as fromPoints } from './fromPoints'
|
|
4
5
|
export { default as fromCompactBinary } from './fromCompactBinary'
|
|
5
6
|
export { default as invert } from './invert'
|
|
@@ -12,6 +12,7 @@ export { default as fromXRotation } from './fromXRotation'
|
|
|
12
12
|
export { default as fromYRotation } from './fromYRotation'
|
|
13
13
|
export { default as fromZRotation } from './fromZRotation'
|
|
14
14
|
export { default as identity } from './identity'
|
|
15
|
+
export { default as isIdentity } from './isIdentity'
|
|
15
16
|
export { default as isMirroring } from './isMirroring'
|
|
16
17
|
export { default as mirrorByPlane } from './mirrorByPlane'
|
|
17
18
|
export { default as multiply } from './multiply'
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const vec3 = require('../vec3')
|
|
2
|
+
const fromNormalAndPoint = require('./fromNormalAndPoint')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a best-fit plane from the given noisy vertices.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: There are two possible orientations for every plane.
|
|
8
|
+
* This function always produces positive orientations.
|
|
9
|
+
*
|
|
10
|
+
* See http://www.ilikebigbits.com for the original discussion
|
|
11
|
+
*
|
|
12
|
+
* @param {Plane} out - receiving plane
|
|
13
|
+
* @param {Array} vertices - list of vertices in any order or position
|
|
14
|
+
* @returns {Plane} out
|
|
15
|
+
* @alias module:modeling/maths/plane.fromNoisyPoints
|
|
16
|
+
*/
|
|
17
|
+
const fromNoisyPoints = (out, ...vertices) => {
|
|
18
|
+
out[0] = 0.0
|
|
19
|
+
out[1] = 0.0
|
|
20
|
+
out[2] = 0.0
|
|
21
|
+
out[3] = 0.0
|
|
22
|
+
|
|
23
|
+
// calculate the centroid of the vertices
|
|
24
|
+
// NOTE: out is the centriod
|
|
25
|
+
const n = vertices.length
|
|
26
|
+
vertices.forEach((v) => {
|
|
27
|
+
vec3.add(out, out, v)
|
|
28
|
+
})
|
|
29
|
+
vec3.scale(out, out, 1.0 / n)
|
|
30
|
+
|
|
31
|
+
// Calculate full 3x3 covariance matrix, excluding symmetries
|
|
32
|
+
let xx = 0.0
|
|
33
|
+
let xy = 0.0
|
|
34
|
+
let xz = 0.0
|
|
35
|
+
let yy = 0.0
|
|
36
|
+
let yz = 0.0
|
|
37
|
+
let zz = 0.0
|
|
38
|
+
|
|
39
|
+
const vn = vec3.create()
|
|
40
|
+
vertices.forEach((v) => {
|
|
41
|
+
// NOTE: out is the centriod
|
|
42
|
+
vec3.subtract(vn, v, out)
|
|
43
|
+
xx += vn[0] * vn[0]
|
|
44
|
+
xy += vn[0] * vn[1]
|
|
45
|
+
xz += vn[0] * vn[2]
|
|
46
|
+
yy += vn[1] * vn[1]
|
|
47
|
+
yz += vn[1] * vn[2]
|
|
48
|
+
zz += vn[2] * vn[2]
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
xx /= n
|
|
52
|
+
xy /= n
|
|
53
|
+
xz /= n
|
|
54
|
+
yy /= n
|
|
55
|
+
yz /= n
|
|
56
|
+
zz /= n
|
|
57
|
+
|
|
58
|
+
// Calculate the smallest Eigenvector of the covariance matrix
|
|
59
|
+
// which becomes the plane normal
|
|
60
|
+
|
|
61
|
+
vn[0] = 0.0
|
|
62
|
+
vn[1] = 0.0
|
|
63
|
+
vn[2] = 0.0
|
|
64
|
+
|
|
65
|
+
// weighted directional vector
|
|
66
|
+
const wdv = vec3.create()
|
|
67
|
+
|
|
68
|
+
// X axis
|
|
69
|
+
let det = yy * zz - yz * yz
|
|
70
|
+
wdv[0] = det
|
|
71
|
+
wdv[1] = xz * yz - xy * zz
|
|
72
|
+
wdv[2] = xy * yz - xz * yy
|
|
73
|
+
|
|
74
|
+
let weight = det * det
|
|
75
|
+
vec3.add(vn, vn, vec3.scale(wdv, wdv, weight))
|
|
76
|
+
|
|
77
|
+
// Y axis
|
|
78
|
+
det = xx * zz - xz * xz
|
|
79
|
+
wdv[0] = xz * yz - xy * zz
|
|
80
|
+
wdv[1] = det
|
|
81
|
+
wdv[2] = xy * xz - yz * xx
|
|
82
|
+
|
|
83
|
+
weight = det * det
|
|
84
|
+
if (vec3.dot(vn, wdv) < 0.0) {
|
|
85
|
+
weight = -weight
|
|
86
|
+
}
|
|
87
|
+
vec3.add(vn, vn, vec3.scale(wdv, wdv, weight))
|
|
88
|
+
|
|
89
|
+
// Z axis
|
|
90
|
+
det = xx * yy - xy * xy
|
|
91
|
+
wdv[0] = xy * yz - xz * yy
|
|
92
|
+
wdv[1] = xy * xz - yz * xx
|
|
93
|
+
wdv[2] = det
|
|
94
|
+
|
|
95
|
+
weight = det * det
|
|
96
|
+
if (vec3.dot(vn, wdv) < 0.0) {
|
|
97
|
+
weight = -weight
|
|
98
|
+
}
|
|
99
|
+
vec3.add(vn, vn, vec3.scale(wdv, wdv, weight))
|
|
100
|
+
|
|
101
|
+
// create the plane from normal and centriod
|
|
102
|
+
// NOTE: out is the centriod
|
|
103
|
+
return fromNormalAndPoint(out, vn, out)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = fromNoisyPoints
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
const { fromNoisyPoints, create } = require('./index')
|
|
3
|
+
|
|
4
|
+
const { compareVectors } = require('../../../test/helpers/index')
|
|
5
|
+
|
|
6
|
+
test('plane: fromNoisyPoints() should return a new plane with correct values', (t) => {
|
|
7
|
+
const obs1 = fromNoisyPoints(create(), [0, 0, 0], [1, 0, 0], [1, 1, 0])
|
|
8
|
+
t.true(compareVectors(obs1, [0, 0, 1, 0]))
|
|
9
|
+
|
|
10
|
+
const obs2 = fromNoisyPoints(obs1, [0, 6, 0], [0, 2, 2], [0, 6, 6])
|
|
11
|
+
t.true(compareVectors(obs2, [1, 0, 0, 0]))
|
|
12
|
+
|
|
13
|
+
// same vertices results in an invalid plane
|
|
14
|
+
const obs3 = fromNoisyPoints(obs1, [0, 6, 0], [0, 6, 0], [0, 6, 0])
|
|
15
|
+
t.true(compareVectors(obs3, [0 / 0, 0 / 0, 0 / 0, 0 / 0]))
|
|
16
|
+
|
|
17
|
+
// co-linear vertices
|
|
18
|
+
const obs4 = fromNoisyPoints(obs1, [0, 0, 0], [1, 0, 0], [2, 0, 0], [0, 1, 0])
|
|
19
|
+
t.true(compareVectors(obs4, [0, 0, 1, 0]))
|
|
20
|
+
|
|
21
|
+
// random vertices
|
|
22
|
+
const obs5 = fromNoisyPoints(obs1, [0, 0, 0], [5, 1, -2], [3, -2, 4], [1, 1, 0])
|
|
23
|
+
t.true(compareVectors(obs5, [0.08054818365229491, 0.8764542170444571, 0.47469990050062555, 0.4185833634679763]))
|
|
24
|
+
})
|
|
@@ -5,6 +5,7 @@ export { default as equals } from './equals'
|
|
|
5
5
|
export { default as flip } from './flip'
|
|
6
6
|
export { default as fromNormalAndPoint } from './fromNormalAndPoint'
|
|
7
7
|
export { default as fromValues } from './fromValues'
|
|
8
|
+
export { default as fromNoisyPoints } from './fromNoisyPoints'
|
|
8
9
|
export { default as fromPoints } from './fromPoints'
|
|
9
10
|
export { default as fromPointsRandom } from './fromPointsRandom'
|
|
10
11
|
export { default as signedDistanceToPoint } from './signedDistanceToPoint'
|
package/src/maths/plane/index.js
CHANGED
|
@@ -32,6 +32,7 @@ module.exports = {
|
|
|
32
32
|
* @function fromValues
|
|
33
33
|
*/
|
|
34
34
|
fromValues: require('../vec4/fromValues'),
|
|
35
|
+
fromNoisyPoints: require('./fromNoisyPoints'),
|
|
35
36
|
fromPoints: require('./fromPoints'),
|
|
36
37
|
fromPointsRandom: require('./fromPointsRandom'),
|
|
37
38
|
projectionOfPoint: require('./projectionOfPoint'),
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Geom2, Geom3 } from '../../geometries/types'
|
|
2
|
+
import RecursiveArray from '../../utils/recursiveArray'
|
|
3
|
+
|
|
4
|
+
export default scission
|
|
5
|
+
|
|
6
|
+
declare function scission(...geometries: RecursiveArray<Geom2>): Geom2
|
|
7
|
+
declare function scission(...geometries: RecursiveArray<Geom3>): Geom3
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Poly3 } from '../../../geometries/types';
|
|
2
|
+
import { Plane } from '../../../maths/types';
|
|
3
|
+
|
|
4
|
+
enum ResType
|
|
5
|
+
{
|
|
6
|
+
coplanar_front = 0,
|
|
7
|
+
coplanar_back = 1,
|
|
8
|
+
front = 2,
|
|
9
|
+
back = 3,
|
|
10
|
+
spanning = 4,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
interface SplitRes
|
|
15
|
+
{
|
|
16
|
+
type: ResType,
|
|
17
|
+
front: Poly3,
|
|
18
|
+
back: Poly3;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Returns object:
|
|
22
|
+
// .type:
|
|
23
|
+
// 0: coplanar-front
|
|
24
|
+
// 1: coplanar-back
|
|
25
|
+
// 2: front
|
|
26
|
+
// 3: back
|
|
27
|
+
// 4: spanning
|
|
28
|
+
// In case the polygon is spanning, returns:
|
|
29
|
+
// .front: a Polygon3 of the front part
|
|
30
|
+
// .back: a Polygon3 of the back part
|
|
31
|
+
declare function splitPolygonByPlane(plane: Plane, polygon: Poly3): SplitRes;
|
|
32
|
+
|
|
33
|
+
export default splitPolygonByPlane;
|
|
@@ -93,7 +93,7 @@ const extrudeRotate = (options, geometry) => {
|
|
|
93
93
|
return [point0, point1]
|
|
94
94
|
})
|
|
95
95
|
// recreate the geometry from the (-) capped points
|
|
96
|
-
geometry = geom2.
|
|
96
|
+
geometry = geom2.create(shapeSides)
|
|
97
97
|
geometry = mirrorX(geometry)
|
|
98
98
|
} else if (pointsWithPositiveX.length >= pointsWithNegativeX.length) {
|
|
99
99
|
shapeSides = shapeSides.map((side) => {
|
|
@@ -2,6 +2,8 @@ const test = require('ava')
|
|
|
2
2
|
|
|
3
3
|
const { comparePoints, comparePolygonsAsPoints } = require('../../../test/helpers')
|
|
4
4
|
|
|
5
|
+
const { measureArea } = require('../../measurements')
|
|
6
|
+
|
|
5
7
|
const { geom2, geom3, path2 } = require('../../geometries')
|
|
6
8
|
|
|
7
9
|
const { mirror, mirrorX, mirrorY, mirrorZ } = require('./index')
|
|
@@ -40,25 +42,29 @@ test('mirror: mirroring of geom2 about X/Y produces expected changes to points',
|
|
|
40
42
|
// mirror about X
|
|
41
43
|
let mirrored = mirror({ normal: [1, 0, 0] }, geometry)
|
|
42
44
|
let obs = geom2.toPoints(mirrored)
|
|
43
|
-
let exp = [[
|
|
45
|
+
let exp = [[0, 5], [5, -5], [-10, -5]]
|
|
44
46
|
t.notThrows(() => geom2.validate(mirrored))
|
|
47
|
+
t.is(measureArea(mirrored), measureArea(geometry))
|
|
45
48
|
t.true(comparePoints(obs, exp))
|
|
46
49
|
|
|
47
50
|
mirrored = mirrorX(geometry)
|
|
48
51
|
obs = geom2.toPoints(mirrored)
|
|
49
52
|
t.notThrows(() => geom2.validate(mirrored))
|
|
53
|
+
t.is(measureArea(mirrored), measureArea(geometry))
|
|
50
54
|
t.true(comparePoints(obs, exp))
|
|
51
55
|
|
|
52
56
|
// mirror about Y
|
|
53
57
|
mirrored = mirror({ normal: [0, 1, 0] }, geometry)
|
|
54
58
|
obs = geom2.toPoints(mirrored)
|
|
55
|
-
exp = [[
|
|
59
|
+
exp = [[0, -5], [-5, 5], [10, 5]]
|
|
56
60
|
t.notThrows(() => geom2.validate(mirrored))
|
|
61
|
+
t.is(measureArea(mirrored), measureArea(geometry))
|
|
57
62
|
t.true(comparePoints(obs, exp))
|
|
58
63
|
|
|
59
64
|
mirrored = mirrorY(geometry)
|
|
60
65
|
obs = geom2.toPoints(mirrored)
|
|
61
66
|
t.notThrows(() => geom2.validate(mirrored))
|
|
67
|
+
t.is(measureArea(mirrored), measureArea(geometry))
|
|
62
68
|
t.true(comparePoints(obs, exp))
|
|
63
69
|
})
|
|
64
70
|
|
|
@@ -146,7 +152,7 @@ test('mirror: mirroring of multiple objects produces an array of mirrored object
|
|
|
146
152
|
t.true(comparePoints(obs, exp))
|
|
147
153
|
|
|
148
154
|
obs = geom2.toPoints(mirrored[2])
|
|
149
|
-
exp = [[
|
|
155
|
+
exp = [[0, -5], [-5, 5], [10, 5]]
|
|
150
156
|
t.notThrows(() => geom2.validate(mirrored[2]))
|
|
151
157
|
t.true(comparePoints(obs, exp))
|
|
152
158
|
})
|
|
@@ -2,10 +2,13 @@ const geom2 = require('../geometries/geom2')
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Construct a polygon in two dimensional space from a list of points, or a list of points and paths.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* NOTE: The ordering of points is important, and must define a counter clockwise rotation of points.
|
|
7
|
+
*
|
|
6
8
|
* @param {Object} options - options for construction
|
|
7
9
|
* @param {Array} options.points - points of the polygon : either flat or nested array of 2D points
|
|
8
10
|
* @param {Array} [options.paths] - paths of the polygon : either flat or nested array of point indexes
|
|
11
|
+
* @param {String} [options.orientation='counterclockwise'] - orientation of points
|
|
9
12
|
* @returns {geom2} new 2D geometry
|
|
10
13
|
* @alias module:modeling/primitives.polygon
|
|
11
14
|
*
|
|
@@ -24,9 +27,10 @@ const geom2 = require('../geometries/geom2')
|
|
|
24
27
|
const polygon = (options) => {
|
|
25
28
|
const defaults = {
|
|
26
29
|
points: [],
|
|
27
|
-
paths: []
|
|
30
|
+
paths: [],
|
|
31
|
+
orientation: 'counterclockwise'
|
|
28
32
|
}
|
|
29
|
-
const { points, paths } = Object.assign({}, defaults, options)
|
|
33
|
+
const { points, paths, orientation } = Object.assign({}, defaults, options)
|
|
30
34
|
|
|
31
35
|
if (!(Array.isArray(points) && Array.isArray(paths))) throw new Error('points and paths must be arrays')
|
|
32
36
|
|
|
@@ -58,13 +62,20 @@ const polygon = (options) => {
|
|
|
58
62
|
const allpoints = []
|
|
59
63
|
listofpolys.forEach((list) => list.forEach((point) => allpoints.push(point)))
|
|
60
64
|
|
|
65
|
+
// convert the list of paths into a list of sides, and accumulate
|
|
61
66
|
let sides = []
|
|
62
67
|
listofpaths.forEach((path) => {
|
|
63
68
|
const setofpoints = path.map((index) => allpoints[index])
|
|
64
69
|
const geometry = geom2.fromPoints(setofpoints)
|
|
65
70
|
sides = sides.concat(geom2.toSides(geometry))
|
|
66
71
|
})
|
|
67
|
-
|
|
72
|
+
|
|
73
|
+
// convert the list of sides into a geometry
|
|
74
|
+
let geometry = geom2.create(sides)
|
|
75
|
+
if (orientation == "clockwise") {
|
|
76
|
+
geometry = geom2.reverse(geometry)
|
|
77
|
+
}
|
|
78
|
+
return geometry
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
module.exports = polygon
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const test = require('ava')
|
|
2
2
|
|
|
3
3
|
const geom2 = require('../geometries/geom2')
|
|
4
|
+
const measureArea = require('../measurements/measureArea')
|
|
4
5
|
|
|
5
6
|
const { polygon } = require('./index')
|
|
6
7
|
|
|
@@ -51,3 +52,12 @@ test('polygon: providing object.points (array) and object.path (array) creates e
|
|
|
51
52
|
t.notThrows(() => geom2.validate(geometry))
|
|
52
53
|
t.true(comparePoints(obs, exp))
|
|
53
54
|
})
|
|
55
|
+
|
|
56
|
+
test('polygon: clockwise points', (t) => {
|
|
57
|
+
const poly = polygon({
|
|
58
|
+
points: [[-10, -0], [-10, -10], [-15, -5]],
|
|
59
|
+
orientation: "clockwise",
|
|
60
|
+
})
|
|
61
|
+
t.is(poly.sides.length, 3)
|
|
62
|
+
t.is(measureArea(poly), 25)
|
|
63
|
+
})
|
|
@@ -150,7 +150,7 @@ const roundedCuboid = (options) => {
|
|
|
150
150
|
|
|
151
151
|
if (roundRadius > (size[0] - EPS) ||
|
|
152
152
|
roundRadius > (size[1] - EPS) ||
|
|
153
|
-
roundRadius > (size[2] - EPS)) throw new Error('roundRadius must be smaller
|
|
153
|
+
roundRadius > (size[2] - EPS)) throw new Error('roundRadius must be smaller than the radius of all dimensions')
|
|
154
154
|
|
|
155
155
|
segments = Math.floor(segments / 4)
|
|
156
156
|
|
|
@@ -38,7 +38,7 @@ const roundedCylinder = (options) => {
|
|
|
38
38
|
if (!isGTE(height, 0)) throw new Error('height must be positive')
|
|
39
39
|
if (!isGTE(radius, 0)) throw new Error('radius must be positive')
|
|
40
40
|
if (!isGTE(roundRadius, 0)) throw new Error('roundRadius must be positive')
|
|
41
|
-
if (roundRadius > radius) throw new Error('roundRadius must be smaller
|
|
41
|
+
if (roundRadius > radius) throw new Error('roundRadius must be smaller than the radius')
|
|
42
42
|
if (!isGTE(segments, 4)) throw new Error('segments must be four or more')
|
|
43
43
|
|
|
44
44
|
// if size is zero return empty geometry
|
|
@@ -44,7 +44,7 @@ const roundedRectangle = (options) => {
|
|
|
44
44
|
size = size.map((v) => v / 2) // convert to radius
|
|
45
45
|
|
|
46
46
|
if (roundRadius > (size[0] - EPS) ||
|
|
47
|
-
roundRadius > (size[1] - EPS)) throw new Error('roundRadius must be smaller
|
|
47
|
+
roundRadius > (size[1] - EPS)) throw new Error('roundRadius must be smaller than the radius of all dimensions')
|
|
48
48
|
|
|
49
49
|
const cornersegments = Math.floor(segments / 4)
|
|
50
50
|
|