Skip to content

Commit

Permalink
feat(geom): add/update asCubic()/asPath() impls/types/signatures
Browse files Browse the repository at this point in the history
BREAKING CHANGE: update asCubic/asPath() to use new CubicOpts

- add support for more shape types, incl. 3D
  • Loading branch information
postspectacular committed May 16, 2024
1 parent 3ba9714 commit 9b4df2e
Show file tree
Hide file tree
Showing 2 changed files with 332 additions and 160 deletions.
275 changes: 200 additions & 75 deletions packages/geom/src/as-cubic.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import type { Maybe } from "@thi.ng/api";
import type { MultiFn1O } from "@thi.ng/defmulti";
import { defmulti } from "@thi.ng/defmulti/defmulti";
import type { CubicOpts, IShape, PCLike } from "@thi.ng/geom-api";
import { unsupported } from "@thi.ng/errors/unsupported";
import type {
Attribs,
CubicOpts,
IShape,
IShape2,
IShape3,
PCLike,
} from "@thi.ng/geom-api";
import {
closedCubicFromBreakPoints,
openCubicFromBreakPoints,
Expand All @@ -10,139 +18,256 @@ import {
closedCubicFromControlPoints,
openCubicFromControlPoints,
} from "@thi.ng/geom-splines/cubic-from-controlpoints";
import { cubicHobby2 } from "@thi.ng/geom-splines/cubic-hobby";
import { TAU } from "@thi.ng/math/api";
import { concat } from "@thi.ng/transducers/concat";
import { flatten1 } from "@thi.ng/transducers/flatten1";
import { mapcat } from "@thi.ng/transducers/mapcat";
import type { ReadonlyVec, Vec } from "@thi.ng/vectors";
import type { BPatch } from "./api/bpatch.js";
import type { Circle } from "./api/circle.js";
import type { ComplexPolygon } from "./api/complex-polygon.js";
import { Cubic } from "./api/cubic.js";
import { Cubic3 } from "./api/cubic3.js";
import type { Group } from "./api/group.js";
import type { Line } from "./api/line.js";
import type { Path } from "./api/path.js";
import type { Polygon } from "./api/polygon.js";
import type { Polygon3 } from "./api/polygon3.js";
import type { Polyline } from "./api/polyline.js";
import type { Polyline3 } from "./api/polyline3.js";
import type { Quadratic } from "./api/quadratic.js";
import type { Quadratic3 } from "./api/quadratic3.js";
import type { Rect } from "./api/rect.js";
import { arc } from "./arc.js";
import { asPolygon } from "./as-polygon.js";
import { cubicFromArc, cubicFromLine, cubicFromQuadratic } from "./cubic.js";
import { __copyAttribs } from "./internal/copy.js";
import { cubicFromLine3, cubicFromQuadratic3 } from "./cubic3.js";
import { __copyAttribsRaw } from "./internal/copy.js";
import { __dispatch } from "./internal/dispatch.js";

export interface AsCubicOpts extends CubicOpts {
attribs: boolean;
}

export type AsCubicFn = {
<T extends IShape2>(shape: T, opts?: Partial<AsCubicOpts>): Cubic[];
<T extends IShape3>(shape: T, opts?: Partial<AsCubicOpts>): Cubic3[];
} & MultiFn1O<IShape, Partial<AsCubicOpts>, (Cubic | Cubic3)[]>;

/**
* Converts given shape into an array of {@link Cubic} curves. For some shapes
* (see below) the conversion supports optionally provided {@link CubicOpts}.
* Converts given shape into an array of {@link Cubic} or {@link Cubic3} curves.
* For some shapes (see below) the conversion supports optionally provided
* {@link CubicOpts}.
*
* @remarks
* Currently implemented for:
*
* - {@link Arc}
* - {@link BPatch}
* - {@link Circle}
* - {@link ComplexPolygon}
* - {@link Cubic}
* - {@link Cubic3}
* - {@link Ellipse}
* - {@link Group}
* - {@link Group3}
* - {@link Line}
* - {@link Line3}
* - {@link Path}
* - {@link Path3}
* - {@link Polygon}
* - {@link Polygon3}
* - {@link Polyline}
* - {@link Polyline3}
* - {@link Quad}
* - {@link Quad3}
* - {@link Quadratic}
* - {@link Quadratic3}
* - {@link Rect}
* - {@link Triangle}
* - {@link Triangle3}
*
* Shape types supporting custom conversion options (see
* [@thi.ng/geom-splines](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-splines#cubic-curve-conversion-from-polygons--polylines)
* for more details):
*
* - {@link Group} (only used for eligible children)
* - {@link Group3} (only used for eligible children)
* - {@link ComplexPolygon}
* - {@link Polygon}
* - {@link Polygon3}
* - {@link Polyline}
* - {@link Polyline3}
* - {@link Quad}
* - {@link Quadratic}
* - {@link Quad3}
* - {@link Rect}
* - {@link Triangle}
* - {@link Triangle3}
*
* @param shape
* @param opts
*/
export const asCubic: MultiFn1O<IShape, Partial<CubicOpts>, Cubic[]> = defmulti<
any,
Maybe<Partial<CubicOpts>>,
Cubic[]
>(
__dispatch,
{
ellipse: "circle",
quad: "poly",
tri: "poly",
},
{
arc: cubicFromArc,

circle: ($: Circle) => asCubic(arc($.pos, $.r, 0, 0, TAU, true, true)),

complexpoly: ($: ComplexPolygon, opts = {}) => [
...mapcat((x) => asCubic(x, opts), [$.boundary, ...$.children]),
],

cubic: ($: Cubic) => [$],

group: ($: Group, opts) => [
...mapcat((x) => asCubic(x, opts), $.children),
],

line: ({ attribs, points }: Line) => [
cubicFromLine(points[0], points[1], { ...attribs }),
],

path: ($: Path) => [
...mapcat(
(segment) => (segment.geo ? asCubic(segment.geo) : null),
concat($.segments, flatten1($.subPaths))
),
],

poly: ($: Polygon, opts = {}) =>
__polyCubic(
$,
opts,
closedCubicFromBreakPoints,
closedCubicFromControlPoints
),

polyline: ($: Polyline, opts = {}) =>
__polyCubic(
$,
opts,
openCubicFromBreakPoints,
openCubicFromControlPoints
),

quadratic: ({ attribs, points }: Quadratic) => [
cubicFromQuadratic(points[0], points[1], points[2], { ...attribs }),
],

rect: ($: Rect, opts) => asCubic(asPolygon($)[0], opts),
}
export const asCubic = <AsCubicFn>(
defmulti<any, Maybe<Partial<AsCubicOpts>>, (Cubic | Cubic3)[]>(
__dispatch,
{
cubic3: "cubic",
ellipse: "circle",
group3: "group",
quad: "poly",
quad3: "poly3",
rect: "$aspoly",
tri: "poly",
},
{
$aspoly: ($, opts) => asCubic(asPolygon($)[0], opts),

arc: cubicFromArc,

bpatch: ({ points, attribs }: BPatch, opts) =>
[
[0, 4],
[12, 1],
[15, -4],
[3, -1],
].map(
([i, s]) =>
new Cubic(
[
points[i],
points[i + s],
points[i + 2 * s],
points[i + 3 * s],
],
__attribs(opts, attribs)
)
),

circle: ($: Circle, opts) =>
asCubic(
arc(
$.pos,
$.r,
0,
0,
TAU,
true,
true,
__attribs(opts, $.attribs)
)
),

complexpoly: (
{ boundary, children, attribs }: ComplexPolygon,
opts
) => [
...mapcat(
(x) =>
asCubic(x, opts).map(
(x) => ((x.attribs = __attribs(opts, attribs)), x)
),
[boundary, ...children]
),
],

cubic: ($: Cubic, opts) => {
const res = $.copy();
if (opts?.attribs === false) res.attribs = undefined;
return [res];
},

group: ($: Group, opts) => [
...mapcat((x) => asCubic(x, opts), $.children),
],

line: ({ points, attribs }: Line, opts) => [
cubicFromLine(points[0], points[1], __attribs(opts, attribs)),
],

line3: ({ points, attribs }: Line, opts) => [
cubicFromLine3(points[0], points[1], __attribs(opts, attribs)),
],

path: ($: Path) => [
...mapcat(
(segment) => (segment.geo ? asCubic(segment.geo) : null),
concat($.segments, flatten1($.subPaths))
),
],

poly: ($: Polygon, opts) =>
__polyCubic(Cubic, $, opts, {
default: closedCubicFromControlPoints,
breakpoints: closedCubicFromBreakPoints,
hobby: (pts, scale) => cubicHobby2(pts, true, scale),
}),

poly3: ($: Polygon3, opts) =>
__polyCubic(Cubic3, $, opts, {
default: closedCubicFromControlPoints,
breakpoints: closedCubicFromBreakPoints,
}),

polyline: ($: Polyline, opts) =>
__polyCubic(Cubic, $, opts, {
default: openCubicFromControlPoints,
breakpoints: openCubicFromBreakPoints,
hobby: (pts, scale) => cubicHobby2(pts, false, scale),
}),

polyline3: ($: Polyline3, opts) =>
__polyCubic(Cubic3, $, opts, {
default: openCubicFromControlPoints,
breakpoints: openCubicFromBreakPoints,
}),

quadratic: ({ points, attribs }: Quadratic, opts) => [
cubicFromQuadratic(
points[0],
points[1],
points[2],
__attribs(opts, attribs)
),
],

quadratic3: ({ points, attribs }: Quadratic3, opts) => [
cubicFromQuadratic3(
points[0],
points[1],
points[2],
__attribs(opts, attribs)
),
],
}
)
);

type CubicConstructor<T extends Cubic | Cubic3> = {
new (pts: Vec[], attribs?: Attribs): T;
};

type CubicConversions = Record<
CubicOpts["mode"],
(points: ReadonlyVec[], scale?: number, uniform?: boolean) => Vec[][]
>;

/**
* @internal
*/
// prettier-ignore
const __polyCubic = (
$: PCLike,
opts: Partial<CubicOpts>,
breakPoints: (pts: ReadonlyVec[], t?: number, uniform?: boolean) => Vec[][],
controlPoints: (pts: ReadonlyVec[], t?: number, uniform?: boolean) => Vec[][]
const __polyCubic = <T extends Cubic | Cubic3>(
ctor: CubicConstructor<T>,
{ points, attribs }: PCLike,
opts: Maybe<Partial<CubicOpts>>,
conversions: Partial<CubicConversions>
) => {
opts = { breakPoints: false, scale: 1 / 3, uniform: false, ...opts };
return (opts.breakPoints
? breakPoints($.points, opts.scale, opts.uniform)
: controlPoints($.points, opts.scale, opts.uniform)
).map((pts) => new Cubic(pts, __copyAttribs($)));
opts = { mode: "default", uniform: false, scale: 1 / 3, ...opts };
const fn = conversions[opts.mode!];
if (!fn) unsupported(`conversion mode: ${opts.mode}`);
return fn(points, opts.scale, opts.uniform).map(
(pts) => new ctor(pts, __attribs(opts, attribs))
);
};

const __attribs = (opts?: Partial<AsCubicOpts>, attribs?: Attribs) =>
attribs && opts?.attribs !== false ? __copyAttribsRaw(attribs) : undefined;
Loading

0 comments on commit 9b4df2e

Please sign in to comment.