From 189446d41062a88cf7b746f0a64ae20d04f3afb9 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Fri, 24 Mar 2023 14:47:18 +0100 Subject: [PATCH] feat(geom): avoid recursive application of __samples attrib - add internal __copyAttribsNoSamples() helper - update implementations for: - asPolygon() - asPolyline() - edges() - resample() - splitArcLength() - update Group ctor to make attribs optional --- packages/geom/src/api/group.ts | 2 +- packages/geom/src/as-polygon.ts | 7 +++++-- packages/geom/src/as-polyline.ts | 11 +++++++---- packages/geom/src/edges.ts | 22 ++++++++++++++++++++-- packages/geom/src/internal/copy.ts | 27 ++++++++++++++++++++++++--- packages/geom/src/resample.ts | 15 ++++++--------- packages/geom/src/split-arclength.ts | 20 ++++++++++++++------ 7 files changed, 77 insertions(+), 27 deletions(-) diff --git a/packages/geom/src/api/group.ts b/packages/geom/src/api/group.ts index 91dd260d89..51a8186b8d 100644 --- a/packages/geom/src/api/group.ts +++ b/packages/geom/src/api/group.ts @@ -5,7 +5,7 @@ import { __copyAttribs } from "../internal/copy.js"; export class Group implements IHiccupShape { constructor( - public attribs: Attribs, + public attribs?: Attribs, public children: IHiccupShape[] = [] ) {} diff --git a/packages/geom/src/as-polygon.ts b/packages/geom/src/as-polygon.ts index d7651dd52a..abbf382352 100644 --- a/packages/geom/src/as-polygon.ts +++ b/packages/geom/src/as-polygon.ts @@ -2,7 +2,7 @@ import type { MultiFn1O } from "@thi.ng/defmulti"; import { defmulti } from "@thi.ng/defmulti/defmulti"; import type { IShape, SamplingOpts } from "@thi.ng/geom-api"; import { Polygon } from "./api/polygon.js"; -import { __copyAttribs } from "./internal/copy.js"; +import { __copyAttribsNoSamples as __attribs } from "./internal/copy.js"; import { __dispatch } from "./internal/dispatch.js"; import { vertices } from "./vertices.js"; @@ -12,6 +12,9 @@ import { vertices } from "./vertices.js"; * or number of target vertices. * * @remarks + * If the shape has a `__samples` attribute, it will be removed in the result to + * avoid recursive application. + * * Currently implemented for: * * - {@link Circle} @@ -45,6 +48,6 @@ export const asPolygon: MultiFn1O< tri: "points", }, { - points: ($, opts) => new Polygon(vertices($, opts), __copyAttribs($)), + points: ($, opts) => new Polygon(vertices($, opts), __attribs($)), } ); diff --git a/packages/geom/src/as-polyline.ts b/packages/geom/src/as-polyline.ts index ea6c0b101c..d327fe1c48 100644 --- a/packages/geom/src/as-polyline.ts +++ b/packages/geom/src/as-polyline.ts @@ -4,7 +4,7 @@ import type { IShape, SamplingOpts } from "@thi.ng/geom-api"; import { set } from "@thi.ng/vectors/set"; import type { Path } from "./api/path.js"; import { Polyline } from "./api/polyline.js"; -import { __copyAttribs } from "./internal/copy.js"; +import { __copyAttribsNoSamples as __attribs } from "./internal/copy.js"; import { __dispatch } from "./internal/dispatch.js"; import { vertices } from "./vertices.js"; @@ -14,6 +14,9 @@ import { vertices } from "./vertices.js"; * or number of target vertices. * * @remarks + * If the shape has a `__samples` attribute, it will be removed in the result to + * avoid recursive application. + * * Currently implemented for: * * - {@link Arc} @@ -51,18 +54,18 @@ export const asPolyline: MultiFn1O< tri: "poly", }, { - points: ($, opts) => new Polyline(vertices($, opts), __copyAttribs($)), + points: ($, opts) => new Polyline(vertices($, opts), __attribs($)), path: ($: Path, opts) => { const pts = vertices($, opts); $.closed && pts.push(set([], pts[0])); - return new Polyline(pts, __copyAttribs($)); + return new Polyline(pts, __attribs($)); }, poly: ($, opts) => { const pts = vertices($, opts); pts.push(set([], pts[0])); - return new Polyline(pts, __copyAttribs($)); + return new Polyline(pts, __attribs($)); }, } ); diff --git a/packages/geom/src/edges.ts b/packages/geom/src/edges.ts index f813f21cc7..e9fa887a6f 100644 --- a/packages/geom/src/edges.ts +++ b/packages/geom/src/edges.ts @@ -1,11 +1,13 @@ import type { MultiFn1O } from "@thi.ng/defmulti"; import { defmulti } from "@thi.ng/defmulti/defmulti"; import type { IShape, SamplingOpts } from "@thi.ng/geom-api"; +import { mapcat } from "@thi.ng/transducers/mapcat"; import type { VecPair } from "@thi.ng/vectors"; import type { AABB } from "./api/aabb.js"; import type { Arc } from "./api/arc.js"; import type { BPatch } from "./api/bpatch.js"; import type { Circle } from "./api/circle.js"; +import type { Group } from "./api/group.js"; import type { Path } from "./api/path.js"; import type { Polygon } from "./api/polygon.js"; import type { Polyline } from "./api/polyline.js"; @@ -18,10 +20,12 @@ import { vertices } from "./vertices.js"; /** * Extracts the edges of given shape's boundary and returns them as an iterable - * of vector pairs. Some shapes also support - * [`SamplingOpts`](https://docs.thi.ng/umbrella/geom-api/interfaces/SamplingOpts.html). + * of vector pairs. * * @remarks + * If the shape has a `__samples` attribute, it will be removed in the result to + * avoid recursive application. + * * Currently implemented for: * * - {@link AABB} @@ -30,6 +34,7 @@ import { vertices } from "./vertices.js"; * - {@link Circle} * - {@link Cubic} * - {@link Ellipse} + * - {@link Group} * - {@link Line} * - {@link Path} * - {@link Polygon} @@ -39,6 +44,17 @@ import { vertices } from "./vertices.js"; * - {@link Rect} * - {@link Triangle} * + * The implementations for the following shapes **do not** support + * [`SamplingOpts`](https://docs.thi.ng/umbrella/geom-api/interfaces/SamplingOpts.html) + * (all others do): + * + * - {@link Line} + * - {@link Polygon} + * - {@link Polyline} + * - {@link Quad} + * - {@link Rect} + * - {@link Triangle} + * * @param shape * @param opts */ @@ -85,6 +101,8 @@ export const edges: MultiFn1O< circle: ($: Circle, opts) => __edges(asPolygon($, opts).points, true), + group: ($: Group, opts) => mapcat((c) => edges(c, opts), $.children), + path: ($: Path, opts) => __edges(asPolygon($, opts).points, $.closed), poly: ($: Polygon) => __edges($.points, true), diff --git a/packages/geom/src/internal/copy.ts b/packages/geom/src/internal/copy.ts index 32b4e7ae25..db261f0928 100644 --- a/packages/geom/src/internal/copy.ts +++ b/packages/geom/src/internal/copy.ts @@ -1,14 +1,35 @@ // thing:export -import { copyVectors } from "@thi.ng/vectors/copy"; +import { withoutKeysObj } from "@thi.ng/associative/without-keys"; import type { Attribs, IShape, PCLike, PCLikeConstructor, } from "@thi.ng/geom-api"; +import { copyVectors } from "@thi.ng/vectors/copy"; -/** @internal */ -export const __copyAttribs = ($: IShape) => { ...$.attribs }; +/** + * Creates a shallow copy of shape's attribs. Any `exclude` keys will be removed + * from result attribs. + * + * @internal + */ +export const __copyAttribs = ($: IShape, ...exclude: string[]) => { + if (!$.attribs) return; + const attribs = { ...$.attribs }; + return exclude.length ? withoutKeysObj(attribs, exclude) : attribs; +}; + +/** + * Syntax sugar for {@link __copyAttribs}, also removing `__samples` key from + * result. + * + * @param x + * + * @internal + */ +export const __copyAttribsNoSamples = (x: IShape) => + __copyAttribs(x, "__samples"); /** @internal */ export const __copyShape = ( diff --git a/packages/geom/src/resample.ts b/packages/geom/src/resample.ts index cf3f8a6b67..5a3911d7d9 100644 --- a/packages/geom/src/resample.ts +++ b/packages/geom/src/resample.ts @@ -5,7 +5,7 @@ import { resample as _resample } from "@thi.ng/geom-resample/resample"; import { Polygon } from "./api/polygon.js"; import { Polyline } from "./api/polyline.js"; import { asPolygon } from "./as-polygon.js"; -import { __copyAttribs } from "./internal/copy.js"; +import { __copyAttribsNoSamples as __attribs } from "./internal/copy.js"; import { __dispatch } from "./internal/dispatch.js"; /** @@ -13,6 +13,9 @@ import { __dispatch } from "./internal/dispatch.js"; * closed) or polyline (if open). * * @remarks + * If the shape has a `__samples` attribute, it will be removed in the result to + * avoid recursive application. + * * Currently implemented for: * * - {@link Circle} @@ -44,15 +47,9 @@ export const resample: MultiFn2< circle: ($: IShape, opts) => asPolygon($, opts), poly: ($: PCLike, opts) => - new Polygon( - _resample($.points, opts, true, true), - __copyAttribs($) - ), + new Polygon(_resample($.points, opts, true, true), __attribs($)), polyline: ($: PCLike, opts) => - new Polyline( - _resample($.points, opts, false, true), - __copyAttribs($) - ), + new Polyline(_resample($.points, opts, false, true), __attribs($)), } ); diff --git a/packages/geom/src/split-arclength.ts b/packages/geom/src/split-arclength.ts index 3c5a727fc2..5ebcaa4c09 100644 --- a/packages/geom/src/split-arclength.ts +++ b/packages/geom/src/split-arclength.ts @@ -7,7 +7,7 @@ import type { Vec } from "@thi.ng/vectors"; import { Group } from "./api/group.js"; import { Polyline } from "./api/polyline.js"; import { asPolyline } from "./as-polyline.js"; -import { __copyAttribs } from "./internal/copy.js"; +import { __copyAttribsNoSamples as __attribs } from "./internal/copy.js"; import { __dispatch } from "./internal/dispatch.js"; import { __pointArraysAsShapes } from "./internal/points-as-shape.js"; @@ -16,6 +16,9 @@ import { __pointArraysAsShapes } from "./internal/points-as-shape.js"; * Returns array of new shapes/polylines. * * @remarks + * If the shape has a `__samples` attribute, it will be removed in the result to + * avoid recursive application. + * * Currently only implemented for: * * - {@link Group} @@ -24,10 +27,15 @@ import { __pointArraysAsShapes } from "./internal/points-as-shape.js"; * Other shape types will be attempted to be auto-converted via * {@link asPolyline} first. * + * Groups will be recursively processed (i.e. by calling {@link splitArcLength} + * for each child). Any nested groups will be retained, but each group might + * have a greater resulting number of children (depending on number of splits + * performed). + * * @param shape * @param dist */ -export const splitArclength: MultiFn2 = defmulti< +export const splitArcLength: MultiFn2 = defmulti< any, number, IShape[] @@ -35,12 +43,12 @@ export const splitArclength: MultiFn2 = defmulti< __dispatch, {}, { - [DEFAULT]: ($: IShape, d: number) => splitArclength(asPolyline($), d), + [DEFAULT]: ($: IShape, d: number) => splitArcLength(asPolyline($), d), group: ($, d) => [ - new Group(__copyAttribs($.attribs), [ + new Group(__attribs($), [ ...mapcat( - (c: IShape) => splitArclength(c, d), + (c: IShape) => splitArcLength(c, d), $.children ), ]), @@ -62,7 +70,7 @@ export const splitArclength: MultiFn2 = defmulti< break; } } - return __pointArraysAsShapes(Polyline, chunks, $.attribs)!; + return __pointArraysAsShapes(Polyline, chunks, __attribs($))!; }, } );