Cocos - 贝塞尔曲线 Bezier
一、序言
本篇只讲述贝塞尔曲线数学公式的运用原理,不进行公式的背景介绍和推导内容,如需请移步贝塞尔曲线公式推导原理。
在现实中,我们也只需要掌握其大致原理和开发中实际应用即可。
二、贝塞尔曲线原理
原文链接:https://www.jianshu.com/p/6075f9782743
A. 二阶贝塞尔曲线
要素:1 个起点,1 个终点,1 个控制点
知识点
三阶的话就是 2 个控制点,四阶的话就是 3 个,以此类推,N 阶的话就是 N - 1 个控制点。而起点和终点始终只有一个。
步骤如下:
1.绘制 1 个起点,1 个终点和 1 个控制点,分别为 S 、E、C。然后将 SC、CE 分别连线。如下图所示。
2.从点 S 向 C 出发找到一个 D 点,从 C 向 E 出发找到一个 F 点,使得SD / SC = CF / CE
。然后连接 DF。如下图所示。
3.在 DF 之间找到点 M,使得SD / SC = CF / CE = DM / DF
总结下:
- (1) 二阶贝塞尔中,起初是 3 个点,然后我们再找 2 个点,然后再找 1 个点。这个点就是我们要找到的点。
- (2) 我们需要由 S 向 C 出发,由 C 向 E 出现,找到所有的 D 和 F,再找到所有的 M。
- (3) 将所有的 M 连接起来就构造出了最后的所需要的贝塞尔曲线了。
借用一个图,来详细观察一下其构造的过程。
B. 三阶贝塞尔曲线
三阶和二阶是类似的:
1.连接 A,B 形成 AB 线段,连接 B,C 形成 BC 线段,连接 C,D 形成 CD 线段。
2.在AB线段取一个点 E,BC 线段取一个点 F,CD 线段取一个点 G,使其满足条件: AE/AB = BF/BE = CG/CD。连接 E,F 形成线段 EF,连接 F,G 形成线段 FG。
3.在EF线段取一个点 H,FG 线段取一个点 I,使其满足条件: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG。连接 H,I 形成线段 HI。
4.在 HI 线段取一个点 J,使其满足条件: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG = HJ/HI。
5.而满足这些条件的所有的J点所形成的轨迹就是三阶贝塞尔曲线,动态过程如下:
综合上式列表,可以总结出一般性规律:
三、cocos中的贝塞尔曲线
在Cocos2d-x中贝塞尔曲线运动分为CCBezierTo和CCBezierBy。
CCBezierTo:参数均为绝对坐标
CCBezierBy:参数均为相对于运动物体当前位置的相对坐标
这两个Action都需要传入一个参数ccBezierConfig,这是一个结构体,这个结构体有三个字段
1.CCPoint endPosition:结束点
2.CCPoint controlPoint_1:控制点1
3.CCPoint controlPoint_2:控制点2
c++示例:
//曲线配置
ccBezierConfig cfg;
cfg.controlPoint_1 = ccp(100, 300);
cfg.controlPoint_2 = ccp(200, 500);
cfg.endPosition = ccp(500, 500);
//使用CCEaseInOut让曲线运动有一个由慢到快的变化,显得更自然
node->runAction(CCSpawn::create(CCEaseInOut::create(CCBezierTo::create(t,cfg),0.5)));
lua中ccBezierConfig参数和顺序为:
1.CCPoint controlPoint_1:控制点1
2.CCPoint controlPoint_2:控制点2
3.CCPoint endPosition:结束点
lua示例:
self.sprite1 = cc.Sprite:create(icon[1])
self.rootNode:addChild(self.sprite1, 99)
self.sprite1:setPosition(cc.p(300, 300))
local x, y = self.sprite1:getPosition()
local offsetX = 200
local offsetY = 200
local bezierPoint1 ={
cc.p( x - offsetX, y ),
cc.p( x - offsetX, y + offsetY ),
cc.p( x, y + offsetY )
}
local bezierPoint2 ={
cc.p( x + offsetX , y + offsetY ),
cc.p( x + offsetX, y ),
cc.p( x, y )
}
local duration = 2
local bezierTo1 = cc.BezierTo:create( duration, bezierPoint1 )
local bezierTo2 = cc.BezierTo:create( duration, bezierPoint2 )
local action = cc.Sequence:create(
bezierTo1,
bezierTo2
)
self.sprite1:runAction(cc.RepeatForever:create(action))
两个控制点的会影响曲线的变化趋势。
Cocos2d-x中实现的是三阶贝塞尔曲线运动。
曲线的每个点的坐标是根据一个区间为0到1的变量t、开始点、结束点和两个控制点,通过方程计算出来的。
如上图所示:A、D分别为起点和终点,B、C分别为控制点1和控制点2。
四、c++源码
更新部分:
void BezierBy::update(float time)
{
if (_target)
{
float xa = 0;
float xb = _config.controlPoint_1.x;
float xc = _config.controlPoint_2.x;
float xd = _config.endPosition.x;
float ya = 0;
float yb = _config.controlPoint_1.y;
float yc = _config.controlPoint_2.y;
float yd = _config.endPosition.y;
float x = bezierat(xa, xb, xc, xd, time);
float y = bezierat(ya, yb, yc, yd, time);
#if CC_ENABLE_STACKABLE_ACTIONS
Vec2 currentPos = _target->getPosition();
Vec2 diff = currentPos - _previousPosition;
_startPosition = _startPosition + diff;
Vec2 newPos = _startPosition + Vec2(x,y);
_target->setPosition(newPos);
_previousPosition = newPos;
#else
_target->setPosition( _startPosition + Vec2(x,y));
#endif // !CC_ENABLE_STACKABLE_ACTIONS
}
}
bezierat方法:
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 );
}
五、应用
抛物线(待优化)
local icon = {
"ccbResources/HXH_league_icon/gonghuitouxiang1.png",
}
self.sprite1 = cc.Sprite:create(icon[1])
self.rootNode:addChild(self.sprite1, 99)
self.sprite1:setPosition(cc.p(300, 300))
local x, y = self.sprite1:getPosition()
-- 参数依次为 运动时间、运动物体、运动终点、最高点、两控制点与y轴夹角、物体自转
local function Parabola(t, startPoint, endPoint, height, angle, selfRotate)
-- 角度转换为弧度
local startPoint = ccp(startPoint:getPosition())
local radian = angle * math.pi/180
local p1x = startPoint.x+(endPoint.x - startPoint.x)/4.0
local p1 = cc.p(p1x, height + startPoint.y + math.cos(radian)*p1x)
local p2x = startPoint.x + (endPoint.x - startPoint.x)/2.0
local p2 = cc.p(p2x, height + startPoint.y + math.cos(radian)*p2x)
local cfg = {p1, p2, endPoint}
return cc.Spawn:create(
cc.RotateBy:create(1,selfRotate),
cc.EaseInOut:create(cc.BezierTo:create(t,cfg), 0.5)
)
end
self.sprite1:runAction(Parabola(1,self.sprite1,ccp(900,300),100,60,360))
上一篇: springboot整合mybatis(配置文件)
下一篇: 每天一个小技巧【8】·贝塞尔曲线