@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.
@@ -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], [0.383022221559489, 0.3213938048432696],
29
- [0.08682408883346521, 0.492403876506104], [-0.2499999999999999, 0.43301270189221935],
30
- [-0.46984631039295416, 0.17101007166283444], [-0.4698463103929542, -0.17101007166283433],
31
- [-0.2500000000000002, -0.43301270189221924], [0.08682408883346499, -0.49240387650610407],
32
- [0.3830222215594889, -0.3213938048432698]
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], [0.5106962954126519, 0.4285250731243595],
39
- [0.11576545177795361, 0.6565385020081387], [-0.33333333333333315, 0.5773502691896257],
40
- [-0.6264617471906055, 0.22801342888377923], [-0.6264617471906055, -0.2280134288837791],
41
- [-0.3333333333333336, -0.5773502691896256], [0.1157654517779533, -0.6565385020081387],
42
- [0.5106962954126518, -0.4285250731243597]
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.7979271820118, 0], [1203.3061290889411, 1009.6890116376164],
49
- [272.7710864950155, 1546.9412033856784], [-785.3989635910059, 1360.3552181729126],
50
- [-1476.0772155839566, 537.2521917480618], [-1476.0772155839566, -537.2521917480618],
51
- [-785.3989635910059, -1360.3552181729126], [272.7710864950155, -1546.9412033856784],
52
- [1203.3061290889411, -1009.6890116376164]
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
  })
@@ -60,8 +60,8 @@ const arc = (options) => {
60
60
  vec2.add(point, point, centerv)
61
61
  pointArray.push(point)
62
62
  } else {
63
- // note: add one additional step to acheive full rotation
64
- const numsteps = Math.max(1, Math.floor(segments * (rotation / TAU))) + 1
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, 33)
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, 17)
44
- t.true(comparePoints(obs, exp))
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
- [2, 0],
49
- [1.8649444588087116, 0.7224833323743058],
50
- [1.4780178344413182, 1.3473912872931144],
51
- [0.8914767115530766, 1.7903265827101247],
52
- [0.18453671892660403, 1.991468352590069],
53
- [-0.5473259801441658, 1.923651286345638],
54
- [-1.2052692727585126, 1.5960344545604792],
55
- [-1.7004342714592284, 1.0528643257547114],
56
- [-1.9659461993678036, 0.36749903563314074],
57
- [-1.9659461993678036, -0.36749903563314024],
58
- [-1.7004342714592282, -1.0528643257547117],
59
- [-1.205269272758513, -1.596034454560479],
60
- [-0.5473259801441662, -1.923651286345638],
61
- [0.1845367189266031, -1.9914683525900692],
62
- [0.8914767115530771, -1.7903265827101245],
63
- [1.4780178344413184, -1.3473912872931142],
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.deepEqual(obs.length, 17)
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
- [6.123233995736766e-17, 1],
76
- [-0.3546048870425357, 0.9350162426854148],
77
- [-0.6631226582407953, 0.748510748171101],
78
- [-0.8854560256532098, 0.4647231720437687],
79
- [-0.992708874098054, 0.12053668025532308],
80
- [-0.970941817426052, -0.23931566428755788],
81
- [-0.8229838658936566, -0.5680647467311556],
82
- [-0.5680647467311559, -0.8229838658936564],
83
- [-0.23931566428755774, -0.970941817426052],
84
- [0.1205366802553232, -0.992708874098054],
85
- [0.4647231720437688, -0.8854560256532098],
86
- [0.7485107481711007, -0.6631226582407955],
87
- [0.9350162426854147, -0.35460488704253595],
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.deepEqual(obs.length, 14)
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.9510565162951535, 0.3090169943749474],
101
- [0.8090169943749475, 0.5877852522924731],
102
- [0.5877852522924731, 0.8090169943749475],
103
- [0.30901699437494745, 0.9510565162951535],
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.deepEqual(obs.length, 6)
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
- [1, 0],
116
- [0.9957341762950345, 0.09226835946330199],
117
- [0.8999557329057603, 0.43598128263728936],
118
- [0.6896020498551045, 0.7241885202318785],
119
- [0.391453692861967, 0.9201978082699006],
120
- [0.04346855052920052, 0.9990547958520045],
121
- [-0.31005066355011174, 0.9507200355689026],
122
- [-0.6240966815770753, 0.781347126470996],
123
- [-0.858687650595474, 0.5124992865505523],
124
- [-0.9839573055048071, 0.1784040945262181],
125
- [-0.9839573055048071, -0.1784040945262183],
126
- [-0.8586876505954741, -0.5124992865505521],
127
- [-0.6240966815770755, -0.7813471264709959],
128
- [-0.3100506635501122, -0.9507200355689025],
129
- [0.04346855052920005, -0.9990547958520045],
130
- [0.39145369286196696, -0.9201978082699006],
131
- [0.6896020498551043, -0.7241885202318787],
132
- [0.8999557329057604, -0.435981282637289],
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.deepEqual(obs.length, 19)
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.766044443118978, 0.6427876096865393],
146
- [0.17364817766693041, 0.984807753012208],
147
- [-0.4999999999999998, 0.8660254037844387],
148
- [-0.9396926207859083, 0.3420201433256689],
149
- [-0.9396926207859084, -0.34202014332566866],
150
- [-0.5000000000000004, -0.8660254037844385],
151
- [0.17364817766692997, -0.9848077530122081],
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.deepEqual(obs.length, 9)
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
- [6.123233995736766e-17, 1],
165
- [-0.30901699437494734, 0.9510565162951536],
166
- [-0.587785252292473, 0.8090169943749475],
167
- [-0.8090169943749473, 0.5877852522924732],
168
- [-0.9510565162951535, 0.3090169943749475],
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.deepEqual(obs.length, 6)
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
- [-1, 1.2246467991473532e-16],
180
- [-0.9396926207859084, -0.34202014332566866],
181
- [-0.7660444431189781, -0.6427876096865393],
182
- [-0.5000000000000004, -0.8660254037844385],
183
- [-0.17364817766693033, -0.984807753012208],
184
- [0.17364817766692997, -0.9848077530122081],
185
- [0.49999999999999933, -0.866025403784439],
186
- [0.7660444431189778, -0.6427876096865396],
187
- [0.9396926207859084, -0.3420201433256686],
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.deepEqual(obs.length, 10)
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
- [-1.8369701987210297e-16, -1],
199
- [0.34202014332566816, -0.9396926207859085],
200
- [0.6427876096865393, -0.7660444431189781],
201
- [0.8660254037844384, -0.5000000000000004],
202
- [0.984807753012208, -0.1736481776669304],
203
- [0.9848077530122081, 0.17364817766692991],
204
- [0.866025403784439, 0.4999999999999993],
205
- [0.6427876096865396, 0.7660444431189778],
206
- [0.34202014332566866, 0.9396926207859084],
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.deepEqual(obs.length, 10)
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 = [[-1.8369701987210297e-16, -1]]
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
  })