1. 程式人生 > >計算機圖形常用演算法實現3 多邊形掃描轉換演算法-掃描線演算法

計算機圖形常用演算法實現3 多邊形掃描轉換演算法-掃描線演算法

執行環境 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);
            }
        }
    }
}