天天看點

貝塞爾曲線 (Bézier curve) 理論及繪制方法

示範位址

數學中公式是很重要的,我們先看公式。

公式

線性公式

給定點P0、P1,線性貝茲曲線隻是一條兩點之間的直線。這條線由下式給出:

B(t)=P0+(P1-P0)t=(1-t)P0+tP1, t ∈[0, 1]

且其等同于線性插值。

二次方公式

二次方貝茲曲線的路徑由給定點P0、P1、P2的函數B(t)追蹤:

貝塞爾曲線 (Bézier curve) 理論及繪制方法

TrueType字型就運用了以貝茲樣條組成的二次貝茲曲線。

三次方公式

P0、P1、P2、P3四個點在平面或在三維空間中定義了三次方貝茲曲線。曲線起始于P0走向P1,并從P2的方向來到P3。一般不會經過P1或P2;這兩個點隻是在那裡提供方向資訊。P0和P1之間的間距,決定了曲線在轉而趨進P3之前,走向P2方向的“長度有多長”。

曲線的參數形式為:

貝塞爾曲線 (Bézier curve) 理論及繪制方法

現代的成象系統,如PostScript、Asymptote和Metafont,運用了以貝茲樣條組成的三次貝茲曲線,用來描繪曲線輪廓。

一般參數公式

階貝茲曲線可如下推斷。給定點P0、P1、…、Pn,其貝茲曲線即:

貝塞爾曲線 (Bézier curve) 理論及繪制方法

如上公式可如下遞歸表達: 用表示由點P0、P1、…、Pn所決定的貝茲曲線。

用平常話來說,階的貝茲曲線,即雙階貝茲曲線之間的插值。

公式說明

1.開始于P0并結束于Pn的曲線,即所謂的端點插值法屬性。

2.曲線是直線的充分必要條件是所有的控制點都位在曲線上。同樣的,貝塞爾曲線是直線的充分必要條件是控制點共線。

3.曲線的起始點(結束點)相切于貝塞爾多邊形的第一節(最後一節)。

4.一條曲線可在任意點切割成兩條或任意多條子曲線,每一條子曲線仍是貝塞爾曲線。

5.一些看似簡單的曲線(如圓)無法以貝塞爾曲線精确的描述,或分段成貝塞爾曲線(雖然當每個内部控制點對機關圓上的外部控制點水準或垂直的的距離為時,分成四段的貝茲曲線,可以小于千分之一的最大半徑誤差近似于圓)。

6.位于固定偏移量的曲線(來自給定的貝塞爾曲線),又稱作偏移曲線(假平行于原來的曲線,如兩條鐵軌之間的偏移)無法以貝茲曲線精确的形成(某些瑣屑執行個體除外)。無論如何,現存的啟發法通常可為實際用途中給出近似值。

轉換矩陣

好,我們一般使用的貝塞爾曲線應該是三階的。

如上公式,這個公式可以寫成矩陣(線性代數自己看,圖形程式設計中常用)的形式。

貝塞爾曲線 (Bézier curve) 理論及繪制方法

其中,t 等同于公式3中的t,是一個自變量,而 P 代表一個點,0-3代表4個點,其上的 x, y 分别代表 坐标。是以給定四個點的坐标,t 變換的時候,會得到無數個點坐标,由此點坐标繪制,即成 貝塞爾曲線。

js代碼參考:

obite-bezier.js

var matrix = function(config) {
    this._init(config)
};
matrix.prototype = {
    _init: function(config) {
        if (config && config.data)
            this.data = config.data;
    },
    /**
     * 矩陣相乘
     * @param {matrix} m 被乘的矩陣
     */
    mul: function(m) {
        var r = [],
            s = this.data,
            m = m.data,
            p = s[].length //每次運算相加的次數
        if (m.length != s[].length) {
            T.trace("矩陣不能相乘")
        }
        for (var i = ; i < s.length; i++) {
            r[ i ] = []
            for (var n = ; n < m[].length; n++) {
                r[ i ][n] = ;
                for (var l = ; l < p; l++) {
                    r[ i ][n] += s[ i ][l] * m[l][n];
                }
            }
        }
        this.data = r;
        return this;
    },
    set: function(data) {
        this.data = data;
    },
    get: function() {
        return this.data
    },
    toString: function() {
        return this.data.to_s()
    }
};


var cubicBezier = function(config) {
    this._init(config)
};
cubicBezier.prototype = {
    _init: function(config) {
        var p = this.points = config.points;
        this.m1 = new obite.matrix();
        this.m2 = new obite.matrix({
            data: [
                [, , , ],
                [-, , , ],
                [, -, , ],
                [-, , -, ]
            ]
        });
        this.m3 = new matrix({
            data: [
                p.p0.toArray(),
                p.p1.toArray(),
                p.p2.toArray(),
                p.p3.toArray()
            ]
        });

        this.m = null
    },
    /**
     * 擷取某個時間點計算出來的坐标值,時間線不由此類控制
     */
    get: function(t) {
        /*this.m1.set([
            [1, t * t, t * t * t, t * t * t * t]
        ]);*/
        this.m1.set([
            [, t, t * t, t * t * t]
        ]);

        this.m = this.m1.mul(this.m2).mul(this.m3)

        return new obite.vector(this.m.get()[][], this.m.get()[][]);
    }
};

var vector = function(x, y) {
    this._init(x, y);
};
vector.prototype = {
    _init: function(x, y) {
        this.x = x;
        this.y = y;
    },
    toArray: function() {
        return [this.x, this.y];
    }
};


function drawPoint2d(canvas, vector) {
    if (typeof canvas === 'string') {
        canvas = document.getElementById(canvas);
    }
    var canvasContext = canvas.getContext('2d');
    //在點P處畫一個點
    with(canvasContext) {
        beginPath();
        lineWidth = ;
        moveTo(vector.x, vector.y);
        lineTo(vector.x + , vector.y + );
        strokeStyle = "green";
        stroke();
    }
}


var obite = function() {};
obite.extend = function(json) {
    for (var i in json) {
        obite[ i ] = json[ i ];
    }
};
obite.extend({
    matrix: matrix,
    cubicBezier: cubicBezier,
    vector: vector,
    drawPoint2d: drawPoint2d
});

window.o = obite;
           

points.js

var points = {
    p0: new o.vector(, ),
    p1: new o.vector(, ),
    p2: new o.vector(, ),
    p3: new o.vector(, )
};
var cb = new o.cubicBezier({
    points: points
});
var p;
var t = ;
var inter = setInterval(function() {
    t += ;
    if (t > ) {
        end()
    }
    p = cb.get(t)
        //根據p的坐标畫點
    o.drawPoint2d(canvas,p);
}, );

function end() {
    clearInterval(inter);
}
           

bezier.html

<!doctype html>
<html lang="zh">

<head>
    <script type="text/javascript" src="obite-bezier.js"></script>
</head>

<body>
    <canvas id="c" height="600" width="800"></canvas>
</body>
<script type="text/javascript">
var canvas = document.getElementById("c");


</script>
<script type="text/javascript" src="points.js"></script>

</html>
           

參考:1. 百度百科:http://baike.baidu.com/view/60154.htm

2. http://bbs.9ria.com/thread-71296-1-1.html

繼續閱讀