@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/path-from-svg.js CHANGED
@@ -4,8 +4,8 @@ import { WS } from "@thi.ng/strings/groups";
4
4
  import { PathBuilder } from "./path-builder.js";
5
5
  const CMD_RE = /[achlmqstvz]/i;
6
6
  const WSC = { ...WS, ",": true };
7
- const pathFromSvg = (svg) => {
8
- const b = new PathBuilder();
7
+ const pathFromSvg = (svg, attribs) => {
8
+ const b = new PathBuilder(attribs);
9
9
  try {
10
10
  let cmd = "";
11
11
  for (let n = svg.length, i = 0; i < n; ) {
@@ -63,7 +63,7 @@ const pathFromSvg = (svg) => {
63
63
  break;
64
64
  }
65
65
  case "z":
66
- b.closePath();
66
+ b.close();
67
67
  break;
68
68
  default:
69
69
  throw new Error(
@@ -71,15 +71,15 @@ const pathFromSvg = (svg) => {
71
71
  );
72
72
  }
73
73
  }
74
- return b.paths;
74
+ const [main, ...subPaths] = b.paths;
75
+ return main.addSubPaths(...subPaths.map((p) => p.segments));
75
76
  } catch (e) {
76
77
  throw e instanceof Error ? e : new Error(`illegal char '${svg.charAt(e)}' @ ${e}`);
77
78
  }
78
79
  };
79
80
  const skipWS = (src, i) => {
80
81
  const n = src.length;
81
- while (i < n && WSC[src.charAt(i)])
82
- i++;
82
+ while (i < n && WSC[src.charAt(i)]) i++;
83
83
  return i;
84
84
  };
85
85
  const readPoint = (src, index) => {
@@ -113,28 +113,24 @@ const readFloat = (src, index) => {
113
113
  continue;
114
114
  }
115
115
  if (c === "-" || c === "+") {
116
- if (!signOk)
117
- break;
116
+ if (!signOk) break;
118
117
  signOk = false;
119
118
  continue;
120
119
  }
121
120
  if (c === ".") {
122
- if (!dotOk)
123
- break;
121
+ if (!dotOk) break;
124
122
  dotOk = false;
125
123
  continue;
126
124
  }
127
125
  if (c === "e") {
128
- if (!expOk)
129
- throw i;
126
+ if (!expOk) throw i;
130
127
  expOk = false;
131
128
  dotOk = false;
132
129
  signOk = true;
133
130
  continue;
134
131
  }
135
132
  if (c === ",") {
136
- if (!commaOk)
137
- throw i;
133
+ if (!commaOk) throw i;
138
134
  i++;
139
135
  }
140
136
  break;
package/path.d.ts CHANGED
@@ -2,8 +2,65 @@ import type { Attribs, PathSegment } from "@thi.ng/geom-api";
2
2
  import type { Vec } from "@thi.ng/vectors";
3
3
  import type { Cubic } from "./api/cubic.js";
4
4
  import { Path } from "./api/path.js";
5
- export declare const path: (segments: Iterable<PathSegment>, attribs?: Attribs) => Path;
5
+ /**
6
+ * Creates a new {@link Path} instance, optional with given `segments`,
7
+ * `subPaths` and `attribs`.
8
+ *
9
+ * @remarks
10
+ * Segments and sub-paths can also be later added via {@link Path.addSegments}
11
+ * or {@link Path.addSubPaths}.
12
+ *
13
+ * @param segments
14
+ * @param subPaths
15
+ * @param attribs
16
+ */
17
+ export declare const path: (segments?: Iterable<PathSegment>, subPaths?: Iterable<PathSegment[]>, attribs?: Attribs) => Path;
18
+ /**
19
+ * Constructs a {@link Path} from given sequence of cubic curves, with optional
20
+ * `attribs`.
21
+ *
22
+ * @remarks
23
+ * If no `attribs` are given, those from the first curve will be used.
24
+ *
25
+ * For each successive curve segment, if the start point of the current curve is
26
+ * not the same as the last point of the previous curve, a new sub path will be
27
+ * started.
28
+ *
29
+ * Also see {@link normalizedPath}.
30
+ *
31
+ * @param cubics
32
+ * @param attribs
33
+ */
6
34
  export declare const pathFromCubics: (cubics: Cubic[], attribs?: Attribs) => Path;
35
+ /**
36
+ * Converts given path into a new one with all segments converted to
37
+ * {@link Cubic} bezier segments.
38
+ *
39
+ * @remarks
40
+ * Also see {@link pathFromCubics}.
41
+ *
42
+ * @param path
43
+ */
7
44
  export declare const normalizedPath: (path: Path) => Path;
8
- export declare const roundedRect: (pos: Vec, size: Vec, r: number | Vec, attribs?: Attribs) => Path;
45
+ /**
46
+ * Creates a new rounded rect {@link Path}, using the given corner radius or
47
+ * radii.
48
+ *
49
+ * @remarks
50
+ * If multiple `radii` are given, the interpretation logic is the same as:
51
+ * https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/roundRect
52
+ *
53
+ * - number: all corners
54
+ * - `[top-left-and-bottom-right, top-right-and-bottom-left]`
55
+ * - `[top-left, top-right-and-bottom-left, bottom-right]`
56
+ * - `[top-left, top-right, bottom-right, bottom-left]`
57
+ *
58
+ * No arc segments will be generated for those corners where the radius <= 0
59
+ *
60
+ * @param pos
61
+ * @param size
62
+ * @param radii
63
+ * @param attribs
64
+ */
65
+ export declare const roundedRect: (pos: Vec, [w, h]: Vec, radii: number | [number, number] | [number, number, number] | [number, number, number, number], attribs?: Attribs) => Path;
9
66
  //# sourceMappingURL=path.d.ts.map
package/path.js CHANGED
@@ -1,33 +1,58 @@
1
1
  import { isNumber } from "@thi.ng/checks/is-number";
2
2
  import { map } from "@thi.ng/transducers/map";
3
3
  import { mapcat } from "@thi.ng/transducers/mapcat";
4
- import { maddN2 } from "@thi.ng/vectors/maddn";
4
+ import { equals2 } from "@thi.ng/vectors/equals";
5
5
  import { Path } from "./api/path.js";
6
6
  import { asCubic } from "./as-cubic.js";
7
7
  import { PathBuilder } from "./path-builder.js";
8
- const path = (segments, attribs) => new Path(segments, attribs);
8
+ const path = (segments, subPaths, attribs) => new Path(segments, subPaths, attribs);
9
9
  const pathFromCubics = (cubics, attribs) => {
10
- const path2 = new Path([], attribs || cubics[0].attribs);
11
- path2.segments.push({ type: "m", point: cubics[0].points[0] });
10
+ let subPaths = [];
11
+ let curr;
12
+ let lastP;
13
+ const $beginPath = (c) => {
14
+ curr = [{ type: "m", point: c.points[0] }];
15
+ subPaths.push(curr);
16
+ };
12
17
  for (let c of cubics) {
13
- path2.segments.push({ type: "c", geo: c });
18
+ if (!(lastP && equals2(lastP, c.points[0]))) $beginPath(c);
19
+ curr.push({ type: "c", geo: c });
20
+ lastP = c.points[3];
14
21
  }
22
+ const path2 = new Path(
23
+ subPaths[0],
24
+ subPaths.slice(1),
25
+ attribs || cubics[0].attribs
26
+ );
15
27
  return path2;
16
28
  };
17
- const normalizedPath = (path2) => new Path(
18
- mapcat(
19
- (s) => s.geo ? map(
20
- (c) => ({ type: "c", geo: c }),
21
- asCubic(s.geo)
22
- ) : [{ ...s }],
23
- path2.segments
24
- ),
25
- path2.attribs
26
- );
27
- const roundedRect = (pos, size, r, attribs) => {
28
- r = isNumber(r) ? [r, r] : r;
29
- const [w, h] = maddN2([], r, -2, size);
30
- return new PathBuilder(attribs).moveTo([pos[0] + r[0], pos[1]]).hlineTo(w, true).arcTo(r, r, 0, false, true, true).vlineTo(h, true).arcTo([-r[0], r[1]], r, 0, false, true, true).hlineTo(-w, true).arcTo([-r[0], -r[1]], r, 0, false, true, true).vlineTo(-h, true).arcTo([r[0], -r[1]], r, 0, false, true, true).current();
29
+ const normalizedPath = (path2) => {
30
+ const $normalize = (segments) => [
31
+ ...mapcat(
32
+ (s) => s.geo ? map(
33
+ (c) => ({ type: "c", geo: c }),
34
+ asCubic(s.geo)
35
+ ) : [{ ...s }],
36
+ segments
37
+ )
38
+ ];
39
+ return new Path(
40
+ $normalize(path2.segments),
41
+ path2.subPaths.map($normalize),
42
+ path2.attribs
43
+ );
44
+ };
45
+ const roundedRect = (pos, [w, h], radii, attribs) => {
46
+ const [tl, tr, br, bl] = isNumber(radii) ? [radii, radii, radii, radii] : radii.length === 2 ? [radii[0], radii[1], radii[0], radii[1]] : radii.length === 3 ? [radii[0], radii[1], radii[2], radii[1]] : radii;
47
+ const b = new PathBuilder(attribs).moveTo([pos[0] + tl, pos[1]]).hlineTo(w - tl - tr, true);
48
+ if (tr > 0) b.arcTo([tr, tr], [tr, tr], 0, false, true, true);
49
+ b.vlineTo(h - tr - br, true);
50
+ if (br > 0) b.arcTo([-br, br], [br, br], 0, false, true, true);
51
+ b.hlineTo(-(w - br - bl), true);
52
+ if (bl > 0) b.arcTo([-bl, -bl], [bl, bl], 0, false, true, true);
53
+ b.vlineTo(-(h - bl - tl), true);
54
+ if (tl > 0) b.arcTo([tl, -tl], [tl, tl], 0, false, true, true);
55
+ return b.current().close();
31
56
  };
32
57
  export {
33
58
  normalizedPath,
package/point-inside.d.ts CHANGED
@@ -7,15 +7,18 @@ import type { ReadonlyVec } from "@thi.ng/vectors";
7
7
  * @remarks
8
8
  * Currently implemented for:
9
9
  *
10
- * - AABB
11
- * - Circle
12
- * - Points (i.e. if `p` is one of the points in the cloud)
13
- * - Points3 (same as w/ Points)
14
- * - Polygon
15
- * - Quad
16
- * - Rect
17
- * - Sphere
18
- * - Triangle
10
+ * - {@link AABB}
11
+ * - {@link Circle}
12
+ * - {@link ComplexPolygon}
13
+ * - {@link Line} (if `p` is on line segment)
14
+ * - {@link Points} (i.e. if `p` is one of the points in the cloud)
15
+ * - {@link Points3} (same as w/ Points)
16
+ * - {@link Polygon}
17
+ * - {@link Polyline} (if `p` is on any of the line segments)
18
+ * - {@link Quad}
19
+ * - {@link Rect}
20
+ * - {@link Sphere}
21
+ * - {@link Triangle}
19
22
  *
20
23
  * @param shape
21
24
  * @param p
package/point-inside.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  pointInPolygon2,
6
6
  pointInRect,
7
7
  pointInSegment,
8
+ pointInSegments,
8
9
  pointInTriangle2
9
10
  } from "@thi.ng/geom-isec/point";
10
11
  import { isInArray } from "@thi.ng/vectors/eqdelta";
@@ -19,9 +20,11 @@ const pointInside = defmulti(
19
20
  {
20
21
  aabb: ($, p) => pointInAABB(p, $.pos, $.size),
21
22
  circle: ($, p) => pointInCircle(p, $.pos, $.r),
23
+ complexpoly: ($, p) => pointInPolygon2(p, $.boundary.points) ? !$.children.some((child) => pointInPolygon2(p, child.points)) : false,
22
24
  line: ($, p) => pointInSegment(p, $.points[0], $.points[1]),
23
25
  points: ({ points }, p) => isInArray(p, points),
24
26
  poly: ($, p) => pointInPolygon2(p, $.points) > 0,
27
+ polyline: ($, p) => pointInSegments(p, $.points, false),
25
28
  rect: ($, p) => pointInRect(p, $.pos, $.size),
26
29
  tri: (tri, p) => pointInTriangle2(p, ...tri.points)
27
30
  }
package/proximity.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { IShape } from "@thi.ng/geom-api";
2
+ import type { ReadonlyVec } from "@thi.ng/vectors";
3
+ /**
4
+ * Computes {@link closestPoint} on `shape` to `p`, and if successful, returns
5
+ * eucledian distance between that point and `p`.
6
+ *
7
+ * @param shape
8
+ * @param p
9
+ */
10
+ export declare const proximity: (shape: IShape, p: ReadonlyVec) => number | undefined;
11
+ //# sourceMappingURL=proximity.d.ts.map
package/proximity.js ADDED
@@ -0,0 +1,9 @@
1
+ import { dist } from "@thi.ng/vectors/dist";
2
+ import { closestPoint } from "./closest-point.js";
3
+ const proximity = (shape, p) => {
4
+ const q = closestPoint(shape, p);
5
+ return q ? dist(p, q) : void 0;
6
+ };
7
+ export {
8
+ proximity
9
+ };
package/resample.d.ts CHANGED
@@ -11,6 +11,7 @@ import type { IShape, SamplingOpts } from "@thi.ng/geom-api";
11
11
  * Currently implemented for:
12
12
  *
13
13
  * - {@link Circle}
14
+ * - {@link ComplexPolygon}
14
15
  * - {@link Ellipse}
15
16
  * - {@link Line}
16
17
  * - {@link Polygon}
package/resample.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { defmulti } from "@thi.ng/defmulti/defmulti";
2
2
  import { resample as _resample } from "@thi.ng/geom-resample/resample";
3
+ import { ComplexPolygon } from "./api/complex-polygon.js";
3
4
  import { Polygon } from "./api/polygon.js";
4
5
  import { Polyline } from "./api/polyline.js";
5
6
  import { asPolygon } from "./as-polygon.js";
@@ -15,7 +16,12 @@ const resample = defmulti(
15
16
  rect: "circle"
16
17
  },
17
18
  {
18
- circle: ($, opts) => asPolygon($, opts),
19
+ circle: ($, opts) => asPolygon($, opts)[0],
20
+ complexpoly: ($, opts) => new ComplexPolygon(
21
+ resample($.boundary, opts),
22
+ $.children.map((child) => resample(child, opts)),
23
+ __attribs($)
24
+ ),
19
25
  poly: ($, opts) => new Polygon(_resample($.points, opts, true, true), __attribs($)),
20
26
  polyline: ($, opts) => new Polyline(_resample($.points, opts, false, true), __attribs($))
21
27
  }
package/rotate.d.ts CHANGED
@@ -8,6 +8,7 @@ import type { IShape } from "@thi.ng/geom-api";
8
8
  *
9
9
  * - {@link Arc}
10
10
  * - {@link Circle}
11
+ * - {@link ComplexPolygon}
11
12
  * - {@link Cubic}
12
13
  * - {@link Ellipse}
13
14
  * - {@link Group}
package/rotate.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { defmulti } from "@thi.ng/defmulti/defmulti";
2
2
  import { rotate as $rotate } from "@thi.ng/vectors/rotate";
3
3
  import { Circle } from "./api/circle.js";
4
+ import { ComplexPolygon } from "./api/complex-polygon.js";
4
5
  import { Cubic } from "./api/cubic.js";
5
6
  import { Line } from "./api/line.js";
6
7
  import { Path } from "./api/path.js";
@@ -17,6 +18,7 @@ import { asPolygon } from "./as-polygon.js";
17
18
  import { __copyAttribs } from "./internal/copy.js";
18
19
  import { __dispatch } from "./internal/dispatch.js";
19
20
  import { __rotatedShape as tx } from "./internal/rotate.js";
21
+ import { __segmentTransformer } from "./internal/transform.js";
20
22
  const rotate = defmulti(
21
23
  __dispatch,
22
24
  {},
@@ -27,21 +29,22 @@ const rotate = defmulti(
27
29
  return a;
28
30
  },
29
31
  circle: ($, theta) => new Circle($rotate([], $.pos, theta), $.r, __copyAttribs($)),
32
+ complexpoly: ($, theta) => new ComplexPolygon(
33
+ rotate($.boundary, theta),
34
+ $.children.map((child) => rotate(child, theta))
35
+ ),
30
36
  cubic: tx(Cubic),
31
37
  ellipse: ($, theta) => rotate(asPath($), theta),
32
38
  group: ($, theta) => $.copyTransformed((x) => rotate(x, theta)),
33
39
  line: tx(Line),
34
40
  path: ($, theta) => {
41
+ const $rotateSegments = __segmentTransformer(
42
+ (geo) => rotate(geo, theta),
43
+ (p) => $rotate([], p, theta)
44
+ );
35
45
  return new Path(
36
- $.segments.map(
37
- (s) => s.geo ? {
38
- type: s.type,
39
- geo: rotate(s.geo, theta)
40
- } : {
41
- type: s.type,
42
- point: $rotate([], s.point, theta)
43
- }
44
- ),
46
+ $rotateSegments($.segments),
47
+ $.subPaths.map($rotateSegments),
45
48
  __copyAttribs($)
46
49
  );
47
50
  },
@@ -57,7 +60,7 @@ const rotate = defmulti(
57
60
  __copyAttribs($)
58
61
  );
59
62
  },
60
- rect: ($, theta) => rotate(asPolygon($), theta),
63
+ rect: ($, theta) => rotate(asPolygon($)[0], theta),
61
64
  text: ($, theta) => new Text($rotate([], $.pos, theta), $.body, __copyAttribs($)),
62
65
  tri: tx(Triangle)
63
66
  }
@@ -0,0 +1,20 @@
1
+ import type { IShape } from "@thi.ng/geom-api";
2
+ import type { ReadonlyVec } from "@thi.ng/vectors";
3
+ /**
4
+ * Applies a sequence of translate and scale operations to return an uniformly
5
+ * scaled version of the given shape using `center` as the point of reference.
6
+ *
7
+ * @remarks
8
+ * This op is likely slower than using {@link transform} with a equivalent
9
+ * transformation matrix (e.g. using [`scaleWithCenter()` from
10
+ * thi.ng/matrices](https://docs.thi.ng/umbrella/matrices/functions/scaleWithCenter23.html)),
11
+ * but will not change the shape type (as might be the case with `transform()`).
12
+ *
13
+ * Also see: {@link scale}, {@link translate}, {@link applyTransforms}.
14
+ *
15
+ * @param shape
16
+ * @param center
17
+ * @param factor
18
+ */
19
+ export declare const scaleWithCenter: (shape: IShape, center: ReadonlyVec, factor: number) => IShape<IShape<any>>;
20
+ //# sourceMappingURL=scale-with-center.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { mulN } from "@thi.ng/vectors/muln";
2
+ import { scale } from "./scale.js";
3
+ import { translate } from "./translate.js";
4
+ const scaleWithCenter = (shape, center, factor) => translate(scale(translate(shape, mulN([], center, -1)), factor), center);
5
+ export {
6
+ scaleWithCenter
7
+ };
package/scale.d.ts CHANGED
@@ -13,6 +13,7 @@ import type { ReadonlyVec } from "@thi.ng/vectors";
13
13
  * - {@link AABB}
14
14
  * - {@link Arc}
15
15
  * - {@link Circle}
16
+ * - {@link ComplexPolygon}
16
17
  * - {@link Cubic}
17
18
  * - {@link Ellipse}
18
19
  * - {@link Group}
package/scale.js CHANGED
@@ -6,6 +6,7 @@ import { mulN2, mulN3 } from "@thi.ng/vectors/muln";
6
6
  import { normalize2 } from "@thi.ng/vectors/normalize";
7
7
  import { AABB } from "./api/aabb.js";
8
8
  import { Circle } from "./api/circle.js";
9
+ import { ComplexPolygon } from "./api/complex-polygon.js";
9
10
  import { Cubic } from "./api/cubic.js";
10
11
  import { Ellipse } from "./api/ellipse.js";
11
12
  import { Line } from "./api/line.js";
@@ -24,6 +25,7 @@ import { __asVec } from "./internal/args.js";
24
25
  import { __copyAttribs } from "./internal/copy.js";
25
26
  import { __dispatch } from "./internal/dispatch.js";
26
27
  import { __scaledShape as tx } from "./internal/scale.js";
28
+ import { __segmentTransformer } from "./internal/transform.js";
27
29
  const scale = defmulti(
28
30
  __dispatch,
29
31
  {},
@@ -52,6 +54,10 @@ const scale = defmulti(
52
54
  mulN2([], delta, $.r),
53
55
  __copyAttribs($)
54
56
  ),
57
+ complexpoly: ($, delta) => new ComplexPolygon(
58
+ scale($.boundary, delta),
59
+ $.children.map((child) => scale(child, delta))
60
+ ),
55
61
  cubic: tx(Cubic),
56
62
  ellipse: ($, delta) => {
57
63
  delta = __asVec(delta);
@@ -65,16 +71,13 @@ const scale = defmulti(
65
71
  line: tx(Line),
66
72
  path: ($, delta) => {
67
73
  delta = __asVec(delta);
74
+ const $scaleSegments = __segmentTransformer(
75
+ (geo) => scale(geo, delta),
76
+ (p) => mul2([], p, delta)
77
+ );
68
78
  return new Path(
69
- $.segments.map(
70
- (s) => s.geo ? {
71
- type: s.type,
72
- geo: scale(s.geo, delta)
73
- } : {
74
- type: s.type,
75
- point: mul2([], s.point, delta)
76
- }
77
- ),
79
+ $scaleSegments($.segments),
80
+ $.subPaths.map($scaleSegments),
78
81
  __copyAttribs($)
79
82
  );
80
83
  },
package/scatter.js CHANGED
@@ -4,8 +4,7 @@ import { bounds } from "./bounds.js";
4
4
  import { pointInside } from "./point-inside.js";
5
5
  const scatter = (shape, num, rnd = SYSTEM, out = []) => {
6
6
  const b = bounds(shape);
7
- if (!b)
8
- return;
7
+ if (!b) return;
9
8
  const mi = b.pos;
10
9
  const mx = b.max();
11
10
  for (; num-- > 0; ) {
package/simplify.d.ts CHANGED
@@ -1,15 +1,16 @@
1
- import type { MultiFn2 } from "@thi.ng/defmulti";
1
+ import type { MultiFn1O } from "@thi.ng/defmulti";
2
2
  import type { IShape } from "@thi.ng/geom-api";
3
3
  /**
4
4
  * Simplifies given 2D shape boundary using Douglas-Peucker algorithm
5
5
  * (implemented by
6
6
  * [`simplify()`](https://docs.thi.ng/umbrella/geom-resample/functions/simplify.html))
7
- * and given `threshold` distance (default: 0, which removes only co-linear
7
+ * and given `threshold` distance (default: 1e-6, which removes only co-linear
8
8
  * vertices).
9
9
  *
10
10
  * @remarks
11
11
  * Currently only implemented for:
12
12
  *
13
+ * - {@link ComplexPolygon}
13
14
  * - {@link Path}
14
15
  * - {@link Polygon}
15
16
  * - {@link Polyline}
@@ -20,5 +21,5 @@ import type { IShape } from "@thi.ng/geom-api";
20
21
  * @param shape
21
22
  * @param threshold
22
23
  */
23
- export declare const simplify: MultiFn2<IShape, number, IShape>;
24
+ export declare const simplify: MultiFn1O<IShape, number, IShape>;
24
25
  //# sourceMappingURL=simplify.d.ts.map
package/simplify.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { peek } from "@thi.ng/arrays/peek";
2
2
  import { defmulti } from "@thi.ng/defmulti/defmulti";
3
3
  import { simplify as _simplify } from "@thi.ng/geom-resample/simplify";
4
+ import { ComplexPolygon } from "./api/complex-polygon.js";
4
5
  import { Path } from "./api/path.js";
5
6
  import { Polygon } from "./api/polygon.js";
6
7
  import { Polyline } from "./api/polyline.js";
@@ -11,39 +12,50 @@ const simplify = defmulti(
11
12
  __dispatch,
12
13
  {},
13
14
  {
14
- path: ($, eps = 0) => {
15
- const res = [];
16
- const orig = $.segments;
17
- const n = orig.length;
18
- let points;
19
- let lastP;
20
- for (let i = 0; i < n; i++) {
21
- const s = orig[i];
22
- if (s.type === "l" || s.type === "p") {
23
- points = points ? points.concat(vertices(s.geo)) : vertices(s.geo);
24
- lastP = peek(points);
25
- } else if (points) {
15
+ complexpoly: ($, eps) => new ComplexPolygon(
16
+ simplify($.boundary, eps),
17
+ $.children.map((child) => simplify(child, eps)),
18
+ __copyAttribs($)
19
+ ),
20
+ path: ($, eps = 1e-6) => {
21
+ const $simplifySegments = (segments) => {
22
+ const res = [];
23
+ const n = segments.length;
24
+ let points;
25
+ let lastP;
26
+ for (let i = 0; i < n; i++) {
27
+ const s = segments[i];
28
+ if (s.type === "l" || s.type === "p") {
29
+ points = points ? points.concat(vertices(s.geo)) : vertices(s.geo);
30
+ lastP = peek(points);
31
+ } else if (points) {
32
+ points.push(lastP);
33
+ res.push({
34
+ geo: new Polyline(_simplify(points, eps)),
35
+ type: "p"
36
+ });
37
+ points = null;
38
+ } else {
39
+ res.push({ ...s });
40
+ }
41
+ }
42
+ if (points) {
26
43
  points.push(lastP);
27
44
  res.push({
28
- geo: new Polyline(_simplify(points, eps)),
45
+ geo: new Polyline(points),
29
46
  type: "p"
30
47
  });
31
- points = null;
32
- } else {
33
- res.push({ ...s });
34
48
  }
35
- }
36
- if (points) {
37
- points.push(lastP);
38
- res.push({
39
- geo: new Polyline(points),
40
- type: "p"
41
- });
42
- }
43
- return new Path(res, __copyAttribs($));
49
+ return res;
50
+ };
51
+ return new Path(
52
+ $simplifySegments($.segments),
53
+ $.subPaths.map($simplifySegments),
54
+ __copyAttribs($)
55
+ );
44
56
  },
45
- poly: ($, eps = 0) => new Polygon(_simplify($.points, eps, true), __copyAttribs($)),
46
- polyline: ($, eps = 0) => new Polyline(_simplify($.points, eps), __copyAttribs($))
57
+ poly: ($, eps = 1e-6) => new Polygon(_simplify($.points, eps, true), __copyAttribs($)),
58
+ polyline: ($, eps = 1e-6) => new Polyline(_simplify($.points, eps), __copyAttribs($))
47
59
  }
48
60
  );
49
61
  export {
@@ -15,7 +15,7 @@ import { Group } from "./api/group.js";
15
15
  * - {@link Polyline}
16
16
  *
17
17
  * Other shape types will be attempted to be auto-converted via
18
- * {@link asPolyline} first.
18
+ * {@link asPolyline} first. Only shapes with a single boundary are supported.
19
19
  *
20
20
  * Nested groups are NOT supported. Groups are processing their child shapes and
21
21
  * forming new child groups of given max. arc lengths and potentially splitting
@@ -10,7 +10,7 @@ const splitArcLength = defmulti(
10
10
  __dispatch,
11
11
  {},
12
12
  {
13
- [DEFAULT]: ($, d) => splitArcLength(asPolyline($), d),
13
+ [DEFAULT]: ($, d) => splitArcLength(asPolyline($)[0], d),
14
14
  group: ($, d) => {
15
15
  const groups = [];
16
16
  let curr = [];
@@ -18,7 +18,7 @@ const splitArcLength = defmulti(
18
18
  const queue = $.children.slice().reverse();
19
19
  while (queue.length) {
20
20
  const child = queue.pop();
21
- const polyline = asPolyline(child);
21
+ const polyline = asPolyline(child)[0];
22
22
  const sampler = new Sampler(polyline.points);
23
23
  const len = sampler.totalLength();
24
24
  if (currLen + len <= d) {
@@ -34,8 +34,7 @@ const splitArcLength = defmulti(
34
34
  queue.push(new Polyline(next, __attribs(child)));
35
35
  }
36
36
  }
37
- if (curr.length)
38
- groups.push(new Group({}, curr));
37
+ if (curr.length) groups.push(new Group({}, curr));
39
38
  return new Group(__attribs($), groups);
40
39
  },
41
40
  polyline: ($, d) => {
@@ -46,8 +45,7 @@ const splitArcLength = defmulti(
46
45
  const total = sampler.totalLength();
47
46
  if (total > d) {
48
47
  const parts = sampler.splitAt(d / total);
49
- if (!parts)
50
- break;
48
+ if (!parts) break;
51
49
  chunks.push(parts[0]);
52
50
  pts = parts[1];
53
51
  } else {