src/bezier.ts
import { BasicPoint, Point } from './point';
export class Bezier {
public static fromPoints(
points: Point[],
widths: { start: number; end: number },
): Bezier {
const c2 = this.calculateControlPoints(points[0], points[1], points[2]).c2;
const c3 = this.calculateControlPoints(points[1], points[2], points[3]).c1;
return new Bezier(points[1], c2, c3, points[2], widths.start, widths.end);
}
private static calculateControlPoints(
s1: BasicPoint,
s2: BasicPoint,
s3: BasicPoint,
): {
c1: BasicPoint;
c2: BasicPoint;
} {
const dx1 = s1.x - s2.x;
const dy1 = s1.y - s2.y;
const dx2 = s2.x - s3.x;
const dy2 = s2.y - s3.y;
const m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
const m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
const l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
const l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
const dxm = m1.x - m2.x;
const dym = m1.y - m2.y;
const k = l1 + l2 == 0 ? 0 : l2 / (l1 + l2);
const cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
const tx = s2.x - cm.x;
const ty = s2.y - cm.y;
return {
c1: new Point(m1.x + tx, m1.y + ty),
c2: new Point(m2.x + tx, m2.y + ty),
};
}
constructor(
public startPoint: Point,
public control2: BasicPoint,
public control1: BasicPoint,
public endPoint: Point,
public startWidth: number,
public endWidth: number,
) {}
// Returns approximated length. Code taken from https://www.lemoda.net/maths/bezier-length/index.html.
public length(): number {
const steps = 10;
let length = 0;
let px;
let py;
for (let i = 0; i <= steps; i += 1) {
const t = i / steps;
const cx = this.point(
t,
this.startPoint.x,
this.control1.x,
this.control2.x,
this.endPoint.x,
);
const cy = this.point(
t,
this.startPoint.y,
this.control1.y,
this.control2.y,
this.endPoint.y,
);
if (i > 0) {
const xdiff = cx - (px as number);
const ydiff = cy - (py as number);
length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
}
px = cx;
py = cy;
}
return length;
}
// Calculate parametric value of x or y given t and the four point coordinates of a cubic bezier curve.
private point(
t: number,
start: number,
c1: number,
c2: number,
end: number,
): number {
// prettier-ignore
return ( start * (1.0 - t) * (1.0 - t) * (1.0 - t))
+ (3.0 * c1 * (1.0 - t) * (1.0 - t) * t)
+ (3.0 * c2 * (1.0 - t) * t * t)
+ ( end * t * t * t);
}
}