pattapatta 0.2.1 → 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 (46) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +0 -43
  3. package/dist/{circle-BDbKfrdR.d.ts → circle-CauDMv8V.d.ts} +1 -1
  4. package/dist/circlePacking/index.d.ts +2 -3
  5. package/dist/construction/index.d.ts +11 -3
  6. package/dist/construction/index.js +429 -2
  7. package/dist/construction/index.js.map +1 -1
  8. package/dist/contour/index.d.ts +90 -4
  9. package/dist/contour/index.js +1326 -3
  10. package/dist/contour/index.js.map +1 -1
  11. package/dist/conversion/index.d.ts +2 -3
  12. package/dist/{group-BNjVVYSQ.d.ts → group-YVmSJuGo.d.ts} +1 -1
  13. package/dist/hatch/index.d.ts +70 -4
  14. package/dist/hatch/index.js +414 -3
  15. package/dist/hatch/index.js.map +1 -1
  16. package/dist/hull/index.d.ts +1 -2
  17. package/dist/index.d.ts +13 -14
  18. package/dist/index.js +2228 -632
  19. package/dist/index.js.map +1 -1
  20. package/dist/meshing/index.d.ts +3 -4
  21. package/dist/morphology/index.d.ts +2 -3
  22. package/dist/optimisation/index.d.ts +2 -3
  23. package/dist/{path-0RjhYjJs.d.ts → path-Boeshk9L.d.ts} +9 -2
  24. package/dist/pointSet/index.d.ts +15 -2
  25. package/dist/pointSet/index.js +148 -2
  26. package/dist/pointSet/index.js.map +1 -1
  27. package/dist/polygonisation/index.d.ts +1 -2
  28. package/dist/polygonisation/index.js +54 -50
  29. package/dist/polygonisation/index.js.map +1 -1
  30. package/dist/predicates/index.d.ts +2 -3
  31. package/dist/processing/index.d.ts +3 -4
  32. package/dist/{segment-BKuIFMKD.d.ts → segment-CpLILSd7.d.ts} +1 -1
  33. package/dist/segmentSet/index.d.ts +41 -3
  34. package/dist/segmentSet/index.js +261 -1
  35. package/dist/segmentSet/index.js.map +1 -1
  36. package/dist/shapeBoolean/index.d.ts +2 -3
  37. package/dist/svg/index.d.ts +2 -3
  38. package/dist/tiling/index.d.ts +10 -4
  39. package/dist/tiling/index.js +148 -13
  40. package/dist/tiling/index.js.map +1 -1
  41. package/dist/transformation/index.d.ts +1 -2
  42. package/dist/triangulation/index.d.ts +1 -2
  43. package/dist/triangulation/index.js.map +1 -1
  44. package/dist/voronoi/index.d.ts +2 -3
  45. package/package.json +2 -2
  46. package/dist/vec2-CWyXN1Wj.d.ts +0 -10
@@ -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