欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Cocos - 贝塞尔曲线 Bezier

程序员文章站 2022-07-12 22:42:51
...

一、序言

本篇只讲述贝塞尔曲线数学公式的运用原理,不进行公式的背景介绍和推导内容,如需请移步贝塞尔曲线公式推导原理

在现实中,我们也只需要掌握其大致原理和开发中实际应用即可。

二、贝塞尔曲线原理

原文链接:https://www.jianshu.com/p/6075f9782743

A. 二阶贝塞尔曲线

要素:1 个起点,1 个终点,1 个控制点

知识点

三阶的话就是 2 个控制点,四阶的话就是 3 个,以此类推,N 阶的话就是 N - 1 个控制点。而起点和终点始终只有一个。

步骤如下:

1.绘制 1 个起点,1 个终点和 1 个控制点,分别为 S 、E、C。然后将 SC、CE 分别连线。如下图所示。

Cocos - 贝塞尔曲线 Bezier

2.从点 S 向 C 出发找到一个 D 点,从 C 向 E 出发找到一个 F 点,使得SD / SC = CF / CE。然后连接 DF。如下图所示。

Cocos - 贝塞尔曲线 Bezier

3.在 DF 之间找到点 M,使得SD / SC = CF / CE = DM / DF

Cocos - 贝塞尔曲线 Bezier

总结下:

  • (1) 二阶贝塞尔中,起初是 3 个点,然后我们再找 2 个点,然后再找 1 个点。这个点就是我们要找到的点。
  • (2) 我们需要由 S 向 C 出发,由 C 向 E 出现,找到所有的 D 和 F,再找到所有的 M。
  • (3) 将所有的 M 连接起来就构造出了最后的所需要的贝塞尔曲线了。

借用一个图,来详细观察一下其构造的过程。

 

Cocos - 贝塞尔曲线 Bezier

B. 三阶贝塞尔曲线

三阶和二阶是类似的:
1.连接 A,B 形成 AB 线段,连接 B,C 形成 BC 线段,连接 C,D 形成 CD 线段。

 

Cocos - 贝塞尔曲线 Bezier

2.在AB线段取一个点 E,BC 线段取一个点 F,CD 线段取一个点 G,使其满足条件: AE/AB = BF/BE = CG/CD。连接 E,F 形成线段 EF,连接 F,G 形成线段 FG。

 

Cocos - 贝塞尔曲线 Bezier

3.在EF线段取一个点 H,FG 线段取一个点 I,使其满足条件: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG。连接 H,I 形成线段 HI。

Cocos - 贝塞尔曲线 Bezier

4.在 HI 线段取一个点 J,使其满足条件: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG = HJ/HI。

Cocos - 贝塞尔曲线 Bezier

5.而满足这些条件的所有的J点所形成的轨迹就是三阶贝塞尔曲线,动态过程如下:

 

Cocos - 贝塞尔曲线 Bezier

综合上式列表,可以总结出一般性规律:

Cocos - 贝塞尔曲线 Bezier

三、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、开始点、结束点和两个控制点,通过方程计算出来的。

Cocos - 贝塞尔曲线 Bezier

如上图所示: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))