想想那些鱼儿游动的漂亮曲线,还记得水果忍者的手指划过那一道道划痕吗,众所周知这一切都是很多个点组成的线段,这些都离不开样条插值算法。
特别注意:CardinalSpline和CatmullRom这两种算法都是过点式,就是形成的曲线一定经过样品点,但是贝塞尔曲线就不一定经过所有的样品点
cocos2dx中主要有两处用到了:
一个是Action下面类:points代表样品点集合,tension代表张力因子,实际效果是如果等于1就是画直直的线,默认等于0.5,就是比较平滑的线,一般去0到1直之间调节
CardinalSplineBy::create(float duration, cocos2d::PointArray *points, float tension)
CardinalSplineTo::create(float duration, cocos2d::PointArray *points, float tension)
CatmullRomTo::create(float duration, cocos2d::PointArray *points)//等效于上面的tension=0.5
CatmullRomBy::create(float duration, cocos2d::PointArray *points)//等效于上面的tension=0.5
一个是DrawNode类下面成员函数,用来画图segments代表共用几个点来形成这条曲线
void drawCardinalSpline(PointArray *points, float tension, unsigned int segments, const Color4F &color);
void drawCatmullRom(PointArray *points, unsigned int segments, const Color4F &color);
cocos2dx中核心源码:
注解:
这里面lt特别不好理解,实际上这里是吧segments个点平均分成points总数个部分,每个segment的点占自己部分的百分比就是这个lt的意思,
这里面p又是什么意思呢,实际上他就表示区间的序号,在同一个区间内p是一样的。
所以此算法大概的原理就是把需要生成的segments点按样品点总数平均分一下,在固定区间内,找到固定的4个实际样品点,然后在这个区间内按点所在这个区间比例位置逐
个使用ccCardinalSplineAt算法生成新点,是一种典型分治法。不过这些仅仅是程序理解,关于样条插值算法推导过程估计还得找找大学专业的书本了。
void DrawNode::drawCardinalSpline(PointArray *config, float tension, unsigned int segments, const Color4F &color)
{
Vec2* vertices = new (std::nothrow) Vec2[segments + 1];
if( ! vertices )
return;
ssize_t p;
float lt;
float deltaT = 1.0f / config->count();
for( unsigned int i=0; i < segments+1;i++) {
float dt = (float)i / segments;
// border
if( dt == 1 ) {
p = config->count() - 1;
lt = 1;
} else {
p = dt / deltaT;
lt = (dt - deltaT * (float)p) / deltaT;
}
// Interpolate
Vec2 pp0 = config->getControlPointAtIndex(p-1);
Vec2 pp1 = config->getControlPointAtIndex(p+0);
Vec2 pp2 = config->getControlPointAtIndex(p+1);
Vec2 pp3 = config->getControlPointAtIndex(p+2);
Vec2 newPos = ccCardinalSplineAt( pp0, pp1, pp2, pp3, tension, lt);
vertices[i].x = newPos.x;
vertices[i].y = newPos.y;
}
drawPoly(vertices, segments+1, false, color);
CC_SAFE_DELETE_ARRAY(vertices);
}
Vec2 ccCardinalSplineAt(Vec2 &p0, Vec2 &p1, Vec2 &p2, Vec2 &p3, float tension, float t)
{
float t2 = t * t;
float t3 = t2 * t;
/*
* Formula: s(-ttt + 2tt - t)P1 + s(-ttt + tt)P2 + (2ttt - 3tt + 1)P2 + s(ttt - 2tt + t)P3 + (-2ttt + 3tt)P3 + s(ttt - tt)P4
*/
float s = (1 - tension) / 2;
float b1 = s * ((-t3 + (2 * t2)) - t); // s(-t3 + 2 t2 - t)P1
float b2 = s * (-t3 + t2) + (2 * t3 - 3 * t2 + 1); // s(-t3 + t2)P2 + (2 t3 - 3 t2 + 1)P2
float b3 = s * (t3 - 2 * t2 + t) + (-2 * t3 + 3 * t2); // s(t3 - 2 t2 + t)P3 + (-2 t3 + 3 t2)P3
float b4 = s * (t3 - t2); // s(t3 - t2)P4
float x = (p0.x*b1 + p1.x*b2 + p2.x*b3 + p3.x*b4);
float y = (p0.y*b1 + p1.y*b2 + p2.y*b3 + p3.y*b4);
return Vec2(x,y);
}
这里通过cocos2dx源码,小小总结一下CatmullRom算法公式:
static Vec2 CatmullRom(Vec2 &p0, Vec2 &p1, Vec2 &p2, Vec2 &p3, float t)
{
float t2 = t * t;
float t3 = t * t * t;
float s =0.5;
float b1 = s * ((-t3 + (2 * t2)) - t);
float b2 = s * (-t3 + t2) + (2 * t3 - 3 * t2 + 1);
float b3 = s * (t3 - 2 * t2 + t) + (-2 * t3 + 3 * t2);
float b4 = s * (t3 - t2);
return p0*b1 + p1*b2 + p2*b3 + p3*b4;
}
static std::vector<Vec2> GetCatmullRom(PointArray *config,unsigned int segments)
{
std::vector<Vec2> vector;
ssize_t p;
float lt;
float deltaT = 1.0f / config->count();
for( unsigned int i=0; i < segments+1;i++) {
float dt = (float)i / segments;
// border
if( dt == 1 ) {
p = config->count() - 1;
lt = 1;
} else {
p = dt / deltaT;
lt = (dt - deltaT * (float)p) / deltaT;
}
// Interpolate
Vec2 pp0 = config->getControlPointAtIndex(p-1);
Vec2 pp1 = config->getControlPointAtIndex(p+0);
Vec2 pp2 = config->getControlPointAtIndex(p+1);
Vec2 pp3 = config->getControlPointAtIndex(p+2);
Vec2 newPos = CatmullRom( pp0, pp1, pp2, pp3, lt);
vector.push_back(newPos);
}
return vector;
}