pattapatta 0.2.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 (74) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +98 -0
  4. package/dist/circle-BDbKfrdR.d.ts +12 -0
  5. package/dist/circlePacking/index.d.ts +78 -0
  6. package/dist/circlePacking/index.js +381 -0
  7. package/dist/circlePacking/index.js.map +1 -0
  8. package/dist/cli.js +365 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/construction/index.d.ts +26 -0
  11. package/dist/construction/index.js +102 -0
  12. package/dist/construction/index.js.map +1 -0
  13. package/dist/contour/index.d.ts +20 -0
  14. package/dist/contour/index.js +118 -0
  15. package/dist/contour/index.js.map +1 -0
  16. package/dist/conversion/index.d.ts +31 -0
  17. package/dist/conversion/index.js +99 -0
  18. package/dist/conversion/index.js.map +1 -0
  19. package/dist/group-BNjVVYSQ.d.ts +13 -0
  20. package/dist/hatch/index.d.ts +41 -0
  21. package/dist/hatch/index.js +253 -0
  22. package/dist/hatch/index.js.map +1 -0
  23. package/dist/hull/index.d.ts +16 -0
  24. package/dist/hull/index.js +97 -0
  25. package/dist/hull/index.js.map +1 -0
  26. package/dist/index.d.ts +67 -0
  27. package/dist/index.js +3933 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/meshing/index.d.ts +125 -0
  30. package/dist/meshing/index.js +1333 -0
  31. package/dist/meshing/index.js.map +1 -0
  32. package/dist/morphology/index.d.ts +58 -0
  33. package/dist/morphology/index.js +268 -0
  34. package/dist/morphology/index.js.map +1 -0
  35. package/dist/optimisation/index.d.ts +43 -0
  36. package/dist/optimisation/index.js +400 -0
  37. package/dist/optimisation/index.js.map +1 -0
  38. package/dist/path-0RjhYjJs.d.ts +21 -0
  39. package/dist/pointSet/index.d.ts +24 -0
  40. package/dist/pointSet/index.js +139 -0
  41. package/dist/pointSet/index.js.map +1 -0
  42. package/dist/polygonisation/index.d.ts +44 -0
  43. package/dist/polygonisation/index.js +310 -0
  44. package/dist/polygonisation/index.js.map +1 -0
  45. package/dist/predicates/index.d.ts +35 -0
  46. package/dist/predicates/index.js +159 -0
  47. package/dist/predicates/index.js.map +1 -0
  48. package/dist/processing/index.d.ts +54 -0
  49. package/dist/processing/index.js +457 -0
  50. package/dist/processing/index.js.map +1 -0
  51. package/dist/segment-BKuIFMKD.d.ts +10 -0
  52. package/dist/segmentSet/index.d.ts +25 -0
  53. package/dist/segmentSet/index.js +109 -0
  54. package/dist/segmentSet/index.js.map +1 -0
  55. package/dist/shapeBoolean/index.d.ts +41 -0
  56. package/dist/shapeBoolean/index.js +211 -0
  57. package/dist/shapeBoolean/index.js.map +1 -0
  58. package/dist/svg/index.d.ts +38 -0
  59. package/dist/svg/index.js +317 -0
  60. package/dist/svg/index.js.map +1 -0
  61. package/dist/tiling/index.d.ts +32 -0
  62. package/dist/tiling/index.js +344 -0
  63. package/dist/tiling/index.js.map +1 -0
  64. package/dist/transformation/index.d.ts +37 -0
  65. package/dist/transformation/index.js +184 -0
  66. package/dist/transformation/index.js.map +1 -0
  67. package/dist/triangulation/index.d.ts +48 -0
  68. package/dist/triangulation/index.js +334 -0
  69. package/dist/triangulation/index.js.map +1 -0
  70. package/dist/vec2-CWyXN1Wj.d.ts +10 -0
  71. package/dist/voronoi/index.d.ts +31 -0
  72. package/dist/voronoi/index.js +211 -0
  73. package/dist/voronoi/index.js.map +1 -0
  74. package/package.json +165 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types/vec2.ts","../../src/types/path.ts","../../src/construction/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** 2D point / vector. */\nexport type Vec2 = {\n x: number\n y: number\n}\n\nexport function vec2(x: number, y: number): Vec2 {\n return { x, y }\n}\n\nexport function cloneVec2(v: Vec2): Vec2 {\n return { x: v.x, y: v.y }\n}\n\nexport function equalsVec2(a: Vec2, b: Vec2, eps = 1e-9): boolean {\n return Math.abs(a.x - b.x) <= eps && Math.abs(a.y - b.y) <= eps\n}\n","import type { Vec2 } from './vec2.js'\nimport { cloneVec2, equalsVec2 } from './vec2.js'\n\n/** Ordered ring of vertices (typically without repeating the closing vertex). */\nexport type Ring = Vec2[]\n\n/**\n * A path: one exterior ring plus optional holes, or an open polyline.\n * `rings[0]` is the exterior (or the open polyline when `closed` is false).\n * Additional rings are holes (only meaningful when `closed` is true).\n */\nexport type Path = {\n rings: Ring[]\n closed: boolean\n}\n\nexport function path(rings: Ring[], closed = true): Path {\n return {\n rings: rings.map((ring) => ring.map(cloneVec2)),\n closed,\n }\n}\n\nexport function polyline(points: Ring): Path {\n return path([points], false)\n}\n\nexport function polygon(exterior: Ring, holes: Ring[] = []): Path {\n return path([exterior, ...holes], true)\n}\n\n/** Drop a trailing vertex that duplicates the first (SVG often closes this way). */\nexport function normalizeRing(ring: Ring, eps = 1e-9): Ring {\n if (ring.length < 2) return ring.map(cloneVec2)\n const first = ring[0]\n const last = ring[ring.length - 1]\n if (first && last && equalsVec2(first, last, eps)) {\n return ring.slice(0, -1).map(cloneVec2)\n }\n return ring.map(cloneVec2)\n}\n\nexport function clonePath(p: Path): Path {\n return {\n closed: p.closed,\n rings: p.rings.map((ring) => ring.map(cloneVec2)),\n }\n}\n","import type { Path, Vec2 } from '../types/index.js'\nimport { path, polygon, polyline } from '../types/index.js'\n\n/** Regular n-gon approximating a circle. */\nexport function createCircle(\n cx: number,\n cy: number,\n radius: number,\n segments = 64,\n): Path {\n return createRegularPolygon(cx, cy, radius, Math.max(3, Math.floor(segments)))\n}\n\nexport function createRect(\n x: number,\n y: number,\n width: number,\n height: number,\n): Path {\n return polygon([\n { x, y },\n { x: x + width, y },\n { x: x + width, y: y + height },\n { x, y: y + height },\n ])\n}\n\nexport function createRegularPolygon(\n cx: number,\n cy: number,\n radius: number,\n sides: number,\n rotation = -Math.PI / 2,\n): Path {\n const n = Math.max(3, Math.floor(sides))\n const ring: Vec2[] = []\n for (let i = 0; i < n; i++) {\n const t = rotation + (i / n) * Math.PI * 2\n ring.push({ x: cx + Math.cos(t) * radius, y: cy + Math.sin(t) * radius })\n }\n return polygon(ring)\n}\n\n/** Annulus as exterior + hole. */\nexport function createRing(\n cx: number,\n cy: number,\n outerRadius: number,\n innerRadius: number,\n segments = 64,\n): Path {\n const outer = createCircle(cx, cy, outerRadius, segments).rings[0]!\n const inner = createCircle(cx, cy, Math.min(innerRadius, outerRadius), segments)\n .rings[0]!\n // hole winding opposite for Clipper\n return path([outer, [...inner].reverse()], true)\n}\n\n/** Open circular arc polyline from `startAngle` to `endAngle` (radians). */\nexport function createArc(\n cx: number,\n cy: number,\n radius: number,\n startAngle: number,\n endAngle: number,\n segments = 32,\n): Path {\n const n = Math.max(2, Math.floor(segments))\n const pts: Vec2[] = []\n for (let i = 0; i <= n; i++) {\n const t = startAngle + ((endAngle - startAngle) * i) / n\n pts.push({ x: cx + Math.cos(t) * radius, y: cy + Math.sin(t) * radius })\n }\n return polyline(pts)\n}\n\n/** Star polygon (outer/inner radius alternating). */\nexport function createStar(\n cx: number,\n cy: number,\n outerRadius: number,\n innerRadius: number,\n points = 5,\n rotation = -Math.PI / 2,\n): Path {\n const n = Math.max(3, Math.floor(points))\n const ring: Vec2[] = []\n for (let i = 0; i < n * 2; i++) {\n const r = i % 2 === 0 ? outerRadius : innerRadius\n const t = rotation + (i / (n * 2)) * Math.PI * 2\n ring.push({ x: cx + Math.cos(t) * r, y: cy + Math.sin(t) * r })\n }\n return polygon(ring)\n}\n\n/** Koch snowflake after `iterations` refinement (0 = equilateral triangle). */\nexport function createKochSnowflake(\n cx: number,\n cy: number,\n radius: number,\n iterations = 3,\n): Path {\n const tri = createRegularPolygon(cx, cy, radius, 3)\n let ring = tri.rings[0]!\n const iters = Math.max(0, Math.min(6, Math.floor(iterations)))\n for (let i = 0; i < iters; i++) {\n ring = kochRefine(ring)\n }\n return polygon(ring)\n}\n\nfunction kochRefine(ring: Vec2[]): Vec2[] {\n const out: Vec2[] = []\n const n = ring.length\n for (let i = 0; i < n; i++) {\n const a = ring[i]!\n const b = ring[(i + 1) % n]!\n const dx = b.x - a.x\n const dy = b.y - a.y\n const p1 = { x: a.x + dx / 3, y: a.y + dy / 3 }\n const p2 = { x: a.x + (2 * dx) / 3, y: a.y + (2 * dy) / 3 }\n // outward peak (left of directed edge in Y-down SVG coords = rotate +90 for CCW tri)\n const px = a.x + dx / 2 - dy * (Math.sqrt(3) / 6)\n const py = a.y + dy / 2 + dx * (Math.sqrt(3) / 6)\n out.push(a, p1, { x: px, y: py }, p2)\n }\n return out\n}\n\nexport const construction = {\n createCircle,\n createRect,\n createRegularPolygon,\n createRing,\n createArc,\n createStar,\n createKochSnowflake,\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import { P as Path } from '../path-0RjhYjJs.js';
2
+ import { G as Group } from '../group-BNjVVYSQ.js';
3
+ import '../vec2-CWyXN1Wj.js';
4
+
5
+ /**
6
+ * Offset the path outward by `distance` (positive buffer).
7
+ * Returns possibly multiple closed contours.
8
+ */
9
+ declare function offsetCurvesOutward(p: Path, distance: number): Group;
10
+ /**
11
+ * Offset the path inward by `distance` (negative buffer / erosion).
12
+ * May return empty group if distance exceeds inradius.
13
+ */
14
+ declare function offsetCurvesInward(p: Path, distance: number): Group;
15
+ declare const contour: {
16
+ offsetCurvesOutward: typeof offsetCurvesOutward;
17
+ offsetCurvesInward: typeof offsetCurvesInward;
18
+ };
19
+
20
+ export { contour, offsetCurvesInward, offsetCurvesOutward };
@@ -0,0 +1,118 @@
1
+ import { FillRule, JoinType, inflatePathsD, EndType, isPositiveD, pointInPolygonD, PointInPolygonResult } from 'clipper2-ts';
2
+
3
+ // src/types/vec2.ts
4
+ function vec2(x, y) {
5
+ return { x, y };
6
+ }
7
+ function cloneVec2(v) {
8
+ return { x: v.x, y: v.y };
9
+ }
10
+ function equalsVec2(a, b, eps = 1e-9) {
11
+ return Math.abs(a.x - b.x) <= eps && Math.abs(a.y - b.y) <= eps;
12
+ }
13
+
14
+ // src/types/path.ts
15
+ function path(rings, closed = true) {
16
+ return {
17
+ rings: rings.map((ring) => ring.map(cloneVec2)),
18
+ closed
19
+ };
20
+ }
21
+ function normalizeRing(ring, eps = 1e-9) {
22
+ if (ring.length < 2) return ring.map(cloneVec2);
23
+ const first = ring[0];
24
+ const last = ring[ring.length - 1];
25
+ if (first && last && equalsVec2(first, last, eps)) {
26
+ return ring.slice(0, -1).map(cloneVec2);
27
+ }
28
+ return ring.map(cloneVec2);
29
+ }
30
+ function clonePath(p) {
31
+ return {
32
+ closed: p.closed,
33
+ rings: p.rings.map((ring) => ring.map(cloneVec2))
34
+ };
35
+ }
36
+
37
+ // src/types/group.ts
38
+ function group(paths = []) {
39
+ return { paths: paths.map(clonePath) };
40
+ }
41
+ FillRule.NonZero;
42
+ function ringToPathD(ring) {
43
+ const pts = [];
44
+ for (const v of normalizeRing(ring)) {
45
+ pts.push({ x: v.x, y: v.y, z: 0 });
46
+ }
47
+ return pts;
48
+ }
49
+ function pathToPathsD(p) {
50
+ if (!p.closed || p.rings.length === 0) {
51
+ throw new Error("Boolean ops require a closed path with at least one ring");
52
+ }
53
+ return p.rings.map((ring) => ringToPathD(ring));
54
+ }
55
+ function pathsDToGroup(paths) {
56
+ if (paths.length === 0) return group([]);
57
+ const outers = [];
58
+ const holes = [];
59
+ for (const pd of paths) {
60
+ const ring = pathDToRing(pd);
61
+ if (ring.length < 3) continue;
62
+ if (isPositiveD(pd)) {
63
+ outers.push({ ring, holes: [] });
64
+ } else {
65
+ holes.push(ring);
66
+ }
67
+ }
68
+ for (const hole of holes) {
69
+ const sample = hole[0];
70
+ if (!sample) continue;
71
+ let assigned = false;
72
+ for (const outer of outers) {
73
+ if (pointInRing(sample, outer.ring)) {
74
+ outer.holes.push(hole);
75
+ assigned = true;
76
+ break;
77
+ }
78
+ }
79
+ if (!assigned) {
80
+ outers.push({ ring: [...hole].reverse(), holes: [] });
81
+ }
82
+ }
83
+ return group(outers.map((o) => path([o.ring, ...o.holes], true)));
84
+ }
85
+ function pathDToRing(pd) {
86
+ return normalizeRing(pd.map((p) => vec2(p.x, p.y)));
87
+ }
88
+ function pointInRing(point, ring) {
89
+ const result = pointInPolygonD(
90
+ { x: point.x, y: point.y, z: 0 },
91
+ ringToPathD(ring)
92
+ );
93
+ return result === PointInPolygonResult.IsInside || result === PointInPolygonResult.IsOn;
94
+ }
95
+
96
+ // src/morphology/buffer.ts
97
+ function buffer(path2, delta, options = {}) {
98
+ const subject = pathToPathsD(path2);
99
+ const join = options.join === "miter" ? JoinType.Miter : options.join === "square" ? JoinType.Square : JoinType.Round;
100
+ const inflated = inflatePathsD(subject, delta, join, EndType.Polygon);
101
+ return pathsDToGroup(inflated);
102
+ }
103
+
104
+ // src/contour/index.ts
105
+ function offsetCurvesOutward(p, distance) {
106
+ return buffer(p, Math.abs(distance));
107
+ }
108
+ function offsetCurvesInward(p, distance) {
109
+ return buffer(p, -Math.abs(distance));
110
+ }
111
+ var contour = {
112
+ offsetCurvesOutward,
113
+ offsetCurvesInward
114
+ };
115
+
116
+ export { contour, offsetCurvesInward, offsetCurvesOutward };
117
+ //# sourceMappingURL=index.js.map
118
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types/vec2.ts","../../src/types/path.ts","../../src/types/group.ts","../../src/clipper/convert.ts","../../src/morphology/buffer.ts","../../src/contour/index.ts"],"names":["path"],"mappings":"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","file":"index.js","sourcesContent":["/** 2D point / vector. */\nexport type Vec2 = {\n x: number\n y: number\n}\n\nexport function vec2(x: number, y: number): Vec2 {\n return { x, y }\n}\n\nexport function cloneVec2(v: Vec2): Vec2 {\n return { x: v.x, y: v.y }\n}\n\nexport function equalsVec2(a: Vec2, b: Vec2, eps = 1e-9): boolean {\n return Math.abs(a.x - b.x) <= eps && Math.abs(a.y - b.y) <= eps\n}\n","import type { Vec2 } from './vec2.js'\nimport { cloneVec2, equalsVec2 } from './vec2.js'\n\n/** Ordered ring of vertices (typically without repeating the closing vertex). */\nexport type Ring = Vec2[]\n\n/**\n * A path: one exterior ring plus optional holes, or an open polyline.\n * `rings[0]` is the exterior (or the open polyline when `closed` is false).\n * Additional rings are holes (only meaningful when `closed` is true).\n */\nexport type Path = {\n rings: Ring[]\n closed: boolean\n}\n\nexport function path(rings: Ring[], closed = true): Path {\n return {\n rings: rings.map((ring) => ring.map(cloneVec2)),\n closed,\n }\n}\n\nexport function polyline(points: Ring): Path {\n return path([points], false)\n}\n\nexport function polygon(exterior: Ring, holes: Ring[] = []): Path {\n return path([exterior, ...holes], true)\n}\n\n/** Drop a trailing vertex that duplicates the first (SVG often closes this way). */\nexport function normalizeRing(ring: Ring, eps = 1e-9): Ring {\n if (ring.length < 2) return ring.map(cloneVec2)\n const first = ring[0]\n const last = ring[ring.length - 1]\n if (first && last && equalsVec2(first, last, eps)) {\n return ring.slice(0, -1).map(cloneVec2)\n }\n return ring.map(cloneVec2)\n}\n\nexport function clonePath(p: Path): Path {\n return {\n closed: p.closed,\n rings: p.rings.map((ring) => ring.map(cloneVec2)),\n }\n}\n","import type { Path } from './path.js'\nimport { clonePath } from './path.js'\n\n/**\n * Ordered collection of paths. Later entries are “on top” for occlusion-style ops.\n */\nexport type Group = {\n paths: Path[]\n}\n\nexport function group(paths: Path[] = []): Group {\n return { paths: paths.map(clonePath) }\n}\n\nexport function groupFromPaths(...paths: Path[]): Group {\n return group(paths)\n}\n\nexport function cloneGroup(g: Group): Group {\n return group(g.paths)\n}\n","import {\n FillRule,\n areaD,\n differenceD,\n intersectD,\n isPositiveD,\n pointInPolygonD,\n PointInPolygonResult,\n unionD,\n xorD,\n type PathD,\n type PathsD,\n type PointD,\n} from 'clipper2-ts'\nimport type { Group, Path, Ring, Vec2 } from '../types/index.js'\nimport { group, normalizeRing, path, vec2 } from '../types/index.js'\n\nexport const DEFAULT_FILL_RULE = FillRule.NonZero\n\nexport function ringToPathD(ring: Ring): PathD {\n const pts: PointD[] = []\n for (const v of normalizeRing(ring)) {\n pts.push({ x: v.x, y: v.y, z: 0 })\n }\n return pts\n}\n\n/** Closed path → Clipper subject/clip paths (exterior + holes). */\nexport function pathToPathsD(p: Path): PathsD {\n if (!p.closed || p.rings.length === 0) {\n throw new Error('Boolean ops require a closed path with at least one ring')\n }\n return p.rings.map((ring) => ringToPathD(ring))\n}\n\nexport function pathsDToGroup(paths: PathsD): Group {\n if (paths.length === 0) return group([])\n\n const outers: { ring: Ring; holes: Ring[] }[] = []\n const holes: Ring[] = []\n\n for (const pd of paths) {\n const ring = pathDToRing(pd)\n if (ring.length < 3) continue\n if (isPositiveD(pd)) {\n outers.push({ ring, holes: [] })\n } else {\n holes.push(ring)\n }\n }\n\n for (const hole of holes) {\n const sample = hole[0]\n if (!sample) continue\n let assigned = false\n for (const outer of outers) {\n if (pointInRing(sample, outer.ring)) {\n outer.holes.push(hole)\n assigned = true\n break\n }\n }\n if (!assigned) {\n // Treat unmatched negative rings as their own outers (orientation flip).\n outers.push({ ring: [...hole].reverse(), holes: [] })\n }\n }\n\n return group(outers.map((o) => path([o.ring, ...o.holes], true)))\n}\n\nexport function pathDToRing(pd: PathD): Ring {\n return normalizeRing(pd.map((p) => vec2(p.x, p.y)))\n}\n\nexport function pointInRing(point: Vec2, ring: Ring): boolean {\n const result = pointInPolygonD(\n { x: point.x, y: point.y, z: 0 },\n ringToPathD(ring),\n )\n return (\n result === PointInPolygonResult.IsInside ||\n result === PointInPolygonResult.IsOn\n )\n}\n\nexport function clipUnion(a: PathsD, b: PathsD): PathsD {\n return unionD(a, b, DEFAULT_FILL_RULE)\n}\n\nexport function clipIntersect(a: PathsD, b: PathsD): PathsD {\n return intersectD(a, b, DEFAULT_FILL_RULE)\n}\n\nexport function clipDifference(a: PathsD, b: PathsD): PathsD {\n return differenceD(a, b, DEFAULT_FILL_RULE)\n}\n\nexport function clipXor(a: PathsD, b: PathsD): PathsD {\n return xorD(a, b, DEFAULT_FILL_RULE)\n}\n\nexport function pathsArea(paths: PathsD): number {\n let sum = 0\n for (const p of paths) {\n sum += Math.abs(areaD(p))\n }\n return sum\n}\n","import type { Group, Path } from '../types/index.js'\nimport { group } from '../types/index.js'\nimport { inflatePathsD, JoinType, EndType } from 'clipper2-ts'\nimport { pathToPathsD, pathsDToGroup } from '../clipper/convert.js'\n\nexport type BufferOptions = {\n /** Join style for offset. Default round. */\n join?: 'round' | 'miter' | 'square'\n}\n\n/**\n * Offset / buffer a closed path by `delta` (positive = expand, negative = erode).\n * Returns a group of resulting closed paths (may split or vanish when eroding).\n */\nexport function buffer(\n path: Path,\n delta: number,\n options: BufferOptions = {},\n): Group {\n const subject = pathToPathsD(path)\n const join =\n options.join === 'miter'\n ? JoinType.Miter\n : options.join === 'square'\n ? JoinType.Square\n : JoinType.Round\n const inflated = inflatePathsD(subject, delta, join, EndType.Polygon)\n return pathsDToGroup(inflated)\n}\n\n/**\n * Erode by `|amount|` then dilate by `|amount|` (opening). Useful for cleaning.\n */\nexport function erosionDilation(p: Path, amount: number): Group {\n const a = Math.abs(amount)\n if (a < 1e-12) return group([p])\n const eroded = buffer(p, -a)\n if (eroded.paths.length === 0) return eroded\n const dilated: Path[] = []\n for (const q of eroded.paths) {\n dilated.push(...buffer(q, a).paths)\n }\n return group(dilated)\n}\n\n/**\n * Dilate by `|amount|` then erode by `|amount|` (closing). Fills small gaps.\n */\nexport function dilationErosion(p: Path, amount: number): Group {\n const a = Math.abs(amount)\n if (a < 1e-12) return group([p])\n const dilated = buffer(p, a)\n if (dilated.paths.length === 0) return dilated\n const eroded: Path[] = []\n for (const q of dilated.paths) {\n eroded.push(...buffer(q, -a).paths)\n }\n return group(eroded)\n}\n","import type { Group, Path } from '../types/index.js'\nimport { buffer } from '../morphology/buffer.js'\n\n/**\n * Offset the path outward by `distance` (positive buffer).\n * Returns possibly multiple closed contours.\n */\nexport function offsetCurvesOutward(p: Path, distance: number): Group {\n return buffer(p, Math.abs(distance))\n}\n\n/**\n * Offset the path inward by `distance` (negative buffer / erosion).\n * May return empty group if distance exceeds inradius.\n */\nexport function offsetCurvesInward(p: Path, distance: number): Group {\n return buffer(p, -Math.abs(distance))\n}\n\nexport const contour = {\n offsetCurvesOutward,\n offsetCurvesInward,\n}\n"]}
@@ -0,0 +1,31 @@
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';
4
+
5
+ /** Deep copy of a path. */
6
+ declare function copy(p: Path): Path;
7
+ /** Flatten a group to a single path list (already flat; returns cloned paths). */
8
+ declare function flatten(g: Group): Path[];
9
+ /** Round all vertex coords (delegates to morphology.reducePrecision). */
10
+ declare function roundVertexCoords(p: Path, decimals: number): Path;
11
+ /** Flat [x0,y0,x1,y1,...] of the exterior ring (holes omitted). */
12
+ declare function toArray(p: Path): number[];
13
+ /** Build a closed polygon from flat [x0,y0,...] coords. */
14
+ declare function fromArray(coords: number[], closed?: boolean): Path;
15
+ /** Exterior + each hole as separate contour paths. */
16
+ declare function toContours(p: Path): Path[];
17
+ /** Merge contour paths into one multi-ring path (first = exterior). */
18
+ declare function fromContours(contours: Path[]): Path;
19
+ declare const conversion: {
20
+ copy: typeof copy;
21
+ flatten: typeof flatten;
22
+ roundVertexCoords: typeof roundVertexCoords;
23
+ toArray: typeof toArray;
24
+ fromArray: typeof fromArray;
25
+ toContours: typeof toContours;
26
+ fromContours: typeof fromContours;
27
+ cloneGroup: typeof cloneGroup;
28
+ group: typeof group;
29
+ };
30
+
31
+ export { conversion, copy, flatten, fromArray, fromContours, roundVertexCoords, toArray, toContours };
@@ -0,0 +1,99 @@
1
+ import 'clipper2-ts';
2
+
3
+ // src/types/vec2.ts
4
+ function cloneVec2(v) {
5
+ return { x: v.x, y: v.y };
6
+ }
7
+ function equalsVec2(a, b, eps = 1e-9) {
8
+ return Math.abs(a.x - b.x) <= eps && Math.abs(a.y - b.y) <= eps;
9
+ }
10
+
11
+ // src/types/path.ts
12
+ function path(rings, closed = true) {
13
+ return {
14
+ rings: rings.map((ring) => ring.map(cloneVec2)),
15
+ closed
16
+ };
17
+ }
18
+ function normalizeRing(ring, eps = 1e-9) {
19
+ if (ring.length < 2) return ring.map(cloneVec2);
20
+ const first = ring[0];
21
+ const last = ring[ring.length - 1];
22
+ if (first && last && equalsVec2(first, last, eps)) {
23
+ return ring.slice(0, -1).map(cloneVec2);
24
+ }
25
+ return ring.map(cloneVec2);
26
+ }
27
+ function clonePath(p) {
28
+ return {
29
+ closed: p.closed,
30
+ rings: p.rings.map((ring) => ring.map(cloneVec2))
31
+ };
32
+ }
33
+
34
+ // src/types/group.ts
35
+ function group(paths = []) {
36
+ return { paths: paths.map(clonePath) };
37
+ }
38
+ function cloneGroup(g) {
39
+ return group(g.paths);
40
+ }
41
+ function reducePrecision(p, decimals) {
42
+ const f = 10 ** Math.max(0, Math.floor(decimals));
43
+ const round = (n) => Math.round(n * f) / f;
44
+ return path(
45
+ p.rings.map(
46
+ (ring) => normalizeRing(ring.map((v) => ({ x: round(v.x), y: round(v.y) })))
47
+ ),
48
+ p.closed
49
+ );
50
+ }
51
+
52
+ // src/conversion/index.ts
53
+ function copy(p) {
54
+ return clonePath(p);
55
+ }
56
+ function flatten(g) {
57
+ return g.paths.map(clonePath);
58
+ }
59
+ function roundVertexCoords(p, decimals) {
60
+ return reducePrecision(p, decimals);
61
+ }
62
+ function toArray(p) {
63
+ const ring = p.rings[0] ?? [];
64
+ const out = [];
65
+ for (const v of ring) {
66
+ out.push(v.x, v.y);
67
+ }
68
+ return out;
69
+ }
70
+ function fromArray(coords, closed = true) {
71
+ const ring = [];
72
+ for (let i = 0; i + 1 < coords.length; i += 2) {
73
+ ring.push({ x: coords[i], y: coords[i + 1] });
74
+ }
75
+ return path([normalizeRing(ring)], closed);
76
+ }
77
+ function toContours(p) {
78
+ return p.rings.filter((r) => r.length >= 3).map((r) => path([normalizeRing(r)], true));
79
+ }
80
+ function fromContours(contours) {
81
+ if (contours.length === 0) return path([], true);
82
+ const rings = contours.map((c) => normalizeRing(c.rings[0] ?? []));
83
+ return path(rings, true);
84
+ }
85
+ var conversion = {
86
+ copy,
87
+ flatten,
88
+ roundVertexCoords,
89
+ toArray,
90
+ fromArray,
91
+ toContours,
92
+ fromContours,
93
+ cloneGroup,
94
+ group
95
+ };
96
+
97
+ export { conversion, copy, flatten, fromArray, fromContours, roundVertexCoords, toArray, toContours };
98
+ //# sourceMappingURL=index.js.map
99
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types/vec2.ts","../../src/types/path.ts","../../src/types/group.ts","../../src/morphology/index.ts","../../src/conversion/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** 2D point / vector. */\nexport type Vec2 = {\n x: number\n y: number\n}\n\nexport function vec2(x: number, y: number): Vec2 {\n return { x, y }\n}\n\nexport function cloneVec2(v: Vec2): Vec2 {\n return { x: v.x, y: v.y }\n}\n\nexport function equalsVec2(a: Vec2, b: Vec2, eps = 1e-9): boolean {\n return Math.abs(a.x - b.x) <= eps && Math.abs(a.y - b.y) <= eps\n}\n","import type { Vec2 } from './vec2.js'\nimport { cloneVec2, equalsVec2 } from './vec2.js'\n\n/** Ordered ring of vertices (typically without repeating the closing vertex). */\nexport type Ring = Vec2[]\n\n/**\n * A path: one exterior ring plus optional holes, or an open polyline.\n * `rings[0]` is the exterior (or the open polyline when `closed` is false).\n * Additional rings are holes (only meaningful when `closed` is true).\n */\nexport type Path = {\n rings: Ring[]\n closed: boolean\n}\n\nexport function path(rings: Ring[], closed = true): Path {\n return {\n rings: rings.map((ring) => ring.map(cloneVec2)),\n closed,\n }\n}\n\nexport function polyline(points: Ring): Path {\n return path([points], false)\n}\n\nexport function polygon(exterior: Ring, holes: Ring[] = []): Path {\n return path([exterior, ...holes], true)\n}\n\n/** Drop a trailing vertex that duplicates the first (SVG often closes this way). */\nexport function normalizeRing(ring: Ring, eps = 1e-9): Ring {\n if (ring.length < 2) return ring.map(cloneVec2)\n const first = ring[0]\n const last = ring[ring.length - 1]\n if (first && last && equalsVec2(first, last, eps)) {\n return ring.slice(0, -1).map(cloneVec2)\n }\n return ring.map(cloneVec2)\n}\n\nexport function clonePath(p: Path): Path {\n return {\n closed: p.closed,\n rings: p.rings.map((ring) => ring.map(cloneVec2)),\n }\n}\n","import type { Path } from './path.js'\nimport { clonePath } from './path.js'\n\n/**\n * Ordered collection of paths. Later entries are “on top” for occlusion-style ops.\n */\nexport type Group = {\n paths: Path[]\n}\n\nexport function group(paths: Path[] = []): Group {\n return { paths: paths.map(clonePath) }\n}\n\nexport function groupFromPaths(...paths: Path[]): Group {\n return group(paths)\n}\n\nexport function cloneGroup(g: Group): Group {\n return group(g.paths)\n}\n","import { simplifyPathD, minkowskiSumD, minkowskiDiffD } from 'clipper2-ts'\nimport type { Group, Path, Ring, Vec2 } from '../types/index.js'\nimport { path, normalizeRing } from '../types/index.js'\nimport { pathDToRing, ringToPathD, pathsDToGroup } from '../clipper/convert.js'\nimport { centroid } from '../predicates/index.js'\nimport { buffer, erosionDilation, dilationErosion } from './buffer.js'\n\nexport type { BufferOptions } from './buffer.js'\nexport { buffer, erosionDilation, dilationErosion } from './buffer.js'\n\n/**\n * Ramer–Douglas–Peucker simplify (Clipper2). `epsilon` is absolute distance.\n */\nexport function simplify(p: Path, epsilon: number): Path {\n if (epsilon <= 0 || p.rings.length === 0) return p\n const rings: Ring[] = []\n for (const ring of p.rings) {\n const simplified = simplifyPathD(ringToPathD(ring), epsilon, p.closed)\n const out = pathDToRing(simplified)\n if (out.length >= (p.closed ? 3 : 2)) rings.push(out)\n }\n if (rings.length === 0) return path([], p.closed)\n return path(rings, p.closed)\n}\n\n/** Round vertex coordinates to `decimals` places. */\nexport function reducePrecision(p: Path, decimals: number): Path {\n const f = 10 ** Math.max(0, Math.floor(decimals))\n const round = (n: number) => Math.round(n * f) / f\n return path(\n p.rings.map((ring) =>\n normalizeRing(ring.map((v) => ({ x: round(v.x), y: round(v.y) }))),\n ),\n p.closed,\n )\n}\n\n/**\n * Chaikin corner cutting. `iterations` ≥ 1; closed rings stay closed.\n * Ratio defaults to classic 1/4–3/4 cut.\n */\nexport function chaikinCut(p: Path, iterations = 1, ratio = 0.25): Path {\n const r = Math.min(0.49, Math.max(0.01, ratio))\n let rings = p.rings\n for (let i = 0; i < Math.max(1, Math.floor(iterations)); i++) {\n rings = rings.map((ring) => chaikinRing(ring, r, p.closed))\n }\n return path(rings, p.closed)\n}\n\n/** Alias: Chaikin smoothing for `iterations` passes. */\nexport function smooth(p: Path, iterations = 2): Path {\n return chaikinCut(p, iterations)\n}\n\n/** Radial displace from centroid: r' = r * (1 + amplitude * cos(freq * θ + phase)). */\nexport function radialWarp(\n p: Path,\n amplitude: number,\n frequency = 3,\n phase = 0,\n): Path {\n const c = centroid(p)\n return mapVerts(p, (v) => {\n const dx = v.x - c.x\n const dy = v.y - c.y\n const dist = Math.hypot(dx, dy)\n if (dist < 1e-12) return v\n const theta = Math.atan2(dy, dx)\n const s = 1 + amplitude * Math.cos(frequency * theta + phase)\n return { x: c.x + dx * s, y: c.y + dy * s }\n })\n}\n\n/** Sine warp along X: y' = y + amplitude * sin(2π * frequency * x + phase). */\nexport function sineWarp(\n p: Path,\n amplitude: number,\n frequency = 1,\n phase = 0,\n): Path {\n return mapVerts(p, (v) => ({\n x: v.x,\n y: v.y + amplitude * Math.sin(2 * Math.PI * frequency * v.x + phase),\n }))\n}\n\n/** Minkowski sum of two closed paths (Clipper2). */\nexport function minkSum(a: Path, b: Path): Group {\n const pattern = ringToPathD(b.rings[0] ?? [])\n const subject = ringToPathD(a.rings[0] ?? [])\n return pathsDToGroup(minkowskiSumD(pattern, subject, true))\n}\n\n/** Minkowski difference of two closed paths (Clipper2). */\nexport function minkDifference(a: Path, b: Path): Group {\n const pattern = ringToPathD(b.rings[0] ?? [])\n const subject = ringToPathD(a.rings[0] ?? [])\n return pathsDToGroup(minkowskiDiffD(pattern, subject, true))\n}\n\nfunction chaikinRing(ring: Ring, ratio: number, closed: boolean): Ring {\n if (ring.length < 2) return ring.map((v) => ({ ...v }))\n const out: Vec2[] = []\n const n = ring.length\n const edges = closed ? n : n - 1\n for (let i = 0; i < edges; i++) {\n const a = ring[i]!\n const b = ring[(i + 1) % n]!\n out.push({\n x: a.x * (1 - ratio) + b.x * ratio,\n y: a.y * (1 - ratio) + b.y * ratio,\n })\n out.push({\n x: a.x * ratio + b.x * (1 - ratio),\n y: a.y * ratio + b.y * (1 - ratio),\n })\n }\n if (!closed) {\n out.unshift({ ...ring[0]! })\n out.push({ ...ring[n - 1]! })\n }\n return closed ? normalizeRing(out) : out\n}\n\nfunction mapVerts(p: Path, fn: (v: Vec2) => Vec2): Path {\n return path(\n p.rings.map((ring) => normalizeRing(ring.map(fn))),\n p.closed,\n )\n}\n\nexport const morphology = {\n buffer,\n erosionDilation,\n dilationErosion,\n simplify,\n reducePrecision,\n chaikinCut,\n smooth,\n radialWarp,\n sineWarp,\n minkSum,\n minkDifference,\n}\n","import type { Group, Path, Vec2 } from '../types/index.js'\nimport {\n cloneGroup,\n clonePath,\n group,\n normalizeRing,\n path,\n} from '../types/index.js'\nimport { reducePrecision } from '../morphology/index.js'\n\n/** Deep copy of a path. */\nexport function copy(p: Path): Path {\n return clonePath(p)\n}\n\n/** Flatten a group to a single path list (already flat; returns cloned paths). */\nexport function flatten(g: Group): Path[] {\n return g.paths.map(clonePath)\n}\n\n/** Round all vertex coords (delegates to morphology.reducePrecision). */\nexport function roundVertexCoords(p: Path, decimals: number): Path {\n return reducePrecision(p, decimals)\n}\n\n/** Flat [x0,y0,x1,y1,...] of the exterior ring (holes omitted). */\nexport function toArray(p: Path): number[] {\n const ring = p.rings[0] ?? []\n const out: number[] = []\n for (const v of ring) {\n out.push(v.x, v.y)\n }\n return out\n}\n\n/** Build a closed polygon from flat [x0,y0,...] coords. */\nexport function fromArray(coords: number[], closed = true): Path {\n const ring: Vec2[] = []\n for (let i = 0; i + 1 < coords.length; i += 2) {\n ring.push({ x: coords[i]!, y: coords[i + 1]! })\n }\n return path([normalizeRing(ring)], closed)\n}\n\n/** Exterior + each hole as separate contour paths. */\nexport function toContours(p: Path): Path[] {\n return p.rings\n .filter((r) => r.length >= 3)\n .map((r) => path([normalizeRing(r)], true))\n}\n\n/** Merge contour paths into one multi-ring path (first = exterior). */\nexport function fromContours(contours: Path[]): Path {\n if (contours.length === 0) return path([], true)\n const rings = contours.map((c) => normalizeRing(c.rings[0] ?? []))\n return path(rings, true)\n}\n\nexport const conversion = {\n copy,\n flatten,\n roundVertexCoords,\n toArray,\n fromArray,\n toContours,\n fromContours,\n cloneGroup,\n group,\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import { P as Path } from './path-0RjhYjJs.js';
2
+
3
+ /**
4
+ * Ordered collection of paths. Later entries are “on top” for occlusion-style ops.
5
+ */
6
+ type Group = {
7
+ paths: Path[];
8
+ };
9
+ declare function group(paths?: Path[]): Group;
10
+ declare function groupFromPaths(...paths: Path[]): Group;
11
+ declare function cloneGroup(g: Group): Group;
12
+
13
+ export { type Group as G, groupFromPaths as a, cloneGroup as c, group as g };
@@ -0,0 +1,41 @@
1
+ import { S as Segment } from '../segment-BKuIFMKD.js';
2
+ import { P as Path } from '../path-0RjhYjJs.js';
3
+ import '../vec2-CWyXN1Wj.js';
4
+
5
+ type ParallelHatchOptions = {
6
+ /** Hatch angle in radians (segment direction). Default `Math.PI / 4`. */
7
+ angle?: number;
8
+ /** Perpendicular spacing between lines. Default `0.15`. */
9
+ spacing?: number;
10
+ /**
11
+ * Half-length of generated lines before clipping (PGS `length`).
12
+ * Default: diagonal of path bounds.
13
+ */
14
+ length?: number;
15
+ /**
16
+ * Number of parallel lines (PGS `n`).
17
+ * Default: enough lines to cover the path AABB at the given spacing.
18
+ */
19
+ count?: number;
20
+ /** Hatch field center. Default: path centroid of exterior bounds center. */
21
+ center?: {
22
+ x: number;
23
+ y: number;
24
+ };
25
+ };
26
+ /**
27
+ * Fill a closed path with parallel hatch strokes (plotter-safe).
28
+ * Composes `parallelSegments` + clip-to-path (PGS dysonHatching recipe).
29
+ *
30
+ * When `count` is omitted, line count is derived from `spacing` and the path
31
+ * bounds so the hatch covers the whole region (no corner gaps).
32
+ */
33
+ declare function parallel(path: Path, options?: ParallelHatchOptions): Segment[];
34
+ /** Cross-hatch: two perpendicular parallel passes. */
35
+ declare function cross(path: Path, options?: ParallelHatchOptions): Segment[];
36
+ declare const hatch: {
37
+ parallel: typeof parallel;
38
+ cross: typeof cross;
39
+ };
40
+
41
+ export { type ParallelHatchOptions, cross, hatch, parallel };
@@ -0,0 +1,253 @@
1
+ import { FillRule, pointInPolygonD, PointInPolygonResult } from 'clipper2-ts';
2
+
3
+ // src/types/vec2.ts
4
+ function vec2(x, y) {
5
+ return { x, y };
6
+ }
7
+ function cloneVec2(v) {
8
+ return { x: v.x, y: v.y };
9
+ }
10
+ function equalsVec2(a, b, eps = 1e-9) {
11
+ return Math.abs(a.x - b.x) <= eps && Math.abs(a.y - b.y) <= eps;
12
+ }
13
+
14
+ // src/types/segment.ts
15
+ function segment(a, b) {
16
+ return { a, b };
17
+ }
18
+
19
+ // src/types/path.ts
20
+ function normalizeRing(ring, eps = 1e-9) {
21
+ if (ring.length < 2) return ring.map(cloneVec2);
22
+ const first = ring[0];
23
+ const last = ring[ring.length - 1];
24
+ if (first && last && equalsVec2(first, last, eps)) {
25
+ return ring.slice(0, -1).map(cloneVec2);
26
+ }
27
+ return ring.map(cloneVec2);
28
+ }
29
+
30
+ // src/segmentSet/index.ts
31
+ function f32(n) {
32
+ return Math.fround(n);
33
+ }
34
+ function parallelSegments(centerX, centerY, length, spacing, angle, n) {
35
+ const edges = [];
36
+ if (n < 1) return edges;
37
+ const center = vec2(f32(centerX), f32(centerY));
38
+ let dx = f32(Math.cos(angle + Math.PI / 2) * spacing);
39
+ let dy = f32(Math.sin(angle + Math.PI / 2) * spacing);
40
+ const cos = f32(Math.cos(angle));
41
+ const sin = f32(Math.sin(angle));
42
+ const l = f32(length);
43
+ let i;
44
+ let offX = 0;
45
+ let offY = 0;
46
+ if (n % 2 === 1) {
47
+ const a = vec2(f32(center.x + cos * l), f32(center.y + sin * l));
48
+ const b = vec2(f32(center.x + cos * -l), f32(center.y + sin * -l));
49
+ edges.push(segment(a, b));
50
+ i = 1;
51
+ } else {
52
+ dx = f32(dx / 2);
53
+ dy = f32(dy / 2);
54
+ i = 1;
55
+ {
56
+ let a = vec2(f32(center.x + dx * i), f32(center.y + dy * i));
57
+ const b = vec2(f32(a.x + cos * l), f32(a.y + sin * l));
58
+ a = vec2(f32(a.x + cos * -l), f32(a.y + sin * -l));
59
+ edges.push(segment(a, b));
60
+ }
61
+ {
62
+ let a = vec2(f32(center.x + dx * -i), f32(center.y + dy * -i));
63
+ const b = vec2(f32(a.x + cos * l), f32(a.y + sin * l));
64
+ a = vec2(f32(a.x + cos * -l), f32(a.y + sin * -l));
65
+ edges.push(segment(a, b));
66
+ }
67
+ i = 2;
68
+ dx = f32(dx * 2);
69
+ dy = f32(dy * 2);
70
+ offX = f32(dx / 2);
71
+ offY = f32(dy / 2);
72
+ }
73
+ for (; i < 1 + Math.floor(n / 2); i++) {
74
+ {
75
+ let a = vec2(
76
+ f32(center.x + dx * i - offX),
77
+ f32(center.y + dy * i - offY)
78
+ );
79
+ const b = vec2(f32(a.x + cos * l), f32(a.y + sin * l));
80
+ a = vec2(f32(a.x + cos * -l), f32(a.y + sin * -l));
81
+ edges.push(segment(a, b));
82
+ }
83
+ {
84
+ let a = vec2(
85
+ f32(center.x + dx * -i + offX),
86
+ f32(center.y + dy * -i + offY)
87
+ );
88
+ const b = vec2(f32(a.x + cos * l), f32(a.y + sin * l));
89
+ a = vec2(f32(a.x + cos * -l), f32(a.y + sin * -l));
90
+ edges.push(segment(a, b));
91
+ }
92
+ }
93
+ return edges;
94
+ }
95
+ FillRule.NonZero;
96
+ function ringToPathD(ring) {
97
+ const pts = [];
98
+ for (const v of normalizeRing(ring)) {
99
+ pts.push({ x: v.x, y: v.y, z: 0 });
100
+ }
101
+ return pts;
102
+ }
103
+ function pointInRing(point, ring) {
104
+ const result = pointInPolygonD(
105
+ { x: point.x, y: point.y, z: 0 },
106
+ ringToPathD(ring)
107
+ );
108
+ return result === PointInPolygonResult.IsInside || result === PointInPolygonResult.IsOn;
109
+ }
110
+
111
+ // src/predicates/index.ts
112
+ function containsPoint(p, point) {
113
+ if (!p.closed || !p.rings[0]) return false;
114
+ if (!pointInRing(point, p.rings[0])) return false;
115
+ for (let i = 1; i < p.rings.length; i++) {
116
+ const hole = p.rings[i];
117
+ if (hole && pointInRing(point, hole)) return false;
118
+ }
119
+ return true;
120
+ }
121
+
122
+ // src/segmentSet/clip.ts
123
+ function clipSegmentToPath(seg, clip) {
124
+ if (!clip.closed || !clip.rings[0]) return [];
125
+ const hits = [];
126
+ const pushHit = (t, p) => {
127
+ if (t < -1e-9 || t > 1 + 1e-9) return;
128
+ const tt = Math.min(1, Math.max(0, t));
129
+ for (const h of hits) {
130
+ if (Math.abs(h.t - tt) < 1e-9) return;
131
+ }
132
+ hits.push({ t: tt, p });
133
+ };
134
+ if (containsPoint(clip, seg.a)) pushHit(0, seg.a);
135
+ if (containsPoint(clip, seg.b)) pushHit(1, seg.b);
136
+ for (const ring of clip.rings) {
137
+ for (let i = 0; i < ring.length; i++) {
138
+ const e0 = ring[i];
139
+ const e1 = ring[(i + 1) % ring.length];
140
+ const hit = segmentIntersection(seg.a, seg.b, e0, e1);
141
+ if (hit) pushHit(hit.t, hit.p);
142
+ }
143
+ }
144
+ hits.sort((a, b) => a.t - b.t);
145
+ if (hits.length < 2) return [];
146
+ const out = [];
147
+ for (let i = 0; i + 1 < hits.length; i++) {
148
+ const h0 = hits[i];
149
+ const h1 = hits[i + 1];
150
+ if (h1.t - h0.t < 1e-9) continue;
151
+ const mid = vec2((h0.p.x + h1.p.x) / 2, (h0.p.y + h1.p.y) / 2);
152
+ if (containsPoint(clip, mid)) {
153
+ out.push(segment(h0.p, h1.p));
154
+ }
155
+ }
156
+ return out;
157
+ }
158
+ function clipSegmentsToPath(segments, clip) {
159
+ const out = [];
160
+ for (const s of segments) {
161
+ out.push(...clipSegmentToPath(s, clip));
162
+ }
163
+ return out;
164
+ }
165
+ function segmentIntersection(a, b, c, d) {
166
+ const rX = b.x - a.x;
167
+ const rY = b.y - a.y;
168
+ const sX = d.x - c.x;
169
+ const sY = d.y - c.y;
170
+ const denom = rX * sY - rY * sX;
171
+ if (Math.abs(denom) < 1e-12) return null;
172
+ const t = ((c.x - a.x) * sY - (c.y - a.y) * sX) / denom;
173
+ const u = ((c.x - a.x) * rY - (c.y - a.y) * rX) / denom;
174
+ if (t < -1e-9 || t > 1 + 1e-9 || u < -1e-9 || u > 1 + 1e-9) return null;
175
+ return {
176
+ t,
177
+ p: vec2(a.x + t * rX, a.y + t * rY)
178
+ };
179
+ }
180
+
181
+ // src/hatch/index.ts
182
+ function parallel(path2, options = {}) {
183
+ if (!path2.closed || !path2.rings[0]) {
184
+ throw new Error("hatch.parallel requires a closed path");
185
+ }
186
+ const bounds = pathBounds(path2);
187
+ const center = options.center ?? {
188
+ x: (bounds.minX + bounds.maxX) / 2,
189
+ y: (bounds.minY + bounds.maxY) / 2
190
+ };
191
+ const diag = Math.hypot(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY);
192
+ const length = options.length ?? Math.max(diag, 1e-6);
193
+ const spacing = Math.max(options.spacing ?? 0.15, 1e-9);
194
+ const angle = options.angle ?? Math.PI / 4;
195
+ const count = options.count ?? hatchLineCount(bounds, spacing, angle);
196
+ const raw = parallelSegments(
197
+ center.x,
198
+ center.y,
199
+ length,
200
+ spacing,
201
+ angle,
202
+ count
203
+ );
204
+ return clipSegmentsToPath(raw, path2);
205
+ }
206
+ function cross(path2, options = {}) {
207
+ const angle = options.angle ?? Math.PI / 4;
208
+ const a = parallel(path2, options);
209
+ const b = parallel(path2, { ...options, angle: angle + Math.PI / 2 });
210
+ return [...a, ...b];
211
+ }
212
+ function hatchLineCount(bounds, spacing, angle) {
213
+ const nx = Math.cos(angle + Math.PI / 2);
214
+ const ny = Math.sin(angle + Math.PI / 2);
215
+ const corners = [
216
+ [bounds.minX, bounds.minY],
217
+ [bounds.maxX, bounds.minY],
218
+ [bounds.maxX, bounds.maxY],
219
+ [bounds.minX, bounds.maxY]
220
+ ];
221
+ let minP = Infinity;
222
+ let maxP = -Infinity;
223
+ for (const [x, y] of corners) {
224
+ const p = x * nx + y * ny;
225
+ minP = Math.min(minP, p);
226
+ maxP = Math.max(maxP, p);
227
+ }
228
+ const extent = maxP - minP;
229
+ return Math.max(1, Math.ceil(extent / spacing) + 1);
230
+ }
231
+ function pathBounds(path2) {
232
+ let minX = Infinity;
233
+ let minY = Infinity;
234
+ let maxX = -Infinity;
235
+ let maxY = -Infinity;
236
+ for (const ring of path2.rings) {
237
+ for (const v of ring) {
238
+ minX = Math.min(minX, v.x);
239
+ minY = Math.min(minY, v.y);
240
+ maxX = Math.max(maxX, v.x);
241
+ maxY = Math.max(maxY, v.y);
242
+ }
243
+ }
244
+ return { minX, minY, maxX, maxY };
245
+ }
246
+ var hatch = {
247
+ parallel,
248
+ cross
249
+ };
250
+
251
+ export { cross, hatch, parallel };
252
+ //# sourceMappingURL=index.js.map
253
+ //# sourceMappingURL=index.js.map