@jscad/modeling 3.0.0-alpha.0 → 3.0.2-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 +49 -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/path2/appendBezier.js +1 -1
- package/src/geometries/poly3/index.js +1 -1
- package/src/geometries/poly3/measureBoundingBox.js +2 -0
- package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -1
- package/src/geometries/poly3/measureBoundingSphere.js +25 -8
- package/src/geometries/poly3/measureBoundingSphere.test.js +12 -8
- package/src/geometries/poly3/type.d.ts +1 -1
- package/src/geometries/slice/validate.js +1 -2
- package/src/index.js +41 -0
- 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/measureBoundingSphere.js +2 -6
- 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/martinez/compareEvents.js +2 -7
- package/src/operations/booleans/martinez/connectEdges.js +30 -41
- package/src/operations/booleans/martinez/contour.js +1 -1
- package/src/operations/booleans/martinez/divideSegment.js +12 -11
- package/src/operations/booleans/martinez/fillQueue.js +24 -28
- package/src/operations/booleans/martinez/index.js +2 -1
- package/src/operations/booleans/martinez/possibleIntersection.js +41 -30
- package/src/operations/booleans/martinez/segmentIntersection.js +7 -9
- package/src/operations/booleans/martinez/splaytree.js +59 -457
- package/src/operations/booleans/martinez/subdivideSegments.js +4 -4
- package/src/operations/booleans/martinez/sweepEvent.js +3 -17
- package/src/operations/booleans/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/Node.js +25 -27
- package/src/operations/booleans/trees/PolygonTreeNode.js +153 -106
- package/src/operations/booleans/trees/Tree.js +9 -4
- package/src/operations/booleans/trees/splitLineSegmentByPlane.js +5 -3
- package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +33 -0
- package/src/operations/booleans/trees/splitPolygonByPlane.js +39 -34
- 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/extrudeWalls.js +3 -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 +24 -30
- 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/mergePolygons.js +2 -3
- package/src/operations/modifiers/reTesselateCoplanarPolygons.js +7 -7
- 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
|
@@ -6,7 +6,7 @@ import { geom2 } from '../../geometries/index.js'
|
|
|
6
6
|
|
|
7
7
|
import { measureArea } from '../../measurements/index.js'
|
|
8
8
|
|
|
9
|
-
import { circle, rectangle } from '../../primitives/index.js'
|
|
9
|
+
import { circle, rectangle, square } from '../../primitives/index.js'
|
|
10
10
|
|
|
11
11
|
import { center, translate } from '../transforms/index.js'
|
|
12
12
|
|
|
@@ -209,3 +209,27 @@ test('union of geom2 with colinear edge (martinez issue #155)', (t) => {
|
|
|
209
209
|
t.is(pts.length, 5)
|
|
210
210
|
t.true(comparePoints(pts, exp))
|
|
211
211
|
})
|
|
212
|
+
|
|
213
|
+
test('union with undefined/null values', (t) => {
|
|
214
|
+
const square1 = square({ size: 8 })
|
|
215
|
+
const square2 = square({ size: 6 })
|
|
216
|
+
const square3 = square({ size: 4 })
|
|
217
|
+
const geometries = [square1, undefined, square2, null, square3]
|
|
218
|
+
|
|
219
|
+
const obs = union(...geometries)
|
|
220
|
+
const pts = geom2.toPoints(obs)
|
|
221
|
+
t.notThrows(() => geom2.validate(obs))
|
|
222
|
+
t.is(pts.length, 4)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('union of nested arrays', (t) => {
|
|
226
|
+
const square1 = square({ size: 8 })
|
|
227
|
+
const square2 = square({ size: 6 })
|
|
228
|
+
const square3 = square({ size: 4 })
|
|
229
|
+
const geometries = [square1, [square2, [square3]]]
|
|
230
|
+
|
|
231
|
+
const obs = union(...geometries)
|
|
232
|
+
const pts = geom2.toPoints(obs)
|
|
233
|
+
t.notThrows(() => geom2.validate(obs))
|
|
234
|
+
t.is(pts.length, 4)
|
|
235
|
+
})
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import { retessellate } from '../modifiers/retessellate.js'
|
|
4
2
|
|
|
5
3
|
import { unionGeom3Sub } from './unionGeom3Sub.js'
|
|
6
4
|
|
|
7
5
|
/*
|
|
8
6
|
* Return a new 3D geometry representing the space in the given 3D geometries.
|
|
9
|
-
* @param {
|
|
7
|
+
* @param {Geom3[]} geometries - a flat list of 3D geometries to union
|
|
10
8
|
* @returns {Geom3} new 3D geometry
|
|
11
9
|
*/
|
|
12
|
-
export const unionGeom3 = (
|
|
13
|
-
geometries = flatten(geometries)
|
|
14
|
-
|
|
10
|
+
export const unionGeom3 = (geometries) => {
|
|
15
11
|
// combine geometries in a way that forms a balanced binary tree pattern
|
|
16
12
|
let i
|
|
17
13
|
for (i = 1; i < geometries.length; i += 2) {
|
|
@@ -4,7 +4,7 @@ import { comparePolygonsAsPoints } from '../../../test/helpers/index.js'
|
|
|
4
4
|
|
|
5
5
|
import { geom3 } from '../../geometries/index.js'
|
|
6
6
|
|
|
7
|
-
import { measureVolume } from '../../measurements/index.js'
|
|
7
|
+
import { measureArea, measureVolume } from '../../measurements/index.js'
|
|
8
8
|
|
|
9
9
|
import { sphere, cuboid } from '../../primitives/index.js'
|
|
10
10
|
|
|
@@ -69,6 +69,7 @@ test('union of one or more geom3 objects produces expected geometry', (t) => {
|
|
|
69
69
|
[[8.65956056235493e-17, 8.659560562354935e-17, 2], [1.4142135623730951, 3.4638242249419736e-16, 1.414213562373095], [0.9999999999999998, 1.0000000000000002, 1.414213562373095]]
|
|
70
70
|
]
|
|
71
71
|
t.notThrows.skip(() => geom3.validate(result1))
|
|
72
|
+
t.is(measureArea(result1), 44.053756306589825)
|
|
72
73
|
t.is(measureVolume(result1), 25.751611331979678)
|
|
73
74
|
t.is(obs.length, 32)
|
|
74
75
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -79,6 +80,7 @@ test('union of one or more geom3 objects produces expected geometry', (t) => {
|
|
|
79
80
|
const result2 = union(geometry1, geometry2)
|
|
80
81
|
obs = geom3.toPoints(result2)
|
|
81
82
|
t.notThrows.skip(() => geom3.validate(result2))
|
|
83
|
+
t.is(measureArea(result2), 140.05375630658983)
|
|
82
84
|
t.is(measureVolume(result2), 89.75161133197969)
|
|
83
85
|
t.is(obs.length, 38)
|
|
84
86
|
|
|
@@ -108,6 +110,7 @@ test('union of one or more geom3 objects produces expected geometry', (t) => {
|
|
|
108
110
|
[[-9, 8, 9], [-9, -9, 9], [9, -9, 9], [9, 8, 9]]
|
|
109
111
|
]
|
|
110
112
|
t.notThrows.skip(() => geom3.validate(result3))
|
|
113
|
+
t.is(measureArea(result3), 2034)
|
|
111
114
|
t.is(measureVolume(result3), 5895)
|
|
112
115
|
t.is(obs.length, 18)
|
|
113
116
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -124,6 +127,7 @@ test('union of one or more geom3 objects produces expected geometry', (t) => {
|
|
|
124
127
|
[[-9, -9, 9], [9, -9, 9], [9, 9, 9], [-9, 9, 9]]
|
|
125
128
|
]
|
|
126
129
|
t.notThrows(() => geom3.validate(result4))
|
|
130
|
+
t.is(measureArea(result4), 1944)
|
|
127
131
|
t.is(measureVolume(result4), 5832)
|
|
128
132
|
t.is(obs.length, 6)
|
|
129
133
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -136,6 +140,7 @@ test('union of geom3 with rounding issues #137', (t) => {
|
|
|
136
140
|
const result = union(geometry1, geometry2)
|
|
137
141
|
const pts = geom3.toPoints(result)
|
|
138
142
|
t.notThrows(() => geom3.validate(result))
|
|
143
|
+
t.is(measureArea(result), 3240.00014)
|
|
139
144
|
t.is(measureVolume(result), 7779.201144000001)
|
|
140
145
|
t.is(pts.length, 6) // number of polygons in union
|
|
141
146
|
})
|
|
@@ -7,7 +7,7 @@ import { mat4 } from '../../maths/index.js'
|
|
|
7
7
|
|
|
8
8
|
import { geom2, geom3, poly3, slice } from '../../geometries/index.js'
|
|
9
9
|
|
|
10
|
-
import { measureVolume } from '../../measurements/index.js'
|
|
10
|
+
import { measureArea, measureVolume } from '../../measurements/index.js'
|
|
11
11
|
|
|
12
12
|
import { circle, square } from '../../primitives/index.js'
|
|
13
13
|
|
|
@@ -32,6 +32,8 @@ test('extrudeFromSlices (defaults)', (t) => {
|
|
|
32
32
|
[[-10, -10, 0], [-10, 10, 0], [10, 10, 0]],
|
|
33
33
|
[[10, 10, 0], [10, -10, 0], [-10, -10, 0]]
|
|
34
34
|
]
|
|
35
|
+
t.is(measureArea(geometry3), 880)
|
|
36
|
+
t.is(measureVolume(geometry3), 400.00000000000006)
|
|
35
37
|
t.is(pts.length, 12)
|
|
36
38
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
37
39
|
|
|
@@ -40,6 +42,7 @@ test('extrudeFromSlices (defaults)', (t) => {
|
|
|
40
42
|
pts = geom3.toPoints(geometry3)
|
|
41
43
|
|
|
42
44
|
t.notThrows(() => geom3.validate(geometry3))
|
|
45
|
+
t.is(measureArea(geometry3), 880)
|
|
43
46
|
t.is(measureVolume(geometry3), 400.00000000000006)
|
|
44
47
|
t.is(pts.length, 12)
|
|
45
48
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -74,6 +77,7 @@ test('extrudeFromSlices (torus)', (t) => {
|
|
|
74
77
|
)
|
|
75
78
|
const pts = geom3.toPoints(geometry3)
|
|
76
79
|
t.notThrows(() => geom3.validate(geometry3))
|
|
80
|
+
t.is(measureArea(geometry3), 7070.694617452831)
|
|
77
81
|
t.is(measureVolume(geometry3), 29393.876913398108)
|
|
78
82
|
t.is(pts.length, 96)
|
|
79
83
|
})
|
|
@@ -95,6 +99,7 @@ test('extrudeFromSlices (same shape, changing dimensions)', (t) => {
|
|
|
95
99
|
const pts = geom3.toPoints(geometry3)
|
|
96
100
|
// expected to throw because capEnd is false (non-closed geometry)
|
|
97
101
|
t.throws(() => geom3.validate(geometry3))
|
|
102
|
+
t.is(measureArea(geometry3), 53.70100297794013)
|
|
98
103
|
t.is(measureVolume(geometry3), 8.5)
|
|
99
104
|
t.is(pts.length, 26)
|
|
100
105
|
})
|
|
@@ -114,6 +119,7 @@ test('extrudeFromSlices (changing shape, changing dimensions)', (t) => {
|
|
|
114
119
|
)
|
|
115
120
|
const pts = geom3.toPoints(geometry3)
|
|
116
121
|
t.notThrows.skip(() => geom3.validate(geometry3))
|
|
122
|
+
t.is(measureArea(geometry3), 1965.8643589631802)
|
|
117
123
|
t.is(measureVolume(geometry3), 5260.067107417433)
|
|
118
124
|
t.is(pts.length, 304)
|
|
119
125
|
})
|
|
@@ -160,6 +166,7 @@ test('extrudeFromSlices (holes)', (t) => {
|
|
|
160
166
|
[[-5, -5, 0], [5, -5, 0], [-10, -10, 0]]
|
|
161
167
|
]
|
|
162
168
|
t.notThrows(() => geom3.validate(geometry3))
|
|
169
|
+
t.is(measureArea(geometry3), 720)
|
|
163
170
|
t.is(measureVolume(geometry3), 300)
|
|
164
171
|
t.is(pts.length, 32)
|
|
165
172
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -19,14 +19,8 @@ import { extrudeFromSlices } from './extrudeFromSlices.js'
|
|
|
19
19
|
* @alias module:modeling/extrusions.extrudeHelical
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
22
|
-
* const myshape =
|
|
23
|
-
*
|
|
24
|
-
* angle: Math.PI * 4,
|
|
25
|
-
* pitch: 10,
|
|
26
|
-
* segmentsPerRotation: 64
|
|
27
|
-
* },
|
|
28
|
-
* circle({size: 3, center: [10, 0]})
|
|
29
|
-
* )
|
|
22
|
+
* const myshape = circle({size: 3, center: [10, 0]}) // position for extrusion about Z
|
|
23
|
+
* const mycoil = extrudeHelical({angle: TAU*2, pitch: 10, segmentsPerRotation: 64}, myshape))
|
|
30
24
|
*/
|
|
31
25
|
export const extrudeHelical = (options, geometry) => {
|
|
32
26
|
const defaults = {
|
|
@@ -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 path2 from '../../geometries/path2/index.js'
|
|
5
3
|
|
|
@@ -30,15 +28,13 @@ export const extrudeLinear = (options, ...objects) => {
|
|
|
30
28
|
}
|
|
31
29
|
const { height, twistAngle, twistSteps, repair } = Object.assign({ }, defaults, options)
|
|
32
30
|
|
|
33
|
-
objects = flatten(objects)
|
|
34
|
-
if (objects.length === 0) throw new Error('wrong number of arguments')
|
|
35
|
-
|
|
36
31
|
options = { offset: [0, 0, height], twistAngle, twistSteps, repair }
|
|
37
32
|
|
|
38
33
|
const results = objects.map((object) => {
|
|
39
34
|
if (path2.isA(object)) return extrudeLinearPath2(options, object)
|
|
40
35
|
if (geom2.isA(object)) return extrudeLinearGeom2(options, object)
|
|
41
36
|
// if (geom3.isA(object)) return geom3.extrude(options, object)
|
|
37
|
+
if (Array.isArray(object)) return extrudeLinear(options, ...object)
|
|
42
38
|
return object
|
|
43
39
|
})
|
|
44
40
|
return results.length === 1 ? results[0] : results
|
|
@@ -8,7 +8,7 @@ import { colorize } from '../../colors/index.js'
|
|
|
8
8
|
|
|
9
9
|
import { geom2, geom3, path2 } 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
|
|
|
@@ -34,6 +34,7 @@ test('extrudeLinear (defaults)', (t) => {
|
|
|
34
34
|
[[5, 5, 0], [5, -5, 0], [-5, -5, 0]]
|
|
35
35
|
]
|
|
36
36
|
t.notThrows(() => geom3.validate(geometry3))
|
|
37
|
+
t.is(measureArea(geometry3), 240)
|
|
37
38
|
t.is(measureVolume(geometry3), 100.00000000000001)
|
|
38
39
|
t.is(pts.length, 12)
|
|
39
40
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -70,6 +71,7 @@ test('extrudeLinear (no twist)', (t) => {
|
|
|
70
71
|
[[5, 5, 0], [5, -5, 0], [-5, -5, 0]]
|
|
71
72
|
]
|
|
72
73
|
t.notThrows(() => geom3.validate(geometry3))
|
|
74
|
+
t.is(measureArea(geometry3), 800)
|
|
73
75
|
t.is(measureVolume(geometry3), 1500)
|
|
74
76
|
t.is(pts.length, 12)
|
|
75
77
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -91,6 +93,7 @@ test('extrudeLinear (no twist)', (t) => {
|
|
|
91
93
|
[[5, -5, 0], [5, 5, 0], [-5, 5, 0]]
|
|
92
94
|
]
|
|
93
95
|
t.notThrows(() => geom3.validate(geometry3))
|
|
96
|
+
t.is(measureArea(geometry3), 800)
|
|
94
97
|
t.is(measureVolume(geometry3), 1500)
|
|
95
98
|
t.is(pts.length, 12)
|
|
96
99
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -124,6 +127,7 @@ test('extrudeLinear (twist)', (t) => {
|
|
|
124
127
|
[[5, 5, 0], [5, -5, 0], [-5, -5, 0]]
|
|
125
128
|
]
|
|
126
129
|
t.notThrows(() => geom3.validate(geometry3))
|
|
130
|
+
t.is(measureArea(geometry3), 805.6920958788816)
|
|
127
131
|
t.is(measureVolume(geometry3), 1707.1067811865476)
|
|
128
132
|
t.is(pts.length, 12)
|
|
129
133
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -166,6 +170,7 @@ test('extrudeLinear (twist)', (t) => {
|
|
|
166
170
|
geometry3 = extrudeLinear({ height: 15, twistAngle: TAU / 2, twistSteps: 30 }, geometry2)
|
|
167
171
|
pts = geom3.toPoints(geometry3)
|
|
168
172
|
t.notThrows(() => geom3.validate(geometry3))
|
|
173
|
+
t.is(measureArea(geometry3), 1091.9932843446968)
|
|
169
174
|
t.is(measureVolume(geometry3), 1444.9967160503095)
|
|
170
175
|
t.is(pts.length, 244)
|
|
171
176
|
})
|
|
@@ -212,6 +217,7 @@ test('extrudeLinear (holes)', (t) => {
|
|
|
212
217
|
[[-2, -2, 0], [2, -2, 0], [-5, -5, 0]]
|
|
213
218
|
]
|
|
214
219
|
t.notThrows(() => geom3.validate(geometry3))
|
|
220
|
+
t.is(measureArea(geometry3), 1008)
|
|
215
221
|
t.is(measureVolume(geometry3), 1260)
|
|
216
222
|
t.is(pts.length, 32)
|
|
217
223
|
t.true(comparePolygonsAsPoints(pts, exp))
|
|
@@ -4,6 +4,7 @@ import * as mat4 from '../../maths/mat4/index.js'
|
|
|
4
4
|
import { mirrorX } from '../transforms/mirror.js'
|
|
5
5
|
|
|
6
6
|
import * as geom2 from '../../geometries/geom2/index.js'
|
|
7
|
+
import * as geom3 from '../../geometries/geom3/index.js'
|
|
7
8
|
import * as slice from '../../geometries/slice/index.js'
|
|
8
9
|
|
|
9
10
|
import { extrudeFromSlices } from './extrudeFromSlices.js'
|
|
@@ -58,7 +59,7 @@ export const extrudeRotate = (options, geometry) => {
|
|
|
58
59
|
|
|
59
60
|
// convert geometry to an array of sides, easier to deal with
|
|
60
61
|
let shapeSides = geom2.toSides(geometry)
|
|
61
|
-
if (shapeSides.length === 0) return
|
|
62
|
+
if (shapeSides.length === 0) return geom3.create()
|
|
62
63
|
let sliceGeometry = geometry
|
|
63
64
|
|
|
64
65
|
// determine if the extrusion can be computed in the first place
|
|
@@ -88,7 +89,7 @@ export const extrudeRotate = (options, geometry) => {
|
|
|
88
89
|
return [point0, point1]
|
|
89
90
|
})
|
|
90
91
|
// recreate the geometry from the (-) capped points
|
|
91
|
-
sliceGeometry = geom2.
|
|
92
|
+
sliceGeometry = geom2.fromSides(shapeSides)
|
|
92
93
|
sliceGeometry = mirrorX(sliceGeometry)
|
|
93
94
|
} else if (pointsWithPositiveX.length >= pointsWithNegativeX.length) {
|
|
94
95
|
shapeSides = shapeSides.map((side) => {
|
|
@@ -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))
|
|
@@ -25,10 +25,11 @@ const repartitionEdges = (newLength, edges) => {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const divisor = vec3.fromValues(multiple, multiple, multiple)
|
|
28
|
+
const increment = vec3.create()
|
|
28
29
|
|
|
29
30
|
const newEdges = []
|
|
30
31
|
edges.forEach((edge) => {
|
|
31
|
-
|
|
32
|
+
vec3.subtract(increment, edge[1], edge[0])
|
|
32
33
|
vec3.divide(increment, increment, divisor)
|
|
33
34
|
|
|
34
35
|
// repartition the edge
|
|
@@ -48,6 +49,7 @@ const EPSAREA = (EPS * EPS / 2) * Math.sin(Math.PI / 3)
|
|
|
48
49
|
* Extrude (build) walls between the given slices.
|
|
49
50
|
* Each wall consists of two triangles, which may be invalid if slices are overlapping.
|
|
50
51
|
*/
|
|
52
|
+
// FIXME this function should take an eps parameter
|
|
51
53
|
export const extrudeWalls = (slice0, slice1) => {
|
|
52
54
|
let edges0 = slice.toEdges(slice0)
|
|
53
55
|
let edges1 = slice.toEdges(slice1)
|
|
@@ -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
|
}
|