Skip to content

Commit

Permalink
feat(geom): add path builder, path & arc op impls
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Jan 16, 2019
1 parent e81d8c3 commit 61cfb0f
Show file tree
Hide file tree
Showing 13 changed files with 657 additions and 44 deletions.
138 changes: 116 additions & 22 deletions packages/geom3/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ICopy, IObjectOf, IToHiccup } from "@thi.ng/api";
import { isNumber } from "@thi.ng/checks";
import { equiv } from "@thi.ng/equiv";
import { illegalState } from "@thi.ng/errors";
import { cossin } from "@thi.ng/math";
import {
add2,
Expand All @@ -13,6 +14,16 @@ import {
} from "@thi.ng/vectors3";
import { copyPoints } from "./internal/copy-points";

export const enum SegmentType {
MOVE,
LINE,
POLYLINE,
ARC,
CUBIC,
QUADRATIC,
CLOSE,
}

export const enum Type {
AABB = 1,
ARC,
Expand Down Expand Up @@ -68,15 +79,7 @@ export interface AABBLike extends IShape {
size: Vec;
}

export interface PCLike extends IShape {
points: Vec[];
}

export interface PCLikeConstructor {
new(pts: Vec[], attribs: Attribs): PCLike;
}

export interface HiccupShape extends IShape, IToHiccup { }
export interface IHiccupShape extends IShape, IToHiccup { }

export interface IHiccupPathSegment {
toHiccupPathSegments(): any[];
Expand All @@ -90,6 +93,20 @@ export interface LineIntersection {
beta?: number;
}

export interface PathSegment {
type: SegmentType;
point?: Vec;
geo?: IShape & IHiccupPathSegment;
}

export interface PCLike extends IShape {
points: Vec[];
}

export interface PCLikeConstructor {
new(pts: Vec[], attribs: Attribs): PCLike;
}

export interface SamplingOpts {
/**
* Number of points to sample & return. Defaults to the implementing
Expand All @@ -109,8 +126,8 @@ export interface SamplingOpts {
/**
* Currently only used by these types:
*
* - Arc2
* - Circle2
* - Arc
* - Circle
*
* Defines the target angle between sampled points. If greater than
* the actual range of the arc, only the two end points will be
Expand Down Expand Up @@ -189,7 +206,7 @@ export class AABB implements
}

export class Arc implements
HiccupShape,
IHiccupShape,
IHiccupPathSegment {

pos: Vec;
Expand Down Expand Up @@ -276,7 +293,7 @@ export class Arc implements
}

export class Circle implements
HiccupShape {
IHiccupShape {

pos: Vec;
r: number;
Expand Down Expand Up @@ -328,7 +345,7 @@ export class Cubic extends APC implements
}

export class Ellipse implements
HiccupShape {
IHiccupShape {

pos: Vec;
r: Vec;
Expand All @@ -354,12 +371,12 @@ export class Ellipse implements
}

export class Group implements
HiccupShape {
IHiccupShape {

children: HiccupShape[];
children: IHiccupShape[];
attribs: Attribs;

constructor(children: HiccupShape[], attribs?: Attribs) {
constructor(children: IHiccupShape[], attribs?: Attribs) {
this.children = children;
this.attribs = attribs;
}
Expand All @@ -374,7 +391,7 @@ export class Group implements

copy() {
return new Group(
<HiccupShape[]>this.children.map((c) => c.copy()),
<IHiccupShape[]>this.children.map((c) => c.copy()),
{ ...this.attribs }
);
}
Expand All @@ -389,7 +406,9 @@ export class Group implements
}
}

export class Line extends APC {
export class Line extends APC implements
IHiccupShape,
IHiccupPathSegment {

get type() {
return Type.LINE;
Expand All @@ -402,6 +421,71 @@ export class Line extends APC {
toHiccup() {
return ["line", this.attribs, this.points[0], this.points[1]];
}

toHiccupPathSegments() {
const [a, b] = this.points;
return [
a[0] === b[0] ?
["V", b[1]] :
a[1] === b[1] ?
["H", b[0]] :
["L", b]
];
}
}

export class Path implements
IHiccupShape {

segments: PathSegment[];
closed: boolean;
attribs: Attribs;

constructor(segments?: PathSegment[], attribs?: Attribs) {
this.segments = segments || [];
this.attribs = attribs;
this.closed = false;
}

get type() {
return Type.PATH;
}

*[Symbol.iterator]() {
yield* this.segments;
}

copy() {
const p = new Path([...this.segments], { ...this.attribs });
p.closed = this.closed;
return p;
}

equiv(o: any) {
return o instanceof Path &&
equiv(this.segments, o.segments);
}

add(s: PathSegment) {
if (this.closed) illegalState("path already closed");
this.segments.push(s);
}

toHiccup() {
let dest: any[] = [];
const segments = this.segments;
const n = segments.length;
if (n > 1) {
dest.push(["M", segments[0].point]);
for (let i = 1; i < n; i++) {
dest = dest.concat(segments[i].geo.toHiccupPathSegments());
}
if (this.closed) {
dest.push(["Z"]);
}
}
return ["path", this.attribs || {}, dest];
}
}

export class Points extends APC {
Expand Down Expand Up @@ -434,7 +518,9 @@ export class Polygon extends APC {
}
}

export class Polyline extends APC {
export class Polyline extends APC implements
IHiccupShape,
IHiccupPathSegment {

get type() {
return Type.POLYLINE;
Expand All @@ -447,6 +533,14 @@ export class Polyline extends APC {
toHiccup() {
return ["polyline", { ...this.attribs, fill: "none" }, this.points];
}

toHiccupPathSegments() {
const res: any[] = [];
for (let pts = this.points, n = pts.length, i = 1; i < n; i++) {
res.push(["L", pts[i]]);
}
return res;
}
}

export class Quad extends APC {
Expand Down Expand Up @@ -491,7 +585,7 @@ export class Quadratic extends APC implements
}

export class Ray implements
HiccupShape {
IHiccupShape {

pos: Vec;
dir: Vec;
Expand All @@ -517,7 +611,7 @@ export class Ray implements
}

export class Rect implements
HiccupShape {
IHiccupShape {

pos: Vec;
size: Vec;
Expand Down
69 changes: 69 additions & 0 deletions packages/geom3/src/ctors/arc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { isNumber } from "@thi.ng/checks";
import { TAU } from "@thi.ng/math";
import {
abs2,
angleBetween2,
mulN2,
ReadonlyVec,
submN2,
Vec,
X2
} from "@thi.ng/vectors3";
import { Arc } from "../api";

export const arc = (
pos: Vec,
r: number | Vec,
axis: number,
start: number,
end: number,
xl = false,
clockwise = false
) => new Arc(pos, isNumber(r) ? [r, r] : r, axis, start, end, xl, clockwise);

export const arcFrom2Points = (
a: ReadonlyVec,
b: ReadonlyVec,
radii: ReadonlyVec,
axisTheta = 0,
large = false,
clockwise = false
) => {

const r = abs2([], radii);
const co = Math.cos(axisTheta);
const si = Math.sin(axisTheta);
const m = submN2([], a, b, 0.5);
// const m = mulN2(null, sub2([], a, b), 0.5);
const px = co * m[0] + si * m[1];
const py = -si * m[0] + co * m[1];
const px2 = px * px;
const py2 = py * py;

const l = px2 / (r[0] * r[0]) + py2 / (r[1] * r[1]);
l > 1 && mulN2(null, r, Math.sqrt(l));

const rx2 = r[0] * r[0];
const ry2 = r[1] * r[1];
const rxpy = rx2 * py2;
const rypx = ry2 * px2;
const rad = ((large === clockwise) ? -1 : 1) *
Math.sqrt(Math.max(0, rx2 * ry2 - rxpy - rypx) / (rxpy + rypx));

const tx = rad * r[0] / r[1] * py;
const ty = -rad * r[1] / r[0] * px;
const c = [co * tx - si * ty + (a[0] + b[0]) / 2, si * tx + co * ty + (a[1] + b[1]) / 2];
const d1 = [(px - tx) / r[0], (py - ty) / r[1]];
const d2 = [(-px - tx) / r[0], (-py - ty) / r[1]];

const theta = angleBetween2(X2, d1);
let delta = angleBetween2(d1, d2);

if (clockwise && delta < 0) {
delta += TAU;
} else if (!clockwise && delta > 0) {
delta -= TAU;
}

return new Arc(c, r, axisTheta, theta, theta + delta, large, clockwise);
};
4 changes: 2 additions & 2 deletions packages/geom3/src/ctors/group.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HiccupShape, Attribs, Group } from "../api";
import { IHiccupShape, Attribs, Group } from "../api";

export const group =
(children: HiccupShape[], attribs?: Attribs) =>
(children: IHiccupShape[], attribs?: Attribs) =>
new Group(children, attribs);
Loading

0 comments on commit 61cfb0f

Please sign in to comment.