-
-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(geom): add new transform ops & helpers
- add applyTransforms(), rotate(), scale() - add internal helpers - update transform() rect coercion (now => Quad, previous Polygon)
- Loading branch information
1 parent
ccb40f1
commit cd8217c
Showing
8 changed files
with
356 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { withoutKeysObj } from "@thi.ng/associative/without-keys"; | ||
import type { MultiFn1 } from "@thi.ng/defmulti"; | ||
import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti"; | ||
import type { IHiccupShape, IShape } from "@thi.ng/geom-api"; | ||
import type { Group } from "./api/group.js"; | ||
import { __dispatch } from "./internal/dispatch.js"; | ||
import { rotate } from "./rotate.js"; | ||
import { scale } from "./scale.js"; | ||
import { transform } from "./transform.js"; | ||
import { translate } from "./translate.js"; | ||
|
||
/** @internal */ | ||
const __apply = ($: IShape) => { | ||
let attribs = $.attribs; | ||
if (!attribs) return $; | ||
const { transform: tx, translate: t, rotate: r, scale: s } = attribs; | ||
if (tx) | ||
return transform( | ||
$.withAttribs(withoutKeysObj(attribs, ["transform"])), | ||
tx | ||
); | ||
if (!(t || r || s)) return $; | ||
$ = $.withAttribs( | ||
withoutKeysObj(attribs, ["translate", "rotate", "scale"]) | ||
); | ||
if (r) $ = rotate($, r); | ||
if (s) $ = scale($, s); | ||
if (t) $ = translate($, t); | ||
return $; | ||
}; | ||
|
||
/** | ||
* Applies any spatial transformation attributes defined (if any) for the given | ||
* shape. If no such attributes exist, the original shape is returned as is. | ||
* | ||
* @remarks | ||
* The following attributes are considered: | ||
* | ||
* - transform: A 2x3 (for 2D) or 4x4 (for 3D) transformation matrix | ||
* - translate: Translation/offset vector | ||
* - scale: A scale factor (scalar or vector) | ||
* - rotate: Rotation angle (in radians) | ||
* | ||
* If the `transform` attrib is given, the others will be ignored. If any of the | ||
* other 3 attribs is provided, the order of application is: rotate, scale, | ||
* translate. Any of these relevant attributes will be removed from the | ||
* transformed shapes to ensure idempotent behavior. | ||
* | ||
* For (@link group} shapes, the children are processed in depth-first order | ||
* with any transformations to the group itself applied last. | ||
* | ||
* Note: Where possible, this function delegates to {@link rotate}, | ||
* {@link scale}, {@link translate} to realize individual/partial transformation | ||
* aspects to increase the likelihodd of retaining original shape types. E.g. | ||
* uniformly scaling a circle with a scalar factor retains a circle, but scaling | ||
* non-uniformly will convert it to an ellipse... Similarly, rotating a rect | ||
* will convert it to a quad etc. | ||
*/ | ||
export const applyTransforms: MultiFn1<IShape, IShape> = defmulti<any, IShape>( | ||
__dispatch, | ||
{}, | ||
{ | ||
[DEFAULT]: __apply, | ||
|
||
group: ($: Group) => | ||
__apply($.copyTransformed((x) => <IHiccupShape>applyTransforms(x))), | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import type { PCLike, PCLikeConstructor } from "@thi.ng/geom-api"; | ||
import type { ReadonlyVec } from "@thi.ng/vectors"; | ||
import { rotate } from "@thi.ng/vectors/rotate"; | ||
import { __copyAttribs } from "./copy.js"; | ||
|
||
export const __rotatedPoints = (pts: ReadonlyVec[], delta: number) => | ||
pts.map((x) => rotate([], x, delta)); | ||
|
||
export const __rotatedShape = | ||
(ctor: PCLikeConstructor) => ($: PCLike, delta: number) => | ||
new ctor(__rotatedPoints($.points, delta), __copyAttribs($)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { isNumber } from "@thi.ng/checks/is-number"; | ||
import type { PCLike, PCLikeConstructor } from "@thi.ng/geom-api"; | ||
import type { ReadonlyVec } from "@thi.ng/vectors"; | ||
import { mul } from "@thi.ng/vectors/mul"; | ||
import { mulN } from "@thi.ng/vectors/muln"; | ||
import { __copyAttribs } from "./copy.js"; | ||
|
||
export const __scaledPoints = ( | ||
pts: ReadonlyVec[], | ||
delta: number | ReadonlyVec | ||
) => | ||
pts.map( | ||
isNumber(delta) ? (x) => mulN([], x, delta) : (x) => mul([], x, delta) | ||
); | ||
|
||
export const __scaledShape = | ||
(ctor: PCLikeConstructor) => ($: PCLike, delta: number | ReadonlyVec) => | ||
new ctor(__scaledPoints($.points, delta), __copyAttribs($)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import type { MultiFn2 } from "@thi.ng/defmulti"; | ||
import { defmulti } from "@thi.ng/defmulti/defmulti"; | ||
import type { IHiccupShape, IShape } from "@thi.ng/geom-api"; | ||
import { rotate as $rotate } from "@thi.ng/vectors/rotate"; | ||
import type { Arc } from "./api/arc.js"; | ||
import { Circle } from "./api/circle.js"; | ||
import { Cubic } from "./api/cubic.js"; | ||
import type { 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 } from "./api/points.js"; | ||
import { Polygon } from "./api/polygon.js"; | ||
import { Polyline } from "./api/polyline.js"; | ||
import { Quad } from "./api/quad.js"; | ||
import { Quadratic } from "./api/quadratic.js"; | ||
import { Ray } from "./api/ray.js"; | ||
import type { Rect } from "./api/rect.js"; | ||
import { Text } from "./api/text.js"; | ||
import { Triangle } from "./api/triangle.js"; | ||
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 { __rotatedShape as tx } from "./internal/rotate.js"; | ||
|
||
export const rotate: MultiFn2<IShape, number, IShape> = defmulti< | ||
any, | ||
number, | ||
IShape | ||
>( | ||
__dispatch, | ||
{}, | ||
{ | ||
arc: ($: Arc, theta) => { | ||
const a = $.copy(); | ||
$rotate(null, a.pos, theta); | ||
return a; | ||
}, | ||
|
||
circle: ($: Circle, theta) => | ||
new Circle($rotate([], $.pos, theta), $.r, __copyAttribs($)), | ||
|
||
cubic: tx(Cubic), | ||
|
||
ellipse: ($: Ellipse, theta) => rotate(asPath($), theta), | ||
|
||
group: ($: Group, theta) => | ||
$.copyTransformed((x) => <IHiccupShape>rotate(x, theta)), | ||
|
||
line: tx(Line), | ||
|
||
path: ($: Path, theta) => { | ||
return new Path( | ||
$.segments.map((s) => | ||
s.geo | ||
? { | ||
type: s.type, | ||
geo: <any>rotate(s.geo, theta), | ||
} | ||
: { | ||
type: s.type, | ||
point: $rotate([], s.point!, theta), | ||
} | ||
), | ||
__copyAttribs($) | ||
); | ||
}, | ||
|
||
points: tx(Points), | ||
|
||
poly: tx(Polygon), | ||
|
||
polyline: tx(Polyline), | ||
|
||
quad: tx(Quad), | ||
|
||
quadratic: tx(Quadratic), | ||
|
||
ray: ($: Ray, theta) => { | ||
return new Ray( | ||
$rotate([], $.pos, theta), | ||
$rotate([], $.dir, theta), | ||
__copyAttribs($) | ||
); | ||
}, | ||
|
||
rect: ($: Rect, theta) => rotate(asPolygon($), theta), | ||
|
||
text: ($: Text, theta) => | ||
new Text($rotate([], $.pos, theta), $.body, __copyAttribs($)), | ||
|
||
tri: tx(Triangle), | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
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 { ReadonlyVec } from "@thi.ng/vectors"; | ||
import { mul2, mul3 } from "@thi.ng/vectors/mul"; | ||
import { mulN2, mulN3 } from "@thi.ng/vectors/muln"; | ||
import { normalize } from "@thi.ng/vectors/normalize"; | ||
import { AABB } from "./api/aabb.js"; | ||
import type { Arc } from "./api/arc.js"; | ||
import { Circle } from "./api/circle.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 { Polygon } from "./api/polygon.js"; | ||
import { Polyline } from "./api/polyline.js"; | ||
import { Quad } from "./api/quad.js"; | ||
import { Quadratic } from "./api/quadratic.js"; | ||
import { Ray } from "./api/ray.js"; | ||
import { Rect } from "./api/rect.js"; | ||
import { Sphere } from "./api/sphere.js"; | ||
import { Text } from "./api/text.js"; | ||
import { Triangle } from "./api/triangle.js"; | ||
import { __asVec } from "./internal/args.js"; | ||
import { __copyAttribs } from "./internal/copy.js"; | ||
import { __dispatch } from "./internal/dispatch.js"; | ||
import { __scaledShape as tx } from "./internal/scale.js"; | ||
|
||
export const scale: MultiFn2<IShape, number | ReadonlyVec, IShape> = defmulti< | ||
any, | ||
number | ReadonlyVec, | ||
IShape | ||
>( | ||
__dispatch, | ||
{}, | ||
{ | ||
aabb: ($: AABB, delta) => { | ||
delta = __asVec(delta, 3); | ||
return new AABB( | ||
mul3([], $.pos, delta), | ||
mul3([], $.size, delta), | ||
__copyAttribs($) | ||
); | ||
}, | ||
|
||
arc: ($: Arc, delta) => { | ||
delta = __asVec(delta); | ||
const a = $.copy(); | ||
mul2(null, a.pos, delta); | ||
mul2(null, a.r, delta); | ||
return a; | ||
}, | ||
|
||
circle: ($: Circle, delta) => | ||
isNumber(delta) | ||
? new Circle( | ||
mulN2([], $.pos, delta), | ||
$.r * delta, | ||
__copyAttribs($) | ||
) | ||
: new Ellipse( | ||
mul2([], $.pos, delta), | ||
mulN2([], delta, $.r), | ||
__copyAttribs($) | ||
), | ||
|
||
cubic: tx(Cubic), | ||
|
||
ellipse: ($: Ellipse, delta) => { | ||
delta = __asVec(delta); | ||
return new Ellipse( | ||
mul2([], $.pos, delta), | ||
mul2([], $.r, delta), | ||
__copyAttribs($) | ||
); | ||
}, | ||
|
||
group: ($: Group, delta) => | ||
$.copyTransformed((x) => <IHiccupShape>scale(x, delta)), | ||
|
||
line: tx(Line), | ||
|
||
path: ($: Path, delta) => { | ||
delta = __asVec(delta); | ||
return new Path( | ||
$.segments.map((s) => | ||
s.geo | ||
? { | ||
type: s.type, | ||
geo: <any>scale(s.geo, delta), | ||
} | ||
: { | ||
type: s.type, | ||
point: mul2([], s.point!, <ReadonlyVec>delta), | ||
} | ||
), | ||
__copyAttribs($) | ||
); | ||
}, | ||
|
||
points: tx(Points), | ||
|
||
points3: tx(Points3), | ||
|
||
poly: tx(Polygon), | ||
|
||
polyline: tx(Polyline), | ||
|
||
quad: tx(Quad), | ||
|
||
quadratic: tx(Quadratic), | ||
|
||
ray: ($: Ray, delta) => { | ||
delta = __asVec(delta); | ||
return new Ray( | ||
mul2([], $.pos, delta), | ||
normalize(null, mul2([], $.dir, delta)), | ||
__copyAttribs($) | ||
); | ||
}, | ||
|
||
rect: ($: Rect, delta) => { | ||
delta = __asVec(delta); | ||
return new Rect( | ||
mul2([], $.pos, delta), | ||
mul2([], $.size, delta), | ||
__copyAttribs($) | ||
); | ||
}, | ||
|
||
sphere: ($: Sphere, delta) => | ||
isNumber(delta) | ||
? new Sphere( | ||
mulN3([], $.pos, delta), | ||
$.r * delta, | ||
__copyAttribs($) | ||
) | ||
: unsupported("can't non-uniformly scale sphere"), | ||
|
||
text: ($: Text, delta) => | ||
new Text(mul2([], $.pos, __asVec(delta)), $.body, __copyAttribs($)), | ||
|
||
tri: tx(Triangle), | ||
} | ||
); |
Oops, something went wrong.