Path: blob/main/plugins/default-human-emulator/Bezier.ts
1030 views
import IPoint from '@secret-agent/interfaces/IPoint';1import curveLength from './curveLength';23// class extract from https://github.com/Pomax/bezierjs45export default class Bezier {6private readonly points: IPoint[];7private readonly derivativePoints: IPoint[][];8constructor(...points: IPoint[]) {9this.points = points;10this.derivativePoints = [];1112let prev = points;13for (let i = points.length; i > 1; i -= 1) {14const list: IPoint[] = [];15const c = i - 1;1617for (let j = 0; j < c; j += 1) {18list.push({19x: c * (prev[j + 1].x - prev[j].x),20y: c * (prev[j + 1].y - prev[j].y),21});22}2324this.derivativePoints.push(list);25prev = list;26}27}2829toString(): string {30const points = this.points.map(p => `${p.x}/${p.y}`).join(', ');31return `[${points}]`;32}3334length() {35return curveLength(t => Bezier.compute(t, this.derivativePoints[0]));36}3738getLookupTable(steps = 100): IPoint[] {39if (Number.isNaN(steps)) steps = 100;40// We want a range from 0 to 1 inclusive, so41// we decrement and then use <= rather than <:42steps -= 1;4344const table: IPointWithT[] = [];45for (let i = 0; i < steps; i += 1) {46const t = i / (steps - 1);47const p = Bezier.compute(t, this.points);48p.t = t;4950table.push(p);51}5253return table;54}5556static compute(t: number, points: IPointWithT[]): IPointWithT {57// shortcuts58if (t === 0) {59points[0].t = 0;60return points[0];61}6263const order = points.length - 1;6465if (t === 1) {66points[order].t = 1;67return points[order];68}6970const mt = 1 - t;7172if (order === 0) {73points[0].t = t;74return points[0];75} // linear?7677if (order === 1) {78return {79x: mt * points[0].x + t * points[1].x,80y: mt * points[0].y + t * points[1].y,81t,82};83} // quadratic/cubic curve?8485if (order < 4) {86const mt2 = mt * mt;87const t2 = t * t;88let a: number;89let b: number;90let c: number;91let d = 0;9293if (order === 2) {94points = [95points[0],96points[1],97points[2],98{99x: 0,100y: 0,101},102];103a = mt2;104b = mt * t * 2;105c = t2;106} else if (order === 3) {107a = mt2 * mt;108b = mt2 * t * 3;109c = mt * t2 * 3;110d = t * t2;111}112113return {114x: a * points[0].x + b * points[1].x + c * points[2].x + d * points[3].x,115y: a * points[0].y + b * points[1].y + c * points[2].y + d * points[3].y,116t,117};118} // higher order curves: use de Casteljau's computation119120// copy list121const dCpts = points.map(x => {122return { ...x };123});124125while (dCpts.length > 1) {126for (let i = 0; i < dCpts.length - 1; i += 1) {127dCpts[i] = {128x: dCpts[i].x + (dCpts[i + 1].x - dCpts[i].x) * t,129y: dCpts[i].y + (dCpts[i + 1].y - dCpts[i].y) * t,130};131}132133dCpts.splice(dCpts.length - 1, 1);134}135136dCpts[0].t = t;137return dCpts[0];138}139}140141interface IPointWithT extends IPoint {142t?: number;143}144145146