@flighthq/path-boolean 0.5.1-next.3302.76ece6b → 0.5.1-next.4304.322dc96

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.
Files changed (66) hide show
  1. package/README.md +2 -2
  2. package/dist/.tsbuildinfo.contract +1 -0
  3. package/dist/.tsbuildinfo.runtime +1 -0
  4. package/dist/booleanPaths.d.ts +33 -6
  5. package/dist/booleanPaths.d.ts.map +1 -1
  6. package/dist/booleanPaths.js +42 -3
  7. package/dist/booleanPaths.js.map +1 -1
  8. package/dist/contract.d.ts +12 -8
  9. package/dist/contract.d.ts.map +1 -1
  10. package/dist/contract.js +11 -8
  11. package/dist/contract.js.map +1 -1
  12. package/dist/contract.path-boolean-types.d.ts +4 -0
  13. package/dist/contract.path-boolean-types.d.ts.map +1 -0
  14. package/dist/contract.path-boolean-types.js +2 -0
  15. package/dist/contract.path-boolean-types.js.map +1 -0
  16. package/dist/explainPathBoolean.d.ts +15 -3
  17. package/dist/explainPathBoolean.d.ts.map +1 -1
  18. package/dist/explainPathBoolean.js +35 -9
  19. package/dist/explainPathBoolean.js.map +1 -1
  20. package/dist/index.d.ts +3 -8
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +3 -8
  23. package/dist/index.js.map +1 -1
  24. package/dist/martinezKernel.d.ts +44 -4
  25. package/dist/martinezKernel.d.ts.map +1 -1
  26. package/dist/martinezKernel.js +777 -559
  27. package/dist/martinezKernel.js.map +1 -1
  28. package/dist/offsetPath.d.ts +15 -2
  29. package/dist/offsetPath.d.ts.map +1 -1
  30. package/dist/offsetPath.js +205 -215
  31. package/dist/offsetPath.js.map +1 -1
  32. package/dist/pathBoolean.d.ts +53 -0
  33. package/dist/pathBoolean.d.ts.map +1 -0
  34. package/dist/pathBoolean.js +115 -0
  35. package/dist/pathBoolean.js.map +1 -0
  36. package/dist/pathBooleanCompat.d.ts +3 -0
  37. package/dist/pathBooleanCompat.d.ts.map +1 -0
  38. package/dist/pathBooleanCompat.js +2 -0
  39. package/dist/pathBooleanCompat.js.map +1 -0
  40. package/dist/resolvePathRegions.d.ts +8 -2
  41. package/dist/resolvePathRegions.d.ts.map +1 -1
  42. package/dist/resolvePathRegions.js +11 -3
  43. package/dist/resolvePathRegions.js.map +1 -1
  44. package/dist/simplifyPath.d.ts +9 -2
  45. package/dist/simplifyPath.d.ts.map +1 -1
  46. package/dist/simplifyPath.js +12 -2
  47. package/dist/simplifyPath.js.map +1 -1
  48. package/dist/unionAllPaths.d.ts +9 -2
  49. package/dist/unionAllPaths.d.ts.map +1 -1
  50. package/dist/unionAllPaths.js +23 -8
  51. package/dist/unionAllPaths.js.map +1 -1
  52. package/dist/writePathBooleanContours.d.ts +8 -2
  53. package/dist/writePathBooleanContours.d.ts.map +1 -1
  54. package/dist/writePathBooleanContours.js +16 -4
  55. package/dist/writePathBooleanContours.js.map +1 -1
  56. package/package.json +5 -4
  57. package/src/booleanPaths.test.ts +237 -114
  58. package/src/explainPathBoolean.test.ts +35 -21
  59. package/src/fuzzInvariants.test.ts +241 -154
  60. package/src/martinezKernel.test.ts +334 -328
  61. package/src/offsetPath.test.ts +296 -166
  62. package/src/pathBoolean.test.ts +95 -0
  63. package/src/resolvePathRegions.test.ts +41 -30
  64. package/src/simplifyPath.test.ts +143 -112
  65. package/src/unionAllPaths.test.ts +93 -61
  66. package/src/writePathBooleanContours.test.ts +44 -6
@@ -1,226 +1,313 @@
1
+ // Generated from crates/flight-path-boolean/tests/fuzz_invariants_test.rs by flight-compiler.
2
+ // Rust is authoritative; run `npm run generate` after changing it.
3
+
1
4
  import { appendPathClose, appendPathLineTo, appendPathMoveTo, createPath, flattenPath } from '@flighthq/path/contract';
2
- import type { Path } from '@flighthq/types/contract';
5
+ import type { Path } from '@flighthq/path/contract';
3
6
  import { describe, expect, it } from 'vitest';
4
7
 
5
- import { differencePaths, unionPaths } from './booleanPaths.ts';
6
- import { martinezPathBooleanKernel } from './martinezKernel.ts';
7
- import { offsetPath } from './offsetPath.ts';
8
- import { simplifyPath } from './simplifyPath.ts';
9
- import { unionAllPaths } from './unionAllPaths.ts';
10
-
11
- const kernel = martinezPathBooleanKernel;
12
-
13
- function makeRandom(seed: number): () => number {
14
- let state = seed >>> 0 || 1;
15
- return () => {
16
- state ^= state << 13;
17
- state >>>= 0;
18
- state ^= state >>> 17;
19
- state ^= state << 5;
20
- state >>>= 0;
21
- return state / 4294967296;
22
- };
8
+ import {
9
+ createMartinezPathBooleanKernel,
10
+ differencePaths,
11
+ offsetPath,
12
+ simplifyPath,
13
+ unionAllPaths,
14
+ unionPaths,
15
+ } from './contract.ts';
16
+ import type { PathBooleanOptions, PathOffsetOptions } from './contract.ts';
17
+
18
+ interface FuzzRandom {
19
+ state: number;
23
20
  }
24
21
 
25
- function polygonPath(vertices: readonly number[]): Path {
26
- const path = createPath('nonZero');
22
+ function flattenedPath(path: Readonly<Path>, tolerance: number | undefined): number[][] {
23
+ const out = flattenPath(path, tolerance);
24
+ return out;
25
+ }
26
+
27
+ function makeRandom(seed: number): FuzzRandom {
28
+ return { state: seed !== 0 ? seed : 1 };
29
+ }
30
+
31
+ function nextRandom(random: FuzzRandom): number {
32
+ let state: number = random.state;
33
+ state = (state ^ (state << 13)) >>> 0;
34
+ state = (state ^ (state >>> 17)) >>> 0;
35
+ state = (state ^ (state << 5)) >>> 0;
36
+ random.state = state;
37
+ return state / 4294967296.0;
38
+ }
39
+
40
+ function polygonPath(vertices: Readonly<number[]>): Path {
41
+ let path = createPath('nonZero');
27
42
  appendPathMoveTo(path, vertices[0], vertices[1]);
28
- for (let i = 2; i < vertices.length; i += 2) appendPathLineTo(path, vertices[i], vertices[i + 1]);
43
+ let i = 2;
44
+ while (i < vertices.length) {
45
+ appendPathLineTo(path, vertices[i], vertices[i + 1]);
46
+ i += 2;
47
+ }
29
48
  appendPathClose(path);
30
49
  return path;
31
50
  }
32
51
 
33
- function randomSimplePolygon(random: () => number, count: number): number[] {
34
- const angles: number[] = [];
35
- for (let i = 0; i < count; i++) angles.push(random() * Math.PI * 2);
36
- angles.sort((a, b) => a - b);
37
- const vertices: number[] = [];
52
+ function randomSimplePolygon(random: FuzzRandom, count: number): number[] {
53
+ let angles: number[] = [] as never[];
54
+ for (let _ = 0; _ < count; _ += 1) {
55
+ angles.push(nextRandom(random) * Math.PI * 2.0);
56
+ }
57
+ angles.sort((a, b) => a - b!);
58
+ let vertices: number[] = [] as never[];
38
59
  for (const angle of angles) {
39
- const radius = 20 + random() * 30;
40
- vertices.push(50 + radius * Math.cos(angle), 50 + radius * Math.sin(angle));
60
+ const radius = 20.0 + nextRandom(random) * 30.0;
61
+ vertices.push(50.0 + radius * Math.cos(angle));
62
+ vertices.push(50.0 + radius * Math.sin(angle));
41
63
  }
42
64
  return vertices;
43
65
  }
44
66
 
45
- function randomConvexPolygon(random: () => number, count: number): number[] {
46
- const points: [number, number][] = [];
47
- for (let i = 0; i < count; i++) points.push([10 + random() * 80, 10 + random() * 80]);
67
+ function randomConvexPolygon(random: FuzzRandom, count: number): number[] {
68
+ let points: Float64Array[] = [] as never[];
69
+ for (let _ = 0; _ < count; _ += 1) {
70
+ points.push(new Float64Array([10.0 + nextRandom(random) * 80.0, 10.0 + nextRandom(random) * 80.0]));
71
+ }
48
72
  return convexHull(points);
49
73
  }
50
74
 
51
- function randomMessyPolygon(random: () => number, count: number): number[] {
52
- const vertices: number[] = [];
53
- for (let i = 0; i < count; i++) vertices.push(random() * 100, random() * 100);
75
+ function randomMessyPolygon(random: FuzzRandom, count: number): number[] {
76
+ let vertices: number[] = [] as never[];
77
+ for (let _ = 0; _ < count; _ += 1) {
78
+ vertices.push(nextRandom(random) * 100.0);
79
+ vertices.push(nextRandom(random) * 100.0);
80
+ }
54
81
  return vertices;
55
82
  }
56
83
 
57
- function convexHull(points: readonly (readonly [number, number])[]): number[] {
58
- const sorted = points.slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]);
59
- const cross = (o: readonly number[], a: readonly number[], b: readonly number[]): number =>
60
- (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
61
- const lower: (readonly number[])[] = [];
84
+ function hullCross(o: Readonly<Float64Array>, a: Readonly<Float64Array>, b: Readonly<Float64Array>): number {
85
+ return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
86
+ }
87
+
88
+ function convexHull(points: Readonly<Float64Array[]>): number[] {
89
+ let sorted = [...points];
90
+ sorted.sort((a, b) => {
91
+ if (a[0] !== b[0]) {
92
+ return a[0] - b[0]!;
93
+ }
94
+ return a[1] - b[1]!;
95
+ });
96
+ let lower: Float64Array[] = [] as never[];
97
+ let lowerLength: number = 0;
62
98
  for (const p of sorted) {
63
- while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) lower.pop();
64
- lower.push(p);
99
+ while (lowerLength >= 2 && hullCross(lower[lowerLength - 2], lower[lowerLength - 1], p) <= 0.0) {
100
+ lowerLength -= 1;
101
+ }
102
+ if (lowerLength < lower.length) {
103
+ lower[lowerLength] = p;
104
+ } else {
105
+ lower.push(p);
106
+ }
107
+ lowerLength += 1;
65
108
  }
66
- const upper: (readonly number[])[] = [];
67
- for (let i = sorted.length - 1; i >= 0; i--) {
109
+ let upper: Float64Array[] = [] as never[];
110
+ let upperLength: number = 0;
111
+ let i = sorted.length;
112
+ while (i > 0) {
113
+ i -= 1;
68
114
  const p = sorted[i];
69
- while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) upper.pop();
70
- upper.push(p);
115
+ while (upperLength >= 2 && hullCross(upper[upperLength - 2], upper[upperLength - 1], p) <= 0.0) {
116
+ upperLength -= 1;
117
+ }
118
+ if (upperLength < upper.length) {
119
+ upper[upperLength] = p;
120
+ } else {
121
+ upper.push(p);
122
+ }
123
+ upperLength += 1;
124
+ }
125
+ let flat: number[] = [] as never[];
126
+ for (let index = 0; index < lowerLength - 1; index += 1) {
127
+ flat.push(lower[index][0]);
128
+ flat.push(lower[index][1]);
129
+ }
130
+ for (let index = 0; index < upperLength - 1; index += 1) {
131
+ flat.push(upper[index][0]);
132
+ flat.push(upper[index][1]);
71
133
  }
72
- lower.pop();
73
- upper.pop();
74
- const hull = lower.concat(upper);
75
- const flat: number[] = [];
76
- for (const p of hull) flat.push(p[0], p[1]);
77
134
  return flat;
78
135
  }
79
136
 
80
137
  function pathArea(path: Readonly<Path>): number {
81
- let total = 0;
82
- for (const ring of flattenPath(path)) {
83
- let area = 0;
138
+ let total: number = 0.0;
139
+ for (const ring of flattenedPath(path, 0.25)) {
140
+ let area: number = 0.0;
84
141
  const n = ring.length >> 1;
85
- for (let i = 0, j = n - 1; i < n; j = i++) area += ring[j * 2] * ring[i * 2 + 1] - ring[i * 2] * ring[j * 2 + 1];
86
- total += area / 2;
142
+ let j = n - 1;
143
+ for (let i = 0; i < n; i += 1) {
144
+ area += ring[j * 2] * ring[i * 2 + 1] - ring[i * 2] * ring[j * 2 + 1];
145
+ j = i;
146
+ }
147
+ total += area / 2.0;
87
148
  }
88
149
  return Math.abs(total);
89
150
  }
90
151
 
91
152
  function ringCount(path: Readonly<Path>): number {
92
- return flattenPath(path).length;
153
+ return flattenedPath(path, 0.25).length;
93
154
  }
94
155
 
95
- function areasClose(a: number, b: number, relative = 1e-3, absolute = 1e-3): boolean {
156
+ function areasClose(a: number, b: number, relative: number, absolute: number): boolean {
96
157
  return Math.abs(a - b) <= absolute + relative * Math.max(Math.abs(a), Math.abs(b));
97
158
  }
98
159
 
99
- describe('fuzz invariants', () => {
100
- it('same-winding self-overlap diverges between nonZero and evenOdd', () => {
101
- const random = makeRandom(0x51a3e00d);
102
- for (let iteration = 0; iteration < 40; iteration++) {
160
+ describe('fuzzInvariants', () => {
161
+ it('same winding self overlap diverges between non zero and even odd', () => {
162
+ const kernel = createMartinezPathBooleanKernel();
163
+ let random = makeRandom(0x51a3e00d);
164
+ for (let iteration = 0; iteration < 40; iteration += 1) {
103
165
  const vertices = randomConvexPolygon(random, 5 + (iteration % 8));
104
- if (vertices.length < 6) continue;
105
- const path = createPath('nonZero');
106
- appendPathMoveTo(path, vertices[0], vertices[1]);
107
- for (let repeat = 0; repeat < 2; repeat++) {
108
- const start = repeat === 0 ? 2 : 0;
109
- for (let i = start; i < vertices.length; i += 2) appendPathLineTo(path, vertices[i], vertices[i + 1]);
166
+ if (!(vertices.length < 6)) {
167
+ let path = createPath('nonZero');
168
+ appendPathMoveTo(path, vertices[0], vertices[1]);
169
+ for (let repeat = 0; repeat < 2; repeat += 1) {
170
+ const start = repeat === 0 ? 2 : 0;
171
+ let i = start;
172
+ while (i < vertices.length) {
173
+ appendPathLineTo(path, vertices[i], vertices[i + 1]);
174
+ i += 2;
175
+ }
176
+ }
177
+ appendPathClose(path);
178
+ const nzOpts: PathBooleanOptions = { fillRule: 'nonZero', tolerance: undefined } as const;
179
+ const eoOpts: PathBooleanOptions = { fillRule: 'evenOdd', tolerance: undefined } as const;
180
+ let nonZero = createPath('nonZero');
181
+ simplifyPath(nonZero, kernel, path, nzOpts);
182
+ let evenOdd = createPath('nonZero');
183
+ simplifyPath(evenOdd, kernel, path, eoOpts);
184
+ expect(pathArea(nonZero) > 0.0).toBe(true);
185
+ expect(pathArea(evenOdd) < 1e-6).toBe(true);
110
186
  }
111
- appendPathClose(path);
112
- const nonZero = createPath();
113
- const evenOdd = createPath();
114
- simplifyPath(nonZero, kernel, path, { fillRule: 'nonZero' });
115
- simplifyPath(evenOdd, kernel, path, { fillRule: 'evenOdd' });
116
- expect(pathArea(nonZero)).toBeGreaterThan(0);
117
- expect(pathArea(evenOdd)).toBeCloseTo(0, 6);
118
187
  }
119
188
  });
120
-
121
- it('union is commutative: A ∪ B has the same area and ring count as B ∪ A', () => {
122
- const random = makeRandom(0x1234abcd);
123
- for (let iteration = 0; iteration < 40; iteration++) {
189
+ it('union is commutative', () => {
190
+ const kernel = createMartinezPathBooleanKernel();
191
+ let random = makeRandom(0x1234abcd);
192
+ for (let iteration = 0; iteration < 40; iteration += 1) {
124
193
  const a = polygonPath(randomSimplePolygon(random, 3 + (iteration % 6)));
125
194
  const b = polygonPath(randomSimplePolygon(random, 3 + ((iteration + 3) % 6)));
126
- const ab = createPath();
127
- const ba = createPath();
128
- unionPaths(ab, kernel, a, b);
129
- unionPaths(ba, kernel, b, a);
130
- expect(areasClose(pathArea(ab), pathArea(ba))).toBe(true);
131
- expect(ringCount(ab)).toBe(ringCount(ba));
195
+ let ab = createPath('nonZero');
196
+ unionPaths(ab, kernel, a, b, undefined);
197
+ let ba = createPath('nonZero');
198
+ unionPaths(ba, kernel, b, a, undefined);
199
+ expect(areasClose(pathArea(ab), pathArea(ba), 1e-3, 1e-3)).toBe(true);
200
+ expect(ringCount(ab)).toEqual(ringCount(ba));
132
201
  }
133
202
  });
134
-
135
- it('self-difference is empty: A ∖ A == ∅', () => {
136
- const random = makeRandom(0x55aa33cc);
137
- for (let iteration = 0; iteration < 40; iteration++) {
203
+ it('self difference is empty', () => {
204
+ const kernel = createMartinezPathBooleanKernel();
205
+ let random = makeRandom(0x55aa33cc);
206
+ for (let iteration = 0; iteration < 40; iteration += 1) {
138
207
  const a = polygonPath(randomMessyPolygon(random, 4 + (iteration % 5)));
139
- const result = createPath();
140
- differencePaths(result, kernel, a, a);
141
- expect(pathArea(result)).toBeCloseTo(0, 6);
208
+ let result = createPath('nonZero');
209
+ differencePaths(result, kernel, a, a, undefined);
210
+ expect(pathArea(result) < 1e-6).toBe(true);
142
211
  }
143
212
  });
144
-
145
- it('self-union equals simplify: A ∪ A == simplifyPath(A)', () => {
146
- const random = makeRandom(0x0f0f0f0f);
147
- for (let iteration = 0; iteration < 40; iteration++) {
213
+ it('self union equals simplify', () => {
214
+ const kernel = createMartinezPathBooleanKernel();
215
+ let random = makeRandom(0x0f0f0f0f);
216
+ for (let iteration = 0; iteration < 40; iteration += 1) {
148
217
  const a = polygonPath(randomSimplePolygon(random, 3 + (iteration % 6)));
149
- const union = createPath();
150
- const simplified = createPath();
151
- unionPaths(union, kernel, a, a);
152
- simplifyPath(simplified, kernel, a);
153
- expect(areasClose(pathArea(union), pathArea(simplified))).toBe(true);
154
- expect(ringCount(union)).toBe(ringCount(simplified));
218
+ let unionResult = createPath('nonZero');
219
+ unionPaths(unionResult, kernel, a, a, undefined);
220
+ let simplified = createPath('nonZero');
221
+ simplifyPath(simplified, kernel, a, undefined);
222
+ expect(areasClose(pathArea(unionResult), pathArea(simplified), 1e-3, 1e-3)).toBe(true);
223
+ expect(ringCount(unionResult)).toEqual(ringCount(simplified));
155
224
  }
156
225
  });
157
-
158
- it('simplify is idempotent: simplify(simplify(A)) == simplify(A)', () => {
159
- const random = makeRandom(0x7e577e57);
160
- for (let iteration = 0; iteration < 40; iteration++) {
226
+ it('simplify is idempotent', () => {
227
+ const kernel = createMartinezPathBooleanKernel();
228
+ let random = makeRandom(0x7e577e57);
229
+ for (let iteration = 0; iteration < 40; iteration += 1) {
161
230
  const a = polygonPath(randomMessyPolygon(random, 4 + (iteration % 6)));
162
- const once = createPath();
163
- const twice = createPath();
164
- simplifyPath(once, kernel, a);
165
- simplifyPath(twice, kernel, once);
166
- expect(areasClose(pathArea(once), pathArea(twice))).toBe(true);
167
- expect(ringCount(once)).toBe(ringCount(twice));
231
+ let once = createPath('nonZero');
232
+ simplifyPath(once, kernel, a, undefined);
233
+ let twice = createPath('nonZero');
234
+ simplifyPath(twice, kernel, once, undefined);
235
+ expect(areasClose(pathArea(once), pathArea(twice), 1e-3, 1e-3)).toBe(true);
236
+ expect(ringCount(once)).toEqual(ringCount(twice));
168
237
  }
169
238
  });
170
-
171
- it('unionAllPaths of one path equals its simplification', () => {
172
- const random = makeRandom(0x13571357);
173
- for (let iteration = 0; iteration < 40; iteration++) {
239
+ it('union all paths of one path equals its simplification', () => {
240
+ const kernel = createMartinezPathBooleanKernel();
241
+ let random = makeRandom(0x13571357);
242
+ for (let iteration = 0; iteration < 40; iteration += 1) {
174
243
  const a = polygonPath(randomMessyPolygon(random, 4 + (iteration % 6)));
175
- const union = createPath();
176
- const simplified = createPath();
177
- unionAllPaths(union, kernel, [a]);
178
- simplifyPath(simplified, kernel, a);
179
- expect(areasClose(pathArea(union), pathArea(simplified))).toBe(true);
180
- expect(ringCount(union)).toBe(ringCount(simplified));
244
+ let unionResult = createPath('nonZero');
245
+ unionAllPaths(unionResult, kernel, [a], undefined);
246
+ let simplified = createPath('nonZero');
247
+ simplifyPath(simplified, kernel, a, undefined);
248
+ expect(areasClose(pathArea(unionResult), pathArea(simplified), 1e-3, 1e-3)).toBe(true);
249
+ expect(ringCount(unionResult)).toEqual(ringCount(simplified));
181
250
  }
182
251
  });
183
-
184
- it('offset identity: offsetPath(A, 0) ≈ A', () => {
185
- const random = makeRandom(0x2468ace0);
186
- for (let iteration = 0; iteration < 40; iteration++) {
252
+ it('offset identity zero delta approximates original', () => {
253
+ const kernel = createMartinezPathBooleanKernel();
254
+ let random = makeRandom(0x2468ace0);
255
+ for (let iteration = 0; iteration < 40; iteration += 1) {
187
256
  const vertices = randomSimplePolygon(random, 3 + (iteration % 6));
188
257
  const a = polygonPath(vertices);
189
- const zeroOffset = createPath();
190
- offsetPath(zeroOffset, kernel, a, 0);
258
+ let zeroOffset = createPath('nonZero');
259
+ offsetPath(zeroOffset, kernel, a, 0.0, undefined);
191
260
  expect(areasClose(pathArea(zeroOffset), pathArea(a), 1e-2, 1e-2)).toBe(true);
192
- expect(ringCount(zeroOffset)).toBe(1);
261
+ expect(ringCount(zeroOffset)).toEqual(1);
193
262
  }
194
263
  });
195
-
196
- it('double-offset stability: offset +d then −d recovers a convex A within tolerance', () => {
197
- const random = makeRandom(0x0badf00d);
198
- for (let iteration = 0; iteration < 40; iteration++) {
264
+ it('double offset stability recovers convex shape within tolerance', () => {
265
+ const kernel = createMartinezPathBooleanKernel();
266
+ let random = makeRandom(0x0badf00d);
267
+ for (let iteration = 0; iteration < 40; iteration += 1) {
199
268
  const vertices = randomConvexPolygon(random, 5 + (iteration % 8));
200
- if (vertices.length < 8) continue;
201
- const a = polygonPath(vertices);
202
- const baseArea = pathArea(a);
203
- if (baseArea < 50) continue;
204
- const grown = createPath();
205
- const recovered = createPath();
206
- offsetPath(grown, kernel, a, 3, { join: 'miter', miterLimit: 100 });
207
- offsetPath(recovered, kernel, grown, -3, { join: 'miter', miterLimit: 100 });
208
- expect(ringCount(recovered)).toBe(1);
209
- expect(areasClose(pathArea(recovered), baseArea, 3e-2, 1)).toBe(true);
269
+ if (!(vertices.length < 8)) {
270
+ const a = polygonPath(vertices);
271
+ const baseArea = pathArea(a);
272
+ if (!(baseArea < 50.0)) {
273
+ const growOpts: PathOffsetOptions = {
274
+ join: 'miter',
275
+ end: undefined,
276
+ miterLimit: 100.0,
277
+ tolerance: undefined,
278
+ arcTolerance: undefined,
279
+ } as const;
280
+ let grown = createPath('nonZero');
281
+ offsetPath(grown, kernel, a, 3.0, growOpts);
282
+ const shrinkOpts: PathOffsetOptions = {
283
+ join: 'miter',
284
+ end: undefined,
285
+ miterLimit: 100.0,
286
+ tolerance: undefined,
287
+ arcTolerance: undefined,
288
+ } as const;
289
+ let recovered = createPath('nonZero');
290
+ offsetPath(recovered, kernel, grown, -3.0, shrinkOpts);
291
+ expect(ringCount(recovered)).toEqual(1);
292
+ expect(areasClose(pathArea(recovered), baseArea, 3e-2, 1.0)).toBe(true);
293
+ }
294
+ }
210
295
  }
211
296
  });
212
-
213
- it('concave-offset validity: an offset outline has no self-intersections (equals its own simplify)', () => {
214
- const random = makeRandom(0x0c0ffee0);
215
- for (let iteration = 0; iteration < 40; iteration++) {
297
+ it('concave offset validity no self intersections', () => {
298
+ const kernel = createMartinezPathBooleanKernel();
299
+ let random = makeRandom(0x0c0ffee0);
300
+ for (let iteration = 0; iteration < 40; iteration += 1) {
216
301
  const a = polygonPath(randomSimplePolygon(random, 5 + (iteration % 8)));
217
- const offset = createPath();
218
- offsetPath(offset, kernel, a, 4);
219
- if (offset.commands.length === 0) continue;
220
- const simplified = createPath();
221
- simplifyPath(simplified, kernel, offset, { fillRule: 'nonZero' });
222
- expect(areasClose(pathArea(simplified), pathArea(offset), 1e-2, 1e-2)).toBe(true);
223
- expect(ringCount(simplified)).toBe(ringCount(offset));
302
+ let offsetResult = createPath('nonZero');
303
+ offsetPath(offsetResult, kernel, a, 4.0, undefined);
304
+ if (!(offsetResult.commands.length === 0)) {
305
+ const nzOpts: PathBooleanOptions = { fillRule: 'nonZero', tolerance: undefined } as const;
306
+ let simplified = createPath('nonZero');
307
+ simplifyPath(simplified, kernel, offsetResult, nzOpts);
308
+ expect(areasClose(pathArea(simplified), pathArea(offsetResult), 1e-2, 1e-2)).toBe(true);
309
+ expect(ringCount(simplified)).toEqual(ringCount(offsetResult));
310
+ }
224
311
  }
225
312
  });
226
313
  });