| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- /**
- * 曲线辅助模块
- */
- import {
- create as v2Create,
- distSquare as v2DistSquare,
- VectorArray
- } from './vector';
- const mathPow = Math.pow;
- const mathSqrt = Math.sqrt;
- const EPSILON = 1e-8;
- const EPSILON_NUMERIC = 1e-4;
- const THREE_SQRT = mathSqrt(3);
- const ONE_THIRD = 1 / 3;
- // 临时变量
- const _v0 = v2Create();
- const _v1 = v2Create();
- const _v2 = v2Create();
- function isAroundZero(val: number) {
- return val > -EPSILON && val < EPSILON;
- }
- function isNotAroundZero(val: number) {
- return val > EPSILON || val < -EPSILON;
- }
- /**
- * 计算三次贝塞尔值
- */
- export function cubicAt(p0: number, p1: number, p2: number, p3: number, t: number): number {
- const onet = 1 - t;
- return onet * onet * (onet * p0 + 3 * t * p1)
- + t * t * (t * p3 + 3 * onet * p2);
- }
- /**
- * 计算三次贝塞尔导数值
- */
- export function cubicDerivativeAt(p0: number, p1: number, p2: number, p3: number, t: number): number {
- const onet = 1 - t;
- return 3 * (
- ((p1 - p0) * onet + 2 * (p2 - p1) * t) * onet
- + (p3 - p2) * t * t
- );
- }
- /**
- * 计算三次贝塞尔方程根,使用盛金公式
- */
- export function cubicRootAt(p0: number, p1: number, p2: number, p3: number, val: number, roots: number[]): number {
- // Evaluate roots of cubic functions
- const a = p3 + 3 * (p1 - p2) - p0;
- const b = 3 * (p2 - p1 * 2 + p0);
- const c = 3 * (p1 - p0);
- const d = p0 - val;
- const A = b * b - 3 * a * c;
- const B = b * c - 9 * a * d;
- const C = c * c - 3 * b * d;
- let n = 0;
- if (isAroundZero(A) && isAroundZero(B)) {
- if (isAroundZero(b)) {
- roots[0] = 0;
- }
- else {
- const t1 = -c / b; //t1, t2, t3, b is not zero
- if (t1 >= 0 && t1 <= 1) {
- roots[n++] = t1;
- }
- }
- }
- else {
- const disc = B * B - 4 * A * C;
- if (isAroundZero(disc)) {
- const K = B / A;
- const t1 = -b / a + K; // t1, a is not zero
- const t2 = -K / 2; // t2, t3
- if (t1 >= 0 && t1 <= 1) {
- roots[n++] = t1;
- }
- if (t2 >= 0 && t2 <= 1) {
- roots[n++] = t2;
- }
- }
- else if (disc > 0) {
- const discSqrt = mathSqrt(disc);
- let Y1 = A * b + 1.5 * a * (-B + discSqrt);
- let Y2 = A * b + 1.5 * a * (-B - discSqrt);
- if (Y1 < 0) {
- Y1 = -mathPow(-Y1, ONE_THIRD);
- }
- else {
- Y1 = mathPow(Y1, ONE_THIRD);
- }
- if (Y2 < 0) {
- Y2 = -mathPow(-Y2, ONE_THIRD);
- }
- else {
- Y2 = mathPow(Y2, ONE_THIRD);
- }
- const t1 = (-b - (Y1 + Y2)) / (3 * a);
- if (t1 >= 0 && t1 <= 1) {
- roots[n++] = t1;
- }
- }
- else {
- const T = (2 * A * b - 3 * a * B) / (2 * mathSqrt(A * A * A));
- const theta = Math.acos(T) / 3;
- const ASqrt = mathSqrt(A);
- const tmp = Math.cos(theta);
- const t1 = (-b - 2 * ASqrt * tmp) / (3 * a);
- const t2 = (-b + ASqrt * (tmp + THREE_SQRT * Math.sin(theta))) / (3 * a);
- const t3 = (-b + ASqrt * (tmp - THREE_SQRT * Math.sin(theta))) / (3 * a);
- if (t1 >= 0 && t1 <= 1) {
- roots[n++] = t1;
- }
- if (t2 >= 0 && t2 <= 1) {
- roots[n++] = t2;
- }
- if (t3 >= 0 && t3 <= 1) {
- roots[n++] = t3;
- }
- }
- }
- return n;
- }
- /**
- * 计算三次贝塞尔方程极限值的位置
- * @return 有效数目
- */
- export function cubicExtrema(p0: number, p1: number, p2: number, p3: number, extrema: number[]): number {
- const b = 6 * p2 - 12 * p1 + 6 * p0;
- const a = 9 * p1 + 3 * p3 - 3 * p0 - 9 * p2;
- const c = 3 * p1 - 3 * p0;
- let n = 0;
- if (isAroundZero(a)) {
- if (isNotAroundZero(b)) {
- const t1 = -c / b;
- if (t1 >= 0 && t1 <= 1) {
- extrema[n++] = t1;
- }
- }
- }
- else {
- const disc = b * b - 4 * a * c;
- if (isAroundZero(disc)) {
- extrema[0] = -b / (2 * a);
- }
- else if (disc > 0) {
- const discSqrt = mathSqrt(disc);
- const t1 = (-b + discSqrt) / (2 * a);
- const t2 = (-b - discSqrt) / (2 * a);
- if (t1 >= 0 && t1 <= 1) {
- extrema[n++] = t1;
- }
- if (t2 >= 0 && t2 <= 1) {
- extrema[n++] = t2;
- }
- }
- }
- return n;
- }
- /**
- * 细分三次贝塞尔曲线
- */
- export function cubicSubdivide(p0: number, p1: number, p2: number, p3: number, t: number, out: number[]) {
- const p01 = (p1 - p0) * t + p0;
- const p12 = (p2 - p1) * t + p1;
- const p23 = (p3 - p2) * t + p2;
- const p012 = (p12 - p01) * t + p01;
- const p123 = (p23 - p12) * t + p12;
- const p0123 = (p123 - p012) * t + p012;
- // Seg0
- out[0] = p0;
- out[1] = p01;
- out[2] = p012;
- out[3] = p0123;
- // Seg1
- out[4] = p0123;
- out[5] = p123;
- out[6] = p23;
- out[7] = p3;
- }
- /**
- * 投射点到三次贝塞尔曲线上,返回投射距离。
- * 投射点有可能会有一个或者多个,这里只返回其中距离最短的一个。
- */
- export function cubicProjectPoint(
- x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number,
- x: number, y: number, out: VectorArray
- ): number {
- // http://pomax.github.io/bezierinfo/#projections
- let t;
- let interval = 0.005;
- let d = Infinity;
- let prev;
- let next;
- let d1;
- let d2;
- _v0[0] = x;
- _v0[1] = y;
- // 先粗略估计一下可能的最小距离的 t 值
- // PENDING
- for (let _t = 0; _t < 1; _t += 0.05) {
- _v1[0] = cubicAt(x0, x1, x2, x3, _t);
- _v1[1] = cubicAt(y0, y1, y2, y3, _t);
- d1 = v2DistSquare(_v0, _v1);
- if (d1 < d) {
- t = _t;
- d = d1;
- }
- }
- d = Infinity;
- // At most 32 iteration
- for (let i = 0; i < 32; i++) {
- if (interval < EPSILON_NUMERIC) {
- break;
- }
- prev = t - interval;
- next = t + interval;
- // t - interval
- _v1[0] = cubicAt(x0, x1, x2, x3, prev);
- _v1[1] = cubicAt(y0, y1, y2, y3, prev);
- d1 = v2DistSquare(_v1, _v0);
- if (prev >= 0 && d1 < d) {
- t = prev;
- d = d1;
- }
- else {
- // t + interval
- _v2[0] = cubicAt(x0, x1, x2, x3, next);
- _v2[1] = cubicAt(y0, y1, y2, y3, next);
- d2 = v2DistSquare(_v2, _v0);
- if (next <= 1 && d2 < d) {
- t = next;
- d = d2;
- }
- else {
- interval *= 0.5;
- }
- }
- }
- // t
- if (out) {
- out[0] = cubicAt(x0, x1, x2, x3, t);
- out[1] = cubicAt(y0, y1, y2, y3, t);
- }
- // console.log(interval, i);
- return mathSqrt(d);
- }
- /**
- * 计算三次贝塞尔曲线长度
- */
- export function cubicLength(
- x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number,
- iteration: number
- ) {
- let px = x0;
- let py = y0;
- let d = 0;
- const step = 1 / iteration;
- for (let i = 1; i <= iteration; i++) {
- let t = i * step;
- const x = cubicAt(x0, x1, x2, x3, t);
- const y = cubicAt(y0, y1, y2, y3, t);
- const dx = x - px;
- const dy = y - py;
- d += Math.sqrt(dx * dx + dy * dy);
- px = x;
- py = y;
- }
- return d;
- }
- /**
- * 计算二次方贝塞尔值
- */
- export function quadraticAt(p0: number, p1: number, p2: number, t: number): number {
- const onet = 1 - t;
- return onet * (onet * p0 + 2 * t * p1) + t * t * p2;
- }
- /**
- * 计算二次方贝塞尔导数值
- */
- export function quadraticDerivativeAt(p0: number, p1: number, p2: number, t: number): number {
- return 2 * ((1 - t) * (p1 - p0) + t * (p2 - p1));
- }
- /**
- * 计算二次方贝塞尔方程根
- * @return 有效根数目
- */
- export function quadraticRootAt(p0: number, p1: number, p2: number, val: number, roots: number[]): number {
- const a = p0 - 2 * p1 + p2;
- const b = 2 * (p1 - p0);
- const c = p0 - val;
- let n = 0;
- if (isAroundZero(a)) {
- if (isNotAroundZero(b)) {
- const t1 = -c / b;
- if (t1 >= 0 && t1 <= 1) {
- roots[n++] = t1;
- }
- }
- }
- else {
- const disc = b * b - 4 * a * c;
- if (isAroundZero(disc)) {
- const t1 = -b / (2 * a);
- if (t1 >= 0 && t1 <= 1) {
- roots[n++] = t1;
- }
- }
- else if (disc > 0) {
- const discSqrt = mathSqrt(disc);
- const t1 = (-b + discSqrt) / (2 * a);
- const t2 = (-b - discSqrt) / (2 * a);
- if (t1 >= 0 && t1 <= 1) {
- roots[n++] = t1;
- }
- if (t2 >= 0 && t2 <= 1) {
- roots[n++] = t2;
- }
- }
- }
- return n;
- }
- /**
- * 计算二次贝塞尔方程极限值
- */
- export function quadraticExtremum(p0: number, p1: number, p2: number): number {
- const divider = p0 + p2 - 2 * p1;
- if (divider === 0) {
- // p1 is center of p0 and p2
- return 0.5;
- }
- else {
- return (p0 - p1) / divider;
- }
- }
- /**
- * 细分二次贝塞尔曲线
- */
- export function quadraticSubdivide(p0: number, p1: number, p2: number, t: number, out: number[]) {
- const p01 = (p1 - p0) * t + p0;
- const p12 = (p2 - p1) * t + p1;
- const p012 = (p12 - p01) * t + p01;
- // Seg0
- out[0] = p0;
- out[1] = p01;
- out[2] = p012;
- // Seg1
- out[3] = p012;
- out[4] = p12;
- out[5] = p2;
- }
- /**
- * 投射点到二次贝塞尔曲线上,返回投射距离。
- * 投射点有可能会有一个或者多个,这里只返回其中距离最短的一个。
- * @param {number} x0
- * @param {number} y0
- * @param {number} x1
- * @param {number} y1
- * @param {number} x2
- * @param {number} y2
- * @param {number} x
- * @param {number} y
- * @param {Array.<number>} out 投射点
- * @return {number}
- */
- export function quadraticProjectPoint(
- x0: number, y0: number, x1: number, y1: number, x2: number, y2: number,
- x: number, y: number, out: VectorArray
- ): number {
- // http://pomax.github.io/bezierinfo/#projections
- let t: number;
- let interval = 0.005;
- let d = Infinity;
- _v0[0] = x;
- _v0[1] = y;
- // 先粗略估计一下可能的最小距离的 t 值
- // PENDING
- for (let _t = 0; _t < 1; _t += 0.05) {
- _v1[0] = quadraticAt(x0, x1, x2, _t);
- _v1[1] = quadraticAt(y0, y1, y2, _t);
- const d1 = v2DistSquare(_v0, _v1);
- if (d1 < d) {
- t = _t;
- d = d1;
- }
- }
- d = Infinity;
- // At most 32 iteration
- for (let i = 0; i < 32; i++) {
- if (interval < EPSILON_NUMERIC) {
- break;
- }
- const prev = t - interval;
- const next = t + interval;
- // t - interval
- _v1[0] = quadraticAt(x0, x1, x2, prev);
- _v1[1] = quadraticAt(y0, y1, y2, prev);
- const d1 = v2DistSquare(_v1, _v0);
- if (prev >= 0 && d1 < d) {
- t = prev;
- d = d1;
- }
- else {
- // t + interval
- _v2[0] = quadraticAt(x0, x1, x2, next);
- _v2[1] = quadraticAt(y0, y1, y2, next);
- const d2 = v2DistSquare(_v2, _v0);
- if (next <= 1 && d2 < d) {
- t = next;
- d = d2;
- }
- else {
- interval *= 0.5;
- }
- }
- }
- // t
- if (out) {
- out[0] = quadraticAt(x0, x1, x2, t);
- out[1] = quadraticAt(y0, y1, y2, t);
- }
- // console.log(interval, i);
- return mathSqrt(d);
- }
- /**
- * 计算二次贝塞尔曲线长度
- */
- export function quadraticLength(
- x0: number, y0: number, x1: number, y1: number, x2: number, y2: number,
- iteration: number
- ) {
- let px = x0;
- let py = y0;
- let d = 0;
- const step = 1 / iteration;
- for (let i = 1; i <= iteration; i++) {
- let t = i * step;
- const x = quadraticAt(x0, x1, x2, t);
- const y = quadraticAt(y0, y1, y2, t);
- const dx = x - px;
- const dy = y - py;
- d += Math.sqrt(dx * dx + dy * dy);
- px = x;
- py = y;
- }
- return d;
- }
|