三次貝塞爾曲線繪制算法(優化過)


源碼: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次的多項式,在上一篇博文中提到,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