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))

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值