@thi.ng/geom 6.1.9 → 7.0.1

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 (85) hide show
  1. package/CHANGELOG.md +114 -1
  2. package/README.md +34 -29
  3. package/api/arc.js +1 -1
  4. package/api/complex-polygon.d.ts +14 -0
  5. package/api/complex-polygon.js +44 -0
  6. package/api/path.d.ts +11 -3
  7. package/api/path.js +45 -29
  8. package/apply-transforms.js +5 -10
  9. package/arc-length.d.ts +1 -0
  10. package/arc-length.js +12 -0
  11. package/arc.d.ts +51 -1
  12. package/arc.js +12 -1
  13. package/area.d.ts +1 -0
  14. package/area.js +4 -1
  15. package/as-cubic.d.ts +2 -0
  16. package/as-cubic.js +10 -2
  17. package/as-path.d.ts +19 -3
  18. package/as-path.js +54 -1
  19. package/as-polygon.d.ts +7 -5
  20. package/as-polygon.js +14 -1
  21. package/as-polyline.d.ts +6 -4
  22. package/as-polyline.js +19 -5
  23. package/bounds.js +14 -7
  24. package/centroid.d.ts +3 -2
  25. package/centroid.js +10 -1
  26. package/classify-point.d.ts +5 -2
  27. package/clip-convex.d.ts +22 -3
  28. package/clip-convex.js +39 -12
  29. package/closest-point.d.ts +2 -0
  30. package/closest-point.js +34 -0
  31. package/complex-polygon-from-path.d.ts +14 -0
  32. package/complex-polygon-from-path.js +9 -0
  33. package/complex-polygon.d.ts +23 -0
  34. package/complex-polygon.js +6 -0
  35. package/convex-hull.d.ts +1 -0
  36. package/convex-hull.js +2 -0
  37. package/edges.d.ts +2 -0
  38. package/edges.js +7 -3
  39. package/fit-into-bounds.js +5 -10
  40. package/flip.d.ts +1 -0
  41. package/flip.js +5 -0
  42. package/index.d.ts +5 -0
  43. package/index.js +5 -0
  44. package/internal/bounds.js +3 -6
  45. package/internal/copy.d.ts +3 -1
  46. package/internal/copy.js +9 -3
  47. package/internal/transform.d.ts +20 -7
  48. package/internal/transform.js +10 -0
  49. package/intersects.js +3 -6
  50. package/package.json +48 -33
  51. package/path-builder.d.ts +21 -1
  52. package/path-builder.js +31 -14
  53. package/path-from-svg.d.ts +13 -1
  54. package/path-from-svg.js +10 -14
  55. package/path.d.ts +59 -2
  56. package/path.js +44 -19
  57. package/point-inside.d.ts +12 -9
  58. package/point-inside.js +3 -0
  59. package/proximity.d.ts +11 -0
  60. package/proximity.js +9 -0
  61. package/resample.d.ts +1 -0
  62. package/resample.js +7 -1
  63. package/rotate.d.ts +1 -0
  64. package/rotate.js +13 -10
  65. package/scale-with-center.d.ts +20 -0
  66. package/scale-with-center.js +7 -0
  67. package/scale.d.ts +1 -0
  68. package/scale.js +12 -9
  69. package/scatter.js +1 -2
  70. package/simplify.d.ts +4 -3
  71. package/simplify.js +39 -27
  72. package/split-arclength.d.ts +1 -1
  73. package/split-arclength.js +4 -6
  74. package/subdiv-curve.d.ts +5 -1
  75. package/subdiv-curve.js +10 -8
  76. package/transform-vertices.d.ts +1 -0
  77. package/transform-vertices.js +19 -19
  78. package/transform.d.ts +1 -0
  79. package/transform.js +17 -16
  80. package/translate.d.ts +1 -0
  81. package/translate.js +17 -12
  82. package/vertices.d.ts +18 -17
  83. package/vertices.js +22 -11
  84. package/with-attribs.d.ts +1 -1
  85. package/with-attribs.js +1 -1
package/area.js CHANGED
@@ -14,9 +14,12 @@ const area = defmulti(
14
14
  ($) => 0.5 * Math.abs($.start - $.end) * $.r[0] * $.r[1]
15
15
  ),
16
16
  circle: ($) => PI * $.r ** 2,
17
+ complexpoly: ($, signed) => area($.boundary, signed) - $.children.reduce((acc, c) => acc + area(c, signed), 0),
17
18
  ellipse: ($) => PI * $.r[0] * $.r[1],
18
19
  group: ({ children }) => children.reduce((sum, $) => sum + area($, false), 0),
19
- path: ($) => $.closed ? area(asPolygon($)) : 0,
20
+ path: ($, signed) => {
21
+ return $.closed ? asPolygon($).reduce((acc, p) => acc + area(p, signed), 0) : 0;
22
+ },
20
23
  plane: () => Infinity,
21
24
  poly: ($, signed) => {
22
25
  const area2 = polyArea2($.points);
package/as-cubic.d.ts CHANGED
@@ -10,6 +10,7 @@ import { Cubic } from "./api/cubic.js";
10
10
  *
11
11
  * - {@link Arc}
12
12
  * - {@link Circle}
13
+ * - {@link ComplexPolygon}
13
14
  * - {@link Cubic}
14
15
  * - {@link Ellipse}
15
16
  * - {@link Group}
@@ -27,6 +28,7 @@ import { Cubic } from "./api/cubic.js";
27
28
  * for more details):
28
29
  *
29
30
  * - {@link Group} (only used for eligible children)
31
+ * - {@link ComplexPolygon}
30
32
  * - {@link Polygon}
31
33
  * - {@link Polyline}
32
34
  * - {@link Quad}
package/as-cubic.js CHANGED
@@ -8,6 +8,8 @@ import {
8
8
  openCubicFromControlPoints
9
9
  } from "@thi.ng/geom-splines/cubic-from-controlpoints";
10
10
  import { TAU } from "@thi.ng/math/api";
11
+ import { concat } from "@thi.ng/transducers/concat";
12
+ import { flatten1 } from "@thi.ng/transducers/flatten1";
11
13
  import { mapcat } from "@thi.ng/transducers/mapcat";
12
14
  import { Cubic } from "./api/cubic.js";
13
15
  import { arc } from "./arc.js";
@@ -25,6 +27,9 @@ const asCubic = defmulti(
25
27
  {
26
28
  arc: cubicFromArc,
27
29
  circle: ($) => asCubic(arc($.pos, $.r, 0, 0, TAU, true, true)),
30
+ complexpoly: ($, opts = {}) => [
31
+ ...mapcat((x) => asCubic(x, opts), [$.boundary, ...$.children])
32
+ ],
28
33
  cubic: ($) => [$],
29
34
  group: ($, opts) => [
30
35
  ...mapcat((x) => asCubic(x, opts), $.children)
@@ -33,7 +38,10 @@ const asCubic = defmulti(
33
38
  cubicFromLine(points[0], points[1], { ...attribs })
34
39
  ],
35
40
  path: ($) => [
36
- ...mapcat((s) => s.geo ? asCubic(s.geo) : null, $.segments)
41
+ ...mapcat(
42
+ (segment) => segment.geo ? asCubic(segment.geo) : null,
43
+ concat($.segments, flatten1($.subPaths))
44
+ )
37
45
  ],
38
46
  poly: ($, opts = {}) => __polyCubic(
39
47
  $,
@@ -50,7 +58,7 @@ const asCubic = defmulti(
50
58
  quadratic: ({ attribs, points }) => [
51
59
  cubicFromQuadratic(points[0], points[1], points[2], { ...attribs })
52
60
  ],
53
- rect: ($, opts) => asCubic(asPolygon($), opts)
61
+ rect: ($, opts) => asCubic(asPolygon($)[0], opts)
54
62
  }
55
63
  );
56
64
  const __polyCubic = ($, opts, breakPoints, controlPoints) => {
package/as-path.d.ts CHANGED
@@ -1,10 +1,26 @@
1
- import type { Attribs, IShape } from "@thi.ng/geom-api";
1
+ import type { Maybe } from "@thi.ng/api";
2
+ import type { MultiFn2O } from "@thi.ng/defmulti";
3
+ import type { Attribs, CubicOpts, IShape } from "@thi.ng/geom-api";
4
+ import { Path } from "./api/path.js";
5
+ export interface AsPathOpts extends CubicOpts {
6
+ /**
7
+ * If true (default: false), creates path consisting of linear segments
8
+ * only.
9
+ */
10
+ linear: boolean;
11
+ }
2
12
  /**
3
- * Converts given shape into a {@link Path} (via {@link asCubic} and
13
+ * Converts given shape into a {@link Path} (by default via {@link asCubic} and
4
14
  * {@link pathFromCubics}).
5
15
  *
16
+ * @remarks
17
+ * If {@link AsPathOpts.linear} is enabled the shape will be converted into a
18
+ * path consisting only of linear segments (NOT cubic beziers). As an interim
19
+ * step this will involve a conversion via {@link asPolygon} or
20
+ * {@link asPolyline} with default opts.
21
+ *
6
22
  * @param src
7
23
  * @param attribs
8
24
  */
9
- export declare const asPath: (src: IShape, attribs?: Attribs) => import("./index.js").Path;
25
+ export declare const asPath: ((shape: IShape, opts?: Partial<AsPathOpts>, attribs?: Attribs) => Path) & MultiFn2O<IShape, Maybe<Partial<AsPathOpts>>, Attribs, Path>;
10
26
  //# sourceMappingURL=as-path.d.ts.map
package/as-path.js CHANGED
@@ -1,7 +1,60 @@
1
+ import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti";
2
+ import { copy } from "@thi.ng/vectors/copy";
3
+ import { Line } from "./api/line.js";
4
+ import { Path } from "./api/path.js";
1
5
  import { asCubic } from "./as-cubic.js";
6
+ import { asPolygon } from "./as-polygon.js";
7
+ import { asPolyline } from "./as-polyline.js";
2
8
  import { __copyAttribs } from "./internal/copy.js";
9
+ import { __dispatch } from "./internal/dispatch.js";
3
10
  import { pathFromCubics } from "./path.js";
4
- const asPath = (src, attribs) => pathFromCubics(asCubic(src), attribs || __copyAttribs(src));
11
+ const asPath = defmulti(
12
+ __dispatch,
13
+ {
14
+ line: "polyline",
15
+ quad: "poly",
16
+ tri: "poly"
17
+ },
18
+ {
19
+ [DEFAULT]: (src, opts, attribs) => opts?.linear ? asPath(asPolygon(src)[0], opts, attribs || __copyAttribs(src)) : __defaultImpl(src, opts, attribs),
20
+ arc: ($, opts, attribs) => opts?.linear ? asPath(asPolyline($)[0], opts, attribs || __copyAttribs($)) : __defaultImpl($, opts, attribs),
21
+ complexpoly: ($, opts, attribs) => {
22
+ attribs = attribs || __copyAttribs($);
23
+ if (opts?.linear) {
24
+ return __linearPath(
25
+ $.boundary,
26
+ $.children.map((c) => __lineSegments(c.points, true)),
27
+ attribs,
28
+ true
29
+ );
30
+ }
31
+ const res = pathFromCubics(asCubic($.boundary, opts), attribs);
32
+ for (let child of $.children) {
33
+ res.addSubPaths(pathFromCubics(asCubic(child, opts)).segments);
34
+ }
35
+ return res;
36
+ },
37
+ poly: ($, opts, attribs) => opts?.linear ? __linearPath($, [], attribs, true) : __defaultImpl($, opts, attribs),
38
+ polyline: ($, opts, attribs) => opts?.linear ? __linearPath($, [], attribs, false) : __defaultImpl($, opts, attribs)
39
+ }
40
+ );
41
+ const __defaultImpl = (src, opts, attribs) => pathFromCubics(asCubic(src, opts), attribs || __copyAttribs(src));
42
+ const __lineSegments = (points, closed) => {
43
+ if (!points.length) return [];
44
+ const segments = [{ type: "m", point: copy(points[0]) }];
45
+ for (let i = 1, n = points.length; i < n; i++)
46
+ segments.push({
47
+ type: "l",
48
+ geo: new Line([copy(points[i - 1]), copy(points[i])])
49
+ });
50
+ if (closed) segments.push({ type: "z" });
51
+ return segments;
52
+ };
53
+ const __linearPath = (shape, subPaths, attribs, closed = false) => new Path(
54
+ __lineSegments(shape.points, closed),
55
+ subPaths,
56
+ attribs || __copyAttribs(shape)
57
+ );
5
58
  export {
6
59
  asPath
7
60
  };
package/as-polygon.d.ts CHANGED
@@ -2,19 +2,21 @@ import type { MultiFn1O } from "@thi.ng/defmulti";
2
2
  import type { IShape, SamplingOpts } from "@thi.ng/geom-api";
3
3
  import { Polygon } from "./api/polygon.js";
4
4
  /**
5
- * Converts given shape into a {@link Polygon}, optionally using provided
5
+ * Converts given shape into an array of {@link Polygon}s, optionally using
6
+ * provided
6
7
  * [`SamplingOpts`](https://docs.thi.ng/umbrella/geom-api/interfaces/SamplingOpts.html)
7
8
  * or number of target vertices.
8
9
  *
9
10
  * @remarks
10
- * If the shape has a `__samples` attribute, it will be removed in the result to
11
- * avoid recursive application.
11
+ * If the input shape has a `__samples` attribute, it will be removed in the
12
+ * results polys to avoid recursive application.
12
13
  *
13
14
  * Currently implemented for:
14
15
  *
15
16
  * - {@link Circle}
17
+ * - {@link ComplexPolygon}
16
18
  * - {@link Ellipse}
17
- * - {@link Line}
19
+ * - {@link Line} (will be closed)
18
20
  * - {@link Path}
19
21
  * - {@link Polygon}
20
22
  * - {@link Polyline} (will be closed)
@@ -25,5 +27,5 @@ import { Polygon } from "./api/polygon.js";
25
27
  * @param shape
26
28
  * @param opts
27
29
  */
28
- export declare const asPolygon: MultiFn1O<IShape, number | Partial<SamplingOpts>, Polygon>;
30
+ export declare const asPolygon: MultiFn1O<IShape, number | Partial<SamplingOpts>, Polygon[]>;
29
31
  //# sourceMappingURL=as-polygon.d.ts.map
package/as-polygon.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { defmulti } from "@thi.ng/defmulti/defmulti";
2
+ import { Path } from "./api/path.js";
2
3
  import { Polygon } from "./api/polygon.js";
3
4
  import { __copyAttribsNoSamples as __attribs } from "./internal/copy.js";
4
5
  import { __dispatch } from "./internal/dispatch.js";
@@ -17,7 +18,19 @@ const asPolygon = defmulti(
17
18
  tri: "points"
18
19
  },
19
20
  {
20
- points: ($, opts) => new Polygon(vertices($, opts), __attribs($))
21
+ complexpoly: ($, opts) => [$.boundary, ...$.children].map(
22
+ (x) => new Polygon(vertices(x, opts), __attribs($))
23
+ ),
24
+ path: ($, opts) => {
25
+ const tmp = new Path();
26
+ return [$.segments, ...$.subPaths].map((segments) => {
27
+ tmp.segments = segments;
28
+ return new Polygon(vertices(tmp, opts), __attribs($));
29
+ });
30
+ },
31
+ points: ($, opts) => [
32
+ new Polygon(vertices($, opts), __attribs($))
33
+ ]
21
34
  }
22
35
  );
23
36
  export {
package/as-polyline.d.ts CHANGED
@@ -2,18 +2,20 @@ import type { MultiFn1O } from "@thi.ng/defmulti";
2
2
  import type { IShape, SamplingOpts } from "@thi.ng/geom-api";
3
3
  import { Polyline } from "./api/polyline.js";
4
4
  /**
5
- * Converts given shape into a {@link Polyline}, optionally using provided
5
+ * Converts given shape into an array of {@link Polyline}s, optionally using
6
+ * provided
6
7
  * [`SamplingOpts`](https://docs.thi.ng/umbrella/geom-api/interfaces/SamplingOpts.html)
7
8
  * or number of target vertices.
8
9
  *
9
10
  * @remarks
10
- * If the shape has a `__samples` attribute, it will be removed in the result to
11
- * avoid recursive application.
11
+ * If the shape has a `__samples` attribute, it will be removed in the results
12
+ * to avoid recursive application.
12
13
  *
13
14
  * Currently implemented for:
14
15
  *
15
16
  * - {@link Arc}
16
17
  * - {@link Circle}
18
+ * - {@link ComplexPolygon}
17
19
  * - {@link Cubic}
18
20
  * - {@link Ellipse}
19
21
  * - {@link Line}
@@ -28,5 +30,5 @@ import { Polyline } from "./api/polyline.js";
28
30
  * @param shape
29
31
  * @param opts
30
32
  */
31
- export declare const asPolyline: MultiFn1O<IShape, number | Partial<SamplingOpts>, Polyline>;
33
+ export declare const asPolyline: MultiFn1O<IShape, number | Partial<SamplingOpts>, Polyline[]>;
32
34
  //# sourceMappingURL=as-polyline.d.ts.map
package/as-polyline.js CHANGED
@@ -1,5 +1,8 @@
1
+ import { peek } from "@thi.ng/arrays/peek";
1
2
  import { defmulti } from "@thi.ng/defmulti/defmulti";
3
+ import { mapcat } from "@thi.ng/transducers/mapcat";
2
4
  import { set } from "@thi.ng/vectors/set";
5
+ import { Path } from "./api/path.js";
3
6
  import { Polyline } from "./api/polyline.js";
4
7
  import { __copyAttribsNoSamples as __attribs } from "./internal/copy.js";
5
8
  import { __dispatch } from "./internal/dispatch.js";
@@ -19,16 +22,27 @@ const asPolyline = defmulti(
19
22
  tri: "poly"
20
23
  },
21
24
  {
22
- points: ($, opts) => new Polyline(vertices($, opts), __attribs($)),
25
+ complexpoly: ($, opts) => [
26
+ ...asPolyline($.boundary, opts),
27
+ ...mapcat((child) => asPolyline(child, opts), $.children)
28
+ ],
29
+ group: ($, opts) => [
30
+ ...mapcat((child) => asPolyline(child, opts), $.children)
31
+ ],
32
+ points: ($, opts) => [new Polyline(vertices($, opts), __attribs($))],
23
33
  path: ($, opts) => {
24
- const pts = vertices($, opts);
25
- $.closed && pts.push(set([], pts[0]));
26
- return new Polyline(pts, __attribs($));
34
+ const tmp = new Path();
35
+ return [$.segments, ...$.subPaths].map((segments) => {
36
+ tmp.segments = segments;
37
+ const pts = vertices(tmp, opts);
38
+ peek(segments).type === "z" && pts.push(set([], pts[0]));
39
+ return new Polyline(pts, __attribs($));
40
+ });
27
41
  },
28
42
  poly: ($, opts) => {
29
43
  const pts = vertices($, opts);
30
44
  pts.push(set([], pts[0]));
31
- return new Polyline(pts, __attribs($));
45
+ return [new Polyline(pts, __attribs($))];
32
46
  }
33
47
  }
34
48
  );
package/bounds.js CHANGED
@@ -7,6 +7,7 @@ import { comp } from "@thi.ng/transducers/comp";
7
7
  import { filter } from "@thi.ng/transducers/filter";
8
8
  import { iterator1 } from "@thi.ng/transducers/iterator";
9
9
  import { map } from "@thi.ng/transducers/map";
10
+ import { mapcat } from "@thi.ng/transducers/mapcat";
10
11
  import { addN2 } from "@thi.ng/vectors/addn";
11
12
  import { max } from "@thi.ng/vectors/max";
12
13
  import { min } from "@thi.ng/vectors/min";
@@ -38,6 +39,10 @@ const bounds = defmulti(
38
39
  subN2([], $.pos, $.r + margin),
39
40
  mulN2(null, [2, 2], $.r + margin)
40
41
  ),
42
+ complexpoly: ($, margin = 0) => {
43
+ const res = __collBounds([$.boundary, ...$.children], bounds);
44
+ return res ? new Rect(...res).offset(margin) : void 0;
45
+ },
41
46
  cubic: ({ points }, margin = 0) => rectFromMinMaxWithMargin(
42
47
  ...cubicBounds(points[0], points[1], points[2], points[3]),
43
48
  margin
@@ -52,15 +57,17 @@ const bounds = defmulti(
52
57
  },
53
58
  line: ({ points: [a, b] }, margin = 0) => rectFromMinMaxWithMargin(min([], a, b), max([], a, b), margin),
54
59
  path: (path, margin = 0) => {
60
+ const $segmentGeo = (segments) => iterator1(
61
+ comp(
62
+ map((s) => s.geo),
63
+ filter((s) => !!s)
64
+ ),
65
+ segments
66
+ );
55
67
  const b = __collBounds(
56
68
  [
57
- ...iterator1(
58
- comp(
59
- map((s) => s.geo),
60
- filter((s) => !!s)
61
- ),
62
- path.segments
63
- )
69
+ ...$segmentGeo(path.segments),
70
+ ...mapcat($segmentGeo, path.subPaths)
64
71
  ],
65
72
  bounds
66
73
  );
package/centroid.d.ts CHANGED
@@ -3,8 +3,8 @@ import type { MultiFn1O } from "@thi.ng/defmulti";
3
3
  import type { IShape } from "@thi.ng/geom-api";
4
4
  import type { Vec } from "@thi.ng/vectors";
5
5
  /**
6
- * Computes centroid of given shape, writes result in optionally provided output
7
- * vector (or creates new one if omitted).
6
+ * Computes (possibly weighted) centroid of given shape, writes result in
7
+ * optionally provided output vector (or creates new one if omitted).
8
8
  *
9
9
  * @remarks
10
10
  * Currently implemented for:
@@ -13,6 +13,7 @@ import type { Vec } from "@thi.ng/vectors";
13
13
  * - {@link Arc}
14
14
  * - {@link BPatch}
15
15
  * - {@link Circle}
16
+ * - {@link ComplexPolygon}
16
17
  * - {@link Cubic}
17
18
  * - {@link Ellipse}
18
19
  * - {@link Group}
package/centroid.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { defmulti } from "@thi.ng/defmulti/defmulti";
2
- import { centerOfWeight2 } from "@thi.ng/geom-poly-utils/center-of-weight";
2
+ import {
3
+ centerOfWeight2,
4
+ complexCenterOfWeight2
5
+ } from "@thi.ng/geom-poly-utils/center-of-weight";
3
6
  import { centroid as _centroid } from "@thi.ng/geom-poly-utils/centroid";
4
7
  import { add } from "@thi.ng/vectors/add";
5
8
  import { addmN } from "@thi.ng/vectors/addmn";
@@ -17,6 +20,7 @@ const centroid = defmulti(
17
20
  bpatch: "points",
18
21
  ellipse: "circle",
19
22
  line3: "line",
23
+ path: "group",
20
24
  points3: "points",
21
25
  polyline: "points",
22
26
  quad: "poly",
@@ -26,6 +30,11 @@ const centroid = defmulti(
26
30
  },
27
31
  {
28
32
  circle: ($, out) => set(out || [], $.pos),
33
+ complexpoly: ($, out) => complexCenterOfWeight2(
34
+ $.boundary.points,
35
+ $.children.map((c) => c.points),
36
+ out
37
+ ),
29
38
  group: ($, out) => {
30
39
  const b = bounds($);
31
40
  return b ? centroid(b, out) : void 0;
@@ -10,7 +10,7 @@ import type { ReadonlyVec } from "@thi.ng/vectors";
10
10
  * Currently only implemented for:
11
11
  *
12
12
  * - {@link Circle}
13
- * - {@link Plane}
13
+ * - {@link Plane} (-1 = below, +1 = above)
14
14
  * - {@link Sphere}
15
15
  * - {@link Triangle}
16
16
  *
@@ -18,7 +18,10 @@ import type { ReadonlyVec } from "@thi.ng/vectors";
18
18
  * more comprehensive feature set (incl. support for more shapes) to perform
19
19
  * similar checks as this function.
20
20
  *
21
- * Also see {@link pointInside}.
21
+ * Also see:
22
+ * - {@link closestPoint}
23
+ * - {@link pointInside}
24
+ * - {@link proximity}
22
25
  *
23
26
  * @param shape
24
27
  * @param p
package/clip-convex.d.ts CHANGED
@@ -3,9 +3,10 @@ import type { MultiFn2 } from "@thi.ng/defmulti";
3
3
  import type { IShape } from "@thi.ng/geom-api";
4
4
  import type { ReadonlyVec } from "@thi.ng/vectors";
5
5
  /**
6
- * Takes a shape and a boundary (both convex). Uses the Sutherland-Hodgman
7
- * algorithm to compute a clipped version of the first shape (against the
8
- * boundary). Returns `undefined` if there're no remaining result vertices.
6
+ * Takes a shape and a boundary (both convex), then uses the Sutherland-Hodgeman
7
+ * algorithm to compute a clipped version of the shape (against the boundary)
8
+ * and returns resulting clipped shape or `undefined` if there're no remaining
9
+ * result vertices (i.e. if the original shape was clipped entirely).
9
10
  *
10
11
  * @remarks
11
12
  * Internally uses
@@ -13,6 +14,24 @@ import type { ReadonlyVec } from "@thi.ng/vectors";
13
14
  * For groups, calls itself for each child shape individually and returns a new
14
15
  * group of results (if any).
15
16
  *
17
+ * For {@link ComplexPolygon}s, children are only processed if the main boundary
18
+ * hasn't been completely clipped. Similarly for paths, where sub-paths are only
19
+ * processed if the main path has remaining vertices. Paths are
20
+ * sampled/converted to polygons and are only processed if the main path is
21
+ * {@link Path.closed}.
22
+ *
23
+ * Currently implemented for:
24
+ *
25
+ * - {@link Circle}
26
+ * - {@link ComplexPolygon}
27
+ * - {@link Ellipse}
28
+ * - {@link Group}
29
+ * - {@link Line}
30
+ * - {@link Path}
31
+ * - {@link Polygon}
32
+ * - {@link Quad}
33
+ * - {@link Triangle}
34
+ *
16
35
  * @param shape
17
36
  * @param boundary
18
37
  */
package/clip-convex.js CHANGED
@@ -2,29 +2,46 @@ import { defmulti } from "@thi.ng/defmulti/defmulti";
2
2
  import { clipLineSegmentPoly } from "@thi.ng/geom-clip-line/clip-poly";
3
3
  import { sutherlandHodgeman } from "@thi.ng/geom-clip-poly";
4
4
  import { centroid } from "@thi.ng/geom-poly-utils/centroid";
5
+ import { ComplexPolygon } from "./api/complex-polygon.js";
5
6
  import { Group } from "./api/group.js";
6
7
  import { Line } from "./api/line.js";
8
+ import { Path } from "./api/path.js";
7
9
  import { Polygon } from "./api/polygon.js";
8
10
  import { __copyAttribs } from "./internal/copy.js";
9
11
  import { __dispatch } from "./internal/dispatch.js";
10
12
  import { ensureVertices, vertices } from "./vertices.js";
13
+ const __clipVertices = ($, boundary) => {
14
+ boundary = ensureVertices(boundary);
15
+ const pts = sutherlandHodgeman(vertices($), boundary, centroid(boundary));
16
+ return pts.length ? new Polygon(pts, __copyAttribs($)) : void 0;
17
+ };
11
18
  const clipConvex = defmulti(
12
19
  __dispatch,
13
20
  {
14
21
  circle: "rect",
15
22
  ellipse: "rect",
16
- path: "rect",
17
23
  quad: "poly",
18
24
  tri: "poly"
19
25
  },
20
26
  {
27
+ complexpoly: ($, boundary) => {
28
+ boundary = ensureVertices(boundary);
29
+ const c = centroid(boundary);
30
+ let clipped = sutherlandHodgeman($.boundary.points, boundary, c);
31
+ if (!clipped.length) return void 0;
32
+ const res = [new Polygon(clipped)];
33
+ for (let child of $.children) {
34
+ clipped = sutherlandHodgeman(child.points, boundary, c);
35
+ if (clipped.length) res.push(new Polygon(clipped));
36
+ }
37
+ return new ComplexPolygon(res[0], res.slice(1), __copyAttribs($));
38
+ },
21
39
  group: ({ children, attribs }, boundary) => {
22
40
  boundary = ensureVertices(boundary);
23
41
  const clipped = [];
24
42
  for (let c of children) {
25
43
  const res = clipConvex(c, boundary);
26
- if (res)
27
- clipped.push(res);
44
+ if (res) clipped.push(res);
28
45
  }
29
46
  return clipped.length ? new Group({ ...attribs }, clipped) : void 0;
30
47
  },
@@ -36,6 +53,24 @@ const clipConvex = defmulti(
36
53
  );
37
54
  return segments && segments.length ? new Line(segments[0], __copyAttribs($)) : void 0;
38
55
  },
56
+ path: ($, boundary) => {
57
+ if ($.closed) return void 0;
58
+ boundary = ensureVertices(boundary);
59
+ let clipped = __clipVertices($, boundary);
60
+ if (!clipped) return void 0;
61
+ const res = new ComplexPolygon(clipped, [], __copyAttribs($));
62
+ for (let sub of $.subPaths) {
63
+ clipped = __clipVertices(
64
+ new Path(sub, [], __copyAttribs($)),
65
+ boundary
66
+ );
67
+ if (clipped) {
68
+ clipped.attribs = void 0;
69
+ res.addChild(clipped);
70
+ }
71
+ }
72
+ return res;
73
+ },
39
74
  poly: ($, boundary) => {
40
75
  boundary = ensureVertices(boundary);
41
76
  const pts = sutherlandHodgeman(
@@ -45,15 +80,7 @@ const clipConvex = defmulti(
45
80
  );
46
81
  return pts.length ? new Polygon(pts, __copyAttribs($)) : void 0;
47
82
  },
48
- rect: ($, boundary) => {
49
- boundary = ensureVertices(boundary);
50
- const pts = sutherlandHodgeman(
51
- vertices($),
52
- boundary,
53
- centroid(boundary)
54
- );
55
- return pts.length ? new Polygon(pts, __copyAttribs($)) : void 0;
56
- }
83
+ rect: __clipVertices
57
84
  }
58
85
  );
59
86
  export {
@@ -12,8 +12,10 @@ import type { ReadonlyVec, Vec } from "@thi.ng/vectors";
12
12
  * - {@link AABB}
13
13
  * - {@link Arc}
14
14
  * - {@link Circle}
15
+ * - {@link ComplexPolygon}
15
16
  * - {@link Cubic}
16
17
  * - {@link Line}
18
+ * - {@link Path}
17
19
  * - {@link Plane}
18
20
  * - {@link Points}
19
21
  * - {@link Points3}
package/closest-point.js CHANGED
@@ -14,6 +14,8 @@ import { closestPointArray } from "@thi.ng/geom-closest-point/points";
14
14
  import { closestPointCubic } from "@thi.ng/geom-splines/cubic-closest-point";
15
15
  import { closestPointQuadratic } from "@thi.ng/geom-splines/quadratic-closest-point";
16
16
  import { add2, add3 } from "@thi.ng/vectors/add";
17
+ import { distSq2 } from "@thi.ng/vectors/distsq";
18
+ import { set2 } from "@thi.ng/vectors/set";
17
19
  import { __dispatch } from "./internal/dispatch.js";
18
20
  const closestPoint = defmulti(
19
21
  __dispatch,
@@ -27,6 +29,20 @@ const closestPoint = defmulti(
27
29
  aabb: ($, p, out) => closestPointAABB(p, $.pos, add3([], $.pos, $.size), out),
28
30
  arc: ($, p, out) => closestPointArc(p, $.pos, $.r, $.axis, $.start, $.end, out),
29
31
  circle: ($, p, out) => closestPointCircle(p, $.pos, $.r, out),
32
+ complexpoly: ($, p, out) => {
33
+ out = closestPointPolyline(p, $.boundary.points, true, out);
34
+ let minD = distSq2(p, out);
35
+ let tmp = [];
36
+ for (let child of $.children) {
37
+ closestPointPolyline(p, child.points, true, tmp);
38
+ const d = distSq2(p, tmp);
39
+ if (d < minD) {
40
+ minD = d;
41
+ set2(out, tmp);
42
+ }
43
+ }
44
+ return out;
45
+ },
30
46
  cubic: ({ points }, p, out) => closestPointCubic(
31
47
  p,
32
48
  points[0],
@@ -36,6 +52,24 @@ const closestPoint = defmulti(
36
52
  out
37
53
  ),
38
54
  line: ({ points }, p, out) => closestPointSegment(p, points[0], points[1], out),
55
+ path: ($, p, out) => {
56
+ let minD = Infinity;
57
+ const $closestPSegment = (segments) => {
58
+ for (let s of segments) {
59
+ if (!s.geo) continue;
60
+ const q = closestPoint(s.geo, p);
61
+ if (!q) continue;
62
+ const d = distSq2(p, q);
63
+ if (d < minD) {
64
+ minD = d;
65
+ out = set2(out || [], q);
66
+ }
67
+ }
68
+ };
69
+ $closestPSegment($.segments);
70
+ for (let sub of $.subPaths) $closestPSegment(sub);
71
+ return out;
72
+ },
39
73
  plane: ($, p, out) => closestPointPlane(p, $.normal, $.w, out),
40
74
  points: ($, p, out) => closestPointArray(p, $.points, out),
41
75
  poly: ($, p, out) => closestPointPolyline(p, $.points, true, out),
@@ -0,0 +1,14 @@
1
+ import type { Attribs, SamplingOpts } from "@thi.ng/geom-api";
2
+ import { ComplexPolygon } from "./api/complex-polygon.js";
3
+ import type { Path } from "./api/path.js";
4
+ /**
5
+ * Converts given path into a {@link ComplexPolygon}, using `opts` to control
6
+ * the conversion (via {@link asPolygon}). If no new `attribs` are given, those
7
+ * of the path will be used (shallow copy).
8
+ *
9
+ * @param path
10
+ * @param opts
11
+ * @param attribs
12
+ */
13
+ export declare const complexPolygonFromPath: (path: Path, opts?: number | Partial<SamplingOpts>, attribs?: Attribs) => ComplexPolygon;
14
+ //# sourceMappingURL=complex-polygon-from-path.d.ts.map
@@ -0,0 +1,9 @@
1
+ import { ComplexPolygon } from "./api/complex-polygon.js";
2
+ import { asPolygon } from "./as-polygon.js";
3
+ const complexPolygonFromPath = (path, opts, attribs) => {
4
+ const [boundary, ...children] = asPolygon(path, opts);
5
+ return new ComplexPolygon(boundary, children, attribs || boundary.attribs);
6
+ };
7
+ export {
8
+ complexPolygonFromPath
9
+ };
@@ -0,0 +1,23 @@
1
+ import type { Attribs } from "@thi.ng/geom-api";
2
+ import { ComplexPolygon } from "./api/complex-polygon.js";
3
+ import { Polygon } from "./api/polygon.js";
4
+ /**
5
+ * Creates a {@link ComplexPolygon} instance from given `boundary` and `child`
6
+ * polygons (the latter are assumed non-overlapping holes and will be
7
+ * interpreted as such).
8
+ *
9
+ * @remarks
10
+ * Child polygons are considered holes and fully enclosed by the boundary poly.
11
+ * Depending on usage, holes should also have the opposite vertex order (e.g.
12
+ * via {@link flip}) than the boundary. This is not enforced automatically and
13
+ * the user's responsibility.
14
+ *
15
+ * Any attribs on `boundary` or `children` will be ignored, only those given as
16
+ * `attribs` will be used.
17
+ *
18
+ * @param boundary
19
+ * @param children
20
+ * @param attribs
21
+ */
22
+ export declare const complexPolygon: (boundary?: Polygon, children?: Iterable<Polygon>, attribs?: Attribs) => ComplexPolygon;
23
+ //# sourceMappingURL=complex-polygon.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { ComplexPolygon } from "./api/complex-polygon.js";
2
+ import { Polygon } from "./api/polygon.js";
3
+ const complexPolygon = (boundary, children, attribs) => new ComplexPolygon(boundary, children, attribs);
4
+ export {
5
+ complexPolygon
6
+ };