您的位置:首页 > 移动开发 > Cocos引擎

贝塞尔曲线

2014-09-03 01:46 183 查看

贝塞尔曲线

一.相遇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,[b]ax^2 + bx + c)[/b]

          于是,当x取遍R上的每一个值,那么函数也就确定了其每一点, 其图像因此确定.

那么你懂了这个以后, 接下来的工作内容为,给定一个u, 请确定Bezier曲线上的一点(u, Bezier(u))

也就是确定y = Bezier(u)
[b]0≤u≤1
的函数解析式.[/b]

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决定,
先对P0P1线段u分点,
得到P10 = (1-u)P0 + uP1
再对P1P2线段u分点,
得到P11 =(1-u)P1 + uP2
于是得到两条线段上的两个u分点P10和[b]P11[/b], 
对于两个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对称性

Bezier曲线的全部控制顶点位置不变,但次序颠倒,那么其曲线形状不变,参数u变为1-u.



2.2.2仿射不变性

即对于贝塞尔曲线做任意的仿射变换A, 可以将A作用在各个控制顶点上后再线性求和.

这点重要的性质, 意味着对Bezier曲线做几何变换是极其便利的.

举个例子, 当一个物体a在某个坐标系内做贝塞尔曲线运动, 而这个坐标系同时在做一个旋转运动,那么,如果旋转变换如果为R(theta), 那么该旋转变换可以相应作用在控制定点上;

2.2.3其他

略.

三.以下是参考的资料:

http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  cocos2dx Cocos2d-x Bezier