整理贝塞尔曲线的各种公式,以及解决如何来获得一个二次贝塞尔曲线上的匀速的点的问题。
线性贝塞尔曲线
给定点$P_0$、$P_1$,线性贝塞尔曲线只是一条两点之间的直线。公式为:
$ B(t) = P_0 + (P_1 - P0)t = (1 - t)P_0 + tP_1, t ∈ [0, 1]$
二次贝塞尔曲线
二次贝塞尔曲线的路径由给定的点$P_0$、$P_1$、$P_2$的函数$B(t)$。公式:
$B(t) = (1 - t)^2P_0 + 2t(1 - t)P_1 + t^2P_2, t ∈ [0, 1]$
三次贝塞尔曲线
$P_0$、 $P_1$、 $P_2$、 $P_3$四个点在平面或三维空间中定义了三次贝塞尔曲线,曲线起始于$P_0$走向$P_1$,并从$P_2$的方向来到$P_3$。一般不会经过$P_1$或$P_2$,这两个点只是提供方向。公式:
$B(t) = P_0(1 - t)^3 + 3P_1t(1 - t)^2 + 3P_2t^2(1 - t) + P_3t^3, t ∈ [0, 1]$
为什么要了解贝塞尔曲线的方程?因为虽然在canvas里面画贝塞尔曲线的时候是有曲线函数的,但是当我们要用到贝塞尔曲线轨迹的时候就没法子了,我们还是得求贝塞尔曲线上的点,用点来做轨迹。
匀速贝塞尔曲线运动
通过在canvas上画图我们可以看到,二次贝塞尔曲线的特性是两端比较稀疏,中间比较密集,而且曲线拐弯角度比较大的话中间更密集,两端更稀疏,这样在做轨迹运动的时候,表现为两端快,中间慢。
我们需要的是在曲线上做匀速运动,有什么法子呢?
搜了一篇文章:二次贝塞尔曲线长度,具体如何推导出来的公式我看不太懂,但是我根据里面的C++的代码以JavaScript的代码写出来了,而且实际应用了发现效果甚好😳。算了,就这样吧,直接看代码。
首先是点的定义:
1 2 3 4 5 6 7 8 9
| export default class Point { public x: number; public y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } }
|
然后定义一个贝塞尔曲线的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| import Point from './point';
export default class BezierLine { middle: Point; start: Point; end: Point; length: number; private A: number; private B: number; private C: number; constructor(start: Point, end: Point) { this.start = start; this.end = end; this.getMiddlePoint(); }
private getMiddlePoint() { this.middle = new Point( (this.start.x + this.end.x) / 2 - (this.start.y - this.end.y) * 0.4, (this.start.y + this.end.y) / 2 - (this.end.x - this.start.x) * 0.4, ); const ax = this.start.x - 2 * this.middle.x + this.end.x; const ay = this.start.y - 2 * this.middle.y + this.end.y; const bx = 2 * this.middle.x - 2 * this.start.x; const by = 2 * this.middle.y - 2 * this.start.y; this.A = 4 * (ax * ax + ay * ay); this.B = 4 * (ax * bx + ay * by); this.C = bx * bx + by * by; }
public calculateBezierPointForQuadratic(t): Point { const temp = 1 - t; return new Point( temp * temp * this.start.x + 2 * t * temp * this.middle.x + t * t * this.end.x, temp * temp * this.start.y + 2 * t * temp * this.middle.y + t * t * this.end.y ); } public getLength(t: number): number { const temp1 = Math.sqrt(this.C + t * (this.B + this.A * t)); const temp2 = (2 * this.A * t * temp1 + this.B * (temp1 - Math.sqrt(this.C))); const temp3 = Math.log(this.B + 2 * Math.sqrt(this.A) * Math.sqrt(this.C)); const temp4 = Math.log(this.B + 2 * this.A * t + 2 * Math.sqrt(this.A) * temp1); const temp5 = 2 * Math.sqrt(this.A) * temp2; const temp6 = (this.B * this.B - 4 * this.A * this.C) * (temp3 - temp4); return (temp5 + temp6) / (8 * Math.pow(this.A, 1.5)); }
public invertL(t, l) { let t1 = t, t2; do { t2 = t1 - (this.getLength(t1) - l) / this.s(t1); if (Math.abs(t1 - t2) < 0.000001) { break; } t1 = t2; } while (true) ; return t2; } private s(t) { return Math.sqrt(this.A * t * t + this.B * t + this.C); } }
|
通过场面的类来创建贝塞尔曲线并获取曲线上的均匀的点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const begin: Point = new Point(20, 280); const end: Point = new Point(680, 280); const line = new BezierLine(begin, end);
const len = Math.floor(line.getLength(1));
for (let j = 0; j < 100; j += 1) { let t = j / 100; const l = t * len; t = line.invertL(t, l);
const point = line.calculateBezierPointForQuadratic(t); const color = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')'; Canvas.drawCycle(this.contextFour, point, 8, color); }
|
可以看下对比的图片:
源自于一个页面的特效,需要画线,按照贝塞尔曲线的路径来完成动画,本身是直接用贝塞尔曲线上的点构造的路径,导致点密集的地方会显得很高亮,使用这个匀速贝塞尔曲线上的点就完全避免了这个问题。
还是得好好学习下数学啊。
参考网站:二次贝塞尔曲线长度