想想那些鱼儿游动的漂亮曲线,还记得水果忍者的手指划过那一道道划痕吗,众所周知这一切都是很多个点组成的线段,这些都离不开样条插值算法。
特别注意:CardinalSpline和CatmullRom这两种算法都是过点式,就是形成的曲线一定经过样品点,但是贝塞尔曲线就不一定经过所有的样品点
cocos2dx中主要有两处用到了:
一个是Action下面类:points代表样品点集合,tension代表张力因子,实际效果是如果等于1就是画直直的线,默认等于0.5,就是比较平滑的线,一般去0到1直之间调节
CardinalSplineBy::create(float duration,cocos2d::PointArray *points,float tension)
CardinalSplineTo::create(float duration,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,const Color4F &color);
cocos2dx中核心源码:
注解:
这里面lt特别不好理解,实际上这里是吧segments个点平均分成points总数个部分,每个segment的点占自己部分的百分比就是这个lt的意思,
这里面p又是什么意思呢,实际上他就表示区间的序号,在同一个区间内p是一样的。
所以此算法大概的原理就是把需要生成的segments点按样品点总数平均分一下,在固定区间内,找到固定的4个实际样品点,然后在这个区间内按点所在这个区间比例位置逐
个使用ccCardinalSplineAt算法生成新点,是一种典型分治法。不过这些仅仅是程序理解,关于样条插值算法推导过程估计还得找找大学专业的书本了。
void DrawNode::drawCardinalSpline(PointArray *config,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 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,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,lt); vector.push_back(newPos); } return vector; }原文链接:https://www.f2er.com/cocos2dx/342857.html