@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.
- package/README.md +2 -2
- package/dist/.tsbuildinfo.contract +1 -0
- package/dist/.tsbuildinfo.runtime +1 -0
- package/dist/booleanPaths.d.ts +33 -6
- package/dist/booleanPaths.d.ts.map +1 -1
- package/dist/booleanPaths.js +42 -3
- package/dist/booleanPaths.js.map +1 -1
- package/dist/contract.d.ts +12 -8
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +11 -8
- package/dist/contract.js.map +1 -1
- package/dist/contract.path-boolean-types.d.ts +4 -0
- package/dist/contract.path-boolean-types.d.ts.map +1 -0
- package/dist/contract.path-boolean-types.js +2 -0
- package/dist/contract.path-boolean-types.js.map +1 -0
- package/dist/explainPathBoolean.d.ts +15 -3
- package/dist/explainPathBoolean.d.ts.map +1 -1
- package/dist/explainPathBoolean.js +35 -9
- package/dist/explainPathBoolean.js.map +1 -1
- package/dist/index.d.ts +3 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -8
- package/dist/index.js.map +1 -1
- package/dist/martinezKernel.d.ts +44 -4
- package/dist/martinezKernel.d.ts.map +1 -1
- package/dist/martinezKernel.js +777 -559
- package/dist/martinezKernel.js.map +1 -1
- package/dist/offsetPath.d.ts +15 -2
- package/dist/offsetPath.d.ts.map +1 -1
- package/dist/offsetPath.js +205 -215
- package/dist/offsetPath.js.map +1 -1
- package/dist/pathBoolean.d.ts +53 -0
- package/dist/pathBoolean.d.ts.map +1 -0
- package/dist/pathBoolean.js +115 -0
- package/dist/pathBoolean.js.map +1 -0
- package/dist/pathBooleanCompat.d.ts +3 -0
- package/dist/pathBooleanCompat.d.ts.map +1 -0
- package/dist/pathBooleanCompat.js +2 -0
- package/dist/pathBooleanCompat.js.map +1 -0
- package/dist/resolvePathRegions.d.ts +8 -2
- package/dist/resolvePathRegions.d.ts.map +1 -1
- package/dist/resolvePathRegions.js +11 -3
- package/dist/resolvePathRegions.js.map +1 -1
- package/dist/simplifyPath.d.ts +9 -2
- package/dist/simplifyPath.d.ts.map +1 -1
- package/dist/simplifyPath.js +12 -2
- package/dist/simplifyPath.js.map +1 -1
- package/dist/unionAllPaths.d.ts +9 -2
- package/dist/unionAllPaths.d.ts.map +1 -1
- package/dist/unionAllPaths.js +23 -8
- package/dist/unionAllPaths.js.map +1 -1
- package/dist/writePathBooleanContours.d.ts +8 -2
- package/dist/writePathBooleanContours.d.ts.map +1 -1
- package/dist/writePathBooleanContours.js +16 -4
- package/dist/writePathBooleanContours.js.map +1 -1
- package/package.json +5 -4
- package/src/booleanPaths.test.ts +237 -114
- package/src/explainPathBoolean.test.ts +35 -21
- package/src/fuzzInvariants.test.ts +241 -154
- package/src/martinezKernel.test.ts +334 -328
- package/src/offsetPath.test.ts +296 -166
- package/src/pathBoolean.test.ts +95 -0
- package/src/resolvePathRegions.test.ts +41 -30
- package/src/simplifyPath.test.ts +143 -112
- package/src/unionAllPaths.test.ts +93 -61
- 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/
|
|
5
|
+
import type { Path } from '@flighthq/path/contract';
|
|
3
6
|
import { describe, expect, it } from 'vitest';
|
|
4
7
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
26
|
-
const
|
|
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
|
-
|
|
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:
|
|
34
|
-
|
|
35
|
-
for (let
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
40
|
-
vertices.push(50 + radius * Math.cos(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:
|
|
46
|
-
|
|
47
|
-
for (let
|
|
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:
|
|
52
|
-
|
|
53
|
-
for (let
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 (
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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 (
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
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
|
|
153
|
+
return flattenedPath(path, 0.25).length;
|
|
93
154
|
}
|
|
94
155
|
|
|
95
|
-
function areasClose(a: number, b: number, relative
|
|
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('
|
|
100
|
-
it('same
|
|
101
|
-
const
|
|
102
|
-
|
|
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)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
unionPaths(ba, kernel, b, a);
|
|
130
|
-
expect(areasClose(pathArea(ab), pathArea(ba))).toBe(true);
|
|
131
|
-
expect(ringCount(ab)).
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
differencePaths(result, kernel, a, a);
|
|
141
|
-
expect(pathArea(result)).
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
simplifyPath(simplified, kernel, a);
|
|
153
|
-
expect(areasClose(pathArea(
|
|
154
|
-
expect(ringCount(
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
simplifyPath(twice, kernel, once);
|
|
166
|
-
expect(areasClose(pathArea(once), pathArea(twice))).toBe(true);
|
|
167
|
-
expect(ringCount(once)).
|
|
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
|
-
|
|
172
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
simplifyPath(simplified, kernel, a);
|
|
179
|
-
expect(areasClose(pathArea(
|
|
180
|
-
expect(ringCount(
|
|
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
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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)).
|
|
261
|
+
expect(ringCount(zeroOffset)).toEqual(1);
|
|
193
262
|
}
|
|
194
263
|
});
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
214
|
-
|
|
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
|
-
|
|
218
|
-
offsetPath(
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
});
|