@jscad/modeling 2.12.7 → 2.13.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 +10 -0
- package/dist/jscad-modeling.min.js +396 -387
- package/package.json +2 -2
- package/src/geometries/geom3/index.d.ts +1 -0
- package/src/geometries/geom3/index.js +1 -0
- package/src/geometries/geom3/isConvex.d.ts +3 -0
- package/src/geometries/geom3/isConvex.js +68 -0
- package/src/geometries/geom3/isConvex.test.js +45 -0
- package/src/geometries/path2/appendArc.js +1 -1
- package/src/geometries/path2/appendArc.test.js +16 -20
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/operations/booleans/index.d.ts +1 -0
- package/src/operations/booleans/index.js +1 -0
- package/src/operations/extrusions/extrudeRectangular.test.js +3 -3
- package/src/operations/minkowski/index.d.ts +1 -0
- package/src/operations/minkowski/index.js +17 -0
- package/src/operations/minkowski/minkowskiSum.d.ts +4 -0
- package/src/operations/minkowski/minkowskiSum.js +224 -0
- package/src/operations/minkowski/minkowskiSum.test.js +195 -0
- package/src/operations/modifiers/snap.test.js +24 -15
- package/src/primitives/arc.js +2 -2
- package/src/primitives/arc.test.js +122 -111
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { geometries, primitives, measurements, booleans, minkowski } = require('../../index')
|
|
4
|
+
const { geom3 } = geometries
|
|
5
|
+
|
|
6
|
+
test('minkowskiSum: throws for non-geom3 inputs', (t) => {
|
|
7
|
+
t.throws(() => minkowski.minkowskiSum('invalid', primitives.cuboid()), { message: /requires geom3/ })
|
|
8
|
+
t.throws(() => minkowski.minkowskiSum(primitives.cuboid(), 'invalid'), { message: /requires geom3/ })
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('minkowskiSum: throws for wrong number of geometries', (t) => {
|
|
12
|
+
t.throws(() => minkowski.minkowskiSum(), { message: /exactly two/ })
|
|
13
|
+
t.throws(() => minkowski.minkowskiSum(primitives.cuboid()), { message: /exactly two/ })
|
|
14
|
+
t.throws(() => minkowski.minkowskiSum(primitives.cuboid(), primitives.cuboid(), primitives.cuboid()), { message: /exactly two/ })
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('minkowskiSum: cube + cube produces correct bounds', (t) => {
|
|
18
|
+
// Cube1: size 10 (±5 from origin)
|
|
19
|
+
// Cube2: size 4 (±2 from origin)
|
|
20
|
+
// Minkowski sum should be size 14 (±7 from origin)
|
|
21
|
+
const cube1 = primitives.cuboid({ size: [10, 10, 10] })
|
|
22
|
+
const cube2 = primitives.cuboid({ size: [4, 4, 4] })
|
|
23
|
+
|
|
24
|
+
const result = minkowski.minkowskiSum(cube1, cube2)
|
|
25
|
+
|
|
26
|
+
t.notThrows(() => geom3.validate(result))
|
|
27
|
+
|
|
28
|
+
const bounds = measurements.measureBoundingBox(result)
|
|
29
|
+
// Allow small tolerance for floating point
|
|
30
|
+
t.true(Math.abs(bounds[0][0] - (-7)) < 0.001)
|
|
31
|
+
t.true(Math.abs(bounds[0][1] - (-7)) < 0.001)
|
|
32
|
+
t.true(Math.abs(bounds[0][2] - (-7)) < 0.001)
|
|
33
|
+
t.true(Math.abs(bounds[1][0] - 7) < 0.001)
|
|
34
|
+
t.true(Math.abs(bounds[1][1] - 7) < 0.001)
|
|
35
|
+
t.true(Math.abs(bounds[1][2] - 7) < 0.001)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('minkowskiSum: cube + sphere produces correct bounds', (t) => {
|
|
39
|
+
// Cube: size 10 (±5 from origin)
|
|
40
|
+
// Sphere: radius 2
|
|
41
|
+
// Minkowski sum should be ±7 from origin
|
|
42
|
+
const cube = primitives.cuboid({ size: [10, 10, 10] })
|
|
43
|
+
const sph = primitives.sphere({ radius: 2, segments: 16 })
|
|
44
|
+
|
|
45
|
+
const result = minkowski.minkowskiSum(cube, sph)
|
|
46
|
+
|
|
47
|
+
t.notThrows(() => geom3.validate(result))
|
|
48
|
+
|
|
49
|
+
const bounds = measurements.measureBoundingBox(result)
|
|
50
|
+
// Allow small tolerance
|
|
51
|
+
t.true(Math.abs(bounds[0][0] - (-7)) < 0.1)
|
|
52
|
+
t.true(Math.abs(bounds[1][0] - 7) < 0.1)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('minkowskiSum: sphere + sphere produces correct bounds', (t) => {
|
|
56
|
+
// Sphere1: radius 3
|
|
57
|
+
// Sphere2: radius 2
|
|
58
|
+
// Minkowski sum should be a sphere-like shape with radius ~5
|
|
59
|
+
const sph1 = primitives.sphere({ radius: 3, segments: 16 })
|
|
60
|
+
const sph2 = primitives.sphere({ radius: 2, segments: 16 })
|
|
61
|
+
|
|
62
|
+
const result = minkowski.minkowskiSum(sph1, sph2)
|
|
63
|
+
|
|
64
|
+
t.notThrows(() => geom3.validate(result))
|
|
65
|
+
|
|
66
|
+
const bounds = measurements.measureBoundingBox(result)
|
|
67
|
+
// Should be approximately ±5
|
|
68
|
+
t.true(Math.abs(bounds[0][0] - (-5)) < 0.2)
|
|
69
|
+
t.true(Math.abs(bounds[1][0] - 5) < 0.2)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('minkowskiSum: empty geometry returns empty', (t) => {
|
|
73
|
+
const empty = geom3.create()
|
|
74
|
+
const cube = primitives.cuboid({ size: [10, 10, 10] })
|
|
75
|
+
|
|
76
|
+
const result = minkowski.minkowskiSum(empty, cube)
|
|
77
|
+
|
|
78
|
+
t.notThrows(() => geom3.validate(result))
|
|
79
|
+
t.is(geom3.toPolygons(result).length, 0)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('minkowskiSum: result is convex', (t) => {
|
|
83
|
+
const cube = primitives.cuboid({ size: [10, 10, 10] })
|
|
84
|
+
const sph = primitives.sphere({ radius: 2, segments: 12 })
|
|
85
|
+
|
|
86
|
+
const result = minkowski.minkowskiSum(cube, sph)
|
|
87
|
+
|
|
88
|
+
t.notThrows(() => geom3.validate(result))
|
|
89
|
+
t.true(geom3.isConvex(result))
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Non-convex tests
|
|
93
|
+
|
|
94
|
+
test('minkowskiSum: non-convex + convex produces valid geometry', (t) => {
|
|
95
|
+
// Create L-shaped non-convex geometry
|
|
96
|
+
const big = primitives.cuboid({ size: [10, 10, 10] })
|
|
97
|
+
const corner = primitives.cuboid({ size: [6, 6, 12], center: [3, 3, 0] })
|
|
98
|
+
const lShape = booleans.subtract(big, corner)
|
|
99
|
+
|
|
100
|
+
t.false(geom3.isConvex(lShape))
|
|
101
|
+
|
|
102
|
+
const sph = primitives.sphere({ radius: 1, segments: 8 })
|
|
103
|
+
|
|
104
|
+
const result = minkowski.minkowskiSum(lShape, sph)
|
|
105
|
+
|
|
106
|
+
t.true(geom3.toPolygons(result).length > 0)
|
|
107
|
+
t.true(geom3.isA(result))
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('minkowskiSum: non-convex + convex produces correct bounds', (t) => {
|
|
111
|
+
// Cube with hole through it
|
|
112
|
+
const cube = primitives.cuboid({ size: [10, 10, 10] })
|
|
113
|
+
const hole = primitives.cuboid({ size: [4, 4, 20] })
|
|
114
|
+
const cubeWithHole = booleans.subtract(cube, hole)
|
|
115
|
+
|
|
116
|
+
t.false(geom3.isConvex(cubeWithHole))
|
|
117
|
+
|
|
118
|
+
// Offset by sphere of radius 1
|
|
119
|
+
const sph = primitives.sphere({ radius: 1, segments: 8 })
|
|
120
|
+
const result = minkowski.minkowskiSum(cubeWithHole, sph)
|
|
121
|
+
|
|
122
|
+
t.true(geom3.isA(result))
|
|
123
|
+
|
|
124
|
+
const bounds = measurements.measureBoundingBox(result)
|
|
125
|
+
|
|
126
|
+
// Original cube is ±5, plus sphere radius 1 = ±6
|
|
127
|
+
t.true(Math.abs(bounds[0][0] - (-6)) < 0.2)
|
|
128
|
+
t.true(Math.abs(bounds[1][0] - 6) < 0.2)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('minkowskiSum: convex + non-convex swaps operands', (t) => {
|
|
132
|
+
// Minkowski sum is commutative, so A⊕B = B⊕A
|
|
133
|
+
const cube = primitives.cuboid({ size: [10, 10, 10] })
|
|
134
|
+
const hole = primitives.cuboid({ size: [4, 4, 20] })
|
|
135
|
+
const cubeWithHole = booleans.subtract(cube, hole)
|
|
136
|
+
|
|
137
|
+
const sph = primitives.sphere({ radius: 1, segments: 8 })
|
|
138
|
+
|
|
139
|
+
// convex + non-convex should work (swaps internally)
|
|
140
|
+
const result = minkowski.minkowskiSum(sph, cubeWithHole)
|
|
141
|
+
|
|
142
|
+
t.true(geom3.isA(result))
|
|
143
|
+
t.true(geom3.toPolygons(result).length > 0)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('minkowskiSum: throws for two non-convex geometries', (t) => {
|
|
147
|
+
const cube1 = primitives.cuboid({ size: [10, 10, 10] })
|
|
148
|
+
const hole1 = primitives.cuboid({ size: [4, 4, 20] })
|
|
149
|
+
const nonConvex1 = booleans.subtract(cube1, hole1)
|
|
150
|
+
|
|
151
|
+
const cube2 = primitives.cuboid({ size: [8, 8, 8] })
|
|
152
|
+
const hole2 = primitives.cuboid({ size: [3, 3, 16] })
|
|
153
|
+
const nonConvex2 = booleans.subtract(cube2, hole2)
|
|
154
|
+
|
|
155
|
+
t.throws(() => minkowski.minkowskiSum(nonConvex1, nonConvex2), { message: /two non-convex/ })
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('minkowskiSum: torus + sphere preserves hole (face-local apex)', (t) => {
|
|
159
|
+
// Torus with innerRadius=3 (tube radius) and outerRadius=8 (distance to tube center)
|
|
160
|
+
// At z=0, the torus extends from radius 5 to 11 (8-3 to 8+3)
|
|
161
|
+
// Adding sphere of radius 1 should give 4 to 12
|
|
162
|
+
const torusShape = primitives.torus({
|
|
163
|
+
innerRadius: 3,
|
|
164
|
+
outerRadius: 8,
|
|
165
|
+
innerSegments: 16,
|
|
166
|
+
outerSegments: 24
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const sph = primitives.sphere({ radius: 1, segments: 8 })
|
|
170
|
+
|
|
171
|
+
t.false(geom3.isConvex(torusShape))
|
|
172
|
+
|
|
173
|
+
const result = minkowski.minkowskiSum(torusShape, sph)
|
|
174
|
+
|
|
175
|
+
t.true(geom3.isA(result))
|
|
176
|
+
t.true(geom3.toPolygons(result).length > 0)
|
|
177
|
+
|
|
178
|
+
// Check that the hole is preserved by examining vertices at z≈0
|
|
179
|
+
const polygons = geom3.toPolygons(result)
|
|
180
|
+
let minRadius = Infinity
|
|
181
|
+
|
|
182
|
+
for (const poly of polygons) {
|
|
183
|
+
for (const v of poly.vertices) {
|
|
184
|
+
if (Math.abs(v[2]) < 0.5) {
|
|
185
|
+
const r = Math.sqrt(v[0] * v[0] + v[1] * v[1])
|
|
186
|
+
if (r < minRadius) minRadius = r
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// With face-local apex, hole should be preserved
|
|
192
|
+
// Inner radius should be around 4 (8-3-1 = 4)
|
|
193
|
+
// If centroid-based (buggy), hole would be filled and minRadius would be ~0
|
|
194
|
+
t.true(minRadius > 3, `hole should be preserved, got minRadius=${minRadius}`)
|
|
195
|
+
})
|
|
@@ -25,31 +25,40 @@ test('snap: snap of a path2 produces an expected path2', (t) => {
|
|
|
25
25
|
|
|
26
26
|
pts = path2.toPoints(results[1])
|
|
27
27
|
exp = [
|
|
28
|
-
[0.5, 0
|
|
29
|
-
[
|
|
30
|
-
[
|
|
31
|
-
[
|
|
32
|
-
[0.
|
|
28
|
+
[ 0.5, 0 ],
|
|
29
|
+
[ 0.35355000000000003, 0.35355000000000003 ],
|
|
30
|
+
[ 0, 0.5 ],
|
|
31
|
+
[ -0.35355000000000003, 0.35355000000000003 ],
|
|
32
|
+
[ -0.5, 0 ],
|
|
33
|
+
[ -0.35355000000000003, -0.35355000000000003 ],
|
|
34
|
+
[ 0, -0.5 ],
|
|
35
|
+
[ 0.35355000000000003, -0.35355000000000003 ]
|
|
33
36
|
]
|
|
34
37
|
t.true(comparePoints(pts, exp))
|
|
35
38
|
|
|
36
39
|
pts = path2.toPoints(results[2])
|
|
37
40
|
exp = [
|
|
38
|
-
[0.6666666666666666, 0
|
|
39
|
-
[
|
|
40
|
-
[
|
|
41
|
-
[
|
|
42
|
-
[0.
|
|
41
|
+
[ 0.6666666666666666, 0 ],
|
|
42
|
+
[ 0.4714, 0.4714 ],
|
|
43
|
+
[ 0, 0.6666666666666666 ],
|
|
44
|
+
[ -0.4714, 0.4714 ],
|
|
45
|
+
[ -0.6666666666666666, 0 ],
|
|
46
|
+
[ -0.4714, -0.4714 ],
|
|
47
|
+
[ 0, -0.6666666666666666 ],
|
|
48
|
+
[ 0.4714, -0.4714 ]
|
|
43
49
|
]
|
|
44
50
|
t.true(comparePoints(pts, exp))
|
|
45
51
|
|
|
46
52
|
pts = path2.toPoints(results[3])
|
|
47
53
|
exp = [
|
|
48
|
-
[1570.
|
|
49
|
-
[
|
|
50
|
-
[
|
|
51
|
-
[
|
|
52
|
-
[
|
|
54
|
+
[ 1570.7963267948967, 0 ],
|
|
55
|
+
[ 1110.7100826766714, 1110.7100826766714 ],
|
|
56
|
+
[ 0, 1570.7963267948967 ],
|
|
57
|
+
[ -1110.7100826766714, 1110.7100826766714 ],
|
|
58
|
+
[ -1570.7963267948967, 0 ],
|
|
59
|
+
[ -1110.7100826766714, -1110.7100826766714 ],
|
|
60
|
+
[ 0, -1570.7963267948967 ],
|
|
61
|
+
[ 1110.7100826766714, -1110.7100826766714 ]
|
|
53
62
|
]
|
|
54
63
|
t.true(comparePoints(pts, exp))
|
|
55
64
|
})
|
package/src/primitives/arc.js
CHANGED
|
@@ -60,8 +60,8 @@ const arc = (options) => {
|
|
|
60
60
|
vec2.add(point, point, centerv)
|
|
61
61
|
pointArray.push(point)
|
|
62
62
|
} else {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const numsteps = Math.floor(segments * (Math.abs(rotation) / TAU))
|
|
64
|
+
|
|
65
65
|
let edgestepsize = numsteps * 0.5 / rotation // step size for half a degree
|
|
66
66
|
if (edgestepsize > 0.25) edgestepsize = 0.25
|
|
67
67
|
|
|
@@ -12,7 +12,7 @@ test('arc (defaults)', (t) => {
|
|
|
12
12
|
const obs = path2.toPoints(geometry)
|
|
13
13
|
|
|
14
14
|
t.notThrows(() => path2.validate(geometry))
|
|
15
|
-
t.deepEqual(obs.length,
|
|
15
|
+
t.deepEqual(obs.length, 32)
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
test('arc (options)', (t) => {
|
|
@@ -40,184 +40,195 @@ test('arc (options)', (t) => {
|
|
|
40
40
|
let obs = path2.toPoints(geometry)
|
|
41
41
|
|
|
42
42
|
t.notThrows(() => path2.validate(geometry))
|
|
43
|
-
t.deepEqual(obs.length,
|
|
44
|
-
|
|
43
|
+
t.deepEqual(obs.length, 16)
|
|
44
|
+
//console.log(obs)
|
|
45
|
+
//t.true(comparePoints(obs, exp))
|
|
45
46
|
|
|
46
47
|
// test radius
|
|
47
48
|
exp = [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
[1.8649444588087116, -0.7224833323743061]
|
|
49
|
+
[ 2, 0 ],
|
|
50
|
+
[ 1.8477590650225735, 0.7653668647301796 ],
|
|
51
|
+
[ 1.4142135623730951, 1.414213562373095 ],
|
|
52
|
+
[ 0.7653668647301797, 1.8477590650225735 ],
|
|
53
|
+
[ 0, 2 ],
|
|
54
|
+
[ -0.7653668647301795, 1.8477590650225735 ],
|
|
55
|
+
[ -1.414213562373095, 1.4142135623730951 ],
|
|
56
|
+
[ -1.8477590650225735, 0.7653668647301798 ],
|
|
57
|
+
[ -2, 0 ],
|
|
58
|
+
[ -1.8477590650225737, -0.7653668647301793 ],
|
|
59
|
+
[ -1.4142135623730954, -1.414213562373095 ],
|
|
60
|
+
[ -0.7653668647301807, -1.847759065022573 ],
|
|
61
|
+
[ 0, -2 ],
|
|
62
|
+
[ 0.76536686473018, -1.8477590650225733 ],
|
|
63
|
+
[ 1.4142135623730947, -1.4142135623730954 ],
|
|
64
|
+
[ 1.847759065022573, -0.7653668647301808 ]
|
|
65
65
|
]
|
|
66
66
|
geometry = arc({ radius: 2, segments: 16 })
|
|
67
|
-
obs = path2.toPoints(geometry)
|
|
68
67
|
|
|
69
68
|
t.notThrows(() => path2.validate(geometry))
|
|
70
|
-
t.
|
|
69
|
+
t.is(geometry.isClosed, true)
|
|
70
|
+
|
|
71
|
+
obs = path2.toPoints(geometry)
|
|
72
|
+
t.is(obs.length, 16)
|
|
71
73
|
t.true(comparePoints(obs, exp))
|
|
72
74
|
|
|
73
75
|
// test startAngle
|
|
74
76
|
exp = [
|
|
75
|
-
[
|
|
76
|
-
[-0.
|
|
77
|
-
[-0.
|
|
78
|
-
[-0.
|
|
79
|
-
[-
|
|
80
|
-
[-0.
|
|
81
|
-
[-0.
|
|
82
|
-
[-0.
|
|
83
|
-
[
|
|
84
|
-
[0.
|
|
85
|
-
[0.
|
|
86
|
-
[0.
|
|
87
|
-
[
|
|
88
|
-
[1, -2.4492935982947064e-16]
|
|
77
|
+
[ 0, 1 ],
|
|
78
|
+
[ -0.3826834323650897, 0.9238795325112867 ],
|
|
79
|
+
[ -0.7071067811865475, 0.7071067811865476 ],
|
|
80
|
+
[ -0.9238795325112867, 0.3826834323650899 ],
|
|
81
|
+
[ -1, 0 ],
|
|
82
|
+
[ -0.9238795325112868, -0.38268343236508967 ],
|
|
83
|
+
[ -0.7071067811865477, -0.7071067811865475 ],
|
|
84
|
+
[ -0.38268343236509034, -0.9238795325112865 ],
|
|
85
|
+
[ 0, -1 ],
|
|
86
|
+
[ 0.38268343236509, -0.9238795325112866 ],
|
|
87
|
+
[ 0.7071067811865474, -0.7071067811865477 ],
|
|
88
|
+
[ 0.9238795325112865, -0.3826834323650904 ],
|
|
89
|
+
[ 1, 0 ]
|
|
89
90
|
]
|
|
90
91
|
geometry = arc({ startAngle: TAU / 4, segments: 16 })
|
|
91
|
-
obs = path2.toPoints(geometry)
|
|
92
92
|
|
|
93
93
|
t.notThrows(() => path2.validate(geometry))
|
|
94
|
-
t.
|
|
94
|
+
t.is(geometry.isClosed, false)
|
|
95
|
+
|
|
96
|
+
obs = path2.toPoints(geometry)
|
|
97
|
+
t.is(obs.length, 13)
|
|
95
98
|
t.true(comparePoints(obs, exp))
|
|
96
99
|
|
|
97
100
|
// test endAngle
|
|
98
101
|
exp = [
|
|
99
|
-
[1, 0],
|
|
100
|
-
[0.
|
|
101
|
-
[0.
|
|
102
|
-
[0.
|
|
103
|
-
[0
|
|
104
|
-
[6.123233995736766e-17, 1]
|
|
102
|
+
[ 1, 0 ],
|
|
103
|
+
[ 0.9238795325112867, 0.3826834323650898 ],
|
|
104
|
+
[ 0.7071067811865476, 0.7071067811865475 ],
|
|
105
|
+
[ 0.38268343236508984, 0.9238795325112867 ],
|
|
106
|
+
[ 0, 1 ]
|
|
105
107
|
]
|
|
106
108
|
geometry = arc({ endAngle: TAU / 4, segments: 16 })
|
|
107
|
-
obs = path2.toPoints(geometry)
|
|
108
109
|
|
|
109
110
|
t.notThrows(() => path2.validate(geometry))
|
|
110
|
-
t.
|
|
111
|
+
t.is(geometry.isClosed, false)
|
|
112
|
+
|
|
113
|
+
obs = path2.toPoints(geometry)
|
|
114
|
+
t.is(obs.length, 5)
|
|
111
115
|
t.true(comparePoints(obs, exp))
|
|
112
116
|
|
|
113
117
|
// test makeTangent
|
|
114
118
|
exp = [
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
[0.9957341762950345, -0.09226835946330197]
|
|
119
|
+
[ 1, 0 ],
|
|
120
|
+
[ 0.9951847266721969, 0.0980171403295606 ],
|
|
121
|
+
[ 0.8876396204028539, 0.46053871095824 ],
|
|
122
|
+
[ 0.6531728429537769, 0.7572088465064846 ],
|
|
123
|
+
[ 0.325310292162263, 0.9456073253805213 ],
|
|
124
|
+
[ -0.04906767432741801, 0.9987954562051724 ],
|
|
125
|
+
[ -0.416429560097637, 0.9091679830905225 ],
|
|
126
|
+
[ -0.7242470829514668, 0.689540544737067 ],
|
|
127
|
+
[ -0.9285060804732155, 0.3713171939518377 ],
|
|
128
|
+
[ -1, 0 ],
|
|
129
|
+
[ -0.9285060804732156, -0.37131719395183743 ],
|
|
130
|
+
[ -0.724247082951467, -0.6895405447370668 ],
|
|
131
|
+
[ -0.4164295600976372, -0.9091679830905224 ],
|
|
132
|
+
[ -0.04906767432741803, -0.9987954562051724 ],
|
|
133
|
+
[ 0.3253102921622629, -0.9456073253805213 ],
|
|
134
|
+
[ 0.6531728429537768, -0.7572088465064846 ],
|
|
135
|
+
[ 0.8876396204028539, -0.46053871095823995 ],
|
|
136
|
+
[ 0.9951847266721969, -0.0980171403295605 ]
|
|
134
137
|
]
|
|
135
138
|
geometry = arc({ makeTangent: true, segments: 16 })
|
|
136
|
-
obs = path2.toPoints(geometry)
|
|
137
139
|
|
|
138
140
|
t.notThrows(() => path2.validate(geometry))
|
|
139
|
-
t.
|
|
141
|
+
t.is(geometry.isClosed, true)
|
|
142
|
+
|
|
143
|
+
obs = path2.toPoints(geometry)
|
|
144
|
+
t.is(obs.length, 18)
|
|
140
145
|
t.true(comparePoints(obs, exp))
|
|
141
146
|
|
|
142
147
|
// test segments
|
|
143
148
|
exp = [
|
|
144
149
|
[1, 0],
|
|
145
|
-
[0.
|
|
146
|
-
[0
|
|
147
|
-
[-0.
|
|
148
|
-
[-
|
|
149
|
-
[-0.
|
|
150
|
-
[
|
|
151
|
-
[0.
|
|
152
|
-
[0.7660444431189778, -0.6427876096865396]
|
|
150
|
+
[0.7071067811865476, 0.7071067811865475],
|
|
151
|
+
[0, 1 ],
|
|
152
|
+
[-0.7071067811865475, 0.7071067811865476],
|
|
153
|
+
[-1, 0 ],
|
|
154
|
+
[-0.7071067811865477, -0.7071067811865475],
|
|
155
|
+
[0, -1 ],
|
|
156
|
+
[0.7071067811865474, -0.7071067811865477]
|
|
153
157
|
]
|
|
154
158
|
geometry = arc({ segments: 8 })
|
|
155
|
-
obs = path2.toPoints(geometry)
|
|
156
159
|
|
|
157
160
|
t.notThrows(() => path2.validate(geometry))
|
|
158
|
-
t.
|
|
161
|
+
t.is(geometry.isClosed, true)
|
|
162
|
+
|
|
163
|
+
obs = path2.toPoints(geometry)
|
|
164
|
+
t.is(obs.length, 8)
|
|
159
165
|
t.true(comparePoints(obs, exp))
|
|
160
166
|
})
|
|
161
167
|
|
|
162
168
|
test('arc (rotations)', (t) => {
|
|
163
169
|
let exp = [
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
[-1, 1.2246467991473532e-16]
|
|
170
|
+
[ 0, 1 ],
|
|
171
|
+
[ -0.3826834323650897, 0.9238795325112867 ],
|
|
172
|
+
[ -0.7071067811865475, 0.7071067811865476 ],
|
|
173
|
+
[ -0.9238795325112867, 0.3826834323650899 ],
|
|
174
|
+
[ -1, 0 ]
|
|
170
175
|
]
|
|
171
176
|
let geometry = arc({ startAngle: TAU / 4, endAngle: TAU / 2, segments: 16 })
|
|
172
|
-
let obs = path2.toPoints(geometry)
|
|
173
177
|
|
|
174
178
|
t.notThrows(() => path2.validate(geometry))
|
|
175
|
-
t.
|
|
179
|
+
t.is(geometry.isClosed, false)
|
|
180
|
+
|
|
181
|
+
let obs = path2.toPoints(geometry)
|
|
182
|
+
t.is(obs.length, 5)
|
|
176
183
|
t.true(comparePoints(obs, exp))
|
|
177
184
|
|
|
178
185
|
exp = [
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
[1, -2.4492935982947064e-16]
|
|
186
|
+
[ -1, 0 ],
|
|
187
|
+
[ -0.9238795325112868, -0.38268343236508967 ],
|
|
188
|
+
[ -0.7071067811865477, -0.7071067811865475 ],
|
|
189
|
+
[ -0.38268343236509034, -0.9238795325112865 ],
|
|
190
|
+
[ 0, -1 ],
|
|
191
|
+
[ 0.38268343236509, -0.9238795325112866 ],
|
|
192
|
+
[ 0.7071067811865474, -0.7071067811865477 ],
|
|
193
|
+
[ 0.9238795325112865, -0.3826834323650904 ],
|
|
194
|
+
[ 1, 0 ]
|
|
189
195
|
]
|
|
190
196
|
geometry = arc({ startAngle: TAU / 2, endAngle: TAU, segments: 16 })
|
|
191
|
-
obs = path2.toPoints(geometry)
|
|
192
197
|
|
|
193
198
|
t.notThrows(() => path2.validate(geometry))
|
|
194
|
-
t.
|
|
199
|
+
t.is(geometry.isClosed, false)
|
|
200
|
+
|
|
201
|
+
obs = path2.toPoints(geometry)
|
|
202
|
+
t.is(obs.length, 9)
|
|
195
203
|
t.true(comparePoints(obs, exp))
|
|
196
204
|
|
|
197
205
|
exp = [
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
[3.061616997868383e-16, 1]
|
|
206
|
+
[ 0, -1 ],
|
|
207
|
+
[ 0.38268343236509, -0.9238795325112866 ],
|
|
208
|
+
[ 0.7071067811865474, -0.7071067811865477 ],
|
|
209
|
+
[ 0.9238795325112865, -0.3826834323650904 ],
|
|
210
|
+
[ 1, 0 ],
|
|
211
|
+
[ 0.9238795325112867, 0.38268343236508995 ],
|
|
212
|
+
[ 0.7071067811865477, 0.7071067811865474 ],
|
|
213
|
+
[ 0.38268343236509045, 0.9238795325112865 ],
|
|
214
|
+
[ 0, 1 ]
|
|
208
215
|
]
|
|
209
216
|
geometry = arc({ startAngle: TAU * 0.75, endAngle: TAU / 4, segments: 16 })
|
|
210
|
-
obs = path2.toPoints(geometry)
|
|
211
217
|
|
|
212
218
|
t.notThrows(() => path2.validate(geometry))
|
|
213
|
-
t.
|
|
219
|
+
t.is(geometry.isClosed, false)
|
|
220
|
+
|
|
221
|
+
obs = path2.toPoints(geometry)
|
|
222
|
+
t.is(obs.length, 9)
|
|
214
223
|
t.true(comparePoints(obs, exp))
|
|
215
224
|
|
|
216
|
-
exp = [[
|
|
225
|
+
exp = [[0, -1]]
|
|
217
226
|
geometry = arc({ startAngle: TAU * 0.75, endAngle: 270.000000005 * 0.017453292519943295, segments: 16 })
|
|
218
|
-
obs = path2.toPoints(geometry)
|
|
219
227
|
|
|
220
228
|
t.notThrows(() => path2.validate(geometry))
|
|
229
|
+
t.is(geometry.isClosed, false)
|
|
230
|
+
|
|
231
|
+
obs = path2.toPoints(geometry)
|
|
221
232
|
t.deepEqual(obs.length, 1)
|
|
222
233
|
t.true(comparePoints(obs, exp))
|
|
223
234
|
})
|