Skip to content

Commit

Permalink
feat(geom): add/update rotate, scale, translate, transform ops (signa…
Browse files Browse the repository at this point in the history
…tures & type support, impls)
  • Loading branch information
postspectacular committed May 13, 2024
1 parent 1cefc37 commit cb04a96
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 124 deletions.
21 changes: 17 additions & 4 deletions packages/geom/src/internal/rotate.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import type { PCLike, PCLikeConstructor } from "@thi.ng/geom-api";
import type { ReadonlyVec } from "@thi.ng/vectors";
import { rotate } from "@thi.ng/vectors/rotate";
import { rotateAroundAxis3 } from "@thi.ng/vectors/rotate-around-axis";
import { __copyAttribs } from "./copy.js";

export const __rotatedPoints = (pts: ReadonlyVec[], delta: number) =>
pts.map((x) => rotate([], x, delta));
export const __rotatedPoints = (pts: ReadonlyVec[], theta: number) =>
pts.map((x) => rotate([], x, theta));

export const __rotatedPoints3 = (
pts: ReadonlyVec[],
axis: ReadonlyVec,
theta: number
) => pts.map((x) => rotateAroundAxis3([], x, axis, theta));

export const __rotatedShape =
(ctor: PCLikeConstructor) => ($: PCLike, delta: number) =>
new ctor(__rotatedPoints($.points, delta), __copyAttribs($));
<T extends PCLike>(ctor: PCLikeConstructor<T>) =>
($: T, theta: number) =>
<T>new ctor(__rotatedPoints($.points, theta), __copyAttribs($));

export const __rotatedShape3 =
<T extends PCLike>(ctor: PCLikeConstructor<T>) =>
($: T, axis: ReadonlyVec, theta: number) =>
<T>new ctor(__rotatedPoints3($.points, axis, theta), __copyAttribs($));
46 changes: 29 additions & 17 deletions packages/geom/src/internal/transform.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// thing:export
import type { Fn, FnU } from "@thi.ng/api";
import type {
IShape,
IShape2,
IShape3,
PCLike,
PCLikeConstructor,
PathSegment,
PathSegment2,
} from "@thi.ng/geom-api";
import type { MatOpMV, ReadonlyMat } from "@thi.ng/matrices";
import { mulV, mulV344 } from "@thi.ng/matrices/mulv";
Expand Down Expand Up @@ -41,13 +43,14 @@ export const __transformedPointsWith = (

/** @internal */
export const __transformedShape =
(ctor: PCLikeConstructor) => ($: PCLike, mat: ReadonlyMat) =>
<T extends PCLike>(ctor: PCLikeConstructor<T>) =>
($: T, mat: ReadonlyMat) =>
new ctor(__transformedPoints($.points, mat), __copyAttribs($));

/** @internal */
export const __transformedShapePoints =
(ctor: PCLikeConstructor) =>
($: PCLike, fn: Fn<ReadonlyVec, ReadonlyMat>) =>
<T extends PCLike>(ctor: PCLikeConstructor<T>) =>
($: T, fn: Fn<ReadonlyVec, ReadonlyMat>) =>
new ctor(__transformedPointsWith($.points, fn), __copyAttribs($));

// 3d versions
Expand Down Expand Up @@ -79,19 +82,28 @@ export const __transformedShapePoints3 =

// path segments

type SegmentShapeMap<T extends PathSegment> = T extends PathSegment2
? IShape2
: IShape3;

/** @internal */
export const __segmentTransformer =
(txGeo: FnU<IShape>, txPoint: FnU<Vec>) => (segments: PathSegment[]) =>
segments.map((s: PathSegment) =>
s.geo
? <PathSegment>{
type: s.type,
geo: txGeo(s.geo),
}
: s.point
? {
type: s.type,
point: txPoint(s.point),
}
: { ...s }
<S extends PathSegment>(
txGeo: FnU<SegmentShapeMap<S>>,
txPoint: FnU<Vec>
) =>
(segments: S[]) =>
segments.map(
(s): S =>
s.geo
? <any>{
type: s.type,
geo: txGeo(<any>s.geo),
}
: s.point
? <S>{
type: s.type,
point: txPoint(s.point),
}
: <S>{ ...s }
);
47 changes: 28 additions & 19 deletions packages/geom/src/rotate.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { MultiFn2 } from "@thi.ng/defmulti";
import { defmulti } from "@thi.ng/defmulti/defmulti";
import type { IHiccupShape, IShape } from "@thi.ng/geom-api";
import type { IHiccupShape2, IShape2, PathSegment2 } from "@thi.ng/geom-api";
import { rotate as $rotate } from "@thi.ng/vectors/rotate";
import type { Arc } from "./api/arc.js";
import { BPatch } from "./api/bpatch.js";
import { Circle } from "./api/circle.js";
import { ComplexPolygon } from "./api/complex-polygon.js";
import { Cubic } from "./api/cubic.js";
Expand All @@ -23,16 +24,25 @@ import { asPath } from "./as-path.js";
import { asPolygon } from "./as-polygon.js";
import { __copyAttribs } from "./internal/copy.js";
import { __dispatch } from "./internal/dispatch.js";
import { __ensureNoArc } from "./internal/error.js";
import { __rotatedShape as tx } from "./internal/rotate.js";
import { __segmentTransformer } from "./internal/transform.js";

export type RotateFn = {
(shape: Arc, theta: number): Path;
(shape: Ellipse, theta: number): Path;
(shape: Rect, theta: number): Polygon;
<T extends IShape2>(shape: T, theta: number): T;
} & MultiFn2<IShape2, number, IShape2>;

/**
* Rotates given 2D shape by `theta` (in radians).
*
* @remarks
* Currently implemented for:
*
* - {@link Arc}
* - {@link BPatch}
* - {@link Circle}
* - {@link ComplexPolygon}
* - {@link Cubic}
Expand All @@ -53,19 +63,19 @@ import { __segmentTransformer } from "./internal/transform.js";
* @param shape
* @param theta
*/
export const rotate: MultiFn2<IShape, number, IShape> = defmulti<
any,
number,
IShape
>(
export const rotate = <RotateFn>defmulti<any, number, IShape2>(
__dispatch,
{},
{
arc: ($: Arc, theta) => {
const a = $.copy();
$rotate(null, a.pos, theta);
return a;
},
arc: "$aspath",
ellipse: "$aspath",
rect: "$aspoly",
},
{
$aspath: ($, theta) => rotate(asPath($), theta),

$aspoly: ($, theta) => rotate(asPolygon($)[0], theta),

bpatch: tx(BPatch),

circle: ($: Circle, theta) =>
new Circle($rotate([], $.pos, theta), $.r, __copyAttribs($)),
Expand All @@ -78,16 +88,17 @@ export const rotate: MultiFn2<IShape, number, IShape> = defmulti<

cubic: tx(Cubic),

ellipse: ($: Ellipse, theta) => rotate(asPath($), theta),

group: ($: Group, theta) =>
$.copyTransformed((x) => <IHiccupShape>rotate(x, theta)),
$.copyTransformed((x) => <IHiccupShape2>rotate(x, theta)),

line: tx(Line),

path: ($: Path, theta) => {
const $rotateSegments = __segmentTransformer(
(geo) => rotate(geo, theta),
const $rotateSegments = __segmentTransformer<PathSegment2>(
(geo) => {
__ensureNoArc(geo);
return rotate(geo, theta);
},
(p) => $rotate([], p, theta)
);
return new Path(
Expand Down Expand Up @@ -115,8 +126,6 @@ export const rotate: MultiFn2<IShape, number, IShape> = defmulti<
);
},

rect: ($: Rect, theta) => rotate(asPolygon($)[0], theta),

text: ($: Text, theta) =>
new Text($rotate([], $.pos, theta), $.body, __copyAttribs($)),

Expand Down
9 changes: 5 additions & 4 deletions packages/geom/src/scale-with-center.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ import { translate } from "./translate.js";
* thi.ng/matrices](https://docs.thi.ng/umbrella/matrices/functions/scaleWithCenter23.html)),
* but will not change the shape type (as might be the case with `transform()`).
*
* Also see: {@link scale}, {@link translate}, {@link applyTransforms}.
* Also see: {@link scaleImpl}, {@link translateImpl}, {@link applyTransforms}.
*
* @param shape
* @param center
* @param factor
*/
export const scaleWithCenter = (
shape: IShape,
export const scaleWithCenter = <T extends IShape>(
shape: T,
center: ReadonlyVec,
factor: number
) => translate(scale(translate(shape, mulN([], center, -1)), factor), center);
) =>
<T>translate(scale(translate(shape, mulN([], center, -1)), factor), center);
33 changes: 23 additions & 10 deletions packages/geom/src/scale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,29 @@ import { isNumber } from "@thi.ng/checks/is-number";
import type { MultiFn2 } from "@thi.ng/defmulti";
import { defmulti } from "@thi.ng/defmulti/defmulti";
import { unsupported } from "@thi.ng/errors/unsupported";
import type { IHiccupShape, IShape } from "@thi.ng/geom-api";
import type {
IHiccupShape2,
IShape,
IShape2,
IShape3,
PathSegment2,
} from "@thi.ng/geom-api";
import type { ReadonlyVec } from "@thi.ng/vectors";
import { mul2, mul3 } from "@thi.ng/vectors/mul";
import { mulN2, mulN3 } from "@thi.ng/vectors/muln";
import { normalize2 } from "@thi.ng/vectors/normalize";
import { AABB } from "./api/aabb.js";
import type { Arc } from "./api/arc.js";
import { BPatch } from "./api/bpatch.js";
import { Circle } from "./api/circle.js";
import { ComplexPolygon } from "./api/complex-polygon.js";
import { Cubic } from "./api/cubic.js";
import { Ellipse } from "./api/ellipse.js";
import type { Group } from "./api/group.js";
import { Line } from "./api/line.js";
import { Path } from "./api/path.js";
import { Points, Points3 } from "./api/points.js";
import { Points } from "./api/points.js";
import { Points3 } from "./api/points3.js";
import { Polygon } from "./api/polygon.js";
import { Polyline } from "./api/polyline.js";
import { Quad } from "./api/quad.js";
Expand All @@ -32,6 +40,13 @@ import { __dispatch } from "./internal/dispatch.js";
import { __scaledShape as tx } from "./internal/scale.js";
import { __segmentTransformer } from "./internal/transform.js";

export type ScaleFn = {
(shape: Circle, factor: number): Circle;
(shape: Circle, factor: ReadonlyVec): Ellipse;
<T extends IShape2>(shape: T, factor: number | ReadonlyVec): T;
<T extends IShape3>(shape: T, factor: number | ReadonlyVec): T;
} & MultiFn2<IShape, number | ReadonlyVec, IShape>;

/**
* Scales given shape uniformly or non-uniformly by given `factor`.
*
Expand Down Expand Up @@ -65,11 +80,7 @@ import { __segmentTransformer } from "./internal/transform.js";
* @param shape
* @param factor
*/
export const scale: MultiFn2<IShape, number | ReadonlyVec, IShape> = defmulti<
any,
number | ReadonlyVec,
IShape
>(
export const scale = <ScaleFn>defmulti<any, number | ReadonlyVec, IShape>(
__dispatch,
{},
{
Expand All @@ -90,6 +101,8 @@ export const scale: MultiFn2<IShape, number | ReadonlyVec, IShape> = defmulti<
return a;
},

bpatch: tx(BPatch),

circle: ($: Circle, delta) =>
isNumber(delta)
? new Circle(
Expand Down Expand Up @@ -121,14 +134,14 @@ export const scale: MultiFn2<IShape, number | ReadonlyVec, IShape> = defmulti<
},

group: ($: Group, delta) =>
$.copyTransformed((x) => <IHiccupShape>scale(x, delta)),
$.copyTransformed((x) => <IHiccupShape2>scale(x, delta)),

line: tx(Line),

path: ($: Path, delta) => {
delta = __asVec(delta);
const $scaleSegments = __segmentTransformer(
(geo) => scale(geo, delta),
const $scaleSegments = __segmentTransformer<PathSegment2>(
(geo) => <IShape2>scale(geo, delta),
(p) => mul2([], p, <ReadonlyVec>delta)
);
return new Path(
Expand Down
Loading

0 comments on commit cb04a96

Please sign in to comment.