源碼: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);
}
}
}
}