Skip to content

Commit

Permalink
refactor(geom): dedupe 2d/3d versions of pathFromCubics(), normalized…
Browse files Browse the repository at this point in the history
…Path()
  • Loading branch information
postspectacular committed Jun 18, 2024
1 parent ef82528 commit da8ed42
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 93 deletions.
70 changes: 70 additions & 0 deletions packages/geom/src/internal/path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { Maybe } from "@thi.ng/api";
import { map } from "@thi.ng/transducers/map";
import { mapcat } from "@thi.ng/transducers/mapcat";
import type { ReadonlyVec } from "@thi.ng/vectors";
import { equals } from "@thi.ng/vectors/equals";
import type {
Attribs,
PathSegment2,
PathSegment3,
SegmentType2,
} from "../api.js";
import type { Cubic } from "../api/cubic.js";
import type { Cubic3 } from "../api/cubic3.js";
import type { Path } from "../api/path.js";
import type { Path3 } from "../api/path3.js";
import { asCubic } from "../as-cubic.js";
import { __copySegment } from "./copy.js";

interface PathType {
2: { path: Path; ctor: typeof Path; cubic: Cubic; seg: PathSegment2 };
3: { path: Path3; ctor: typeof Path3; cubic: Cubic3; seg: PathSegment3 };
}

export const __pathFromCubics = <T extends 2 | 3>(
ctor: PathType[T]["ctor"],
cubics: PathType[T]["cubic"][],
attribs?: Attribs
): PathType[T]["path"] => {
let subPaths: PathType[T]["seg"][][] = [];
let curr: PathType[T]["seg"][];
let lastP: Maybe<ReadonlyVec>;
const $beginPath = (c: PathType[T]["cubic"]) => {
curr = [{ type: "m", point: c.points[0] }];
subPaths.push(curr);
};
for (let c of cubics) {
if (!(lastP && equals(lastP, c.points[0]))) $beginPath(c);
curr!.push(<PathType[T]["seg"]>{ type: "c", geo: c });
lastP = c.points[3];
}
const path = new ctor(
<any>subPaths[0],
<any>subPaths.slice(1),
attribs || cubics[0].attribs
);
return path;
};

export const __normalizedPath = <T extends 2 | 3>(
ctor: PathType[T]["ctor"],
path: PathType[T]["path"],
only?: SegmentType2[]
): PathType[T]["path"] => {
const $normalize = (segments: PathType[T]["seg"][]) => [
...mapcat((s) => {
if (s.geo && (!only || only.includes(<any>s.type))) {
return map<PathType[T]["cubic"], PathType[T]["seg"]>(
(c) => <PathType[T]["seg"]>{ type: "c", geo: c },
asCubic(s.geo)
);
}
return [__copySegment(s)];
}, segments),
];
return new ctor(
<any>$normalize(path.segments),
<any>path.subPaths.map($normalize),
path.attribs
);
};
53 changes: 6 additions & 47 deletions packages/geom/src/path.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import type { Maybe } from "@thi.ng/api";
import { isNumber } from "@thi.ng/checks/is-number";
import type { Vec } from "@thi.ng/vectors";
import type { Attribs, PathSegment2, SegmentType2 } from "./api.js";
import { map } from "@thi.ng/transducers/map";
import { mapcat } from "@thi.ng/transducers/mapcat";
import type { ReadonlyVec, Vec } from "@thi.ng/vectors";
import { equals2 } from "@thi.ng/vectors/equals";
import type { Cubic } from "./api/cubic.js";
import { Path } from "./api/path.js";
import { asCubic } from "./as-cubic.js";
import { __copySegment } from "./internal/copy.js";
import { __normalizedPath, __pathFromCubics } from "./internal/path.js";
import { PathBuilder } from "./path-builder.js";

/**
Expand All @@ -34,7 +29,7 @@ export const path = (
* `attribs`.
*
* @remarks
* If no `attribs` are given, those from the first curve will be used.
* If no `attribs` are given, those from the first curve will be used (if any).
*
* For each successive curve segment, if the start point of the current curve is
* not the same as the last point of the previous curve, a new sub path will be
Expand All @@ -45,26 +40,8 @@ export const path = (
* @param cubics
* @param attribs
*/
export const pathFromCubics = (cubics: Cubic[], attribs?: Attribs) => {
let subPaths: PathSegment2[][] = [];
let curr: PathSegment2[];
let lastP: Maybe<ReadonlyVec>;
const $beginPath = (c: Cubic) => {
curr = [{ type: "m", point: c.points[0] }];
subPaths.push(curr);
};
for (let c of cubics) {
if (!(lastP && equals2(lastP, c.points[0]))) $beginPath(c);
curr!.push({ type: "c", geo: c });
lastP = c.points[3];
}
const path = new Path(
subPaths[0],
subPaths.slice(1),
attribs || cubics[0].attribs
);
return path;
};
export const pathFromCubics = (cubics: Cubic[], attribs?: Attribs) =>
__pathFromCubics<2>(Path, cubics, attribs);

/**
* Converts given path into a new one with segments converted to {@link Cubic}
Expand All @@ -80,25 +57,7 @@ export const pathFromCubics = (cubics: Cubic[], attribs?: Attribs) => {
export const normalizedPath = (
path: Path,
only?: Extract<SegmentType2, "a" | "l" | "p" | "q">[]
) => {
const $normalize = (segments: PathSegment2[]) => [
...mapcat((s) => {
s.type;
if (s.geo && (!only || only.includes(<any>s.type))) {
return map<Cubic, PathSegment2>(
(c) => ({ type: "c", geo: c }),
asCubic(s.geo)
);
}
return [__copySegment(s)];
}, segments),
];
return new Path(
$normalize(path.segments),
path.subPaths.map($normalize),
path.attribs
);
};
) => __normalizedPath<2>(Path, path, only);

/**
* Creates a new rounded rect {@link Path}, using the given corner radius or
Expand Down
51 changes: 5 additions & 46 deletions packages/geom/src/path3.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import type { Maybe } from "@thi.ng/api";
import type { Attribs, PathSegment3, SegmentType3 } from "./api.js";
import { map } from "@thi.ng/transducers/map";
import { mapcat } from "@thi.ng/transducers/mapcat";
import type { ReadonlyVec } from "@thi.ng/vectors";
import { equals3 } from "@thi.ng/vectors/equals";
import type { Cubic3 } from "./api/cubic3.js";
import { Path3 } from "./api/path3.js";
import { asCubic } from "./as-cubic.js";
import { __copySegment } from "./internal/copy.js";
import { __normalizedPath, __pathFromCubics } from "./internal/path.js";

/**
* Creates a new {@link Path3} instance, optional with given `segments`,
Expand All @@ -32,7 +26,7 @@ export const path3 = (
* `attribs`.
*
* @remarks
* If no `attribs` are given, those from the first curve will be used.
* If no `attribs` are given, those from the first curve will be used (if any).
*
* For each successive curve segment, if the start point of the current curve is
* not the same as the last point of the previous curve, a new sub path will be
Expand All @@ -43,26 +37,8 @@ export const path3 = (
* @param cubics
* @param attribs
*/
export const pathFromCubics3 = (cubics: Cubic3[], attribs?: Attribs) => {
let subPaths: PathSegment3[][] = [];
let curr: PathSegment3[];
let lastP: Maybe<ReadonlyVec>;
const $beginPath = (c: Cubic3) => {
curr = [{ type: "m", point: c.points[0] }];
subPaths.push(curr);
};
for (let c of cubics) {
if (!(lastP && equals3(lastP, c.points[0]))) $beginPath(c);
curr!.push({ type: "c", geo: c });
lastP = c.points[3];
}
const path = new Path3(
subPaths[0],
subPaths.slice(1),
attribs || cubics[0].attribs
);
return path;
};
export const pathFromCubics3 = (cubics: Cubic3[], attribs?: Attribs) =>
__pathFromCubics<3>(Path3, cubics, attribs);

/**
* Converts given path into a new one with segments converted to {@link Cubic3}
Expand All @@ -78,21 +54,4 @@ export const pathFromCubics3 = (cubics: Cubic3[], attribs?: Attribs) => {
export const normalizedPath3 = (
path: Path3,
only?: Extract<SegmentType3, "l" | "p" | "q">[]
) => {
const $normalize = (segments: PathSegment3[]) => [
...mapcat((s) => {
if (s.geo && (!only || only.includes(<any>s.type))) {
return map<Cubic3, PathSegment3>(
(c) => ({ type: "c", geo: c }),
asCubic(s.geo)
);
}
return [__copySegment(s)];
}, segments),
];
return new Path3(
$normalize(path.segments),
path.subPaths.map($normalize),
path.attribs
);
};
) => __normalizedPath<3>(Path3, path, only);

0 comments on commit da8ed42

Please sign in to comment.