【轉】三次貝塞爾曲線繪制算法


原文:http://www.cnblogs.com/flash3d/archive/2012/01/30/2332176.html

 

源碼:http://files.cnblogs.com/flash3d/bezier.rar

 

====================================================

 

這學期學圖形學,就把自己的一些粗淺的理解發上去讓大家拍磚。前些天做三次貝塞爾曲線繪制的上機練習,正好將從直線掃描算法中啟發得來的n次多項式批量計算用上了,自認為優化得還可以。原來寫的版本是C++,為了便於網上觀看特改寫成AS3,對這方面內行的朋友,希望還多多指點!

在講三次貝塞爾曲線之前,先給出n次貝塞爾曲線的一般式:

[R]( t ) = ( +..i=0,n ) ( [R]i * Bi,n( t ) ) , t屬於[0,1]

Bi,n( t ) = Cni * pow( 1 - t , n - i ) * pow( t , i )

這里我把符號說明下,中括號[]括起來的表示矩陣。比如[R]就是個矩陣,如果我們討論的貝塞爾曲線的點在平面內,那么矩陣里面有兩個元素,比如我們熟知的x,y,如果是空間的曲線,就是三個元素,元素再多。。那就不知道是幾次元了,反正是個數學概念。在計算的時候,將矩陣的元素單獨拎出來計算也是可以的,因為公式中並沒有出現矩陣和矩陣相乘,也就是說,各元素計算時其實各不相干。

( +..i=0,n ) 這個運算符,這個符號是我自己發明的,其實是累加符號,表示公式后面括號內將i分別用0,1,2,3..n代替,每個代替運算的結果全部加起來。

Cni是二項式系數,其值為 n! / ( i! * ( n - i )! )

pow是求指數函數。

其中 Bi,n( t ) = Cni * pow( 1 - t , n - i ) * pow( t , i ) 為n次二項式展開后的一項。其意義在后面的三次貝塞爾曲線里面討論更容易理解

那么,現在先討論3次貝塞爾曲線,我們把公式中的n全部替換成3,得到三次貝塞爾曲線的一般式如下:

[R]( t ) = ( +..i=0,3 ) ( [R]i * Bi,3( t ) ) , t屬於[0,1]

Bi,3( t ) = C3i * pow( 1 - t , 3 - i ) * pow( t , i )

直接把Bi,3( t )代入吧,得到式子:

[R]( t ) = ( +..i=0,3 ) ( [R]i * C3i * pow( 1 - t , 3 - i ) * pow( t , i ) ) , t屬於[0,1]

對於不熟悉線性代數或者不習慣用矩陣表示數據的朋友,我們就直接把矩陣拆開好了,[R]矩陣中總共就兩個元素,分別是x和y,這里就把x和y分別計算,得到關於x和y的參數曲線:

X( t ) = ( +..i=0,3 ) ( Xi * C3i * pow( 1 - t , 3 - i ) * pow( t , i ) ) , t屬於[0,1]

Y( t ) = ( +..i=0,3 ) ( Yi * C3i * pow( 1 - t , 3 - i ) * pow( t , i ) ) , t屬於[0,1]

如果對參數曲線不熟悉,可以參考直線的參數方程

比如一條直線經過(x1,y1),(x2,y2),那么其參數方程可表示為:

X(t)=x1+t*(x2-x1)

Y(t)=y1+t*(y2-y1)

如果是線段那么t在0和1之間,如果是直線,t是實數。

言歸正傳,下面我們開始處理參數曲線,由於x的參數方程和y的形式差不多,我們就以x的參數方程做例子來分析

X( t ) = ( +..i=0,3 ) ( Xi * C3i * pow( 1 - t , 3 - i ) * pow( t , i ) ) , t屬於[0,1]

首先我們把連加拆開,那么i分別等於0,1,2,3,得到一個式子:

X( t ) =

X0 * C30 * pow( 1 - t , 3 - 0 ) * pow( t , 0 )

+

X1 * C31 * pow( 1 - t , 3 - 1 ) * pow( t , 1 )

+

X2 * C32 * pow( 1 - t , 3 - 2 ) * pow( t , 2 )

+

X3 * C33 * pow( 1 - t , 3 - 3 ) * pow( t , 3 )

t屬於[0,1]

看這個式子,我們可以發現,C30 * pow( 1 - t , 3 - 0 ) * pow( t , 0 )+C31 * pow( 1 - t , 3 - 1 ) * pow( t , 1 )+C32 * pow( 1 - t , 3 - 2 ) * pow( t , 2 )+C33 * pow( 1 - t , 3 - 3 ) * pow( t , 3 )=pow( 1 - t + t , 3)=1

那就是說,x0,x1,x2,x3相加前的系數和為1,我們稱這些系數為各點的權重。當然,點的權重是隨着t的值連續變化的,這也是為什么三次貝塞爾曲線能光滑地通過四個點控制的原因。

下面我們進一步對式子進行化簡。

其中C30等於1,C31等於3,C32等於3,C33等於1,把能合並的都合並,能計算值的都計算出來,將式子化簡:

X( t ) =

X0 * pow( 1 - t , 3 )

+

X1 * 3 * pow( 1 - t , 2 ) * t

+

X2 * 3 * ( 1 - t ) * pow( t , 2 )

+

X3 * pow( t , 3 )

t屬於[0,1]

這樣的式子應該很好求了,他是一個最高次為3次的多項式,在Alchemy的使用和多項式批量計算的優化中提到,a*pow(x,n)+b*pow(x,n-1)+..這樣形式的式子,都能通過多項式批量計算方法來快速求得結果。

對於最高三次的式子來說,我們需要前三個原始數據,以及式子求導三次的常數,算法細節就不講了,細節參考上面那篇博文。

 

package
{

    import flash.display.Bitmap;

    import flash.display.BitmapData;

    import flash.display.Shape;

    import flash.display.Sprite;

    import flash.events.Event;

    import flash.events.MouseEvent;

    import flash.geom.Point;

    import flash.geom.Rectangle;

    /**

        *貝塞爾曲線繪制算法

        * @author boycy815

        */

    public class Main extends Sprite

    {

        /*C30到C33的值總共才4個直接枚舉*/

        private const C30:int = 1;

        private const C31:int = 3;

        private const C32:int = 3;

        private const C33:int = 1;

        /*定義曲線上點的個數(包括兩端)*/

        private const LENGTH:int = 10000;

        /*btC30到btC33分別是R0 R1 R2 R3四個點當t取前三個值時的權重值*/

        private var btC30:Vector.<Number> = new Vector.<Number>(3);

        private var btC31:Vector.<Number> = new Vector.<Number>(3);

        private var btC32:Vector.<Number> = new Vector.<Number>(3);

        private var btC33:Vector.<Number> = new Vector.<Number>(3);

        /*畫點的位圖*/

        private var data:BitmapData;

        private var map:Bitmap;

        /*用於連接四個點的四條直線*/

        private var lin:Shape;

        /*分別是四個點的顯示對象*/

        private var ct0:Sprite;

        private var ct1:Sprite;

        private var ct2:Sprite;

        private var ct3:Sprite;

        public function Main():void

        {

            if (stage) init();

            else addEventListener(Event.ADDED_TO_STAGE, init);

        }

        

        private function init(e:Event = null):void

        {

            removeEventListener(Event.ADDED_TO_STAGE, init);

            /*初始化位圖和一些顯示對象*/

            data = new BitmapData(800, 600,false);

            map = new Bitmap(data);

            this.addChild(map);

            lin = new Shape();

            this.addChild(lin);

            ct0 = new Sprite();

            ct0.buttonMode = true;

            ct0.graphics.lineStyle(0, 0x00000);

            ct0.graphics.beginFill(0xffffff);

            ct0.graphics.drawCircle(0, 0, 5);

            ct0.graphics.endFill();

            ct0.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);

            ct0.addEventListener(MouseEvent.MOUSE_UP, mouseUp);

            this.addChild(ct0);

            ct1 = new Sprite();

            ct1.buttonMode = true;

            ct1.graphics.lineStyle(0, 0x00000);

            ct1.graphics.beginFill(0xffffff);

            ct1.graphics.drawCircle(0, 0, 5);

            ct1.graphics.endFill();

            ct1.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);

            ct1.addEventListener(MouseEvent.MOUSE_UP, mouseUp);

            this.addChild(ct1);

            ct2 = new Sprite();

            ct2.buttonMode = true;

            ct2.graphics.lineStyle(0, 0x00000);

            ct2.graphics.beginFill(0xffffff);

            ct2.graphics.drawCircle(0, 0, 5);

            ct2.graphics.endFill();

            ct2.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);

            ct2.addEventListener(MouseEvent.MOUSE_UP, mouseUp);

            this.addChild(ct2);

            ct3 = new Sprite();

            ct3.buttonMode = true;

            ct3.graphics.lineStyle(0, 0x00000);

            ct3.graphics.beginFill(0xffffff);

            ct3.graphics.drawCircle(0, 0, 5);

            ct3.graphics.endFill();

            ct3.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);

            ct3.addEventListener(MouseEvent.MOUSE_UP, mouseUp);

            this.addChild(ct3);
            /*初始化計算的一些准備數據*/

            btInit();

            /*默認進行第一次定位和繪制*/

            ct0.x = 100;

            ct0.y = 100;

            ct1.x = 700;

            ct1.y = 100;

            ct2.x = 700;

            ct2.y = 500;

            ct3.x = 100;

            ct3.y = 500;

            lin.graphics.lineStyle(0, 0x000000);

            lin.graphics.moveTo(ct0.x, ct0.y);

            lin.graphics.lineTo(ct1.x, ct1.y);

            lin.graphics.lineTo(ct2.x, ct2.y);

            lin.graphics.lineTo(ct3.x, ct3.y);

            drawBezier3(new Point(ct0.x, ct0.y), new Point(ct1.x, ct1.y), new Point(ct2.x, ct2.y), new Point(ct3.x, ct3.y));

        }

        /*按鈕按下便開始可以拖動點*/

        private function mouseDown(e:MouseEvent):void

        {

            e.target.startDrag();

            stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);

        }

        /*鼠標移動便不斷繪制曲線*/

        private function mouseMove(e:MouseEvent):void

        {

            /*繪制連接四個點的直線*/

            lin.graphics.clear();

            lin.graphics.lineStyle(0, 0x000000);

            lin.graphics.moveTo(ct0.x, ct0.y);

            lin.graphics.lineTo(ct1.x, ct1.y);

            lin.graphics.lineTo(ct2.x, ct2.y);

            lin.graphics.lineTo(ct3.x, ct3.y);

            /*繪制曲線*/

            data.fillRect(new Rectangle(0, 0, 800, 600), 0xffffffff);

            drawBezier3(new Point(ct0.x, ct0.y), new Point(ct1.x, ct1.y), new Point(ct2.x, ct2.y), new Point(ct3.x, ct3.y));

        }

        /*釋放按鈕停止拖動*/

        private function mouseUp(e:MouseEvent):void

        {

            var n:int;

            e.target.stopDrag();

            stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMove);

        }

        /***********算法部分**********/

        

        /*初始化計算所需數據,主要是btC30到btC33*/

        private function btInit():void

        {

            var add:Number;

            /*由於t的值域為[0,1],要繪制LENGTH個點,則必須計算出每次t的增量*/

            add = 1.0 / (LENGTH - 1.0);

            /*三次白賽爾曲線Ri的權重公式為:C3i * pow( t - 1 , 3 - i ) * pow( t , i )

                    *前三次t的值分別為0,add,add*2

                    *則代入公式就能求出R0 R1 R2 R3的前三次的權重值

                    * */

            btC30[0] = 1;

            btC30[1] = C30 * Math.pow(1 - add, 3);

            btC30[2] = C30 * Math.pow(1 - add * 2, 3);

            btC31[0] = 0;

            btC31[1] = C31 * Math.pow(1 - add, 2) * add;

            btC31[2] = C31 * Math.pow(1 - add * 2, 2) * add * 2;

            btC32[0] = 0;

            btC32[1] = C32 * (Math.pow(add, 2) - Math.pow(add, 3));

            btC32[2] = C32 * (Math.pow(add * 2, 2) - Math.pow(add * 2, 3));

            btC33[0] = 0;

            btC33[1] = C33 * Math.pow(add, 3);

            btC33[2] = C33 * Math.pow(add * 2, 3);

        }

        /*利用准備好的初始數據,通過遞推方法求出四個點對應的貝塞爾曲線上每個點的坐標,並繪制

            *計算中用到多項式批量計算優化策略,上一篇博文中有提及具體的方法和思想

            * */

        private function drawBezier3(p0:Point,p1:Point,p2:Point,p3:Point):void

        {

            /*qun_x和qun_x本別為x坐標和y坐標的臨時數據數組*/

            var qun_x:Vector.<Number> = new Vector.<Number>(3);

            var qun_y:Vector.<Number> = new Vector.<Number>(3);

            /*dd_x和dd_y分別為x坐標的多項式和y坐標式求導三次后得到的常數*/

            var dd_x:Number;

            var dd_y:Number;

            var n:int;

            /*計算前三次種子數據*/

            dd_x = 6 * (3 * p1.x - 3 * p2.x - p0.x + p3.x) / Math.pow(LENGTH - 1.0, 3);

            qun_x[0] = p0.x * btC30[0] + p1.x * btC31[0] + p2.x * btC32[0] + p3.x * btC33[0];

            dd_y = 6 * (3 * p1.y - 3 * p2.y - p0.y + p3.y) / Math.pow(LENGTH - 1.0, 3);

            qun_y[0] = p0.y * btC30[0] + p1.y * btC31[0] + p2.y * btC32[0] + p3.y * btC33[0];

            data.setPixel(qun_x[0], qun_y[0], 0x000000);

            qun_x[1] = p0.x * btC30[1] + p1.x * btC31[1] + p2.x * btC32[1] + p3.x * btC33[1];

            qun_x[0] = qun_x[1] - qun_x[0];

            qun_y[1] = p0.y * btC30[1] + p1.y * btC31[1] + p2.y * btC32[1] + p3.y * btC33[1];

            qun_y[0] = qun_y[1] - qun_y[0];

            data.setPixel(qun_x[1], qun_y[1], 0x000000);

            qun_x[2] = p0.x * btC30[2] + p1.x * btC31[2] + p2.x * btC32[2] + p3.x * btC33[2];

            qun_x[1] = qun_x[2] - qun_x[1];

            qun_x[0] = qun_x[1] - qun_x[0];

            qun_y[2] = p0.y * btC30[2] + p1.y * btC31[2] + p2.y * btC32[2] + p3.y * btC33[2];

            qun_y[1] = qun_y[2] - qun_y[1];

            qun_y[0] = qun_y[1] - qun_y[0];

            data.setPixel(qun_x[2], qun_y[2], 0x000000);

            /*進行遞推計算的循環*/

            for (n = 3; n < LENGTH; n++)

            {

                qun_x[0] += dd_x;

                qun_x[1] += qun_x[0];

                qun_x[2] += qun_x[1];

                qun_y[0] += dd_y;

                qun_y[1] += qun_y[0];

                qun_y[2] += qun_y[1];

                data.setPixel(qun_x[2], qun_y[2], 0x000000);

            }

        }

    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM