贝塞尔曲线
欢迎前往个人博客 驽马点滴 和视频空间 哔哩哔哩-《挨踢日志》
一.相遇Bezier曲线
在写这篇文章的时候,我正在广州一家游戏公司从事客户端的研发工作,使用Cocos2dx引擎。在游戏的开发过程中,有时候需要让对象沿着一条光滑的曲线运动,比如说怒气粒子沿着光滑的曲线运动到UI层的指定怒气槽。关于曲线的实现,直接调用Cocos2dx引擎中类CCBezierBy的API接口:static CCBezierBy* create(float t, const ccBezierConfig& c)
该接口需要曲线运动的时间t以及贝塞尔曲线的结构为ccBezierConfig的参数c.
引擎中ccBezierConfig定义如下:
/** @typedef bezier configuration structure
*/
typedef struct _ccBezierConfig {
//! end position of the bezier
CCPoint endPosition;
//! Bezier control point 1
CCPoint controlPoint_1;
//! Bezier control point 2
CCPoint controlPoint_2;
} ccBezierConfig;
简而言之,ccBezierConfig结构定义了三阶贝塞尔曲线的4个控制点: 起点(0,0), 控制点controlPoint_1, controlPoint_2和结束点endPosition.(当然,其中(0,0)点是隐藏知识)
转到其update函数中看看:
void CCBezierBy::update(float time)
{
if (m_pTarget)
{
float xa = 0;
float xb = m_sConfig.controlPoint_1.x;
float xc = m_sConfig.controlPoint_2.x;
float xd = m_sConfig.endPosition.x;
float ya = 0;
float yb = m_sConfig.controlPoint_1.y;
float yc = m_sConfig.controlPoint_2.y;
float yd = m_sConfig.endPosition.y;
float x = bezierat(xa, xb, xc, xd, time);
float y = bezierat(ya, yb, yc, yd, time);
#if CC_ENABLE_STACKABLE_ACTIONS
CCPoint currentPos = m_pTarget->getPosition();
CCPoint diff = ccpSub(currentPos, m_previousPosition);
m_startPosition = ccpAdd( m_startPosition, diff);
CCPoint newPos = ccpAdd( m_startPosition, ccp(x,y));
m_pTarget->setPosition(newPos);
m_previousPosition = newPos;
#else
m_pTarget->setPosition(ccpAdd( m_startPosition, ccp(x,y)));
#endif // !CC_ENABLE_STACKABLE_ACTIONS
}
}
可以看出,在知道了4个点p0, p1, p2, p3后,贝塞尔曲线在各个维度上的计算公式由bezierat(p0, p1, p2, p3)给出.
转到实现,其实现细节如下:
// Bezier cubic formula:
// ((1 - t) + t)3 = 1
// Expands to…
// (1 - t)3 + 3t(1-t)2 + 3t2(1 - t) + t3 = 1
static inline float bezierat( float a, float b, float c, float d, float t )
{
return (powf(1-t,3) * a +
3*t*(powf(1-t,2))*b +
3*powf(t,2)*(1-t)*c +
powf(t,3)*d );
}
好,这就是和Bezier曲线的正式相遇,到现在,只知道了所谓的3阶Bezier曲线的一个应用,那么,它究竟是什么样的一条曲线?
二.贝塞尔曲线初探
为了给对函数不太理解的朋友一些入门, 我这里用一个例子描述一下接下来内容所要表达的意思:
接下来的内容, 要做的事情就是:假定贝塞尔曲线的表达式是y=f(u),0≤u≤1, 其表达式是怎么样的?
举个例子: 我们都知道抛物线的表达式是
y=f(x)=ax^2 + bx + c (a≠0) 也就是说,给定一个x值, 就确定了一点(x,ax^2 + bx + c)
于是,当x取遍R上的每一个值,那么函数也就确定了其每一点, 其图像因此确定.
那么你懂了这个以后, 接下来的工作内容为,给定一个u, 请确定Bezier曲线上的一点(u, Bezier(u))
也就是确定y = Bezier(u) 0≤u≤1的函数解析式.
1.de Casteljau's 算法
根据Bezier曲线的结构,一个重要的任务是:对于给定的u,0≤u≤1,如何确定贝塞尔曲线上的一点Bezier(u) ?
1.1 线段u分点
从A点出发,B为终点的向量为B-A, 假若u是0~1之间的一个数,那么u*(B-A)是向量B-A的u倍,加上起点A,那么向量 A+u(B-A)确定了点C, 且它分线段AB的比例为u:(1-u).
由于C=A+u(B-A)=(1-u)A + uB,我们称:
线段AB的u分点C的表达式为 C=(1-u)A + uB
1.2 de Casteljau's 算法
n阶贝塞尔曲线有n+1个控制点.
- 当n=1时, 也就是贝塞尔曲线只由两个点P0, P1决定,直接取线段u分点,得到Bezier(u)=(1-u)P0 + uP1, 于是1阶Bezier曲线就是连接P0和P1的线段.
- 当n=2时,贝塞尔曲线由三个点P0,P1,P2决定,
- 先对P0和P1取线段u分点, 得到P10 = (1-u)P0 + uP1,
- 再对P1和P2取线段u分点, 得到P11 =(1-u)P1 + uP2,
- 于是得到两条线段上的两个u分点P10和P11,
- 对于两个u分点P10和P11, 再取线段u分点, 得到P20 = (1-u)P10 + uP11 = (1-u)((1-u)P0 + uP1) + u((1-u)P1 + uP2)=(1-u)^2P0 + 2(1-u)uP1 + u^2P2
- 而P20即为贝塞尔曲线在u时的点;
- 即: Bezier(u) = (1-u)^2P0 + 2(1-u)uP1 + u^2P2
- 当n时, 贝塞尔曲线由n+1个点P0, P1,P2, ..., Pn决定.
- 分别对线段P0P1, P1P2, ..., Pn-1Pn取线段u分点, 得到n个点P10, P11, ... P1n-1
- 分别对线段P10P11, ... P1n-2P1n-1取线段u分点, 得到n-1个点P20, P21, ... P2n-2
- ......
- 分别对Pn-10Pn-11取线段u分点, 得到最后1个点Pn0. 这个点即为Bezier曲线在给定u时的点.
此过程可以由递归式:
表示.
而我们所求即为i=n, j=0时的值:
(此公式可以应用数学归纳法并利用递推公式证明)
至此, 在Coco2dx引擎中关于Bezier曲线的计算方法,就很明朗了, 它就是3阶的贝塞尔曲线:
其系数恰是3阶二项展开式的系数.
二.Bezier曲线的性质
2.1 系数性质
Bezier曲线其系数为下面的二项展开式第K项的系数:
因此二项式系数拥有的性质,它都有,包括但不限于[权性: 系数和为1].
2.2 曲线性质
2.2.1对称性

2.2.2仿射不变性
2.2.3其他
三.以下是参考的资料:
http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html
欢迎前往个人博客 驽马点滴 和视频空间 哔哩哔哩-《挨踢日志》