@rdme/curve-2d
Version:
2d curves in typescript
141 lines (124 loc) • 3.96 kB
text/typescript
import { Catmull2d } from "src/catmull2d";
import { Curve2d } from "src/curve2d";
import { Vec2d, Vec2dLike } from "src/vec2d";
export class Bezier2d extends Curve2d {
public constructor(points: Vec2dLike[]) {
super(points);
}
public get(t: number): Vec2d {
const points = this._points;
const order = this.order;
if (t === 0) return points[0];
if (t === 1) return points[order];
switch (order) {
case 0:
return points[0];
case 1:
return points[0].lerp(points[1], t);
case 2:
return Bezier2d._getQuadratic(points[0], points[1], points[2], t);
case 3:
return Bezier2d._getCubic(
points[0],
points[1],
points[2],
points[3],
t
);
default:
return Bezier2d._getDeCasteljau(points, t);
}
}
public getSegment(tStart: number, tEnd: number): this {
// constant point
if (tStart === tEnd) return new Bezier2d([this.get(tStart)]) as this;
// reverse segment
if (tStart > tEnd) return this.getSegment(tEnd, tStart).reverse();
// just calculate upper bound points, lower is initial
if (tStart === 0)
return new Bezier2d(
Bezier2d._getDeCasteljauPoints(this._points, tEnd)
) as this;
// just calculate lower bound points, upper is initial
if (tEnd === 1)
return new Bezier2d(
Bezier2d._getDeCasteljauPoints(
this._points.slice().reverse(),
1 - tStart
)
).reverse() as this;
// first calculate upper bounds
const points = Bezier2d._getDeCasteljauPoints(this._points, tEnd).reverse();
// then lower bounds
return new Bezier2d(
Bezier2d._getDeCasteljauPoints(points, (1 - tStart) * tEnd)
).reverse() as this;
}
public clone(): this {
return this._applyClone(new Bezier2d(this._points) as this);
}
public get order(): number {
return this._points.length - 1;
}
public length(precision?: number): number {
const order = this.order;
if (order === 0) return 0;
if (order === 1) return this._points[0].distance(this._points[1]);
return super.length(precision);
}
public catmull(precision: number = this._points.length + 2): Catmull2d {
return super.catmull(precision);
}
public simplify(precision?: number): Bezier2d[] {
return (this as any).beziers(precision);
}
private static _getQuadratic(p0: Vec2d, p1: Vec2d, p2: Vec2d, t: number) {
// implementation stolen and adapted from bezier-js
const mt = 1 - t;
const a = mt * mt;
const b = mt * t * 2;
const c = t * t;
return new Vec2d(
a * p0.x + b * p1.x + c * p2.x,
a * p0.y + b * p1.y + c * p2.y
);
}
private static _getCubic(
p0: Vec2d,
p1: Vec2d,
p2: Vec2d,
p3: Vec2d,
t: number
) {
// implementation stolen and adapted from bezier-js
const mt = 1 - t,
mt2 = mt * mt,
t2 = t * t;
const a = mt2 * mt;
const b = mt2 * t * 3;
const c = mt * t2 * 3;
const d = t * t2;
return new Vec2d(
a * p0.x + b * p1.x + c * p3.x + d * p3.x,
a * p0.y + b * p1.y + c * p2.y + d * p3.y
);
}
private static _getDeCasteljau(points: Vec2d[], t: number) {
// implementation stolen and adapted from bezier-js
const p = points.slice();
while (p.length > 1) {
for (let i = 0; i < p.length - 1; ++i) p[i] = p[i].lerp(p[i + 1], t);
p.splice(p.length - 1, 1);
}
return p[0];
}
private static _getDeCasteljauPoints(points: Vec2d[], t: number) {
// to split a curve at point t, you have to calculate all the casteljau
// points at that value, the resulting curve is of the same order and
// ends at the point the curve would have had at t
const p = points.slice();
for (let upper = p.length - 1; upper > 0; --upper)
for (let i = 0; i < upper - 1; ++i) p[i] = p[i].lerp(p[i + 1], t);
return p;
}
}