pattapatta 0.3.0 → 0.4.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 +6 -0
- package/dist/{circle-BDbKfrdR.d.ts → circle-CauDMv8V.d.ts} +1 -1
- package/dist/circlePacking/index.d.ts +2 -3
- package/dist/construction/index.d.ts +11 -3
- package/dist/construction/index.js +429 -2
- package/dist/construction/index.js.map +1 -1
- package/dist/contour/index.d.ts +3 -4
- package/dist/contour/index.js.map +1 -1
- package/dist/conversion/index.d.ts +2 -3
- package/dist/{group-BNjVVYSQ.d.ts → group-YVmSJuGo.d.ts} +1 -1
- package/dist/hatch/index.d.ts +70 -4
- package/dist/hatch/index.js +414 -3
- package/dist/hatch/index.js.map +1 -1
- package/dist/hull/index.d.ts +1 -2
- package/dist/index.d.ts +12 -13
- package/dist/index.js +1816 -1260
- package/dist/index.js.map +1 -1
- package/dist/meshing/index.d.ts +3 -4
- package/dist/morphology/index.d.ts +2 -3
- package/dist/optimisation/index.d.ts +2 -3
- package/dist/{path-0RjhYjJs.d.ts → path-Boeshk9L.d.ts} +9 -2
- package/dist/pointSet/index.d.ts +15 -2
- package/dist/pointSet/index.js +148 -2
- package/dist/pointSet/index.js.map +1 -1
- package/dist/polygonisation/index.d.ts +1 -2
- package/dist/polygonisation/index.js +54 -50
- package/dist/polygonisation/index.js.map +1 -1
- package/dist/predicates/index.d.ts +2 -3
- package/dist/processing/index.d.ts +3 -4
- package/dist/{segment-BKuIFMKD.d.ts → segment-CpLILSd7.d.ts} +1 -1
- package/dist/segmentSet/index.d.ts +41 -3
- package/dist/segmentSet/index.js +261 -1
- package/dist/segmentSet/index.js.map +1 -1
- package/dist/shapeBoolean/index.d.ts +2 -3
- package/dist/svg/index.d.ts +2 -3
- package/dist/tiling/index.d.ts +10 -4
- package/dist/tiling/index.js +148 -13
- package/dist/tiling/index.js.map +1 -1
- package/dist/transformation/index.d.ts +1 -2
- package/dist/triangulation/index.d.ts +1 -2
- package/dist/triangulation/index.js.map +1 -1
- package/dist/voronoi/index.d.ts +2 -3
- package/package.json +1 -1
- package/dist/vec2-CWyXN1Wj.d.ts +0 -10
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { P as Path } from '../path-
|
|
2
|
-
import { G as Group, c as cloneGroup, g as group } from '../group-
|
|
3
|
-
import '../vec2-CWyXN1Wj.js';
|
|
1
|
+
import { P as Path } from '../path-Boeshk9L.js';
|
|
2
|
+
import { G as Group, c as cloneGroup, g as group } from '../group-YVmSJuGo.js';
|
|
4
3
|
|
|
5
4
|
/** Deep copy of a path. */
|
|
6
5
|
declare function copy(p: Path): Path;
|
package/dist/hatch/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { S as Segment } from '../segment-
|
|
2
|
-
import { P as Path } from '../path-
|
|
3
|
-
import '../
|
|
1
|
+
import { S as Segment } from '../segment-CpLILSd7.js';
|
|
2
|
+
import { P as Path } from '../path-Boeshk9L.js';
|
|
3
|
+
import { SegmentLengthFn } from '../segmentSet/index.js';
|
|
4
4
|
|
|
5
5
|
type ParallelHatchOptions = {
|
|
6
6
|
/** Hatch angle in radians (segment direction). Default `Math.PI / 4`. */
|
|
@@ -23,6 +23,48 @@ type ParallelHatchOptions = {
|
|
|
23
23
|
y: number;
|
|
24
24
|
};
|
|
25
25
|
};
|
|
26
|
+
type WeaveHatchOptions = {
|
|
27
|
+
/** Grid cell size. Default: ~1/12 of the shorter AABB side. */
|
|
28
|
+
cellSize?: number;
|
|
29
|
+
/** Weft (horizontal on-top) run length in cells. Default `1`. */
|
|
30
|
+
A?: number;
|
|
31
|
+
/** Warp (vertical on-top) run length in cells. Default `1`. */
|
|
32
|
+
B?: number;
|
|
33
|
+
/** Row phase shift in cells. Default `1`. */
|
|
34
|
+
C?: number;
|
|
35
|
+
swapColors?: boolean;
|
|
36
|
+
cellFraction?: number;
|
|
37
|
+
extendSingletonsToEdge?: boolean;
|
|
38
|
+
};
|
|
39
|
+
type StochasticHatchOptions = {
|
|
40
|
+
/** Number of segments before clipping. Default `40`. */
|
|
41
|
+
count?: number;
|
|
42
|
+
/** Fixed segment length (overrides min/max). */
|
|
43
|
+
length?: number;
|
|
44
|
+
/** Minimum segment length. Default `1`. */
|
|
45
|
+
minLength?: number;
|
|
46
|
+
/** Maximum segment length (exclusive when ≠ min). Default: min(AABB w,h). */
|
|
47
|
+
maxLength?: number;
|
|
48
|
+
/** PRNG seed. Default `1`. */
|
|
49
|
+
seed?: number;
|
|
50
|
+
};
|
|
51
|
+
type PerpendicularHatchOptions = {
|
|
52
|
+
/** Arc-length spacing between ticks. Default `0.15`. */
|
|
53
|
+
spacing?: number;
|
|
54
|
+
/** Tick length, or a per-sample length function. Default `0.2`. */
|
|
55
|
+
length?: number | SegmentLengthFn;
|
|
56
|
+
/** Fractional phase along each ring in `[0,1)`. Default `0`. */
|
|
57
|
+
startOffset?: number;
|
|
58
|
+
};
|
|
59
|
+
type ConcentricHatchOptions = {
|
|
60
|
+
/** Inward offset step between shells. Default `0.15`. */
|
|
61
|
+
spacing?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Max number of shells. Default: enough to erode until empty
|
|
64
|
+
* (capped by AABB diagonal / spacing).
|
|
65
|
+
*/
|
|
66
|
+
count?: number;
|
|
67
|
+
};
|
|
26
68
|
/**
|
|
27
69
|
* Fill a closed path with parallel hatch strokes (plotter-safe).
|
|
28
70
|
* Composes `parallelSegments` + clip-to-path (PGS dysonHatching recipe).
|
|
@@ -33,9 +75,33 @@ type ParallelHatchOptions = {
|
|
|
33
75
|
declare function parallel(path: Path, options?: ParallelHatchOptions): Segment[];
|
|
34
76
|
/** Cross-hatch: two perpendicular parallel passes. */
|
|
35
77
|
declare function cross(path: Path, options?: ParallelHatchOptions): Segment[];
|
|
78
|
+
/**
|
|
79
|
+
* Weave fill: ABC fabric segments over the path AABB, clipped to the path
|
|
80
|
+
* (PGS `weaveSegments` recipe).
|
|
81
|
+
*/
|
|
82
|
+
declare function weave(path: Path, options?: WeaveHatchOptions): Segment[];
|
|
83
|
+
/**
|
|
84
|
+
* Stochastic fill: random non-intersecting segments in the path AABB, clipped
|
|
85
|
+
* to the path (PGS `stochasticSegments` recipe).
|
|
86
|
+
*/
|
|
87
|
+
declare function stochastic(path: Path, options?: StochasticHatchOptions): Segment[];
|
|
88
|
+
/**
|
|
89
|
+
* Perimeter ticks: perpendicular segments centered on the path boundary
|
|
90
|
+
* (PGS `perpendicularPathSegments`). Outline texture, not an area fill.
|
|
91
|
+
*/
|
|
92
|
+
declare function perpendicular(path: Path, options?: PerpendicularHatchOptions): Segment[];
|
|
93
|
+
/**
|
|
94
|
+
* Concentric shells: repeated inward offsets as closed stroke paths.
|
|
95
|
+
* Plotter-safe nested contours (not SVG paint).
|
|
96
|
+
*/
|
|
97
|
+
declare function concentric(path: Path, options?: ConcentricHatchOptions): Path[];
|
|
36
98
|
declare const hatch: {
|
|
37
99
|
parallel: typeof parallel;
|
|
38
100
|
cross: typeof cross;
|
|
101
|
+
weave: typeof weave;
|
|
102
|
+
stochastic: typeof stochastic;
|
|
103
|
+
perpendicular: typeof perpendicular;
|
|
104
|
+
concentric: typeof concentric;
|
|
39
105
|
};
|
|
40
106
|
|
|
41
|
-
export { type ParallelHatchOptions, cross, hatch, parallel };
|
|
107
|
+
export { type ConcentricHatchOptions, type ParallelHatchOptions, type PerpendicularHatchOptions, type StochasticHatchOptions, type WeaveHatchOptions, concentric, cross, hatch, parallel, perpendicular, stochastic, weave };
|
package/dist/hatch/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FillRule, pointInPolygonD, PointInPolygonResult } from 'clipper2-ts';
|
|
1
|
+
import { FillRule, JoinType, inflatePathsD, EndType, isPositiveD, pointInPolygonD, PointInPolygonResult } from 'clipper2-ts';
|
|
2
2
|
|
|
3
3
|
// src/types/vec2.ts
|
|
4
4
|
function vec2(x, y) {
|
|
@@ -17,6 +17,12 @@ function segment(a, b) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// src/types/path.ts
|
|
20
|
+
function path(rings, closed = true) {
|
|
21
|
+
return {
|
|
22
|
+
rings: rings.map((ring) => ring.map(cloneVec2)),
|
|
23
|
+
closed
|
|
24
|
+
};
|
|
25
|
+
}
|
|
20
26
|
function normalizeRing(ring, eps = 1e-9) {
|
|
21
27
|
if (ring.length < 2) return ring.map(cloneVec2);
|
|
22
28
|
const first = ring[0];
|
|
@@ -26,6 +32,17 @@ function normalizeRing(ring, eps = 1e-9) {
|
|
|
26
32
|
}
|
|
27
33
|
return ring.map(cloneVec2);
|
|
28
34
|
}
|
|
35
|
+
function clonePath(p) {
|
|
36
|
+
return {
|
|
37
|
+
closed: p.closed,
|
|
38
|
+
rings: p.rings.map((ring) => ring.map(cloneVec2))
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/types/group.ts
|
|
43
|
+
function group(paths = []) {
|
|
44
|
+
return { paths: paths.map(clonePath) };
|
|
45
|
+
}
|
|
29
46
|
|
|
30
47
|
// src/segmentSet/index.ts
|
|
31
48
|
function f32(n) {
|
|
@@ -92,6 +109,263 @@ function parallelSegments(centerX, centerY, length, spacing, angle, n) {
|
|
|
92
109
|
}
|
|
93
110
|
return edges;
|
|
94
111
|
}
|
|
112
|
+
function stochasticSegments(width, height, n, minLength, maxLength, seed = 1, originX = 0, originY = 0) {
|
|
113
|
+
const minL = minLength ?? 1;
|
|
114
|
+
const maxL = maxLength ?? Math.min(width, height);
|
|
115
|
+
if (n < 1 || width <= 0 || height <= 0) return [];
|
|
116
|
+
const monoLength = minL === maxL;
|
|
117
|
+
const rng = mulberry32(seed >>> 0);
|
|
118
|
+
const edges = [];
|
|
119
|
+
const maxAttempts = Math.max(1e4, n * 500);
|
|
120
|
+
let attempts = 0;
|
|
121
|
+
while (edges.length < n && attempts < maxAttempts) {
|
|
122
|
+
attempts++;
|
|
123
|
+
const length = monoLength ? minL : minL + rng() * (maxL - minL);
|
|
124
|
+
if (!(length > 0) || length * 2 >= width || length * 2 >= height) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const ax = originX + rng() * (width - length * 2) + length;
|
|
128
|
+
const ay = originY + rng() * (height - length * 2) + length;
|
|
129
|
+
const theta = rng() * Math.PI * 2;
|
|
130
|
+
const bx = ax + Math.cos(theta) * length;
|
|
131
|
+
const by = ay + Math.sin(theta) * length;
|
|
132
|
+
const a = vec2(ax, ay);
|
|
133
|
+
const b = vec2(bx, by);
|
|
134
|
+
let hits = false;
|
|
135
|
+
for (const s of edges) {
|
|
136
|
+
if (segmentsIntersect(a, b, s.a, s.b)) {
|
|
137
|
+
hits = true;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!hits) edges.push(segment(a, b));
|
|
142
|
+
}
|
|
143
|
+
return edges;
|
|
144
|
+
}
|
|
145
|
+
function weaveSegments(width, height, cellSize, A = 1, B = 1, C = 1, options = {}) {
|
|
146
|
+
if (!(cellSize > 0)) {
|
|
147
|
+
throw new Error("weaveSegments: cellSize must be > 0");
|
|
148
|
+
}
|
|
149
|
+
if (A <= 0 || B <= 0) {
|
|
150
|
+
throw new Error("weaveSegments: A and B must be > 0");
|
|
151
|
+
}
|
|
152
|
+
const {
|
|
153
|
+
originX = 0,
|
|
154
|
+
originY = 0,
|
|
155
|
+
swapColors = false,
|
|
156
|
+
cellFraction = 1,
|
|
157
|
+
extendSingletonsToEdge = true
|
|
158
|
+
} = options;
|
|
159
|
+
if (!(cellFraction > 0)) {
|
|
160
|
+
throw new Error("weaveSegments: cellFraction must be > 0");
|
|
161
|
+
}
|
|
162
|
+
const P = A + B;
|
|
163
|
+
const f = Math.max(0, Math.min(1, cellFraction));
|
|
164
|
+
const cols = Math.max(1, Math.floor(width / cellSize));
|
|
165
|
+
const rows = Math.max(1, Math.floor(height / cellSize));
|
|
166
|
+
const gridW = cols * cellSize;
|
|
167
|
+
const gridH = rows * cellSize;
|
|
168
|
+
const dx = originX + (width - gridW) * 0.5;
|
|
169
|
+
const dy = originY + (height - gridH) * 0.5;
|
|
170
|
+
const gridLeft = dx;
|
|
171
|
+
const gridRight = dx + gridW;
|
|
172
|
+
const gridBottom = dy;
|
|
173
|
+
const gridTop = dy + gridH;
|
|
174
|
+
const weft = Array.from(
|
|
175
|
+
{ length: rows },
|
|
176
|
+
() => Array.from({ length: cols }, () => false)
|
|
177
|
+
);
|
|
178
|
+
for (let r = 0; r < rows; r++) {
|
|
179
|
+
const shift = floorMod(r * C, P);
|
|
180
|
+
for (let c = 0; c < cols; c++) {
|
|
181
|
+
const idx = floorMod(c + shift, P);
|
|
182
|
+
let isWeft = idx < A;
|
|
183
|
+
if (swapColors) isWeft = !isWeft;
|
|
184
|
+
weft[r][c] = isWeft;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const segs = [];
|
|
188
|
+
for (let r = 0; r < rows; r++) {
|
|
189
|
+
let c = 0;
|
|
190
|
+
while (c < cols) {
|
|
191
|
+
if (!weft[r][c]) {
|
|
192
|
+
c++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const start = c;
|
|
196
|
+
while (c + 1 < cols && weft[r][c + 1]) c++;
|
|
197
|
+
const end = c;
|
|
198
|
+
const runLen = end - start + 1;
|
|
199
|
+
const y = dy + (r + 0.5) * cellSize;
|
|
200
|
+
let x0 = dx + (start + (1 - f)) * cellSize;
|
|
201
|
+
let x1 = dx + (end + f) * cellSize;
|
|
202
|
+
if (extendSingletonsToEdge && runLen === 1) {
|
|
203
|
+
const cx = dx + (start + 0.5) * cellSize;
|
|
204
|
+
if (start === 0) {
|
|
205
|
+
x0 = gridLeft;
|
|
206
|
+
x1 = cx;
|
|
207
|
+
} else if (end === cols - 1) {
|
|
208
|
+
x0 = cx;
|
|
209
|
+
x1 = gridRight;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
x0 = clamp(x0, gridLeft, gridRight);
|
|
213
|
+
x1 = clamp(x1, gridLeft, gridRight);
|
|
214
|
+
segs.push(segment(vec2(x0, y), vec2(x1, y)));
|
|
215
|
+
c++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
for (let c = 0; c < cols; c++) {
|
|
219
|
+
let r = 0;
|
|
220
|
+
while (r < rows) {
|
|
221
|
+
if (weft[r][c]) {
|
|
222
|
+
r++;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const start = r;
|
|
226
|
+
while (r + 1 < rows && !weft[r + 1][c]) r++;
|
|
227
|
+
const end = r;
|
|
228
|
+
const runLen = end - start + 1;
|
|
229
|
+
const x = dx + (c + 0.5) * cellSize;
|
|
230
|
+
let y0 = dy + (start + (1 - f)) * cellSize;
|
|
231
|
+
let y1 = dy + (end + f) * cellSize;
|
|
232
|
+
if (extendSingletonsToEdge && runLen === 1) {
|
|
233
|
+
const cy = dy + (start + 0.5) * cellSize;
|
|
234
|
+
if (start === 0) {
|
|
235
|
+
y0 = gridBottom;
|
|
236
|
+
y1 = cy;
|
|
237
|
+
} else if (end === rows - 1) {
|
|
238
|
+
y0 = cy;
|
|
239
|
+
y1 = gridTop;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
y0 = clamp(y0, gridBottom, gridTop);
|
|
243
|
+
y1 = clamp(y1, gridBottom, gridTop);
|
|
244
|
+
segs.push(segment(vec2(x, y0), vec2(x, y1)));
|
|
245
|
+
r++;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return segs;
|
|
249
|
+
}
|
|
250
|
+
function perpendicularPathSegments(path2, interSegmentDistance, length, startOffset = 0) {
|
|
251
|
+
if (!(interSegmentDistance > 0)) return [];
|
|
252
|
+
const lengthFn = typeof length === "function" ? length : () => length;
|
|
253
|
+
const startNorm = (startOffset % 1 + 1) % 1;
|
|
254
|
+
const edges = [];
|
|
255
|
+
for (const ring of path2.rings) {
|
|
256
|
+
if (ring.length < 2) continue;
|
|
257
|
+
const closed = path2.closed;
|
|
258
|
+
let pts = ring.map((v) => vec2(v.x, v.y));
|
|
259
|
+
if (closed && shoelace(pts) > 0) {
|
|
260
|
+
pts = pts.slice().reverse();
|
|
261
|
+
}
|
|
262
|
+
const indexed = lengthIndex(pts, closed);
|
|
263
|
+
const end = indexed.total;
|
|
264
|
+
if (!(end > 0)) continue;
|
|
265
|
+
const count = Math.max(1, Math.round(end / interSegmentDistance));
|
|
266
|
+
const increment = 1 / count;
|
|
267
|
+
const eps = Math.max(
|
|
268
|
+
end * 1e-4,
|
|
269
|
+
Math.min(interSegmentDistance * 0.35, end * 0.01)
|
|
270
|
+
);
|
|
271
|
+
for (let i = 0; i < count; i++) {
|
|
272
|
+
const posFrac = (startNorm + i * increment) % 1;
|
|
273
|
+
const idx = posFrac * end;
|
|
274
|
+
const idxM = closed ? wrapIndex(idx - eps, end) : clamp(idx - eps, 0, end);
|
|
275
|
+
const idxP = closed ? wrapIndex(idx + eps, end) : clamp(idx + eps, 0, end);
|
|
276
|
+
const pm = indexed.pointAt(idxM);
|
|
277
|
+
const pp = indexed.pointAt(idxP);
|
|
278
|
+
const pc = indexed.pointAt(idx);
|
|
279
|
+
const tx = pp.x - pm.x;
|
|
280
|
+
const ty = pp.y - pm.y;
|
|
281
|
+
const tlen = Math.hypot(tx, ty);
|
|
282
|
+
if (tlen === 0) continue;
|
|
283
|
+
const nx = -ty / tlen;
|
|
284
|
+
const ny = tx / tlen;
|
|
285
|
+
const normalAngle = Math.atan2(ny, nx);
|
|
286
|
+
const L = lengthFn(pc.x, pc.y, posFrac, normalAngle);
|
|
287
|
+
if (!(L > 0)) continue;
|
|
288
|
+
const half = L * 0.5;
|
|
289
|
+
edges.push(
|
|
290
|
+
segment(
|
|
291
|
+
vec2(pc.x - nx * half, pc.y - ny * half),
|
|
292
|
+
vec2(pc.x + nx * half, pc.y + ny * half)
|
|
293
|
+
)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return edges;
|
|
298
|
+
}
|
|
299
|
+
function floorMod(n, m) {
|
|
300
|
+
return (n % m + m) % m;
|
|
301
|
+
}
|
|
302
|
+
function clamp(v, lo, hi) {
|
|
303
|
+
return Math.max(lo, Math.min(hi, v));
|
|
304
|
+
}
|
|
305
|
+
function wrapIndex(idx, end) {
|
|
306
|
+
if (end <= 0) return 0;
|
|
307
|
+
let x = idx % end;
|
|
308
|
+
if (x < 0) x += end;
|
|
309
|
+
return x;
|
|
310
|
+
}
|
|
311
|
+
function shoelace(ring) {
|
|
312
|
+
let sum = 0;
|
|
313
|
+
for (let i = 0; i < ring.length; i++) {
|
|
314
|
+
const a = ring[i];
|
|
315
|
+
const b = ring[(i + 1) % ring.length];
|
|
316
|
+
sum += a.x * b.y - b.x * a.y;
|
|
317
|
+
}
|
|
318
|
+
return sum / 2;
|
|
319
|
+
}
|
|
320
|
+
function segmentsIntersect(a, b, c, d) {
|
|
321
|
+
return ccw(a, c, d) !== ccw(b, c, d) && ccw(a, b, c) !== ccw(a, b, d);
|
|
322
|
+
}
|
|
323
|
+
function ccw(a, b, c) {
|
|
324
|
+
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
|
|
325
|
+
}
|
|
326
|
+
function mulberry32(seed) {
|
|
327
|
+
return function() {
|
|
328
|
+
let t = seed += 1831565813;
|
|
329
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
330
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
331
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function lengthIndex(ring, closed) {
|
|
335
|
+
const n = ring.length;
|
|
336
|
+
const edgeCount = closed ? n : n - 1;
|
|
337
|
+
const lengths = [];
|
|
338
|
+
let total = 0;
|
|
339
|
+
for (let i = 0; i < edgeCount; i++) {
|
|
340
|
+
const a = ring[i];
|
|
341
|
+
const b = ring[(i + 1) % n];
|
|
342
|
+
const L = Math.hypot(b.x - a.x, b.y - a.y);
|
|
343
|
+
lengths.push(L);
|
|
344
|
+
total += L;
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
total,
|
|
348
|
+
pointAt(dist) {
|
|
349
|
+
let remain = dist;
|
|
350
|
+
if (remain <= 0) return { ...ring[0] };
|
|
351
|
+
if (remain >= total) return { ...ring[closed ? 0 : n - 1] };
|
|
352
|
+
for (let i = 0; i < lengths.length; i++) {
|
|
353
|
+
const L = lengths[i];
|
|
354
|
+
if (remain <= L || i === lengths.length - 1) {
|
|
355
|
+
const t = L < 1e-12 ? 0 : remain / L;
|
|
356
|
+
const a = ring[i];
|
|
357
|
+
const b = ring[(i + 1) % n];
|
|
358
|
+
return {
|
|
359
|
+
x: a.x + (b.x - a.x) * t,
|
|
360
|
+
y: a.y + (b.y - a.y) * t
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
remain -= L;
|
|
364
|
+
}
|
|
365
|
+
return { ...ring[0] };
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
95
369
|
FillRule.NonZero;
|
|
96
370
|
function ringToPathD(ring) {
|
|
97
371
|
const pts = [];
|
|
@@ -100,6 +374,45 @@ function ringToPathD(ring) {
|
|
|
100
374
|
}
|
|
101
375
|
return pts;
|
|
102
376
|
}
|
|
377
|
+
function pathToPathsD(p) {
|
|
378
|
+
if (!p.closed || p.rings.length === 0) {
|
|
379
|
+
throw new Error("Boolean ops require a closed path with at least one ring");
|
|
380
|
+
}
|
|
381
|
+
return p.rings.map((ring) => ringToPathD(ring));
|
|
382
|
+
}
|
|
383
|
+
function pathsDToGroup(paths) {
|
|
384
|
+
if (paths.length === 0) return group([]);
|
|
385
|
+
const outers = [];
|
|
386
|
+
const holes = [];
|
|
387
|
+
for (const pd of paths) {
|
|
388
|
+
const ring = pathDToRing(pd);
|
|
389
|
+
if (ring.length < 3) continue;
|
|
390
|
+
if (isPositiveD(pd)) {
|
|
391
|
+
outers.push({ ring, holes: [] });
|
|
392
|
+
} else {
|
|
393
|
+
holes.push(ring);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
for (const hole of holes) {
|
|
397
|
+
const sample = hole[0];
|
|
398
|
+
if (!sample) continue;
|
|
399
|
+
let assigned = false;
|
|
400
|
+
for (const outer of outers) {
|
|
401
|
+
if (pointInRing(sample, outer.ring)) {
|
|
402
|
+
outer.holes.push(hole);
|
|
403
|
+
assigned = true;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (!assigned) {
|
|
408
|
+
outers.push({ ring: [...hole].reverse(), holes: [] });
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return group(outers.map((o) => path([o.ring, ...o.holes], true)));
|
|
412
|
+
}
|
|
413
|
+
function pathDToRing(pd) {
|
|
414
|
+
return normalizeRing(pd.map((p) => vec2(p.x, p.y)));
|
|
415
|
+
}
|
|
103
416
|
function pointInRing(point, ring) {
|
|
104
417
|
const result = pointInPolygonD(
|
|
105
418
|
{ x: point.x, y: point.y, z: 0 },
|
|
@@ -177,6 +490,12 @@ function segmentIntersection(a, b, c, d) {
|
|
|
177
490
|
p: vec2(a.x + t * rX, a.y + t * rY)
|
|
178
491
|
};
|
|
179
492
|
}
|
|
493
|
+
function buffer(path2, delta, options = {}) {
|
|
494
|
+
const subject = pathToPathsD(path2);
|
|
495
|
+
const join = options.join === "miter" ? JoinType.Miter : options.join === "square" ? JoinType.Square : JoinType.Round;
|
|
496
|
+
const inflated = inflatePathsD(subject, delta, join, EndType.Polygon);
|
|
497
|
+
return pathsDToGroup(inflated);
|
|
498
|
+
}
|
|
180
499
|
|
|
181
500
|
// src/hatch/index.ts
|
|
182
501
|
function parallel(path2, options = {}) {
|
|
@@ -209,6 +528,94 @@ function cross(path2, options = {}) {
|
|
|
209
528
|
const b = parallel(path2, { ...options, angle: angle + Math.PI / 2 });
|
|
210
529
|
return [...a, ...b];
|
|
211
530
|
}
|
|
531
|
+
function weave(path2, options = {}) {
|
|
532
|
+
if (!path2.closed || !path2.rings[0]) {
|
|
533
|
+
throw new Error("hatch.weave requires a closed path");
|
|
534
|
+
}
|
|
535
|
+
const bounds = pathBounds(path2);
|
|
536
|
+
const width = Math.max(bounds.maxX - bounds.minX, 1e-9);
|
|
537
|
+
const height = Math.max(bounds.maxY - bounds.minY, 1e-9);
|
|
538
|
+
const cellSize = options.cellSize ?? Math.max(Math.min(width, height) / 12, 1e-6);
|
|
539
|
+
const weaveOpts = {
|
|
540
|
+
originX: bounds.minX,
|
|
541
|
+
originY: bounds.minY
|
|
542
|
+
};
|
|
543
|
+
if (options.swapColors !== void 0) weaveOpts.swapColors = options.swapColors;
|
|
544
|
+
if (options.cellFraction !== void 0) {
|
|
545
|
+
weaveOpts.cellFraction = options.cellFraction;
|
|
546
|
+
}
|
|
547
|
+
if (options.extendSingletonsToEdge !== void 0) {
|
|
548
|
+
weaveOpts.extendSingletonsToEdge = options.extendSingletonsToEdge;
|
|
549
|
+
}
|
|
550
|
+
const raw = weaveSegments(
|
|
551
|
+
width,
|
|
552
|
+
height,
|
|
553
|
+
cellSize,
|
|
554
|
+
options.A ?? 1,
|
|
555
|
+
options.B ?? 1,
|
|
556
|
+
options.C ?? 1,
|
|
557
|
+
weaveOpts
|
|
558
|
+
);
|
|
559
|
+
return clipSegmentsToPath(raw, path2);
|
|
560
|
+
}
|
|
561
|
+
function stochastic(path2, options = {}) {
|
|
562
|
+
if (!path2.closed || !path2.rings[0]) {
|
|
563
|
+
throw new Error("hatch.stochastic requires a closed path");
|
|
564
|
+
}
|
|
565
|
+
const bounds = pathBounds(path2);
|
|
566
|
+
const width = Math.max(bounds.maxX - bounds.minX, 1e-9);
|
|
567
|
+
const height = Math.max(bounds.maxY - bounds.minY, 1e-9);
|
|
568
|
+
const count = options.count ?? 40;
|
|
569
|
+
const minLength = options.length ?? options.minLength ?? 1;
|
|
570
|
+
const maxLength = options.length ?? options.maxLength ?? Math.min(width, height);
|
|
571
|
+
const raw = stochasticSegments(
|
|
572
|
+
width,
|
|
573
|
+
height,
|
|
574
|
+
count,
|
|
575
|
+
minLength,
|
|
576
|
+
maxLength,
|
|
577
|
+
options.seed ?? 1,
|
|
578
|
+
bounds.minX,
|
|
579
|
+
bounds.minY
|
|
580
|
+
);
|
|
581
|
+
return clipSegmentsToPath(raw, path2);
|
|
582
|
+
}
|
|
583
|
+
function perpendicular(path2, options = {}) {
|
|
584
|
+
if (!path2.rings[0]) {
|
|
585
|
+
throw new Error("hatch.perpendicular requires a path with at least one ring");
|
|
586
|
+
}
|
|
587
|
+
const spacing = Math.max(options.spacing ?? 0.15, 1e-9);
|
|
588
|
+
const length = options.length ?? 0.2;
|
|
589
|
+
return perpendicularPathSegments(
|
|
590
|
+
path2,
|
|
591
|
+
spacing,
|
|
592
|
+
length,
|
|
593
|
+
options.startOffset ?? 0
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
function concentric(path2, options = {}) {
|
|
597
|
+
if (!path2.closed || !path2.rings[0]) {
|
|
598
|
+
throw new Error("hatch.concentric requires a closed path");
|
|
599
|
+
}
|
|
600
|
+
const spacing = Math.max(options.spacing ?? 0.15, 1e-9);
|
|
601
|
+
const bounds = pathBounds(path2);
|
|
602
|
+
const diag = Math.hypot(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY);
|
|
603
|
+
const maxCount = options.count ?? Math.max(1, Math.floor(diag / (2 * spacing)));
|
|
604
|
+
const out = [];
|
|
605
|
+
for (let i = 1; i <= maxCount; i++) {
|
|
606
|
+
const shell = buffer(path2, -spacing * i);
|
|
607
|
+
if (shell.paths.length === 0) break;
|
|
608
|
+
for (const p of shell.paths) {
|
|
609
|
+
out.push(
|
|
610
|
+
path(
|
|
611
|
+
p.rings.map((r) => r.map((v) => ({ x: v.x, y: v.y }))),
|
|
612
|
+
true
|
|
613
|
+
)
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return out;
|
|
618
|
+
}
|
|
212
619
|
function hatchLineCount(bounds, spacing, angle) {
|
|
213
620
|
const nx = Math.cos(angle + Math.PI / 2);
|
|
214
621
|
const ny = Math.sin(angle + Math.PI / 2);
|
|
@@ -245,9 +652,13 @@ function pathBounds(path2) {
|
|
|
245
652
|
}
|
|
246
653
|
var hatch = {
|
|
247
654
|
parallel,
|
|
248
|
-
cross
|
|
655
|
+
cross,
|
|
656
|
+
weave,
|
|
657
|
+
stochastic,
|
|
658
|
+
perpendicular,
|
|
659
|
+
concentric
|
|
249
660
|
};
|
|
250
661
|
|
|
251
|
-
export { cross, hatch, parallel };
|
|
662
|
+
export { concentric, cross, hatch, parallel, perpendicular, stochastic, weave };
|
|
252
663
|
//# sourceMappingURL=index.js.map
|
|
253
664
|
//# sourceMappingURL=index.js.map
|