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.
Files changed (44) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/{circle-BDbKfrdR.d.ts → circle-CauDMv8V.d.ts} +1 -1
  3. package/dist/circlePacking/index.d.ts +2 -3
  4. package/dist/construction/index.d.ts +11 -3
  5. package/dist/construction/index.js +429 -2
  6. package/dist/construction/index.js.map +1 -1
  7. package/dist/contour/index.d.ts +3 -4
  8. package/dist/contour/index.js.map +1 -1
  9. package/dist/conversion/index.d.ts +2 -3
  10. package/dist/{group-BNjVVYSQ.d.ts → group-YVmSJuGo.d.ts} +1 -1
  11. package/dist/hatch/index.d.ts +70 -4
  12. package/dist/hatch/index.js +414 -3
  13. package/dist/hatch/index.js.map +1 -1
  14. package/dist/hull/index.d.ts +1 -2
  15. package/dist/index.d.ts +12 -13
  16. package/dist/index.js +1816 -1260
  17. package/dist/index.js.map +1 -1
  18. package/dist/meshing/index.d.ts +3 -4
  19. package/dist/morphology/index.d.ts +2 -3
  20. package/dist/optimisation/index.d.ts +2 -3
  21. package/dist/{path-0RjhYjJs.d.ts → path-Boeshk9L.d.ts} +9 -2
  22. package/dist/pointSet/index.d.ts +15 -2
  23. package/dist/pointSet/index.js +148 -2
  24. package/dist/pointSet/index.js.map +1 -1
  25. package/dist/polygonisation/index.d.ts +1 -2
  26. package/dist/polygonisation/index.js +54 -50
  27. package/dist/polygonisation/index.js.map +1 -1
  28. package/dist/predicates/index.d.ts +2 -3
  29. package/dist/processing/index.d.ts +3 -4
  30. package/dist/{segment-BKuIFMKD.d.ts → segment-CpLILSd7.d.ts} +1 -1
  31. package/dist/segmentSet/index.d.ts +41 -3
  32. package/dist/segmentSet/index.js +261 -1
  33. package/dist/segmentSet/index.js.map +1 -1
  34. package/dist/shapeBoolean/index.d.ts +2 -3
  35. package/dist/svg/index.d.ts +2 -3
  36. package/dist/tiling/index.d.ts +10 -4
  37. package/dist/tiling/index.js +148 -13
  38. package/dist/tiling/index.js.map +1 -1
  39. package/dist/transformation/index.d.ts +1 -2
  40. package/dist/triangulation/index.d.ts +1 -2
  41. package/dist/triangulation/index.js.map +1 -1
  42. package/dist/voronoi/index.d.ts +2 -3
  43. package/package.json +1 -1
  44. package/dist/vec2-CWyXN1Wj.d.ts +0 -10
@@ -1,6 +1,5 @@
1
- import { P as Path } from '../path-0RjhYjJs.js';
2
- import { G as Group, c as cloneGroup, g as group } from '../group-BNjVVYSQ.js';
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;
@@ -1,4 +1,4 @@
1
- import { P as Path } from './path-0RjhYjJs.js';
1
+ import { P as Path } from './path-Boeshk9L.js';
2
2
 
3
3
  /**
4
4
  * Ordered collection of paths. Later entries are “on top” for occlusion-style ops.
@@ -1,6 +1,6 @@
1
- import { S as Segment } from '../segment-BKuIFMKD.js';
2
- import { P as Path } from '../path-0RjhYjJs.js';
3
- import '../vec2-CWyXN1Wj.js';
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 };
@@ -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