@jscad/modeling 2.9.2 → 2.9.5
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 +50 -0
- package/README.md +4 -4
- package/dist/jscad-modeling.min.js +435 -444
- package/package.json +3 -2
- package/src/colors/colorize.d.ts +6 -5
- package/src/colors/colorize.test.js +1 -1
- package/src/geometries/geom2/toOutlines.js +66 -52
- package/src/geometries/geom2/type.d.ts +3 -2
- package/src/geometries/geom3/applyTransforms.js +1 -2
- package/src/geometries/geom3/create.js +1 -1
- package/src/geometries/geom3/create.test.js +1 -1
- package/src/geometries/geom3/fromPoints.js +1 -1
- package/src/geometries/geom3/type.d.ts +3 -2
- package/src/geometries/path2/index.d.ts +0 -1
- package/src/geometries/path2/index.js +0 -1
- package/src/geometries/path2/type.d.ts +3 -2
- package/src/geometries/poly3/create.js +1 -1
- package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -2
- package/src/geometries/poly3/measureBoundingSphere.js +46 -8
- package/src/geometries/poly3/measureBoundingSphere.test.js +16 -26
- package/src/geometries/poly3/type.d.ts +3 -2
- package/src/geometries/poly3/validate.js +14 -0
- package/src/geometries/types.d.ts +4 -2
- package/src/maths/constants.d.ts +1 -0
- package/src/maths/constants.js +11 -0
- package/src/maths/mat4/fromRotation.js +9 -7
- package/src/maths/mat4/fromTaitBryanRotation.js +8 -6
- package/src/maths/mat4/fromXRotation.js +4 -2
- package/src/maths/mat4/fromYRotation.js +4 -2
- package/src/maths/mat4/fromZRotation.js +4 -2
- package/src/maths/mat4/isMirroring.js +11 -11
- package/src/maths/mat4/rotate.js +9 -5
- package/src/maths/mat4/rotateX.js +4 -2
- package/src/maths/mat4/rotateY.js +4 -2
- package/src/maths/mat4/rotateZ.js +4 -2
- package/src/maths/mat4/translate.test.js +2 -3
- package/src/maths/utils/aboutEqualNormals.js +1 -5
- package/src/maths/utils/index.d.ts +1 -0
- package/src/maths/utils/index.js +2 -0
- package/src/{utils → maths/utils}/trigonometry.d.ts +0 -0
- package/src/{utils → maths/utils}/trigonometry.js +1 -2
- package/src/{utils → maths/utils}/trigonometry.test.js +0 -0
- package/src/maths/vec2/distance.js +1 -1
- package/src/maths/vec2/fromAngleRadians.js +4 -2
- package/src/maths/vec2/length.js +1 -1
- package/src/maths/vec2/length.test.js +0 -10
- package/src/maths/vec3/angle.js +2 -2
- package/src/maths/vec3/angle.test.js +0 -12
- package/src/maths/vec3/distance.js +1 -1
- package/src/maths/vec3/length.js +1 -1
- package/src/maths/vec3/length.test.js +0 -10
- package/src/operations/booleans/intersectGeom2.test.js +69 -0
- package/src/operations/booleans/intersectGeom3.js +2 -1
- package/src/operations/booleans/{intersect.test.js → intersectGeom3.test.js} +3 -71
- package/src/operations/booleans/subtractGeom2.test.js +72 -0
- package/src/operations/booleans/subtractGeom3.js +2 -1
- package/src/operations/booleans/{subtract.test.js → subtractGeom3.test.js} +3 -74
- package/src/operations/booleans/to3DWalls.js +1 -1
- package/src/operations/booleans/trees/PolygonTreeNode.js +2 -2
- package/src/operations/booleans/unionGeom2.test.js +166 -0
- package/src/operations/booleans/unionGeom3.js +2 -1
- package/src/operations/booleans/{union.test.js → unionGeom3.test.js} +3 -168
- package/src/operations/expansions/expandGeom3.test.js +14 -14
- package/src/operations/expansions/expandShell.js +5 -4
- package/src/operations/expansions/extrudePolygon.js +7 -7
- package/src/operations/extrusions/extrudeFromSlices.js +3 -2
- package/src/operations/extrusions/extrudeFromSlices.test.js +2 -2
- package/src/operations/extrusions/extrudeRotate.js +5 -1
- package/src/operations/extrusions/extrudeRotate.test.js +47 -47
- package/src/operations/extrusions/extrudeWalls.js +2 -2
- package/src/operations/extrusions/project.js +11 -14
- package/src/operations/extrusions/project.test.js +49 -49
- package/src/operations/extrusions/slice/repair.js +62 -0
- package/src/operations/hulls/hullChain.test.js +4 -4
- package/src/operations/hulls/hullGeom2.js +6 -18
- package/src/operations/hulls/hullGeom3.js +5 -18
- package/src/operations/hulls/hullPath2.js +4 -14
- package/src/operations/hulls/hullPoints2.js +43 -92
- package/src/operations/hulls/toUniquePoints.js +34 -0
- package/src/operations/modifiers/generalize.js +2 -13
- package/src/operations/modifiers/generalize.test.js +0 -32
- package/src/operations/modifiers/insertTjunctions.js +1 -1
- package/src/operations/modifiers/insertTjunctions.test.js +21 -21
- package/src/operations/modifiers/mergePolygons.js +11 -14
- package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.js +33 -32
- package/src/operations/{booleans → modifiers}/reTesselateCoplanarPolygons.test.js +5 -5
- package/src/operations/{booleans → modifiers}/retessellate.js +2 -9
- package/src/operations/{booleans → modifiers}/retessellate.test.js +0 -0
- package/src/operations/modifiers/snapPolygons.test.js +12 -12
- package/src/operations/modifiers/triangulatePolygons.js +3 -3
- package/src/operations/transforms/center.js +1 -1
- package/src/primitives/circle.test.js +7 -0
- package/src/primitives/cuboid.js +1 -1
- package/src/primitives/cylinderElliptic.js +5 -3
- package/src/primitives/cylinderElliptic.test.js +7 -1
- package/src/primitives/ellipse.js +1 -1
- package/src/primitives/ellipse.test.js +7 -0
- package/src/primitives/ellipsoid.js +3 -3
- package/src/primitives/geodesicSphere.js +3 -2
- package/src/primitives/polyhedron.js +1 -1
- package/src/primitives/roundedCuboid.js +10 -8
- package/src/primitives/roundedCylinder.js +2 -2
- package/src/primitives/torus.test.js +11 -7
- package/src/primitives/triangle.js +1 -2
- package/src/utils/index.d.ts +0 -1
- package/src/utils/index.js +1 -3
- package/src/geometries/path2/eachPoint.d.ts +0 -9
- package/src/geometries/path2/eachPoint.js +0 -17
- package/src/geometries/path2/eachPoint.test.js +0 -11
- package/src/maths/mat4/constants.d.ts +0 -1
- package/src/maths/mat4/constants.js +0 -5
- package/src/operations/extrusions/slice/repairSlice.js +0 -47
- package/src/operations/modifiers/edges.js +0 -195
- package/src/operations/modifiers/repairTjunctions.js +0 -44
|
@@ -11,7 +11,7 @@ test('extrudeRotate: (defaults) extruding of a geom2 produces an expected geom3'
|
|
|
11
11
|
|
|
12
12
|
const geometry3 = extrudeRotate({ }, geometry2)
|
|
13
13
|
const pts = geom3.toPoints(geometry3)
|
|
14
|
-
t.notThrows
|
|
14
|
+
t.notThrows(() => geom3.validate(geometry3))
|
|
15
15
|
t.is(pts.length, 96)
|
|
16
16
|
})
|
|
17
17
|
|
|
@@ -22,18 +22,18 @@ test('extrudeRotate: (angle) extruding of a geom2 produces an expected geom3', (
|
|
|
22
22
|
let geometry3 = extrudeRotate({ segments: 4, angle: Math.PI / 4 }, geometry2)
|
|
23
23
|
let pts = geom3.toPoints(geometry3)
|
|
24
24
|
const exp = [
|
|
25
|
-
[[10,
|
|
26
|
-
[[10,
|
|
27
|
-
[[10,
|
|
28
|
-
[[10,
|
|
29
|
-
[[26,
|
|
30
|
-
[[26,
|
|
31
|
-
[[26,
|
|
32
|
-
[[26,
|
|
33
|
-
[[7.
|
|
34
|
-
[[18.
|
|
35
|
-
[[26,
|
|
36
|
-
[[10,
|
|
25
|
+
[[10, 0, 8], [26, 0, 8], [18.38477631085024, 18.384776310850235, 8]],
|
|
26
|
+
[[10, 0, 8], [18.38477631085024, 18.384776310850235, 8], [7.0710678118654755, 7.071067811865475, 8]],
|
|
27
|
+
[[10, 0, -8], [10, 0, 8], [7.0710678118654755, 7.071067811865475, 8]],
|
|
28
|
+
[[10, 0, -8], [7.0710678118654755, 7.071067811865475, 8], [7.0710678118654755, 7.071067811865475, -8]],
|
|
29
|
+
[[26, 0, -8], [10, 0, -8], [7.0710678118654755, 7.071067811865475, -8]],
|
|
30
|
+
[[26, 0, -8], [7.0710678118654755, 7.071067811865475, -8], [18.38477631085024, 18.384776310850235, -8]],
|
|
31
|
+
[[26, 0, 8], [26, 0, -8], [18.38477631085024, 18.384776310850235, -8]],
|
|
32
|
+
[[26, 0, 8], [18.38477631085024, 18.384776310850235, -8], [18.38477631085024, 18.384776310850235, 8]],
|
|
33
|
+
[[7.0710678118654755, 7.071067811865475, -8], [7.0710678118654755, 7.071067811865475, 8], [18.38477631085024, 18.384776310850235, 8]],
|
|
34
|
+
[[18.38477631085024, 18.384776310850235, 8], [18.38477631085024, 18.384776310850235, -8], [7.0710678118654755, 7.071067811865475, -8]],
|
|
35
|
+
[[26, 0, 8], [10, 0, 8], [10, 0, -8]],
|
|
36
|
+
[[10, 0, -8], [26, 0, -8], [26, 0, 8]]
|
|
37
37
|
]
|
|
38
38
|
t.notThrows(() => geom3.validate(geometry3))
|
|
39
39
|
t.is(pts.length, 12)
|
|
@@ -61,7 +61,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
|
|
|
61
61
|
[18.38477631085024, 18.384776310850235, 8],
|
|
62
62
|
[-11.803752993228215, 23.166169628897567, 8]
|
|
63
63
|
]
|
|
64
|
-
t.notThrows
|
|
64
|
+
t.notThrows(() => geom3.validate(geometry3))
|
|
65
65
|
t.is(pts.length, 40)
|
|
66
66
|
t.true(comparePoints(pts[0], exp))
|
|
67
67
|
|
|
@@ -72,7 +72,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
|
|
|
72
72
|
[18.38477631085024, -18.384776310850235, 8],
|
|
73
73
|
[23.166169628897567, 11.803752993228215, 8]
|
|
74
74
|
]
|
|
75
|
-
t.notThrows
|
|
75
|
+
t.notThrows(() => geom3.validate(geometry3))
|
|
76
76
|
t.is(pts.length, 40)
|
|
77
77
|
t.true(comparePoints(pts[0], exp))
|
|
78
78
|
})
|
|
@@ -83,26 +83,26 @@ test('extrudeRotate: (segments) extruding of a geom2 produces an expected geom3'
|
|
|
83
83
|
// test segments
|
|
84
84
|
let geometry3 = extrudeRotate({ segments: 4 }, geometry2)
|
|
85
85
|
let pts = geom3.toPoints(geometry3)
|
|
86
|
-
t.notThrows
|
|
86
|
+
t.notThrows(() => geom3.validate(geometry3))
|
|
87
87
|
t.is(pts.length, 32)
|
|
88
88
|
|
|
89
89
|
geometry3 = extrudeRotate({ segments: 64 }, geometry2)
|
|
90
90
|
pts = geom3.toPoints(geometry3)
|
|
91
|
-
t.notThrows
|
|
91
|
+
t.notThrows(() => geom3.validate(geometry3))
|
|
92
92
|
t.is(pts.length, 512)
|
|
93
93
|
|
|
94
94
|
// test overlapping edges
|
|
95
95
|
geometry2 = geom2.fromPoints([[0, 0], [2, 1], [1, 2], [1, 3], [3, 4], [0, 5]])
|
|
96
96
|
geometry3 = extrudeRotate({ segments: 8 }, geometry2)
|
|
97
97
|
pts = geom3.toPoints(geometry3)
|
|
98
|
-
t.notThrows
|
|
98
|
+
t.notThrows(() => geom3.validate(geometry3))
|
|
99
99
|
t.is(pts.length, 64)
|
|
100
100
|
|
|
101
101
|
// test overlapping edges that produce hollow shape
|
|
102
102
|
geometry2 = geom2.fromPoints([[30, 0], [30, 60], [0, 60], [0, 50], [10, 40], [10, 30], [0, 20], [0, 10], [10, 0]])
|
|
103
103
|
geometry3 = extrudeRotate({ segments: 8 }, geometry2)
|
|
104
104
|
pts = geom3.toPoints(geometry3)
|
|
105
|
-
t.notThrows
|
|
105
|
+
t.notThrows(() => geom3.validate(geometry3))
|
|
106
106
|
t.is(pts.length, 80)
|
|
107
107
|
})
|
|
108
108
|
|
|
@@ -113,16 +113,16 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
|
|
|
113
113
|
let obs = extrudeRotate({ segments: 4, angle: Math.PI / 2 }, geometry)
|
|
114
114
|
let pts = geom3.toPoints(obs)
|
|
115
115
|
let exp = [
|
|
116
|
-
[[0,
|
|
117
|
-
[[7,
|
|
118
|
-
[[7,
|
|
119
|
-
[[7,
|
|
120
|
-
[[
|
|
121
|
-
[[
|
|
122
|
-
[[7,
|
|
123
|
-
[[0,
|
|
116
|
+
[[0, 0, 8], [7, 0, 8], [0, 7, 8]],
|
|
117
|
+
[[7, 0, -8], [0, 0, -8], [0, 7, -8]],
|
|
118
|
+
[[7, 0, 8], [7, 0, -8], [0, 7, -8]],
|
|
119
|
+
[[7, 0, 8], [0, 7, -8], [0, 7, 8]],
|
|
120
|
+
[[0, 0, -8], [0, 0, 8], [0, 7, 8]],
|
|
121
|
+
[[0, 7, 8], [0, 7, -8], [0, 0, -8]],
|
|
122
|
+
[[7, 0, 8], [0, 0, 8], [0, 0, -8]],
|
|
123
|
+
[[0, 0, -8], [7, 0, -8], [7, 0, 8]]
|
|
124
124
|
]
|
|
125
|
-
t.notThrows
|
|
125
|
+
t.notThrows(() => geom3.validate(obs))
|
|
126
126
|
t.is(pts.length, 8)
|
|
127
127
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
128
128
|
|
|
@@ -132,26 +132,26 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
|
|
|
132
132
|
obs = extrudeRotate({ segments: 8, angle: Math.PI / 2 }, geometry)
|
|
133
133
|
pts = geom3.toPoints(obs)
|
|
134
134
|
exp = [
|
|
135
|
-
[[1,
|
|
136
|
-
[[2,
|
|
137
|
-
[[2,
|
|
138
|
-
[[1,
|
|
139
|
-
[[1,
|
|
140
|
-
[[0,
|
|
141
|
-
[[0.
|
|
142
|
-
[[1.
|
|
143
|
-
[[1.
|
|
144
|
-
[[0.
|
|
145
|
-
[[0.
|
|
146
|
-
[[
|
|
147
|
-
[[
|
|
148
|
-
[[
|
|
149
|
-
[[
|
|
150
|
-
[[0,
|
|
151
|
-
[[2,
|
|
152
|
-
[[0,
|
|
135
|
+
[[1, 0, -8], [0, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]],
|
|
136
|
+
[[2, 0, 4], [1, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]],
|
|
137
|
+
[[2, 0, 4], [0.7071067811865476, 0.7071067811865475, -8], [1.4142135623730951, 1.414213562373095, 4]],
|
|
138
|
+
[[1, 0, 8], [2, 0, 4], [1.4142135623730951, 1.414213562373095, 4]],
|
|
139
|
+
[[1, 0, 8], [1.4142135623730951, 1.414213562373095, 4], [0.7071067811865476, 0.7071067811865475, 8]],
|
|
140
|
+
[[0, 0, 8], [1, 0, 8], [0.7071067811865476, 0.7071067811865475, 8]],
|
|
141
|
+
[[0.7071067811865476, 0.7071067811865475, -8], [0, 0, -8], [0, 1, -8]],
|
|
142
|
+
[[1.4142135623730951, 1.414213562373095, 4], [0.7071067811865476, 0.7071067811865475, -8], [0, 1, -8]],
|
|
143
|
+
[[1.4142135623730951, 1.414213562373095, 4], [0, 1, -8], [0, 2, 4]],
|
|
144
|
+
[[0.7071067811865476, 0.7071067811865475, 8], [1.4142135623730951, 1.414213562373095, 4], [0, 2, 4]],
|
|
145
|
+
[[0.7071067811865476, 0.7071067811865475, 8], [0, 2, 4], [0, 1, 8]],
|
|
146
|
+
[[0, 0, 8], [0.7071067811865476, 0.7071067811865475, 8], [0, 1, 8]],
|
|
147
|
+
[[0, 1, -8], [0, 0, -8], [0, 0, 8]],
|
|
148
|
+
[[0, 0, 8], [0, 1, 8], [0, 2, 4]],
|
|
149
|
+
[[0, 2, 4], [0, 1, -8], [0, 0, 8]],
|
|
150
|
+
[[0, 0, 8], [0, 0, -8], [1, 0, -8]],
|
|
151
|
+
[[2, 0, 4], [1, 0, 8], [0, 0, 8]],
|
|
152
|
+
[[0, 0, 8], [1, 0, -8], [2, 0, 4]]
|
|
153
153
|
]
|
|
154
|
-
t.notThrows
|
|
154
|
+
t.notThrows(() => geom3.validate(obs))
|
|
155
155
|
t.is(pts.length, 18)
|
|
156
156
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
157
157
|
})
|
|
@@ -64,11 +64,11 @@ const extrudeWalls = (slice0, slice1) => {
|
|
|
64
64
|
edges0.forEach((edge0, i) => {
|
|
65
65
|
const edge1 = edges1[i]
|
|
66
66
|
|
|
67
|
-
const poly0 = poly3.
|
|
67
|
+
const poly0 = poly3.create([edge0[0], edge0[1], edge1[1]])
|
|
68
68
|
const poly0area = poly3.measureArea(poly0)
|
|
69
69
|
if (Number.isFinite(poly0area) && poly0area > EPSAREA) walls.push(poly0)
|
|
70
70
|
|
|
71
|
-
const poly1 = poly3.
|
|
71
|
+
const poly1 = poly3.create([edge0[0], edge1[1], edge1[0]])
|
|
72
72
|
const poly1area = poly3.measureArea(poly1)
|
|
73
73
|
if (Number.isFinite(poly1area) && poly1area > EPSAREA) walls.push(poly1)
|
|
74
74
|
})
|
|
@@ -11,7 +11,6 @@ const poly3 = require('../../geometries/poly3')
|
|
|
11
11
|
const measureEpsilon = require('../../measurements/measureEpsilon')
|
|
12
12
|
|
|
13
13
|
const unionGeom2 = require('../booleans/unionGeom2')
|
|
14
|
-
const unionGeom3 = require('../booleans/unionGeom3')
|
|
15
14
|
|
|
16
15
|
const projectGeom3 = (options, geometry) => {
|
|
17
16
|
// create a plane from the options, and verify
|
|
@@ -27,32 +26,30 @@ const projectGeom3 = (options, geometry) => {
|
|
|
27
26
|
|
|
28
27
|
// project the polygons to the plane
|
|
29
28
|
const polygons = geom3.toPolygons(geometry)
|
|
30
|
-
|
|
29
|
+
let projpolys = []
|
|
31
30
|
for (let i = 0; i < polygons.length; i++) {
|
|
32
31
|
const newpoints = polygons[i].vertices.map((v) => plane.projectionOfPoint(projplane, v))
|
|
33
32
|
const newpoly = poly3.create(newpoints)
|
|
34
|
-
// only keep projections that have a measurable area
|
|
35
|
-
if (poly3.measureArea(newpoly) < epsilonArea) continue
|
|
36
33
|
// only keep projections that face the same direction as the plane
|
|
37
34
|
const newplane = poly3.plane(newpoly)
|
|
38
35
|
if (!aboutEqualNormals(projplane, newplane)) continue
|
|
36
|
+
// only keep projections that have a measurable area
|
|
37
|
+
if (poly3.measureArea(newpoly) < epsilonArea) continue
|
|
39
38
|
projpolys.push(newpoly)
|
|
40
39
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
projection = unionGeom3(projection, projection)
|
|
44
|
-
// rotate the projection to lay on X/Y axes if necessary
|
|
40
|
+
|
|
41
|
+
// rotate the polygons to lay on X/Y axes if necessary
|
|
45
42
|
if (!aboutEqualNormals(projplane, [0, 0, 1])) {
|
|
46
43
|
const rotation = mat4.fromVectorRotation(mat4.create(), projplane, [0, 0, 1])
|
|
47
|
-
|
|
44
|
+
projpolys = projpolys.map((p) => poly3.transform(rotation, p))
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
// union the 2D geometries to obtain the outline of the projection
|
|
53
|
-
projection = unionGeom2(projections2D)
|
|
47
|
+
// sort the polygons to allow the union to ignore small pieces efficiently
|
|
48
|
+
projpolys = projpolys.sort((a, b) => poly3.measureArea(b) - poly3.measureArea(a))
|
|
54
49
|
|
|
55
|
-
|
|
50
|
+
// convert polygons to geometry, and union all pieces into a single geometry
|
|
51
|
+
const projgeoms = projpolys.map((p) => geom2.fromPoints(p.vertices))
|
|
52
|
+
return unionGeom2(projgeoms)
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
/**
|
|
@@ -26,14 +26,14 @@ test('project (defaults)', (t) => {
|
|
|
26
26
|
t.notThrows(() => geom2.validate(result))
|
|
27
27
|
const pts = geom2.toPoints(result)
|
|
28
28
|
const exp = [
|
|
29
|
-
[0, -2.9999933333333333],
|
|
30
29
|
[0, -5.000013333333333],
|
|
31
|
-
[0, 5.000013333333333],
|
|
32
30
|
[5.000013333333333, 0],
|
|
31
|
+
[-5.000013333333333, 0],
|
|
33
32
|
[0, 2.9999933333333333],
|
|
34
33
|
[-2.9999933333333333, 0],
|
|
34
|
+
[0, -2.9999933333333333],
|
|
35
35
|
[2.9999933333333333, 0],
|
|
36
|
-
[
|
|
36
|
+
[0, 5.000013333333333]
|
|
37
37
|
]
|
|
38
38
|
t.true(comparePoints(pts, exp))
|
|
39
39
|
})
|
|
@@ -43,40 +43,40 @@ test('project (X and Y axis)', (t) => {
|
|
|
43
43
|
t.notThrows(() => geom2.validate(result))
|
|
44
44
|
let pts = geom2.toPoints(result)
|
|
45
45
|
let exp = [
|
|
46
|
-
[-1.0000200000000001, -3.999986666666667],
|
|
47
|
-
[-0.8314600000000001, 4.555553333333334],
|
|
48
|
-
[-0.9807933333333334, -4.1951],
|
|
49
|
-
[-0.7070933333333335, 4.707126666666667],
|
|
50
|
-
[-0.9238600000000001, -4.382700000000001],
|
|
51
|
-
[-0.5555666666666668, 4.831446666666667],
|
|
52
|
-
[-0.8314600000000001, -4.555553333333334],
|
|
53
|
-
[-0.3826666666666667, 4.923893333333334],
|
|
54
|
-
[-0.7070933333333335, -4.707126666666667],
|
|
55
|
-
[-0.19511333333333336, 4.98078],
|
|
56
|
-
[-0.5555666666666668, -4.831446666666667],
|
|
57
|
-
[0, 5.000006666666668],
|
|
58
|
-
[-0.3826666666666667, -4.923893333333334],
|
|
59
|
-
[0.19511333333333336, 4.98078],
|
|
60
46
|
[-0.19511333333333336, -4.98078],
|
|
61
|
-
[0.3826666666666667, 4.923893333333334],
|
|
62
47
|
[0, -5.000006666666668],
|
|
63
|
-
[0
|
|
48
|
+
[0, 5.000006666666668],
|
|
49
|
+
[0.3826666666666667, 4.923893333333334],
|
|
50
|
+
[-0.3826666666666667, -4.923893333333334],
|
|
64
51
|
[0.19511333333333336, -4.98078],
|
|
65
|
-
[0.
|
|
52
|
+
[-0.19511333333333336, 4.98078],
|
|
53
|
+
[-0.5555666666666668, -4.831446666666667],
|
|
54
|
+
[0.5555666666666668, 4.831446666666667],
|
|
55
|
+
[-0.3826666666666667, 4.923893333333334],
|
|
66
56
|
[0.3826666666666667, -4.923893333333334],
|
|
67
|
-
[0.
|
|
57
|
+
[0.7070933333333335, 4.707126666666667],
|
|
58
|
+
[-0.7070933333333335, -4.707126666666667],
|
|
68
59
|
[0.5555666666666668, -4.831446666666667],
|
|
69
|
-
[0.
|
|
60
|
+
[-0.5555666666666668, 4.831446666666667],
|
|
61
|
+
[0.8314600000000001, 4.555553333333334],
|
|
62
|
+
[-0.8314600000000001, -4.555553333333334],
|
|
70
63
|
[0.7070933333333335, -4.707126666666667],
|
|
71
|
-
[0.
|
|
64
|
+
[-0.7070933333333335, 4.707126666666667],
|
|
65
|
+
[-0.9238600000000001, -4.382700000000001],
|
|
66
|
+
[0.9238600000000001, 4.382700000000001],
|
|
67
|
+
[-0.8314600000000001, 4.555553333333334],
|
|
72
68
|
[0.8314600000000001, -4.555553333333334],
|
|
69
|
+
[0.9807933333333334, 4.1951],
|
|
70
|
+
[-0.9807933333333334, -4.1951],
|
|
71
|
+
[0.9238600000000001, -4.382700000000001],
|
|
72
|
+
[-0.9238600000000001, 4.382700000000001],
|
|
73
73
|
[1.0000200000000001, 3.999986666666667],
|
|
74
|
+
[-1.0000200000000001, -3.999986666666667],
|
|
74
75
|
[1.0000200000000001, -3.999986666666667],
|
|
75
|
-
[0.9238600000000001, -4.382700000000001],
|
|
76
|
-
[0.9807933333333334, -4.1951],
|
|
77
76
|
[-1.0000200000000001, 3.999986666666667],
|
|
78
77
|
[-0.9807933333333334, 4.1951],
|
|
79
|
-
[
|
|
78
|
+
[0.9807933333333334, -4.1951],
|
|
79
|
+
[0.19511333333333336, 4.98078]
|
|
80
80
|
]
|
|
81
81
|
t.true(comparePoints(pts, exp))
|
|
82
82
|
|
|
@@ -84,40 +84,40 @@ test('project (X and Y axis)', (t) => {
|
|
|
84
84
|
t.notThrows(() => geom2.validate(result))
|
|
85
85
|
pts = geom2.toPoints(result)
|
|
86
86
|
exp = [
|
|
87
|
-
[3.999986666666667, -1.0000200000000001],
|
|
88
|
-
[-4.555553333333334, -0.8314600000000001],
|
|
89
|
-
[4.1951, -0.9807933333333334],
|
|
90
|
-
[-4.707126666666667, -0.7070933333333335],
|
|
91
|
-
[4.382700000000001, -0.9238600000000001],
|
|
92
|
-
[-4.831446666666667, -0.5555666666666668],
|
|
93
|
-
[4.555553333333334, -0.8314600000000001],
|
|
94
|
-
[-4.923893333333334, -0.3826666666666667],
|
|
95
|
-
[4.707126666666667, -0.7070933333333335],
|
|
96
|
-
[-4.98078, -0.19511333333333336],
|
|
97
|
-
[4.831446666666667, -0.5555666666666668],
|
|
98
|
-
[-5.000006666666668, 0],
|
|
99
|
-
[4.923893333333334, -0.3826666666666667],
|
|
100
|
-
[-4.98078, 0.19511333333333336],
|
|
101
87
|
[4.98078, -0.19511333333333336],
|
|
102
|
-
[-4.923893333333334, 0.3826666666666667],
|
|
103
88
|
[5.000006666666668, 0],
|
|
104
|
-
[-
|
|
89
|
+
[-5.000006666666668, 0],
|
|
90
|
+
[-4.923893333333334, 0.3826666666666667],
|
|
91
|
+
[4.923893333333334, -0.3826666666666667],
|
|
105
92
|
[4.98078, 0.19511333333333336],
|
|
106
|
-
[-4.
|
|
93
|
+
[-4.98078, -0.19511333333333336],
|
|
94
|
+
[4.831446666666667, -0.5555666666666668],
|
|
95
|
+
[-4.831446666666667, 0.5555666666666668],
|
|
96
|
+
[-4.923893333333334, -0.3826666666666667],
|
|
107
97
|
[4.923893333333334, 0.3826666666666667],
|
|
108
|
-
[-4.
|
|
98
|
+
[-4.707126666666667, 0.7070933333333335],
|
|
99
|
+
[4.707126666666667, -0.7070933333333335],
|
|
109
100
|
[4.831446666666667, 0.5555666666666668],
|
|
110
|
-
[-4.
|
|
101
|
+
[-4.831446666666667, -0.5555666666666668],
|
|
102
|
+
[4.555553333333334, -0.8314600000000001],
|
|
103
|
+
[-4.555553333333334, 0.8314600000000001],
|
|
111
104
|
[4.707126666666667, 0.7070933333333335],
|
|
112
|
-
[-4.
|
|
105
|
+
[-4.707126666666667, -0.7070933333333335],
|
|
106
|
+
[4.382700000000001, -0.9238600000000001],
|
|
107
|
+
[-4.382700000000001, 0.9238600000000001],
|
|
108
|
+
[-4.555553333333334, -0.8314600000000001],
|
|
113
109
|
[4.555553333333334, 0.8314600000000001],
|
|
110
|
+
[-4.1951, 0.9807933333333334],
|
|
111
|
+
[4.1951, -0.9807933333333334],
|
|
112
|
+
[4.382700000000001, 0.9238600000000001],
|
|
113
|
+
[-4.382700000000001, -0.9238600000000001],
|
|
114
|
+
[3.999986666666667, -1.0000200000000001],
|
|
114
115
|
[-3.999986666666667, 1.0000200000000001],
|
|
115
116
|
[3.999986666666667, 1.0000200000000001],
|
|
116
|
-
[4.382700000000001, 0.9238600000000001],
|
|
117
|
-
[4.1951, 0.9807933333333334],
|
|
118
117
|
[-3.999986666666667, -1.0000200000000001],
|
|
118
|
+
[4.1951, 0.9807933333333334],
|
|
119
119
|
[-4.1951, -0.9807933333333334],
|
|
120
|
-
[-4.
|
|
120
|
+
[-4.98078, 0.19511333333333336]
|
|
121
121
|
]
|
|
122
122
|
t.true(comparePoints(pts, exp))
|
|
123
123
|
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const vec3 = require('../../../maths/vec3')
|
|
2
|
+
const create = require('./create')
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Mend gaps in a 2D slice to make it a closed polygon
|
|
6
|
+
*/
|
|
7
|
+
const repair = (slice) => {
|
|
8
|
+
if (!slice.edges) return slice
|
|
9
|
+
let edges = slice.edges
|
|
10
|
+
const vertexMap = new Map() // string key to vertex map
|
|
11
|
+
const edgeCount = new Map() // count of (in - out) edges
|
|
12
|
+
|
|
13
|
+
// Remove self-edges
|
|
14
|
+
edges = edges.filter((e) => !vec3.equals(e[0], e[1]))
|
|
15
|
+
|
|
16
|
+
// build vertex and edge count maps
|
|
17
|
+
edges.forEach((edge) => {
|
|
18
|
+
const inKey = edge[0].toString()
|
|
19
|
+
const outKey = edge[1].toString()
|
|
20
|
+
vertexMap.set(inKey, edge[0])
|
|
21
|
+
vertexMap.set(outKey, edge[1])
|
|
22
|
+
edgeCount.set(inKey, (edgeCount.get(inKey) || 0) + 1) // in
|
|
23
|
+
edgeCount.set(outKey, (edgeCount.get(outKey) || 0) - 1) // out
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// find vertices which are missing in or out edges
|
|
27
|
+
const missingIn = []
|
|
28
|
+
const missingOut = []
|
|
29
|
+
edgeCount.forEach((count, vertex) => {
|
|
30
|
+
if (count < 0) missingIn.push(vertex)
|
|
31
|
+
if (count > 0) missingOut.push(vertex)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// pairwise distance of bad vertices
|
|
35
|
+
missingIn.forEach((key1) => {
|
|
36
|
+
const v1 = vertexMap.get(key1)
|
|
37
|
+
|
|
38
|
+
// find the closest vertex that is missing an out edge
|
|
39
|
+
let bestDistance = Infinity
|
|
40
|
+
let bestReplacement
|
|
41
|
+
missingOut.forEach((key2) => {
|
|
42
|
+
const v2 = vertexMap.get(key2)
|
|
43
|
+
const distance = vec3.distance(v1, v2)
|
|
44
|
+
if (distance < bestDistance) {
|
|
45
|
+
bestDistance = distance
|
|
46
|
+
bestReplacement = v2
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
console.warn(`slice.repair: repairing vertex gap ${v1} to ${bestReplacement} distance ${bestDistance}`)
|
|
50
|
+
|
|
51
|
+
// merge broken vertices
|
|
52
|
+
edges = edges.map((edge) => {
|
|
53
|
+
if (edge[0].toString() === key1) return [bestReplacement, edge[1]]
|
|
54
|
+
if (edge[1].toString() === key1) return [edge[0], bestReplacement]
|
|
55
|
+
return edge
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
return create(edges)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = repair
|
|
@@ -12,14 +12,14 @@ test('hullChain (two, geom2)', (t) => {
|
|
|
12
12
|
let obs = hullChain(geometry1, geometry1)
|
|
13
13
|
let pts = geom2.toPoints(obs)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
t.notThrows(() => geom2.validate(obs))
|
|
16
16
|
t.is(pts.length, 4)
|
|
17
17
|
|
|
18
18
|
// different
|
|
19
19
|
obs = hullChain(geometry1, geometry2)
|
|
20
20
|
pts = geom2.toPoints(obs)
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
t.notThrows(() => geom2.validate(obs))
|
|
23
23
|
t.is(pts.length, 6)
|
|
24
24
|
})
|
|
25
25
|
|
|
@@ -33,7 +33,7 @@ test('hullChain (three, geom2)', (t) => {
|
|
|
33
33
|
let pts = geom2.toPoints(obs)
|
|
34
34
|
|
|
35
35
|
// the sides change based on the bestplane chosen in trees/Node.js
|
|
36
|
-
|
|
36
|
+
t.notThrows(() => geom2.validate(obs))
|
|
37
37
|
t.is(pts.length, 10)
|
|
38
38
|
|
|
39
39
|
// closed
|
|
@@ -41,7 +41,7 @@ test('hullChain (three, geom2)', (t) => {
|
|
|
41
41
|
pts = geom2.toPoints(obs)
|
|
42
42
|
|
|
43
43
|
// the sides change based on the bestplane chosen in trees/Node.js
|
|
44
|
-
|
|
44
|
+
t.notThrows(() => geom2.validate(obs))
|
|
45
45
|
t.is(pts.length, 10)
|
|
46
46
|
})
|
|
47
47
|
|
|
@@ -3,6 +3,7 @@ const flatten = require('../../utils/flatten')
|
|
|
3
3
|
const geom2 = require('../../geometries/geom2')
|
|
4
4
|
|
|
5
5
|
const hullPoints2 = require('./hullPoints2')
|
|
6
|
+
const toUniquePoints = require('./toUniquePoints')
|
|
6
7
|
|
|
7
8
|
/*
|
|
8
9
|
* Create a convex hull of the given geom2 geometries.
|
|
@@ -13,28 +14,15 @@ const hullGeom2 = (...geometries) => {
|
|
|
13
14
|
geometries = flatten(geometries)
|
|
14
15
|
|
|
15
16
|
// extract the unique points from the geometries
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const sides = geom2.toSides(geometries[g])
|
|
20
|
-
for (let s = 0; s < sides.length; s++) {
|
|
21
|
-
const side = sides[s]
|
|
22
|
-
const point = side[0]
|
|
23
|
-
const id = `${point[0]},${point[1]}`
|
|
24
|
-
if (found.has(id)) continue
|
|
25
|
-
uniquepoints.push(point)
|
|
26
|
-
found.set(id, true)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
found.clear()
|
|
30
|
-
|
|
31
|
-
const hullpoints = hullPoints2(uniquepoints)
|
|
17
|
+
const unique = toUniquePoints(geometries)
|
|
18
|
+
|
|
19
|
+
const hullPoints = hullPoints2(unique)
|
|
32
20
|
|
|
33
21
|
// NOTE: more than three points are required to create a new geometry
|
|
34
|
-
if (
|
|
22
|
+
if (hullPoints.length < 3) return geom2.create()
|
|
35
23
|
|
|
36
24
|
// assemble a new geometry from the list of points
|
|
37
|
-
return geom2.fromPoints(
|
|
25
|
+
return geom2.fromPoints(hullPoints)
|
|
38
26
|
}
|
|
39
27
|
|
|
40
28
|
module.exports = hullGeom2
|
|
@@ -4,6 +4,7 @@ const geom3 = require('../../geometries/geom3')
|
|
|
4
4
|
const poly3 = require('../../geometries/poly3')
|
|
5
5
|
|
|
6
6
|
const quickhull = require('./quickhull')
|
|
7
|
+
const toUniquePoints = require('./toUniquePoints')
|
|
7
8
|
|
|
8
9
|
/*
|
|
9
10
|
* Create a convex hull of the given geometries (geom3).
|
|
@@ -16,26 +17,12 @@ const hullGeom3 = (...geometries) => {
|
|
|
16
17
|
if (geometries.length === 1) return geometries[0]
|
|
17
18
|
|
|
18
19
|
// extract the unique vertices from the geometries
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const polygons = geom3.toPolygons(geometries[g])
|
|
23
|
-
for (let p = 0; p < polygons.length; ++p) {
|
|
24
|
-
const vertices = polygons[p].vertices
|
|
25
|
-
for (let v = 0; v < vertices.length; ++v) {
|
|
26
|
-
const id = `${vertices[v]}`
|
|
27
|
-
if (found.has(id)) continue
|
|
28
|
-
uniquevertices.push(vertices[v])
|
|
29
|
-
found.set(id, true)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
found.clear()
|
|
34
|
-
|
|
35
|
-
const faces = quickhull(uniquevertices, { skipTriangulation: true })
|
|
20
|
+
const unique = toUniquePoints(geometries)
|
|
21
|
+
|
|
22
|
+
const faces = quickhull(unique, { skipTriangulation: true })
|
|
36
23
|
|
|
37
24
|
const polygons = faces.map((face) => {
|
|
38
|
-
const vertices = face.map((index) =>
|
|
25
|
+
const vertices = face.map((index) => unique[index])
|
|
39
26
|
return poly3.create(vertices)
|
|
40
27
|
})
|
|
41
28
|
|
|
@@ -3,6 +3,7 @@ const flatten = require('../../utils/flatten')
|
|
|
3
3
|
const path2 = require('../../geometries/path2')
|
|
4
4
|
|
|
5
5
|
const hullPoints2 = require('./hullPoints2')
|
|
6
|
+
const toUniquePoints = require('./toUniquePoints')
|
|
6
7
|
|
|
7
8
|
/*
|
|
8
9
|
* Create a convex hull of the given geometries (path2).
|
|
@@ -13,23 +14,12 @@ const hullPath2 = (...geometries) => {
|
|
|
13
14
|
geometries = flatten(geometries)
|
|
14
15
|
|
|
15
16
|
// extract the unique points from the geometries
|
|
16
|
-
const
|
|
17
|
-
const found = new Set()
|
|
18
|
-
geometries.forEach((geometry) => {
|
|
19
|
-
const points = path2.toPoints(geometry)
|
|
20
|
-
points.forEach((point) => {
|
|
21
|
-
const key = point.toString()
|
|
22
|
-
if (!found.has(key)) {
|
|
23
|
-
uniquepoints.push(point)
|
|
24
|
-
found.add(key)
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
})
|
|
17
|
+
const unique = toUniquePoints(geometries)
|
|
28
18
|
|
|
29
|
-
const
|
|
19
|
+
const hullPoints = hullPoints2(unique)
|
|
30
20
|
|
|
31
21
|
// assemble a new geometry from the list of points
|
|
32
|
-
return path2.fromPoints({ closed: true },
|
|
22
|
+
return path2.fromPoints({ closed: true }, hullPoints)
|
|
33
23
|
}
|
|
34
24
|
|
|
35
25
|
module.exports = hullPath2
|