運行環境 vs2015 winform
其他環境只需要替換對應的畫點畫線算法即可。
這個算法其實很複雜的,實現起來需要有耐心,一步一步按照算法思路來寫代碼。
在算法中,我使用的是靜態鏈表(c#沒有指針-_-||)
下面的AET爲活性邊表,NET存儲新邊表
1.定義數組,變量
class AET//定義AET表,不論是AET還是NET表用的都是這個AET類,因爲裏面的內容都是一樣的
{
public float x;
public float deltax;
public int ymax;
public int next;
public AET()
{
x = -1;
deltax = -1;
ymax = -1;
next = -1;
}
}
private Point[] polygon = new Point[10000];//多邊形,依次連接數組中的各點以及最後一個點和第一個點形成的封閉多邊形
private int indexOfPolygon;//多邊形點的個數
//定義和初始化AET鏈表的空間
private AET[] AETSource;
void initAET()
{
AETSource = new AET[1000000];
for (int i = 0; i < 1000000; i++)
{
AETSource[i] = new AET();
}
AETSourceindex = 0;
}
2.初始化AET表和NET表
對於AET表的初始化,我們只需要簡單的將其賦值爲-1,對於NET的話,需要對掃描線進行遍歷,把所有低端的定點等於掃描線的值的邊加入NET表,因爲我們多邊形是用點數組來表示的,因此對於低端定點在不同位置需要有不同的寫法,此外,對於斜率無窮的線(水平線)需要另外特殊討論,此前我試過斜率無窮就不加入新邊表,會造成掃描線交點的不匹配,使得該行掃描線只填充了一半,寫好之後可以註釋掉這個判斷來試試看。
Graphics g = this.CreateGraphics();
Pen p = new Pen(Brushes.Red);
initAET();
//1.find max and min of y
int ymin = 0x3f3f3f, ymax = 0;
for (int i = 0; i < indexOfPolygon; i++)
{
if (polygon[i].Y > ymax)
ymax = polygon[i].Y;
if (polygon[i].Y < ymin)
ymin = polygon[i].Y;
}
int mAET = -1;//靜態鏈表只需要一個int表示其在AETSource中的位置即可
int[] mNET = new int[ymax - ymin];
for (int i = 0; i < ymax - ymin; i++)
{
mNET[i] = -1;
for (int j = 0; j < indexOfPolygon; j++)
{
if (Math.Min(polygon[j].Y, polygon[(j + 1) % indexOfPolygon].Y) == i + ymin )
{
if (mNET[i] == -1)//首個
mNET[i] = AETSourceindex;
else//非首個,需要找到最後一個
{
int index = mNET[i];
while (AETSource[index].next != -1)
index = AETSource[index].next;
AETSource[index].next = AETSourceindex;
}
if (polygon[j].Y == i + ymin)//j爲y比較小的值
{
AETSource[AETSourceindex].x = polygon[j].X;
if (polygon[j].Y != polygon[(j + 1) % indexOfPolygon].Y)
AETSource[AETSourceindex].deltax = (float)(polygon[(j + 1) % indexOfPolygon].X - polygon[j].X) / (float)(polygon[(j + 1) % indexOfPolygon].Y - polygon[j].Y);
else
AETSource[AETSourceindex].deltax = 0;
AETSource[AETSourceindex].ymax = polygon[(j + 1) % indexOfPolygon].Y;
AETSourceindex++;
}
else//j+1爲y比較小的值
{
AETSource[AETSourceindex].x = polygon[(j + 1) % indexOfPolygon].X;
if (polygon[j].Y != polygon[(j + 1) % indexOfPolygon].Y)
AETSource[AETSourceindex].deltax = (float)(polygon[(j + 1) % indexOfPolygon].X - polygon[j].X) / (float)(polygon[(j + 1) % indexOfPolygon].Y - polygon[j].Y);
else
AETSource[AETSourceindex].deltax = 0;
AETSource[AETSourceindex].ymax = polygon[j].Y;
AETSourceindex++;
}
}
}
}
3.算法執行
對於掃描線i,以下算法是通過NET表來更新AET表的算法
當NET[i]不爲-1的時候,也就是存在底端爲i的邊,需要把其從新邊表讀出,插入到AET中,爲了保證AET的有序性,我們使用了插入排序來對AET進行插入新元素。
int index = mNET[i];
while (index != -1)
{
if (mAET == -1)//如果AET爲空表,直接將其賦值到第一位
{
mAET = AETSourceindex;
AETSource[AETSourceindex].x = AETSource[index].x;
AETSource[AETSourceindex].ymax = AETSource[index].ymax;
AETSource[AETSourceindex].deltax = AETSource[index].deltax;
AETSource[AETSourceindex].next = -1;
index = AETSource[index].next;
AETSourceindex++;
continue;
}
else
{
//插入排序,有重合的k<0在前面
int index1 = mAET;
int j,k=0;//k保存前一個
//在j前面插入
for (j = index1; j != -1;j = AETSource[j].next)
{
if (AETSource[index].x == AETSource[j].x)
{
//插在右邊
if (AETSource[index].deltax > 0)
{
j = j = AETSource[j].next;
break;
}//插在左邊
else
break;
}
if (AETSource[index].x < AETSource[j].x)
{
break;
}
}
//是第一個
if (j == index1)
{
AETSource[AETSourceindex].x = AETSource[index].x;
AETSource[AETSourceindex].ymax = AETSource[index].ymax;
AETSource[AETSourceindex].deltax = AETSource[index].deltax;
AETSource[AETSourceindex].next = mAET;
mAET = AETSourceindex;
AETSourceindex++;
}
else
{
for (k = mAET; AETSource[k].next != j; k = AETSource[k].next) ;
AETSource[AETSourceindex].x = AETSource[index].x;
AETSource[AETSourceindex].ymax = AETSource[index].ymax;
AETSource[AETSourceindex].deltax = AETSource[index].deltax;
AETSource[AETSourceindex].next = AETSource[k].next;
AETSource[k].next = AETSourceindex;
AETSourceindex++;
}
index = AETSource[index].next;
}
}
4.刪除AET過期的邊,進行區間填充
如果掃描線已經大於那條邊的ymax之後,需要對其進行刪除,根據刪除的元素是不是第一個,刪除的方法有略微不同。
更新好了AET表之後,我們對生成的AET表對區間進行填充。
這裏涉及到重複點的去除問題,方法是該邊另外一個端點是否在重複端點的上面,最後進行兩兩填充。畫線可以使用前面所寫的任何畫線函數。
index = mAET;
//判斷交點位置
List<int> l = new List<int>();//存儲該掃描線交點
int prior=index;
for (int j = index; j != -1;j = AETSource[j].next)
{
if (AETSource[j].ymax < i+ymin)
{
if (j == index)
mAET = AETSource[mAET].next;
else
{
prior = index;
for (prior = index; AETSource[prior].next != j; prior = AETSource[prior].next) ;
AETSource[prior].next = AETSource[j].next;
}
}
else
l.Add((int)(AETSource[j].x + 0.5));
AETSource[j].x += AETSource[j].deltax;
}
//l.Sort();
//更新l把重複點去掉
for (int j = 0; j < l.Count-1;j++)
if (l[j] == l[j + 1])
{
for (int q = 0; q < indexOfPolygon; q++)
if (polygon[q].X == l[j] && polygon[q].Y == i + ymin)
{
if (polygon[(q + 1) % indexOfPolygon].Y >= i + ymin)
l.RemoveAt(j);
if (polygon[(q - 1 + indexOfPolygon) % indexOfPolygon].Y >= i + ymin)
l.RemoveAt(j);
break;
}
}
for (int j = 0; j < l.Count; j += 2)
{
if (j + 1 < l.Count)
{
Point p1 = new Point(l[j], i+ymin);
Point p2 = new Point(l[j + 1], i+ymin);
g.DrawLine(p, p1, p2);
}
}
至此,掃描線算法已經實現了,示意圖及完整代碼如下:
void polyFill()
{
Graphics g = this.CreateGraphics();
Pen p = new Pen(Brushes.Red);
initAET();
//1.find max and min of y
int ymin = 0x3f3f3f, ymax = 0;
for (int i = 0; i < indexOfPolygon; i++)
{
if (polygon[i].Y > ymax)
ymax = polygon[i].Y;
if (polygon[i].Y < ymin)
ymin = polygon[i].Y;
}
//初始化mAET和mNET
int mAET = -1;
int[] mNET = new int[ymax - ymin];
for (int i = 0; i < ymax - ymin; i++)
{
mNET[i] = -1;
for (int j = 0; j < indexOfPolygon; j++)
{
if (Math.Min(polygon[j].Y, polygon[(j + 1) % indexOfPolygon].Y) == i + ymin )
{
if (mNET[i] == -1)
mNET[i] = AETSourceindex;
else
{
int index = mNET[i];
while (AETSource[index].next != -1)
index = AETSource[index].next;
AETSource[index].next = AETSourceindex;
}
if (polygon[j].Y == i + ymin)
{
AETSource[AETSourceindex].x = polygon[j].X;
if (polygon[j].Y != polygon[(j + 1) % indexOfPolygon].Y)
AETSource[AETSourceindex].deltax = (float)(polygon[(j + 1) % indexOfPolygon].X - polygon[j].X) / (float)(polygon[(j + 1) % indexOfPolygon].Y - polygon[j].Y);
else
AETSource[AETSourceindex].deltax = 0;
AETSource[AETSourceindex].ymax = polygon[(j + 1) % indexOfPolygon].Y;
AETSourceindex++;
}
else
{
AETSource[AETSourceindex].x = polygon[(j + 1) % indexOfPolygon].X;
if (polygon[j].Y != polygon[(j + 1) % indexOfPolygon].Y)
AETSource[AETSourceindex].deltax = (float)(polygon[(j + 1) % indexOfPolygon].X - polygon[j].X) / (float)(polygon[(j + 1) % indexOfPolygon].Y - polygon[j].Y);
else
AETSource[AETSourceindex].deltax = 0;
AETSource[AETSourceindex].ymax = polygon[j].Y;
AETSourceindex++;
}
}
}
}
//執行算法
for (int i = 0; i < ymax - ymin; i++)
{
//把NET表插入到AET表
int index = mNET[i];
while (index != -1)
{
if (mAET == -1)
{
mAET = AETSourceindex;
AETSource[AETSourceindex].x = AETSource[index].x;
AETSource[AETSourceindex].ymax = AETSource[index].ymax;
AETSource[AETSourceindex].deltax = AETSource[index].deltax;
AETSource[AETSourceindex].next = -1;
index = AETSource[index].next;
AETSourceindex++;
continue;
}
else
{
//插入排序,有重合的k<0在前面
int index1 = mAET;
int j,k=0;//k保存前一個
//在j前面插入
for (j = index1; j != -1;j = AETSource[j].next)
{
if (AETSource[index].x == AETSource[j].x)
{
//插在右邊
if (AETSource[index].deltax > 0)
{
j = j = AETSource[j].next;
break;
}//插在左邊
else
break;
}
if (AETSource[index].x < AETSource[j].x)
{
break;
}
}
//是第一個
if (j == index1)
{
AETSource[AETSourceindex].x = AETSource[index].x;
AETSource[AETSourceindex].ymax = AETSource[index].ymax;
AETSource[AETSourceindex].deltax = AETSource[index].deltax;
AETSource[AETSourceindex].next = mAET;
mAET = AETSourceindex;
AETSourceindex++;
}
else
{
for (k = mAET; AETSource[k].next != j; k = AETSource[k].next) ;
AETSource[AETSourceindex].x = AETSource[index].x;
AETSource[AETSourceindex].ymax = AETSource[index].ymax;
AETSource[AETSourceindex].deltax = AETSource[index].deltax;
AETSource[AETSourceindex].next = AETSource[k].next;
AETSource[k].next = AETSourceindex;
AETSourceindex++;
}
index = AETSource[index].next;
}
}
//遍歷AET,畫區間
index = mAET;
//判斷交點位置
List<int> l = new List<int>();//存儲該掃描線交點
int prior=index;
for (int j = index; j != -1;j = AETSource[j].next)
{
if (AETSource[j].ymax < i+ymin)
{
if (j == index)
mAET = AETSource[mAET].next;
else
{
prior = index;
for (prior = index; AETSource[prior].next != j; prior = AETSource[prior].next) ;
AETSource[prior].next = AETSource[j].next;
}
}
else
l.Add((int)(AETSource[j].x + 0.5));
AETSource[j].x += AETSource[j].deltax;
}
//l.Sort();
//更新l把重複點去掉
for (int j = 0; j < l.Count-1;j++)
if (l[j] == l[j + 1])
{
for (int q = 0; q < indexOfPolygon; q++)
if (polygon[q].X == l[j] && polygon[q].Y == i + ymin)
{
if (polygon[(q + 1) % indexOfPolygon].Y >= i + ymin)
l.RemoveAt(j);
if (polygon[(q - 1 + indexOfPolygon) % indexOfPolygon].Y >= i + ymin)
l.RemoveAt(j);
break;
}
}
for (int j = 0; j < l.Count; j += 2)
{
if (j + 1 < l.Count)
{
Point p1 = new Point(l[j], i+ymin);
Point p2 = new Point(l[j + 1], i+ymin);
g.DrawLine(p, p1, p2);
}
}
}
}