Select Git revision
-
Andres Montalban authoredAndres Montalban authored
This project manages its dependencies using Bundler.
Learn more
CubicBezierCurve2d.ts 4.51 KiB
import { Vector2 } from "three";
function lerp(a: Vector2, b: Vector2, t: number): Vector2 {
return new Vector2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
}
export class CubicBezierCurve2d {
private _startPoint: Vector2;
private _endPoint: Vector2;
private _startControlPoint: Vector2;
private _endControlPoint: Vector2;
private _dirty: boolean = true;
private _resolution: number = 5;
private _points: Vector2[] = [];
private _coefficients: number[][] = [];
constructor(
startPoint: Vector2,
endPoint: Vector2,
startControlPoint: Vector2,
endControlPoint: Vector2
) {
this._startPoint = startPoint;
this._endPoint = endPoint;
this._startControlPoint = startControlPoint;
this._endControlPoint = endControlPoint;
}
get startPoint(): Vector2 {
return this._startPoint;
}
get endPoint(): Vector2 {
return this._endPoint;
}
get startControlPoint(): Vector2 {
return this._startControlPoint;
}
get endControlPoint(): Vector2 {
return this._endControlPoint;
}
get points(): Vector2[] {
return this._points;
}
get coefficients(): number[][] {
return this._coefficients;
}
set startPoint(value: Vector2) {
this._startPoint = value;
this._dirty = true;
this.generatePoints(this._resolution);
}
set endPoint(value: Vector2) {
this._endPoint = value;
this._dirty = true;
this.generatePoints(this._resolution);
}
set startControlPoint(value: Vector2) {
this._startControlPoint = value;
this._dirty = true;
this.generatePoints(this._resolution);
}
set endControlPoint(value: Vector2) {
this._endControlPoint = value;
this._dirty = true;
this.generatePoints(this._resolution);
}
bernstain(s: number): [Vector2, number[]] {
// p(t) = (1-t)³ * p0 + 3 * (1-t)² * t * p1 +
// 3 * (1-t) * t² * p2 + t³ * p3
// => subtitute: 1 - t = s
// p(t) = k³ * p0 + 3 * k² * t * p1 +
// 3 * k * t² * p2 + t³ * p3
const p0 = this.startPoint;
const p1 = this.startControlPoint;
const p2 = this.endControlPoint;
const p3 = this.endPoint;
// set k = 1 - s => substitution
const t = s;
const k = 1 - s;
// calculate the coefficients
const u0 = 1 * Math.pow(k, 3) * Math.pow(t, 0);
const u1 = 3 * Math.pow(k, 2) * Math.pow(t, 1);
const u2 = 3 * Math.pow(k, 1) * Math.pow(t, 2);
const u3 = 1 * Math.pow(k, 0) * Math.pow(t, 3);
// calculate the point
const p = new Vector2(
u0 * p0.x + u1 * p1.x + u2 * p2.x + u3 * p3.x,
u0 * p0.y + u1 * p1.y + u2 * p2.y + u3 * p3.y
);
return [p, [u0, u1, u2, u3]];
}
deCasteljau(t: number): Vector2[][] {
const p0 = this.startPoint;
const p1 = this.startControlPoint;
const p2 = this.endControlPoint;
const p3 = this.endPoint;
// iterative implementation of deCasteljau
let points: Vector2[] = [p0, p1, p2, p3];
let temporary: Vector2[] = [];
const results: Vector2[][] = [];
results.push(points);
while (points.length > 1) {
temporary = [];
for (let i = 0; i < points.length - 1; i++)
temporary.push(lerp(points[i], points[i + 1], t));
points = temporary;
results.push(points); // save every iteration
}
return results;
}
generatePoints(resolution: number): Vector2[] {
if (!this._dirty && this._points.length === resolution)
return this._points;
this._coefficients = [];
this._points = [];
this._resolution = resolution;
const sampleSize = 1 / resolution;
for (let t = 0; t <= 1; t += sampleSize) {
t = Math.round(t * 10000) / 10000;
const [point, coefficients] = this.bernstain(t);
this._points.push(point);
this._coefficients.push(coefficients);
}
this._dirty = false;
return this._points;
}
generatePointAt(t: number): [Vector2[][], number[], Vector2] {
const points = this.deCasteljau(t);
const coefficients = this._coefficients[Math.floor(t * this._resolution)];
const point = points[points.length - 1];
return [points, coefficients, point[0]];
}
}