@jscad/modeling 3.0.0-alpha.0 → 3.0.1-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 +35 -0
- package/LICENSE +1 -1
- package/dist/jscad-modeling.es.js +2 -2
- package/dist/jscad-modeling.min.js +2 -2
- package/package.json +2 -2
- package/rollup.config.js +1 -1
- package/src/colors/colorize.js +1 -5
- package/src/colors/colorize.test.js +8 -8
- package/src/geometries/geom2/transform.js +9 -1
- package/src/geometries/geom2/transform.test.js +57 -0
- package/src/geometries/geom3/fromPointsConvex.d.ts +4 -0
- package/src/geometries/geom3/fromPointsConvex.js +25 -0
- package/src/geometries/geom3/fromPointsConvex.test.js +32 -0
- package/src/geometries/geom3/index.d.ts +1 -0
- package/src/geometries/geom3/index.js +1 -0
- package/src/geometries/index.js +3 -4
- package/src/geometries/poly3/type.d.ts +1 -1
- package/src/geometries/slice/validate.js +1 -2
- package/src/maths/index.js +1 -0
- package/src/maths/mat4/isOnlyTransformScale.js +1 -1
- package/src/measurements/measureAggregateArea.js +0 -1
- package/src/measurements/measureAggregateBoundingBox.js +0 -1
- package/src/measurements/measureAggregateEpsilon.js +0 -1
- package/src/measurements/measureAggregateVolume.js +0 -1
- package/src/measurements/measureArea.js +0 -1
- package/src/measurements/measureBoundingBox.js +0 -1
- package/src/measurements/measureEpsilon.js +0 -1
- package/src/measurements/measureVolume.js +0 -1
- package/src/operations/booleans/index.d.ts +1 -0
- package/src/operations/booleans/intersect.js +5 -5
- package/src/operations/booleans/intersect.test.js +6 -7
- package/src/operations/booleans/intersectGeom2.js +2 -6
- package/src/operations/booleans/intersectGeom2.test.js +25 -1
- package/src/operations/booleans/intersectGeom3.js +2 -6
- package/src/operations/booleans/intersectGeom3.test.js +5 -1
- package/src/operations/booleans/mayOverlap.js +0 -1
- package/src/operations/booleans/scission.d.ts +5 -0
- package/src/operations/booleans/scission.js +3 -5
- package/src/operations/booleans/scission.test.js +6 -0
- package/src/operations/booleans/subtract.js +5 -5
- package/src/operations/booleans/subtract.test.js +6 -7
- package/src/operations/booleans/subtractGeom2.js +2 -6
- package/src/operations/booleans/subtractGeom2.test.js +25 -1
- package/src/operations/booleans/subtractGeom3.js +2 -6
- package/src/operations/booleans/subtractGeom3.test.js +5 -1
- package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +33 -0
- package/src/operations/booleans/union.js +5 -5
- package/src/operations/booleans/union.test.js +6 -7
- package/src/operations/booleans/unionGeom2.js +2 -6
- package/src/operations/booleans/unionGeom2.test.js +25 -1
- package/src/operations/booleans/unionGeom3.js +2 -6
- package/src/operations/booleans/unionGeom3.test.js +6 -1
- package/src/operations/extrusions/extrudeFromSlices.test.js +8 -1
- package/src/operations/extrusions/extrudeHelical.js +2 -8
- package/src/operations/extrusions/extrudeLinear.js +1 -5
- package/src/operations/extrusions/extrudeLinear.test.js +7 -1
- package/src/operations/extrusions/extrudeRotate.js +3 -2
- package/src/operations/extrusions/extrudeRotate.test.js +13 -1
- package/src/operations/extrusions/project.js +1 -5
- package/src/operations/hulls/hull.js +6 -5
- package/src/operations/hulls/hull.test.js +56 -3
- package/src/operations/hulls/hullChain.js +11 -6
- package/src/operations/hulls/hullChain.test.js +12 -2
- package/src/operations/hulls/hullGeom2.js +5 -6
- package/src/operations/hulls/hullGeom3.js +9 -18
- package/src/operations/hulls/hullPath2.js +6 -7
- package/src/operations/hulls/hullPath2.test.js +1 -1
- package/src/operations/hulls/hullPoints2.d.ts +3 -0
- package/src/operations/hulls/hullPoints2.js +4 -2
- package/src/operations/hulls/hullPoints3.d.ts +4 -0
- package/src/operations/hulls/hullPoints3.js +21 -0
- package/src/operations/hulls/index.d.ts +2 -0
- package/src/operations/hulls/index.js +3 -1
- package/src/operations/modifiers/generalize.js +2 -6
- package/src/operations/modifiers/index.js +1 -1
- package/src/operations/modifiers/snap.js +2 -6
- package/src/operations/offsets/offset.js +1 -5
- package/src/operations/offsets/offsetFromPoints.test.js +0 -1
- package/src/operations/offsets/offsetGeom2.test.js +1 -0
- package/src/operations/offsets/offsetGeom3.js +0 -2
- package/src/operations/offsets/offsetGeom3.test.js +9 -1
- package/src/operations/offsets/offsetPath2.js +3 -3
- package/src/operations/transforms/align.js +8 -7
- package/src/operations/transforms/align.test.js +2 -2
- package/src/operations/transforms/center.js +6 -9
- package/src/operations/transforms/center.test.js +19 -1
- package/src/operations/transforms/mirror.js +5 -8
- package/src/operations/transforms/mirror.test.js +7 -7
- package/src/operations/transforms/rotate.js +5 -8
- package/src/operations/transforms/scale.js +5 -8
- package/src/operations/transforms/transform.js +2 -5
- package/src/operations/transforms/translate.js +5 -8
- package/src/primitives/arc.js +2 -0
- package/src/primitives/arc.test.js +11 -11
- package/src/primitives/circle.test.js +18 -8
- package/src/primitives/cube.test.js +10 -0
- package/src/primitives/cuboid.test.js +10 -0
- package/src/primitives/cylinder.test.js +12 -0
- package/src/primitives/cylinderElliptic.test.js +21 -1
- package/src/primitives/ellipse.test.js +18 -8
- package/src/primitives/ellipsoid.test.js +12 -0
- package/src/primitives/geodesicSphere.test.js +8 -0
- package/src/primitives/line.test.js +1 -1
- package/src/primitives/polygon.d.ts +1 -0
- package/src/primitives/polygon.js +13 -4
- package/src/primitives/polygon.test.js +15 -0
- package/src/primitives/polyhedron.js +1 -0
- package/src/primitives/polyhedron.test.js +8 -2
- package/src/primitives/rectangle.test.js +9 -3
- package/src/primitives/roundedCuboid.js +1 -1
- package/src/primitives/roundedCuboid.test.js +20 -4
- package/src/primitives/roundedCylinder.js +1 -1
- package/src/primitives/roundedCylinder.test.js +20 -0
- package/src/primitives/roundedRectangle.js +1 -1
- package/src/primitives/roundedRectangle.test.js +15 -6
- package/src/primitives/sphere.test.js +12 -0
- package/src/primitives/square.test.js +10 -4
- package/src/primitives/star.test.js +14 -6
- package/src/primitives/torus.js +1 -1
- package/src/primitives/torus.test.js +11 -1
- package/src/primitives/triangle.test.js +17 -9
- package/src/utils/coalesce.d.ts +3 -0
- package/src/utils/coalesce.js +20 -0
- package/src/utils/index.js +2 -2
- package/src/maths/mat4/leftMultiplyVec2.d.ts +0 -4
- package/src/maths/mat4/leftMultiplyVec2.js +0 -26
- package/src/maths/mat4/leftMultiplyVec3.d.ts +0 -4
- package/src/maths/mat4/leftMultiplyVec3.js +0 -27
- package/src/maths/mat4/mirror.d.ts +0 -4
- package/src/maths/mat4/mirror.js +0 -32
- package/src/maths/mat4/rightMultiplyVec2.d.ts +0 -4
- package/src/maths/mat4/rightMultiplyVec2.js +0 -27
- package/src/maths/mat4/rightMultiplyVec3.d.ts +0 -4
- package/src/maths/mat4/rightMultiplyVec3.js +0 -28
|
@@ -8,7 +8,7 @@ import { colorize } from '../../colors/index.js'
|
|
|
8
8
|
|
|
9
9
|
import { geom2, geom3 } from '../../geometries/index.js'
|
|
10
10
|
|
|
11
|
-
import { measureVolume } from '../../measurements/index.js'
|
|
11
|
+
import { measureArea, measureVolume } from '../../measurements/index.js'
|
|
12
12
|
|
|
13
13
|
import { square } from '../../primitives/index.js'
|
|
14
14
|
|
|
@@ -20,6 +20,7 @@ test('extrudeRotate: (defaults) extruding of a geom2 produces an expected geom3'
|
|
|
20
20
|
const geometry3 = extrudeRotate({ }, geometry2)
|
|
21
21
|
const pts = geom3.toPoints(geometry3)
|
|
22
22
|
t.notThrows(() => geom3.validate(geometry3))
|
|
23
|
+
t.is(measureArea(geometry3), 7033.914479497244)
|
|
23
24
|
t.is(measureVolume(geometry3), 27648.000000000007)
|
|
24
25
|
t.is(pts.length, 96)
|
|
25
26
|
})
|
|
@@ -51,6 +52,7 @@ test('extrudeRotate: (angle) extruding of a geom2 produces an expected geom3', (
|
|
|
51
52
|
[[10, 0, -8], [26, 0, -8], [26, 0, 8]]
|
|
52
53
|
]
|
|
53
54
|
t.notThrows(() => geom3.validate(geometry3))
|
|
55
|
+
t.is(measureArea(geometry3), 1360.144820048035)
|
|
54
56
|
t.is(measureVolume(geometry3), 3258.3480477076105)
|
|
55
57
|
t.is(pts.length, 12)
|
|
56
58
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -58,12 +60,14 @@ test('extrudeRotate: (angle) extruding of a geom2 produces an expected geom3', (
|
|
|
58
60
|
geometry3 = extrudeRotate({ segments: 4, angle: -250 * 0.017453292519943295 }, geometry2)
|
|
59
61
|
pts = geom3.toPoints(geometry3)
|
|
60
62
|
t.notThrows(() => geom3.validate(geometry3))
|
|
63
|
+
t.is(measureArea(geometry3), 4525.850393739846)
|
|
61
64
|
t.is(measureVolume(geometry3), 13730.527057424617)
|
|
62
65
|
t.is(pts.length, 28)
|
|
63
66
|
|
|
64
67
|
geometry3 = extrudeRotate({ segments: 4, angle: 250 * 0.017453292519943295 }, geometry2)
|
|
65
68
|
pts = geom3.toPoints(geometry3)
|
|
66
69
|
t.notThrows(() => geom3.validate(geometry3))
|
|
70
|
+
t.is(measureArea(geometry3), 4525.8503937398455)
|
|
67
71
|
t.is(measureVolume(geometry3), 13730.527057424617)
|
|
68
72
|
t.is(pts.length, 28)
|
|
69
73
|
})
|
|
@@ -80,6 +84,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
|
|
|
80
84
|
[-11.803752993228215, 23.166169628897567, 8]
|
|
81
85
|
]
|
|
82
86
|
t.notThrows(() => geom3.validate(geometry3))
|
|
87
|
+
t.is(measureArea(geometry3), 6124.6858201346895)
|
|
83
88
|
t.is(measureVolume(geometry3), 21912.342135440336)
|
|
84
89
|
t.is(pts.length, 40)
|
|
85
90
|
t.true(comparePoints(pts[6], exp))
|
|
@@ -92,6 +97,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom
|
|
|
92
97
|
[23.166169628897567, 11.803752993228215, 8]
|
|
93
98
|
]
|
|
94
99
|
t.notThrows(() => geom3.validate(geometry3))
|
|
100
|
+
t.is(measureArea(geometry3), 6124.685820134688)
|
|
95
101
|
t.is(measureVolume(geometry3), 21912.342135440336)
|
|
96
102
|
t.is(pts.length, 40)
|
|
97
103
|
t.true(comparePoints(pts[6], exp))
|
|
@@ -104,12 +110,14 @@ test('extrudeRotate: (segments) extruding of a geom2 produces an expected geom3'
|
|
|
104
110
|
let geometry3 = extrudeRotate({ segments: 4 }, geometry2)
|
|
105
111
|
let pts = geom3.toPoints(geometry3)
|
|
106
112
|
t.notThrows(() => geom3.validate(geometry3))
|
|
113
|
+
t.is(measureArea(geometry3), 5562.34804770761)
|
|
107
114
|
t.is(measureVolume(geometry3), 18432)
|
|
108
115
|
t.is(pts.length, 32)
|
|
109
116
|
|
|
110
117
|
geometry3 = extrudeRotate({ segments: 64 }, geometry2)
|
|
111
118
|
pts = geom3.toPoints(geometry3)
|
|
112
119
|
t.notThrows(() => geom3.validate(geometry3))
|
|
120
|
+
t.is(measureArea(geometry3), 7230.965353920782)
|
|
113
121
|
t.is(measureVolume(geometry3), 28906.430888871357)
|
|
114
122
|
t.is(pts.length, 512)
|
|
115
123
|
|
|
@@ -118,6 +126,7 @@ test('extrudeRotate: (segments) extruding of a geom2 produces an expected geom3'
|
|
|
118
126
|
geometry3 = extrudeRotate({ segments: 8 }, geometry2)
|
|
119
127
|
pts = geom3.toPoints(geometry3)
|
|
120
128
|
t.notThrows(() => geom3.validate(geometry3))
|
|
129
|
+
t.is(measureArea(geometry3), 84.28200374166053)
|
|
121
130
|
t.is(measureVolume(geometry3), 33.94112549695427)
|
|
122
131
|
t.is(pts.length, 64)
|
|
123
132
|
|
|
@@ -126,6 +135,7 @@ test('extrudeRotate: (segments) extruding of a geom2 produces an expected geom3'
|
|
|
126
135
|
geometry3 = extrudeRotate({ segments: 8 }, geometry2)
|
|
127
136
|
pts = geom3.toPoints(geometry3)
|
|
128
137
|
t.notThrows(() => geom3.validate(geometry3))
|
|
138
|
+
t.is(measureArea(geometry3), 17692.315375839215)
|
|
129
139
|
t.is(measureVolume(geometry3), 147078.2104868019)
|
|
130
140
|
t.is(pts.length, 80)
|
|
131
141
|
})
|
|
@@ -147,6 +157,7 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
|
|
|
147
157
|
[[7, 0, -8], [7, 0, 8], [0, 0, 8]]
|
|
148
158
|
]
|
|
149
159
|
t.notThrows(() => geom3.validate(obs))
|
|
160
|
+
t.is(measureArea(obs), 431.3919189857867)
|
|
150
161
|
t.is(measureVolume(obs), 391.99999999999994)
|
|
151
162
|
t.is(pts.length, 8)
|
|
152
163
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -177,6 +188,7 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo
|
|
|
177
188
|
[[2, 0, 4], [0, 0, 8], [0, 0, -8]]
|
|
178
189
|
]
|
|
179
190
|
t.notThrows(() => geom3.validate(obs))
|
|
191
|
+
t.is(measureArea(obs), 86.47516025670329)
|
|
180
192
|
t.is(measureVolume(obs), 26.398653164297773)
|
|
181
193
|
t.is(pts.length, 18)
|
|
182
194
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import { aboutEqualNormals } from '../../maths/utils/aboutEqualNormals.js'
|
|
4
2
|
|
|
5
3
|
import * as plane from '../../maths/plane/index.js'
|
|
@@ -80,15 +78,13 @@ export const project = (options, ...objects) => {
|
|
|
80
78
|
}
|
|
81
79
|
const { axis, origin } = Object.assign({ }, defaults, options)
|
|
82
80
|
|
|
83
|
-
objects = flatten(objects)
|
|
84
|
-
if (objects.length === 0) throw new Error('wrong number of arguments')
|
|
85
|
-
|
|
86
81
|
options = { axis, origin }
|
|
87
82
|
|
|
88
83
|
const results = objects.map((object) => {
|
|
89
84
|
// if (path.isA(object)) return project(options, object)
|
|
90
85
|
// if (geom2.isA(object)) return project(options, object)
|
|
91
86
|
if (geom3.isA(object)) return projectGeom3(options, object)
|
|
87
|
+
if (Array.isArray(object)) return project(options, ...object)
|
|
92
88
|
return object
|
|
93
89
|
})
|
|
94
90
|
return results.length === 1 ? results[0] : results
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { areAllShapesTheSameType } from '../../utils/areAllShapesTheSameType.js'
|
|
2
|
-
import {
|
|
2
|
+
import { coalesce } from '../../utils/coalesce.js'
|
|
3
3
|
|
|
4
4
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
5
5
|
import * as geom3 from '../../geometries/geom3/index.js'
|
|
@@ -12,8 +12,8 @@ import { hullGeom3 } from './hullGeom3.js'
|
|
|
12
12
|
/**
|
|
13
13
|
* Create a convex hull of the given geometries.
|
|
14
14
|
* The given geometries should be of the same type, either geom2 or geom3 or path2.
|
|
15
|
-
* @param {...
|
|
16
|
-
* @returns {Geom2|Geom3} new geometry
|
|
15
|
+
* @param {...Object} geometries - list of geometries from which to create a hull
|
|
16
|
+
* @returns {Geom2|Geom3|Path2} new geometry
|
|
17
17
|
* @alias module:modeling/hulls.hull
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
@@ -33,8 +33,9 @@ import { hullGeom3 } from './hullGeom3.js'
|
|
|
33
33
|
* +-------+ +-------+
|
|
34
34
|
*/
|
|
35
35
|
export const hull = (...geometries) => {
|
|
36
|
-
geometries =
|
|
37
|
-
|
|
36
|
+
geometries = coalesce(geometries)
|
|
37
|
+
|
|
38
|
+
if (geometries.length === 0) return undefined
|
|
38
39
|
|
|
39
40
|
if (!areAllShapesTheSameType(geometries)) {
|
|
40
41
|
throw new Error('only hulls of the same type are supported')
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import test from 'ava'
|
|
2
2
|
|
|
3
3
|
import { geom2, geom3, path2 } from '../../geometries/index.js'
|
|
4
|
-
|
|
5
|
-
import {
|
|
4
|
+
|
|
5
|
+
import { measureArea, measureVolume } from '../../measurements/index.js'
|
|
6
|
+
|
|
7
|
+
import { cuboid, ellipsoid, sphere, square, torus } from '../../primitives/index.js'
|
|
8
|
+
|
|
6
9
|
import { center } from '../transforms/index.js'
|
|
7
10
|
|
|
8
11
|
import { hull } from './index.js'
|
|
@@ -41,6 +44,7 @@ test('hull (single, geom2)', (t) => {
|
|
|
41
44
|
obs = hull(geometry)
|
|
42
45
|
pts = geom2.toPoints(obs)
|
|
43
46
|
t.notThrows(() => geom2.validate(obs))
|
|
47
|
+
t.is(measureArea(obs), 232.09469999999993)
|
|
44
48
|
t.is(pts.length, 7)
|
|
45
49
|
})
|
|
46
50
|
|
|
@@ -68,6 +72,7 @@ test('hull (multiple, overlapping, geom2)', (t) => {
|
|
|
68
72
|
let pts = geom2.toPoints(obs)
|
|
69
73
|
|
|
70
74
|
t.notThrows(() => geom2.validate(obs))
|
|
75
|
+
t.is(measureArea(obs), 100)
|
|
71
76
|
t.is(pts.length, 4)
|
|
72
77
|
|
|
73
78
|
// one inside another
|
|
@@ -75,6 +80,7 @@ test('hull (multiple, overlapping, geom2)', (t) => {
|
|
|
75
80
|
pts = geom2.toPoints(obs)
|
|
76
81
|
|
|
77
82
|
t.notThrows(() => geom2.validate(obs))
|
|
83
|
+
t.is(measureArea(obs), 100)
|
|
78
84
|
t.is(pts.length, 4)
|
|
79
85
|
|
|
80
86
|
// one overlapping another
|
|
@@ -82,12 +88,14 @@ test('hull (multiple, overlapping, geom2)', (t) => {
|
|
|
82
88
|
pts = geom2.toPoints(obs)
|
|
83
89
|
|
|
84
90
|
t.notThrows(() => geom2.validate(obs))
|
|
91
|
+
t.is(measureArea(obs), 116)
|
|
85
92
|
t.is(pts.length, 8)
|
|
86
93
|
|
|
87
94
|
obs = hull(geometry2, geometry4)
|
|
88
95
|
pts = geom2.toPoints(obs)
|
|
89
96
|
|
|
90
97
|
t.notThrows(() => geom2.validate(obs))
|
|
98
|
+
t.is(measureArea(obs), 232.09469999999993)
|
|
91
99
|
t.is(pts.length, 7)
|
|
92
100
|
})
|
|
93
101
|
|
|
@@ -114,26 +122,31 @@ test('hull (multiple, various, geom2)', (t) => {
|
|
|
114
122
|
let obs = hull(geometry1, geometry2)
|
|
115
123
|
let pts = geom2.toPoints(obs)
|
|
116
124
|
t.notThrows(() => geom2.validate(obs))
|
|
125
|
+
t.is(measureArea(obs), 99)
|
|
117
126
|
t.is(pts.length, 5)
|
|
118
127
|
|
|
119
128
|
obs = hull(geometry1, geometry3)
|
|
120
129
|
pts = geom2.toPoints(obs)
|
|
121
130
|
t.notThrows(() => geom2.validate(obs))
|
|
131
|
+
t.is(measureArea(obs), 308)
|
|
122
132
|
t.is(pts.length, 5)
|
|
123
133
|
|
|
124
134
|
obs = hull(geometry2, geometry3)
|
|
125
135
|
pts = geom2.toPoints(obs)
|
|
126
136
|
t.notThrows(() => geom2.validate(obs))
|
|
137
|
+
t.is(measureArea(obs), 308)
|
|
127
138
|
t.is(pts.length, 5)
|
|
128
139
|
|
|
129
140
|
obs = hull(geometry1, geometry2, geometry3)
|
|
130
141
|
pts = geom2.toPoints(obs)
|
|
131
142
|
t.notThrows(() => geom2.validate(obs))
|
|
143
|
+
t.is(measureArea(obs), 341)
|
|
132
144
|
t.is(pts.length, 6)
|
|
133
145
|
|
|
134
146
|
obs = hull(geometry5, geometry4)
|
|
135
147
|
pts = geom2.toPoints(obs)
|
|
136
148
|
t.notThrows(() => geom2.validate(obs))
|
|
149
|
+
t.is(measureArea(obs), 513.39595)
|
|
137
150
|
t.is(pts.length, 8)
|
|
138
151
|
})
|
|
139
152
|
|
|
@@ -215,7 +228,9 @@ test('hull (single, geom3)', (t) => {
|
|
|
215
228
|
obs = hull(geometry)
|
|
216
229
|
pts = geom3.toPoints(obs)
|
|
217
230
|
|
|
218
|
-
t.notThrows
|
|
231
|
+
t.notThrows(() => geom3.validate(obs))
|
|
232
|
+
t.is(measureArea(obs), 44.05375630658983)
|
|
233
|
+
t.is(measureVolume(obs), 25.75161133197968)
|
|
219
234
|
t.is(pts.length, 32)
|
|
220
235
|
})
|
|
221
236
|
|
|
@@ -234,6 +249,8 @@ test('hull (multiple, geom3)', (t) => {
|
|
|
234
249
|
]
|
|
235
250
|
|
|
236
251
|
t.notThrows(() => geom3.validate(obs))
|
|
252
|
+
t.is(measureArea(obs), 24)
|
|
253
|
+
t.is(measureVolume(obs), 7.999999999999999)
|
|
237
254
|
t.is(pts.length, 6)
|
|
238
255
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
239
256
|
|
|
@@ -257,6 +274,8 @@ test('hull (multiple, geom3)', (t) => {
|
|
|
257
274
|
]
|
|
258
275
|
|
|
259
276
|
t.notThrows(() => geom3.validate(obs))
|
|
277
|
+
t.is(measureArea(obs), 145.59502802663923)
|
|
278
|
+
t.is(measureVolume(obs), 112.49999999999999)
|
|
260
279
|
t.is(pts.length, 12)
|
|
261
280
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
262
281
|
})
|
|
@@ -270,5 +289,39 @@ test('hull (multiple, overlapping, geom3)', (t) => {
|
|
|
270
289
|
const pts = geom3.toPoints(obs)
|
|
271
290
|
|
|
272
291
|
t.notThrows(() => geom3.validate(obs))
|
|
292
|
+
t.is(measureArea(obs), 282.26819685563686)
|
|
293
|
+
t.is(measureVolume(obs), 366.67641200012866)
|
|
273
294
|
t.is(pts.length, 92)
|
|
274
295
|
})
|
|
296
|
+
|
|
297
|
+
test('hull (multiple with undefined/null values)', (t) => {
|
|
298
|
+
const square1 = square({ size: 4 })
|
|
299
|
+
const square2 = square({ size: 6 })
|
|
300
|
+
const square3 = square({ size: 8 })
|
|
301
|
+
const geometries = [square1, undefined, square2, null, square3]
|
|
302
|
+
|
|
303
|
+
const obs = hull(...geometries)
|
|
304
|
+
const pts = geom2.toPoints(obs)
|
|
305
|
+
t.notThrows(() => geom2.validate(obs))
|
|
306
|
+
t.is(measureArea(obs), 64)
|
|
307
|
+
t.is(pts.length, 4)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
test('hull (multiple with nested arrays)', (t) => {
|
|
311
|
+
const square1 = square({ size: 4 })
|
|
312
|
+
const square2 = square({ size: 6 })
|
|
313
|
+
const square3 = square({ size: 8 })
|
|
314
|
+
const geometries = [square1, [square2, [square3]]]
|
|
315
|
+
|
|
316
|
+
const obs = hull(...geometries)
|
|
317
|
+
const pts = geom2.toPoints(obs)
|
|
318
|
+
t.notThrows(() => geom2.validate(obs))
|
|
319
|
+
t.is(measureArea(obs), 64)
|
|
320
|
+
t.is(pts.length, 4)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
test('hull (single, unconvex, geom3)', (t) => {
|
|
324
|
+
const geometry = torus()
|
|
325
|
+
const obs = hull(geometry)
|
|
326
|
+
t.assert(measureVolume(obs) > measureVolume(geometry))
|
|
327
|
+
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { areAllShapesTheSameType } from '../../utils/areAllShapesTheSameType.js'
|
|
2
|
+
import { coalesce } from '../../utils/coalesce.js'
|
|
2
3
|
|
|
3
4
|
import { union } from '../booleans/union.js'
|
|
4
5
|
|
|
@@ -10,7 +11,7 @@ import { hull } from './hull.js'
|
|
|
10
11
|
* The given geometries should be of the same type, either geom2 or geom3 or path2.
|
|
11
12
|
*
|
|
12
13
|
* @param {...Objects} geometries - list of geometries from which to create a hull
|
|
13
|
-
* @returns {Geom2|Geom3} new geometry
|
|
14
|
+
* @returns {Geom2|Geom3|Path2} new geometry
|
|
14
15
|
* @alias module:modeling/hulls.hullChain
|
|
15
16
|
*
|
|
16
17
|
* @example
|
|
@@ -30,12 +31,16 @@ import { hull } from './hull.js'
|
|
|
30
31
|
* +-------+ +-------+
|
|
31
32
|
*/
|
|
32
33
|
export const hullChain = (...geometries) => {
|
|
33
|
-
geometries =
|
|
34
|
-
|
|
34
|
+
geometries = coalesce(geometries)
|
|
35
|
+
|
|
36
|
+
if (geometries.length === 0) return undefined
|
|
37
|
+
if (geometries.length === 1) return geometries[0]
|
|
35
38
|
|
|
36
|
-
if (geometries
|
|
37
|
-
|
|
39
|
+
if (!areAllShapesTheSameType(geometries)) {
|
|
40
|
+
throw new Error('only hulls of the same type are supported')
|
|
41
|
+
}
|
|
38
42
|
|
|
43
|
+
const hulls = []
|
|
39
44
|
for (let i = 1; i < geometries.length; i++) {
|
|
40
45
|
hulls.push(hull(geometries[i - 1], geometries[i]))
|
|
41
46
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import test from 'ava'
|
|
2
2
|
|
|
3
3
|
import { geom2, geom3 } from '../../geometries/index.js'
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import { measureArea, measureVolume } from '../../measurements/index.js'
|
|
6
|
+
|
|
5
7
|
import { square } from '../../primitives/square.js'
|
|
6
8
|
|
|
7
9
|
import { hullChain } from './index.js'
|
|
8
10
|
|
|
9
11
|
test('hullChain: hullChain single geometry', (t) => {
|
|
10
|
-
const result = hullChain([
|
|
12
|
+
const result = hullChain([square({ size: 1 })])
|
|
11
13
|
t.notThrows(() => geom2.validate(result))
|
|
12
14
|
t.is(measureArea(result), 1)
|
|
13
15
|
t.is(geom2.toPoints(result).length, 4)
|
|
@@ -22,6 +24,7 @@ test('hullChain (two, geom2)', (t) => {
|
|
|
22
24
|
let pts = geom2.toPoints(obs)
|
|
23
25
|
|
|
24
26
|
t.notThrows(() => geom2.validate(obs))
|
|
27
|
+
t.is(measureArea(obs), 9)
|
|
25
28
|
t.is(pts.length, 4)
|
|
26
29
|
|
|
27
30
|
// different
|
|
@@ -29,6 +32,7 @@ test('hullChain (two, geom2)', (t) => {
|
|
|
29
32
|
pts = geom2.toPoints(obs)
|
|
30
33
|
|
|
31
34
|
t.notThrows(() => geom2.validate(obs))
|
|
35
|
+
t.is(measureArea(obs), 81)
|
|
32
36
|
t.is(pts.length, 6)
|
|
33
37
|
})
|
|
34
38
|
|
|
@@ -43,6 +47,7 @@ test('hullChain (three, geom2)', (t) => {
|
|
|
43
47
|
|
|
44
48
|
// the sides change based on the bestplane chosen in trees/Node.js
|
|
45
49
|
t.notThrows(() => geom2.validate(obs))
|
|
50
|
+
t.is(measureArea(obs), 126)
|
|
46
51
|
t.is(pts.length, 10)
|
|
47
52
|
|
|
48
53
|
// closed
|
|
@@ -51,6 +56,7 @@ test('hullChain (three, geom2)', (t) => {
|
|
|
51
56
|
|
|
52
57
|
// the sides change based on the bestplane chosen in trees/Node.js
|
|
53
58
|
t.notThrows(() => geom2.validate(obs))
|
|
59
|
+
t.is(measureArea(obs), 148.21875)
|
|
54
60
|
t.is(pts.length, 10)
|
|
55
61
|
})
|
|
56
62
|
|
|
@@ -85,6 +91,8 @@ test('hullChain (three, geom3)', (t) => {
|
|
|
85
91
|
let pts = geom3.toPoints(obs)
|
|
86
92
|
|
|
87
93
|
t.notThrows.skip(() => geom3.validate(obs))
|
|
94
|
+
t.is(measureArea(obs), 266.1454764345133)
|
|
95
|
+
t.is(measureVolume(obs), 239.2012987012987)
|
|
88
96
|
t.is(pts.length, 23)
|
|
89
97
|
|
|
90
98
|
// closed
|
|
@@ -92,5 +100,7 @@ test('hullChain (three, geom3)', (t) => {
|
|
|
92
100
|
pts = geom3.toPoints(obs)
|
|
93
101
|
|
|
94
102
|
t.notThrows.skip(() => geom3.validate(obs))
|
|
103
|
+
t.is(measureArea(obs), 272.2887171436021)
|
|
104
|
+
t.is(measureVolume(obs), 261.96982218883045)
|
|
95
105
|
t.is(pts.length, 28)
|
|
96
106
|
})
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
4
2
|
|
|
5
3
|
import { hullPoints2 } from './hullPoints2.js'
|
|
@@ -7,12 +5,13 @@ import { toUniquePoints } from './toUniquePoints.js'
|
|
|
7
5
|
|
|
8
6
|
/*
|
|
9
7
|
* Create a convex hull of the given geom2 geometries.
|
|
10
|
-
*
|
|
8
|
+
*
|
|
9
|
+
* NOTE: The given geometries must be valid geom2 geometries.
|
|
10
|
+
*
|
|
11
|
+
* @param {Geom2[]} geometries - a flat list of 2D geometries
|
|
11
12
|
* @returns {Geom2} new geometry
|
|
12
13
|
*/
|
|
13
|
-
export const hullGeom2 = (
|
|
14
|
-
geometries = flatten(geometries)
|
|
15
|
-
|
|
14
|
+
export const hullGeom2 = (geometries) => {
|
|
16
15
|
// extract the unique points from the geometries
|
|
17
16
|
const unique = toUniquePoints(geometries)
|
|
18
17
|
|
|
@@ -1,30 +1,21 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import * as geom3 from '../../geometries/geom3/index.js'
|
|
4
|
-
import * as poly3 from '../../geometries/poly3/index.js'
|
|
5
2
|
|
|
6
|
-
import {
|
|
3
|
+
import { hullPoints3 } from './hullPoints3.js'
|
|
7
4
|
import { toUniquePoints } from './toUniquePoints.js'
|
|
8
5
|
|
|
9
6
|
/*
|
|
10
|
-
* Create a convex hull of the given geometries
|
|
11
|
-
*
|
|
7
|
+
* Create a convex hull of the given geom3 geometries.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: The given geometries must be valid geom3 geometries.
|
|
10
|
+
*
|
|
11
|
+
* @param {Geom3[]} geometries - a flat list of 3D geometries
|
|
12
12
|
* @returns {Geom3} new geometry
|
|
13
13
|
*/
|
|
14
|
-
export const hullGeom3 = (
|
|
15
|
-
geometries = flatten(geometries)
|
|
16
|
-
|
|
17
|
-
if (geometries.length === 1) return geometries[0]
|
|
18
|
-
|
|
14
|
+
export const hullGeom3 = (geometries) => {
|
|
19
15
|
// extract the unique vertices from the geometries
|
|
20
16
|
const unique = toUniquePoints(geometries)
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const polygons = faces.map((face) => {
|
|
25
|
-
const vertices = face.map((index) => unique[index])
|
|
26
|
-
return poly3.create(vertices)
|
|
27
|
-
})
|
|
18
|
+
if (unique.length === 0) return geom3.create()
|
|
28
19
|
|
|
29
|
-
return geom3.create(
|
|
20
|
+
return geom3.create(hullPoints3(unique))
|
|
30
21
|
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import * as path2 from '../../geometries/path2/index.js'
|
|
4
2
|
|
|
5
3
|
import { hullPoints2 } from './hullPoints2.js'
|
|
6
4
|
import { toUniquePoints } from './toUniquePoints.js'
|
|
7
5
|
|
|
8
6
|
/*
|
|
9
|
-
* Create a convex hull of the given geometries
|
|
10
|
-
*
|
|
7
|
+
* Create a convex hull of the given path2 geometries.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: The given geometries must be valid path2 geometry.
|
|
10
|
+
*
|
|
11
|
+
* @param {Path2[]} geometries - a flat list of path2 geometries
|
|
11
12
|
* @returns {Path2} new geometry
|
|
12
13
|
*/
|
|
13
|
-
export const hullPath2 = (
|
|
14
|
-
geometries = flatten(geometries)
|
|
15
|
-
|
|
14
|
+
export const hullPath2 = (geometries) => {
|
|
16
15
|
// extract the unique points from the geometries
|
|
17
16
|
const unique = toUniquePoints(geometries)
|
|
18
17
|
|
|
@@ -9,7 +9,7 @@ test('hullPath2', (t) => {
|
|
|
9
9
|
const geometry1 = path2.fromPoints({ closed }, [[0, 0], [-4, 4], [-4, -4]])
|
|
10
10
|
const geometry2 = path2.fromPoints({ closed }, [[0, 0], [4, -4], [4, 4]])
|
|
11
11
|
|
|
12
|
-
const obs = hullPath2(geometry1, geometry2)
|
|
12
|
+
const obs = hullPath2([geometry1, geometry2])
|
|
13
13
|
t.notThrows(() => path2.validate(obs))
|
|
14
14
|
const pts = path2.toPoints(obs)
|
|
15
15
|
t.is(pts.length, 4)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as vec2 from '../../maths/vec2/index.js'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
4
|
* Create a convex hull of the given set of points, where each point is an array of [x,y].
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* @see https://en.wikipedia.org/wiki/Graham_scan
|
|
6
7
|
* @param {Array} uniquePoints - list of UNIQUE points from which to create a hull
|
|
7
8
|
* @returns {Array} a list of points that form the hull
|
|
9
|
+
* @alias module:modeling/hulls.hullPoints2
|
|
8
10
|
*/
|
|
9
11
|
export const hullPoints2 = (uniquePoints) => {
|
|
10
12
|
// find min point
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as poly3 from '../../geometries/poly3/index.js'
|
|
2
|
+
|
|
3
|
+
import { runner } from './quickhull/index.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a convex hull of the given set of points, where each point is an array of [x,y,z].
|
|
7
|
+
*
|
|
8
|
+
* @param {Array} uniquePoints - list of UNIQUE points from which to create a hull
|
|
9
|
+
* @returns {Array} a list of polygons (poly3)
|
|
10
|
+
* @alias module:modeling/hulls.hullPoints3
|
|
11
|
+
*/
|
|
12
|
+
export const hullPoints3 = (uniquePoints) => {
|
|
13
|
+
const faces = runner(uniquePoints, { skipTriangulation: true })
|
|
14
|
+
|
|
15
|
+
const polygons = faces.map((face) => {
|
|
16
|
+
const vertices = face.map((index) => uniquePoints[index])
|
|
17
|
+
return poly3.create(vertices)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return polygons
|
|
21
|
+
}
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* In all cases, the function returns the results, and never changes the original shapes.
|
|
5
5
|
* @module modeling/hulls
|
|
6
6
|
* @example
|
|
7
|
-
* import { hull, hullChain } from '@jscad/modeling'
|
|
7
|
+
* import { hull, hullChain, hullPoints2, hullPoints3 } from '@jscad/modeling'
|
|
8
8
|
*/
|
|
9
9
|
export { hull } from './hull.js'
|
|
10
10
|
export { hullChain } from './hullChain.js'
|
|
11
|
+
export { hullPoints2 } from './hullPoints2.js'
|
|
12
|
+
export { hullPoints3 } from './hullPoints3.js'
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import { measureEpsilon } from '../../measurements/measureEpsilon.js'
|
|
4
2
|
|
|
5
3
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
@@ -67,14 +65,12 @@ const generalizeGeom3 = (options, geometry) => {
|
|
|
67
65
|
* @alias module:modeling/modifiers.generalize
|
|
68
66
|
*/
|
|
69
67
|
export const generalize = (options, ...geometries) => {
|
|
70
|
-
geometries = flatten(geometries)
|
|
71
|
-
if (geometries.length === 0) throw new Error('wrong number of arguments')
|
|
72
|
-
|
|
73
68
|
const results = geometries.map((geometry) => {
|
|
74
69
|
if (path2.isA(geometry)) return generalizePath2(options, geometry)
|
|
75
70
|
if (geom2.isA(geometry)) return generalizeGeom2(options, geometry)
|
|
76
71
|
if (geom3.isA(geometry)) return generalizeGeom3(options, geometry)
|
|
77
|
-
|
|
72
|
+
if (Array.isArray(geometry)) return generalize(options, ...geometry)
|
|
73
|
+
return geometry
|
|
78
74
|
})
|
|
79
75
|
return results.length === 1 ? results[0] : results
|
|
80
76
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* In all cases, these functions returns the results, and never changes the original geometry.
|
|
4
4
|
* @module modeling/modifiers
|
|
5
5
|
* @example
|
|
6
|
-
* import { generalize, snap } from '@jscad/modeling'
|
|
6
|
+
* import { generalize, snap, retessellate } from '@jscad/modeling'
|
|
7
7
|
*/
|
|
8
8
|
export { generalize } from './generalize.js'
|
|
9
9
|
export { snap } from './snap.js'
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import * as vec2 from '../../maths/vec2/index.js'
|
|
4
2
|
|
|
5
3
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
@@ -48,20 +46,18 @@ const snapGeom3 = (geometry) => {
|
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
/**
|
|
51
|
-
* Snap the given geometries to the
|
|
49
|
+
* Snap the given geometries to the precision (calculated epsilon) of the geometry.
|
|
52
50
|
* @see measurements.measureEpsilon()
|
|
53
51
|
* @param {...Object} geometries - the geometries to snap
|
|
54
52
|
* @return {Object|Array} the snapped geometry, or a list of snapped geometries
|
|
55
53
|
* @alias module:modeling/modifiers.snap
|
|
56
54
|
*/
|
|
57
55
|
export const snap = (...geometries) => {
|
|
58
|
-
geometries = flatten(geometries)
|
|
59
|
-
if (geometries.length === 0) throw new Error('wrong number of arguments')
|
|
60
|
-
|
|
61
56
|
const results = geometries.map((geometry) => {
|
|
62
57
|
if (path2.isA(geometry)) return snapPath2(geometry)
|
|
63
58
|
if (geom2.isA(geometry)) return snapGeom2(geometry)
|
|
64
59
|
if (geom3.isA(geometry)) return snapGeom3(geometry)
|
|
60
|
+
if (Array.isArray(geometry)) return snap(...geometry)
|
|
65
61
|
return geometry
|
|
66
62
|
})
|
|
67
63
|
return results.length === 1 ? results[0] : results
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
4
2
|
import * as geom3 from '../../geometries/geom3/index.js'
|
|
5
3
|
import * as path2 from '../../geometries/path2/index.js'
|
|
@@ -23,13 +21,11 @@ import { offsetPath2 } from './offsetPath2.js'
|
|
|
23
21
|
* let small = offset({ delta: -4, corners: 'chamfer' }, square({size: 40})) // contract
|
|
24
22
|
*/
|
|
25
23
|
export const offset = (options, ...objects) => {
|
|
26
|
-
objects = flatten(objects)
|
|
27
|
-
if (objects.length === 0) throw new Error('wrong number of arguments')
|
|
28
|
-
|
|
29
24
|
const results = objects.map((object) => {
|
|
30
25
|
if (path2.isA(object)) return offsetPath2(options, object)
|
|
31
26
|
if (geom2.isA(object)) return offsetGeom2(options, object)
|
|
32
27
|
if (geom3.isA(object)) return offsetGeom3(options, object)
|
|
28
|
+
if (Array.isArray(object)) return offset(options, ...object)
|
|
33
29
|
return object
|
|
34
30
|
})
|
|
35
31
|
return results.length === 1 ? results[0] : results
|
|
@@ -200,7 +200,6 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
|
|
|
200
200
|
|
|
201
201
|
test('offset (corners: round): offset of a path2 produces expected offset path2', (t) => {
|
|
202
202
|
const openline = [[-5, -5], [5, -5], [5, 5], [3, 5], [3, 0], [-3, 0], [-3, 5], [-5, 5]]
|
|
203
|
-
const closeline = [[-5, -5], [5, -5], [5, 5], [3, 5], [3, 0], [-3, 0], [-3, 5], [-5, 5], [-5, -5]]
|
|
204
203
|
|
|
205
204
|
let pts = offsetFromPoints({ delta: 1, corners: 'round', segments: 16 }, openline)
|
|
206
205
|
let obs = path2.create(pts)
|