Thuật tốn vẽ đường elip

Một phần của tài liệu Giao trinh đồ họa máy tính 2012 (Trang 47)

2.3.1 Nguyên lý chung

Cho elip tâm (h, k), độ dài trục chính là a, độ dài trục phụ là b Phương trình đường elip được xác định như sau :

(x-h)2/a2+(y-k)2/b2=1.

Phương trình elip với tâm tại gốc tọa độ

x2/b2+y2/b2=1

Elip được chia thành 4 phần đối xứng qua 2 trục tọa độ, do vậy ta chỉ cần vẽ cung ¼ elip sau đĩ thực hiện lấy đối xứng để thu được các phần cịn lại.

Để vẽ elip tâm (h,k) ta cĩ thể thực hiện vẽ elip tâm tại gốc tọa độ sau đĩ tịnh tiến theo véc tơ (h,k).

Tại mỗi bước ta cho x tăng từ 0 đến a sau đĩ tính giá trị y tương ứng qua biểu thức trên, sau đĩ lấy giá trị nguyên gần với giá trị y thực nhất.

Phương pháp này khơng hiệu quả do phải làm nhiều phép tốn lấy bình phương và phép khai căn. Tốc độ thuật tốn chậm.

2.3.2 Thuật tốn trung điểm (MidPoint) vẽ elip

Xét elip tâm tại gốc tọa độ. Phương trình đường elip:

F(x,y)=b2x2+a2y2−a2b2=0

Xét vẽ cung ¼ elip, sau đĩ lấy đối xứng để thu được các phần cịn lại 0<=x<=a

0<=y<=b

Chia cung ¼ elip này thành 2 vùng với điểm chia P là tiếp điểm của tiếp tuyến cĩ hệ số gĩc là -1.

Hình 2.18

Véc tơ gradient vuơng gĩc với tiếp tuyến tại tiếp điểm được xác định như sau: GradF(x,y) = (∂F/∂x)i+(∂F/∂y)j=2b2x.i+2a2y.j

Ta cĩ tiếp tuyến với cung trịn (độ dốc) = -1

Vector gradient cĩ độ dốc là 1, do đĩ tại P các thành phần i và j của vecto gradient cĩ cùng độ lớn.

Trong mỗi vùng thành phần nào của vecto gradient lớn hơn thì xác định trung điểm theo hướng đĩ. Thành phần nhỏ hơn sẽ biến thiên theo từng đơn vị.

Trong vùng 1 thành phần j lớn hơn thành phần i của gradient. a2(yp-0.5)>b2(xp+1).

Trong vùng 2 thì ngược lại. + Xét trên phần 1:

Bắt đầu từ (0,b),

Tại mỗi bước x tăng lên một đơn vị, y biến đổi theo x

bước thứ i (xi,yi), Ở bước i+1 chọn tiếp A(xi+1, yi) hoặc B(xi+1,yi-1). Chọn điểm gần với đường elip nhất dựa trên việc xét dấu của hàm F tại trung điểm M của AB.

Tham số quyết định:

pi =F(M)= F(xi+1,yi-1/2) = b2(xi+1)2 + a2(yi-1/2)2 -a2b2 pi+1 = F(xi+1+1,yi+1-1/2) = b2(xi+1+1)2 + a2(yi+1-1/2)2 -a2b2 - Nếu pi <0 chọn A

xi+1=xi+1 yi+1=yi

pi+1 = b2(xi+1+1)2 + a2(yi+1-1/2)2 -a2b2 = b2(xi+2)2 + a2(yi-1/2)2 -a2b2 = pi + b2(2xi +3) - Nếu pi >=0 chọn B xi+1=xi+1 yi+1=yi -1 pi+1 = b2(xi+2)2 + a2(yi-1.5)2 -a2b2 = pi + b2(2xi +3) + a2(-2yi +2) - Tính P1 khởi tạo tại (0,b)

p1 = F(1,b-1/2) = b2 + a2(b-1/2)2 -a2b2 p1 = b2 - a2b +a2/4

+ Xét trên phần 2:

Ta lấy toạ độ của Pixel sau cùng trong phần 1 của đường cong để tính giá trị ban đầu cho phần 2.

Tại mỗi bước y giảm từng đơn vị, x biến đổi theo y. Tại bước j ta cĩ điểm (xj,yj).

C(xj,yj-1) hoặc D(xj+1, yj-1). Chúng ta sẽ lựa chọn điểm gần với đường elip nhất. Để quyết định chọn điểm nào chúng ta cĩ thể dựa vào dấu của hàm F tại trung điểm M của đoạn CD Tham số quyết định: qj =F(M)= F(xj+1/2,yj-1) = b2(xj+1/2)2 + a2(yj-1)2 -a2b2 qj+1 = F(xj+1+1/2,yj+1-1) = b2(xj+1+1/2)2 + a2(yj+1-1)2 -a2b2 - Nếu qj <0 chọn D yj+1=yj-1 xj+1=xj +1 qj+1 = b2(xj+1+1/2)2 + a2(yj+1-1)2 -a2b2 = b2(xj+1.5)2 + a2(yj-2)2 -a2b2 =qj + b2(2xj +2) +a2 (-2yj +3) - Nếu qj >=0 chọn C yj+1=yj -1 xj+1= xj qj+1 = b2(xj+1+1/2)2 + a2(yj+1-1)2 -a2b2 = b2(xj+1/2)2 + a2(yj-2)2 -a2b2 =qj + a2(- 2yj+3) - Tính q1 khởi tạo q1 = f(xp+1/2,yp -1) = b2(xp+1/2)2 + a2(yp-1)2 -a2b2 #include <graphics.h> #include <conio.h>

#define ROUND(a) ((long)(a+0.5))

void plot(int xc, int yc, int x, int y, int color){ putpixel(xc+x, yc+y, color);

putpixel(xc-x, yc+y, color); putpixel(xc+x, yc-y, color); putpixel(xc-x, yc-y, color); }

long x, y, fx, fy, a2, b2, p; x = 0; y = b; a2 = a * a; //a2 b2 = b * b; // b2 fx = 0; fy = 2 * a2 * y; // 2a2y plot(xc, yc, x,y, color);

p = ROUND(b2-(a2*b)+(0.25*a)); // p=b2 - a2b + a2/4 while (fx < fy){ x++; fx += 2*b2; //2b2 if (p<0) p += b2*(2*x +3); // p=p + b2 (2x +3) else{ y--; p+= b2*(2*x +3) + a2*(-2*y +2); // p = p + b2(2x +3) + a2 (-2y +2) fy -= 2*a2; // 2a2 }

plot(xc, yc, x, y, color); }

p = ROUND(b2*(x+0.5)*(x+0.5) + a2*(y-1)*(y-1) - a2*b2);//b2(x+1/2)2+a2(y- 1)2 - a2b2

while (y>0){ y--;

fy -= 2*a2; // 2a2 if (p>=0)

p+=a2*(3 - 2*y); p =p + a2(3-2y) else{

x++;

fx += 2*b2; // 2b2

p += b2*(2*x+2) + a2*(-2*y +3); //p=p + b2(2x +2) +a2(-2y +3) }

plot(xc, yc, x, y, color); }

}

int gr_drive = DETECT, gr_mode; initgraph(&gr_drive, &gr_mode, ""); Mid_Ellipse(getmaxx() / 2, getmaxy() / 2, 150, 80, 4); getch(); closegraph(); }

2.4 Các thuật tốn tơ màu

Một vùng tơ bao gồm đường biên và vùng bên trong. Đường biên là một đường khép kín ví dụ như đa giác.

Các thuộc tính của vùng tơ bao gồm:

+ Thuộc tính của đường biên : chính là các thuộc tính như thuộc tính của đoạn thẳng.

+ Thuộc tính của vùng bên trong : bao gồm màu tơ và mẫu tơ. Hình 2.19

Các vùng tơ là một trong những đối tượng đồ họa cơ sở được hầu hết các cơng cụ lập trình đồ họa hỗ trợ. Cĩ hai dạng vùng tơ thường gặp đĩ là : tơ bằng một màu thuần nhất (solid fill) hay tơ theo một mẫu tơ (fill-pattern) nào đĩ.

Một vùng tơ thường được xác định bởi một đường khép kín nào đĩ gọi là đường biên. Một trong những dạng đường biên đơn giản nhất đĩ là đa giác.

Để tơ màu một vùng tơ, người ta thường chia làm hai cơng đoạn : cơng đoạn thứ nhất là xác định các điểm nào để tơ và cơng đoạn cịn lại đơn giản hơn đĩ là quyết định tơ các điểm đĩ bằng giá trị màu nào. Cơng đoạn thứ hai chỉ thực sự phức tạp nếu ta tơ theo một mẫu tơ nào đĩ khơng phải là tơ thuần một màu.

Cĩ hai cách tiếp cận chính để tơ màu một vùng tơ đối với thiết bị hiển thị dạng điểm đĩ là : tơ theo dịng quét (scan-line fill) và tơ dựa theo đường biên (boundary fill).

Phương pháp tơ theo dịng quét sẽ xác định các phần giao của các dịng quét kế tiếp nhau với đường biên của vùng tơ, sau đĩ sẽ tơ màu các điểm thuộc về phần giao này. Cách tiếp cận này thường được dùng để tơ màu các đa giác, đường trịn, ellipse, và một số đường cong đơn giản khác. Phương pháp tơ dựa theo đường biên sẽ bắt đầu từ một điểm ở bên trong vùng tơ và từ đĩ loang dần ra cho tới khi ta gặp các điểm biên. Cách tiếp cận này thường được dùng cho các vùng tơ cĩ dạng đường biên phức tạp hơn.

2.4.1 Thuật tốn tơ màu dựa theo dịng quét

Giả sử vùng tơ được cho bởi một đa giác N đỉnh : Pi(xi, yi),i=0,…,N-1. Đa giác này cĩ thể là đa giác lồi, đa giác lõm, và cả đa giác tự cắt, …

Hình sau minh họa ý tưởng chính của thuật tốn. Với mỗi dịng quét, ta sẽ xác định phần giao của đa giác và dịng quét rồi tơ màu các pixel thuộc đoạn giao đĩ. Để xác định các đoạn giao ta tiến hành việc tìm giao điểm của dịng quét với các cạnh của đa giác, sau đĩ các giao điểm này sẽ được sắp theo thứ tự tăng dần của hồnh độ giao điểm. Các đoạn giao chính là các đoạn thẳng được giới hạn bởi từng cặp giao điểm một, ví dụ như (0,1), (2,3), ….

Hình 2.20: Thuật tốn Scan-line với một dịng quét nào đĩ. Ta cĩ thể tĩm bắt các bước chính của thuật tốn :

- Tìm ytop, ybottom lần lượt là giá trị lớn nhất, nhỏ nhất của tập các tung độ của các đỉnh của đa giác đã cho:

ytop= max{yi,(xi,yi) ϵ P}, ybottom= min{yi, (xi, yi)ϵP}

- Ứng với mỗi dịng quét y=k, với k thay đổi từ ybottom đến ytop lặp :

+ Sắp xếp các hồnh độ giao điểm theo thứ tự tăng dần : x0, x1, …

+ Tơ màu các đoạn thẳng trên đường thẳng y=k lần lượt được giới hạn bởi các cặp

(x0,x1), (x2, x3), ….

Nếu chỉ dừng ở mức này và chuyển sang cài đặt, chúng ta sẽ gặp một số vấn đề sau :

+ Nhận xét rằng, ứng với mỗi dịng quét, khơng phải lúc nào tất cả các cạnh của đa giác cũng tham gia cắt dịng quét. Do đĩ để cải thiện tốc độ cần phải cĩ một cách nào đĩ để hạn chế được số cạnh cần tìm giao điểm ứng với mỗi dịng quét.

+ Việc tìm giao điểm của cạnh đa giác với mỗi dịng quét sẽ gặp các phép tốn phức tạp như nhân, chia, … trên số thực nếu ta dùng cách giải hệ phương trình tìm giao điểm. Điều này sẽ làm giảm tốc độ thuật tốn khi phải lặp đi lặp lại nhiều lần thao tác này khi dịng quét quét qua đa giác.

+ Nếu số giao điểm tìm được giữa các cạnh đa giác và dịng quét là lẻ thì việc nhĩm từng cặp giao điểm kế tiếp nhau để hình thành các đoạn tơ cĩ thể sẽ khơng chính xác. Điều này chỉ xảy ra khi dịng quét đi ngang qua các đỉnh của đa giác. Nếu tính số giao điểm tại đỉnh dịng quét đi ngang qua là hai thì cĩ thể sẽ cho kết quả tơ khơng chính xác như trong trường hợp của hình dưới. Ngồi ra, việc tìm giao điểm của dịng quét với các cạnh nằm ngang là một trường hợp đặc biệt cần phải cĩ cách xử lí thích hợp.

Để giải quyết các vấn đề trên, cần phải xây dựng một cấu trúc dữ liệu và thuật tốn thích hợp đối với chúng.

Danh sách các cạnh kích hoạt AET (Active Edge Table) Hình 2.21

Cạnh đa giác (EDGE)

Mỗi cạnh của đa giác được xây dựng từ hai đỉnh kề nhau Pi(xi,yi) và Pi+1(xi+1,yi+1) gồm các thơng tin sau :

ymin: giá trị tung độ nhỏ nhất trong 2 đỉnh của cạnh.

xIntersect : hồnh độ giao điểm của cạnh với dịng quét hiện đang xét. DxPerScan : giá trị 1/m (m là hệ số gĩc của cạnh).

deltaY : khoảng cách từ dịng quét hiện hành tới đỉnh ymax

Danh sách các cạnh kích hoạt AET

Danh sách này dùng để lưu các tập cạnh của đa giác cĩ thể cắt ứng với dịng quét hiện hành và tập các điểm giao tương ứng. Nĩ cĩ một số đặc điểm :

+ Các cạnh trong danh sách được sắp theo thứ tự tăng dần của các hồnh độ giao điểm để cĩ thể tơ màu các đoạn giao một cách dễ dàng.

+ Thay đổi ứng với mỗi dịng quét đang xét, do đĩ danh sách này sẽ đƣợc cập nhật liên tục trong quá trình thực hiện thuật tốn. Để hỗ trợ cho thao tác này, đầu tiên người ta sẽ tổ chức một danh sách chứa tồn bộ các cạnh của đa giác gọi là ET (Edge Table) được sắp theo thứ tự tăng dần của ymin, rồi sau mỗi lần dịng quét thay đổi sẽ di chuyển các

cạnh trong ET thỏa điều kiện sang AET.

+ Một dịng quét y = k chỉ cắt một cạnh của đa giác khi và chỉ khi: k>= ymin

delta Y>0

Chính vì vậy mà với cách tổ chức của ET (sắp theo thứ tự tăng dần của ymin ) điều kiện để chuyển các cạnh từ ET sang AET sẽ là k>= ymin; và điều kiện để loại một cạnh ra khỏi AET là deltaY <= 0

Cơng thức tìm giao điểm nhanh

Nếu gọi xk, xk+1 lần lượt là các hồnh độ giao điểm của một cạnh nào đĩ với các dịng quét y=k và y=k+1 , ta cĩ :

Xk+1-xk=(1/m)((k+1)-k)=1/m hay xk+1=xk+1/m

Như vậy nếu lưu hồnh độ giao điểm ứng với dịng quét trước lại, cùng với hệ số gĩc của cạnh, ta cĩ thể dễ dàng xác định hồnh độ giao điểm ứng với dịng quét kế tiếp một cách đơn giản theo cơng thức trên. Điều này rút gọn đáng kể thao tác tìm giao điểm của cạnh ứng với dịng quét. Chính vì vậy mà trong thơng tin của một cạnh chúng ta cĩ hai biến DxPerScan và xIntersect.

Hình 2.23

Giải quyết trường hợp dịng quét đi ngang qua đỉnh

Người ta đưa ra quy tắc sau để tính số giao điểm khi dịng quét đi ngang qua đỉnh :

+ Tính một giao điểm nếu chiều của hai cạnh kề của đỉnh đĩ cĩ xu hướng tăng hay giảm.

+ Tính hai giao điểm nếu chiều của hai cạnh kề của đỉnh đĩ cĩ xu hướng thay đổi, nghĩa là tăng-giảm hay giảm-tăng.

Khi cài đặt để khỏi phải xét điều kiện này cho phức tạp, khi xây dựng dữ liệu cho mỗi cạnh trước khi đưa vào ET, người ta sẽ xử lí các cạnh cĩ đỉnh tính hai giao điểm bằng cách loại đi một pixel trên cùng của một trong hai cạnh như hình dưới

Hình 2.25: Cạnh Pi-1Pi được lưu trong ET chỉ là Pi-1Pi*

Cài đặt minh họa sau sử dụng chung một danh sách EDGELIST cho cả ET và AET. AET được quản lí nhờ vào hai con trỏ FirstId và LastId.

Cài đặt minh họa thuật tốn tơ màu scan-line

#include <stdio.h> #include <conio.h> #include <stdlib.h> #include <graphics.h> #include <dos.h> #define MAXVERTEX 20 #define MAXEDGE 20 #define TRUE 1 #define FALSE 0 typedef struct { int x; int y; }POINT; typedef struct{ int NumVertex;

POINT aVertex[MAXVERTEX]; }POLYGON;

typedef struct { int NumPt;

float xPt[MAXEDGE]; }XINTERSECT;

typedef struct {

int yMin; // Gia tri y nho nhat cua 2 dinh

float xIntersect; // Hoanh do giao diem cua canh & dong quet

float dxPerScan; // Gia tri 1/m

int DeltaY; }EDGE;

typedef struct {

int NumEdge;

EDGE aEdge[MAXEDGE]; }EDGELIST;

/*

Dat 1 canh vao danh sach canh.

Cac canh duoc sap theo thu tu giam dan cua yMin (yMin la gia tri y lon nhat cua 2 dinh 1 canh)

Xu li luon truong hop dong quet di ngang qua dinh ma tai do chi tinh 1 diem giao

*/

void PutEdgeInList(EDGELIST &EdgeList, POINT p1, POINT p2, int NextY)

{

EDGE EdgeTmp;

EdgeTmp.dxPerScan = float(p2.x-p1.x)/(p2.y-p1.y); // 1/m

if(p1.y < p2.y) {

/*

Truong hop dong quet di ngang qua dinh la giao diem cua 2 canh co huong y cung tang

*/

if(p2.y < NextY) {

p2.y--;

p2.x -= EdgeTmp.dxPerScan; }

EdgeTmp.yMin = p1.y;

EdgeTmp.xIntersect= p1.x;

EdgeTmp.DeltaY = abs(p2.y-p1.y)+1; } // if

else { /*

Truong hop dong quet di ngang qua dinh la giao diem cua 2 canh co huong y cung giam

*/

if(p2.y > NextY) { p2.y++; p2.x+= EdgeTmp.dxPerScan; } EdgeTmp.yMin = p2.y; EdgeTmp.xIntersect= p2.x;

EdgeTmp.DeltaY = abs(p2.y-p1.y)+1; }//else

// xac dinh vi tri chen

int j = EdgeList.NumEdge;

while((j>0) && (EdgeList.aEdge[j-1].yMin>EdgeTmp.yMin)) {

EdgeList.aEdge[j] = EdgeList.aEdge[j-1];

j--; }

// tien hanh chen dinh moi vao canh EdgeList.NumEdge++;

EdgeList.aEdge[j] = EdgeTmp; } // PutEdgeInList

/*

Tim dinh ke tiep sao cho khong nam tren cung duong thang voi dinh dang xet

*/

int FindNextY(POLYGON P, int id) {

int j = (id+1)%P.NumVertex;

j++;

if(j<P.NumVertex) return (P.aVertex[j].y); return 0;

} // FindNextY

// Tao danh sach cac canh tu polygon da cho

void MakeSortedEdge(POLYGON P, EDGELIST &EdgeList, int &TopScan, int &BottomScan)

{

TopScan = BottomScan = P.aVertex[0].y;

EdgeList.NumEdge = 0;

for(int i=0; i<P.NumVertex; i++) {

// Truong hop canh khong phai la canh nam ngang if(P.aVertex[i].y !=

P.aVertex[i+1].y)

PutEdgeInList(EdgeList, P.aVertex[i], P.aVertex[i+1], FindNextY(P, i+1)); //else Xu li truong hop canh nam ngang

if(P.aVertex[i+1].y > TopScan)

TopScan = P.aVertex[i+1].y; }

BottomScan = EdgeList.aEdge[0].yMin; } //MakeSortedEdge

// Cap nhat lai hai con tro FirstId, LastId cho biet danhsach cac canh active

void UpdateActiveEdgeList(EDGELIST EdgeList, int yScan, int &FirstId, int &LastId)

{

while((FirstId<EdgeList.NumEdge-1) &&(EdgeList.aEdge[FirstId].DeltaY ==

0))

FirstId++;

while((LastId<EdgeList.NumEdge-1) &&(EdgeList.aEdge[LastId+1].yMin<=yScan))

LastId++;

} // UpdateActiveEdgeList

void SortOnX(XINTERSECT & aIntersectPt) {

{

int Min = i, t;

for(int j=i+1; j<aIntersectPt.NumPt; j++) if( aIntersectPt.xPt[j] < aIntersectPt.xPt[Min])

Min = j;

t = aIntersectPt.xPt[Min];

aIntersectPt.xPt[Min] = aIntersectPt.xPt[i];

aIntersectPt.xPt[i] = t; }

} // SortOnX

/*

Tim cac hoanh do giao diem cua cac canh cua da giac voi dong quet yScan. Sau khi tim cac hoanh do giao diem, ta sap xep lai theo chieu tang cua x

*/

void FindXIntersection(EDGELIST EdgeList, XINTERSECT &aIntersectPt, int FirstId, int LastId)

{

aIntersectPt.NumPt = 0;

for(int i=FirstId; i<=LastId; i++) {

if(EdgeList.aEdge[i].DeltaY>0) {

aIntersectPt.xPt[aIntersectPt.NumPt] = EdgeList.aEdge[i].xIntersect;

aIntersectPt.NumPt++; }

}

SortOnX(aIntersectPt); } //FindXIntersection

#define Round(x) int(x+0.5)

void FillLine(XINTERSECT aIntersectPt, int yScan) {

for(int i=0; i<aIntersectPt.NumPt; i+=2)

line(Round(aIntersectPt.xPt[i]), yScan, Round(aIntersectPt.xPt[i+1]), yScan); } // FillLine

void UpdateEdgeList(EDGELIST &EdgeList,int FirstId,int LastId) {

{

if(EdgeList.aEdge[i].DeltaY>0) {

EdgeList.aEdge[i].DeltaY--;

EdgeList.aEdge[i].xIntersect += EdgeList.aEdge[i].dxPerScan; }

}

} //FillLine

void ScanLineFill(POLYGON P) {

EDGELIST EdgeList;

XINTERSECT aIntersectPt;

int TopScan, BottomScan, FirstId, LastId;

MakeSortedEdge(P, EdgeList, TopScan, BottomScan);

FirstId = LastId = 0;

for(int i=BottomScan; i<=TopScan; i++) {

// Cap nhat lai danh sach cac canh active - tuc la cac canh cat dong quet i UpdateActiveEdgeList(EdgeList, i, FirstId, LastId);

// Tim cac hoanh do giao diem cua dong quet voi cac canh cua da giac va sap xep lai cac hoanh do giao diem truc tiep tren EdgeList

FindXIntersection(EdgeList, aIntersectPt, FirstId, LastId);

FillLine(aIntersectPt, i);

UpdateEdgeList(EdgeList, FirstId, LastId); }

} //ScanLineFill

2.4.2 Thuật tốn tơ màu dựa theo đường biên

Khác với thuật tốn tơ màu dựa theo dịng quét, đường biên của vùng tơ được xác

Một phần của tài liệu Giao trinh đồ họa máy tính 2012 (Trang 47)

Tải bản đầy đủ (DOC)

(189 trang)
w