簡(jiǎn)介
貝塞爾曲線(Bézier curve)又被稱為貝茲曲線或貝濟(jì)埃曲線,是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學(xué)曲線,它的數(shù)學(xué)基礎(chǔ)是伯恩斯坦多項(xiàng)式(Bernstein polynomial,since 1912),1959年法國(guó)數(shù)學(xué)家Paul de Casteljau提出了數(shù)值穩(wěn)定的de Casteljau算法,開始貝塞爾曲線的圖形化應(yīng)用研究,而貝塞爾曲線的名稱來源于一位就職于雷諾的法國(guó)工程師Pierre Bézier,他在1962年開始對(duì)貝塞爾曲線做了廣泛的宣傳,他使用這種只需要很少的控制點(diǎn)就能生成復(fù)雜平滑曲線的方法來進(jìn)行汽車車體的工業(yè)設(shè)計(jì)。
貝塞爾曲線因?yàn)樗刂坪?jiǎn)便卻具有極強(qiáng)的描述能力,迅速在工業(yè)設(shè)計(jì)和計(jì)算機(jī)圖形學(xué)等相關(guān)領(lǐng)域得到了廣泛應(yīng)用。比如在矢量繪圖中,貝塞爾曲線用來給需要無限制地縮放的平滑曲線定模,許多繪圖軟件都提供了繪制貝塞爾曲線的功能。貝塞爾曲線還用于動(dòng)畫時(shí)間控制以實(shí)現(xiàn)美觀逼真的緩動(dòng)效果,還用于機(jī)器人轉(zhuǎn)動(dòng)手臂等方面的設(shè)計(jì)。
對(duì)于貝塞爾曲線來說,最重要的點(diǎn)是,數(shù)據(jù)點(diǎn)和控制點(diǎn)。
數(shù)據(jù)點(diǎn): 指一條路徑的起始點(diǎn)和終止點(diǎn)。
控制點(diǎn):控制點(diǎn)決定了一條路徑的彎曲軌跡,根據(jù)控制點(diǎn)的個(gè)數(shù),貝塞爾曲線被分為一階貝塞爾曲線(0個(gè)控制點(diǎn))、二階貝塞爾曲線(1個(gè)控制點(diǎn))、三階貝塞爾曲線(2個(gè)控制點(diǎn))等等。從1-n階的連續(xù)函數(shù),都可以計(jì)算得到一條光滑曲線。
貝塞爾曲線展示
以下公式中:B(t)為t時(shí)間下點(diǎn)的坐標(biāo),P0為起點(diǎn),Pn為終點(diǎn),Pi為控制點(diǎn)
一階貝塞爾曲線


二階貝塞爾曲線


三階貝塞爾曲線


四階貝塞爾曲線

五階貝塞爾曲線


cocos引擎中的Bezier
cocos-2dx引擎中自帶的是三階貝塞爾曲線,需要兩個(gè)控制點(diǎn),可以通過讓兩個(gè)控制點(diǎn)坐標(biāo)相同,轉(zhuǎn)化成二階,即拋物線。
推薦一個(gè)網(wǎng)站,可以模擬和生成多種樣式的曲線在線生成貝塞爾曲線(英文)或在線生成貝塞爾曲線(中文)
C++函數(shù)原型
//結(jié)構(gòu)體
typedef struct _ccBezierConfig {
Vec2 endPosition; // end position of the bezier
Vec2 controlPoint_1; // Bezier control point 1
Vec2 controlPoint_2; // Bezier control point 2
} ccBezierConfig;
// BezierTo創(chuàng)建函數(shù)
static BezierTo* create(float t, const ccBezierConfig& c);
參數(shù)解讀
endPosition:曲線的終點(diǎn)
controlPoint_1: 曲線起點(diǎn)的控制點(diǎn)(控制第一個(gè)波峰或波谷)
controlPoint_2:曲線終點(diǎn)的控制點(diǎn)(控制第二個(gè)波峰或波谷)
當(dāng) controlPoint_1 == controlPoint_2 時(shí),曲線只有一個(gè)波峰或波谷
注意:lua中調(diào)用的時(shí)候,參數(shù)的順序發(fā)生了變化,下面截取了部分代碼
ccBezierConfig config;
config.controlPoint_1 = arr[0];
config.controlPoint_2 = arr[1];
config.endPosition = arr[2];
CC_SAFE_DELETE_ARRAY(arr);
BezierTo* tolua_ret = BezierTo::create(t, config);
if (NULL != tolua_ret)
{
int nID = (tolua_ret) ? (int)tolua_ret->_ID : -1;
int* pLuaID = (tolua_ret) ? &tolua_ret->_luaID : NULL;
toluafix_pushusertype_ccobject(tolua_S, nID, pLuaID, (void*)tolua_ret,"cc.BezierTo");
return 1;
}
所以這里的順序是 controlPoint_1,controlPoint_2,endPosition
實(shí)例
要求:模擬一個(gè)射箭的過程,路徑是拋物線,運(yùn)動(dòng)過程中箭頭的方向要與曲線運(yùn)動(dòng)方向一致。
思路:利用貝塞爾曲線讓箭頭運(yùn)動(dòng)起來,運(yùn)動(dòng)的同時(shí)每一幀計(jì)算曲線斜率,斜率轉(zhuǎn)換成角度,并設(shè)置箭頭的旋轉(zhuǎn)角度即可。
lua調(diào)用
-- 創(chuàng)建箭頭sprite
local target = display.newSprite("ui_jiantou.png")
target:addTo(self)
-- 一些坐標(biāo)
local originP = cc.p(100, 100) -- 起點(diǎn)
local controlP1 = cc.p(550, 500) -- 控制點(diǎn)1
local controlP2 = cc.p(550, 500) -- 控制點(diǎn)2
local endP = cc.p(1000, 100) -- 終點(diǎn)
local bezierPos = {controlP1, controlP2, endP}
-- 初始化
target:setPosition(originP)
target:setRotation(-30) -- 大概給一個(gè)起始角度,注意:setRotation()參數(shù)是角度值,正值表示順時(shí)針旋轉(zhuǎn),與我們斜率的正負(fù)值相反
local angle -- 箭頭角度值
local lastP = originP -- 上一幀點(diǎn)坐標(biāo)
local scheduler = require("cocos.framework.scheduler") -- 全局計(jì)時(shí)器
local action1 = cc.CallFunc:create(function()
self.handler = scheduler.scheduleGlobal(function() -- 注冊(cè)全局計(jì)時(shí)器
local curP = cc.p(target:getPosition()) -- 當(dāng)前幀坐標(biāo)
local radian = cc.pToAngleSelf((cc.pSub(curP, lastP))) --向量夾角弧度
angle = radian * 180 / math.pi -- 弧度值轉(zhuǎn)換為角度 公式:弧度=度*π/180
target:setRotation(-angle)
lastP = curP
end, 0)
end)
local action2 = cc.BezierTo:create(5, bezierPos) -- 自帶的為三階貝塞爾,可以讓control1等于control2變?yōu)槎A,實(shí)現(xiàn)拋物線
local action3 = cc.CallFunc:create(function()
if self.handler then
scheduler.unscheduleGlobal(self.handler) -- 注銷全局計(jì)時(shí)器
target:stopAllActions()
end
end)
-- 動(dòng)作1動(dòng)作2同時(shí)運(yùn)行結(jié)束后,再執(zhí)行動(dòng)作3
local action = transition.sequence({cc.Spawn:create({action1, action2}), action3})
target:runAction(action)
輔助畫線
為了更直觀準(zhǔn)確的觀測(cè)運(yùn)動(dòng)軌跡,我們可以把軌跡畫出來
引擎中DrawNode類,可以畫出各種形狀
C++源碼
/** Draw a cubic bezier curve with color and number of segments
*
* @param origin The origin of the bezier path.
* @param control1 The first control of the bezier path.
* @param control2 The second control of the bezier path.
* @param destination The destination of the bezier path.
* @param segments The number of segments.
* @param color Set the cubic bezier color.
*/
void drawCubicBezier(const Vec2 &origin, const Vec2 &control1, const Vec2 &control2, const Vec2 &destination, unsigned int segments, const Color4F &color);
lua調(diào)用
-- 畫線 貝塞爾曲線
local myDrawNode = cc.DrawNode:create()
myDrawNode:drawCubicBezier(originP, controlP1, controlP2, endP, 100, cc.c4f(0,0,1.0,1))
myDrawNode:addTo(self, 100)
-- 畫點(diǎn)
local myPoint = cc.DrawNode:create()
myPoint:drawPoints({originP, controlP1, controlP2, endP}, 4, 10, cc.c4f(1,0,0,1))
myPoint:addTo(self, 100)
運(yùn)行展示
圖中,紅點(diǎn)依次是 起點(diǎn)、控制點(diǎn)1(與控制點(diǎn)2重合)、終點(diǎn)
