计算机图形学常用算法实现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 l = new List();//存储该扫描线交点
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);
    }
}

至此,扫描线算法已经实现了,示意图及完整代码如下:
计算机图形学常用算法实现3 多边形扫描转换算法-扫描线算法_第1张图片

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 l = new List();//存储该扫描线交点
        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);
            }
        }
    }
}

你可能感兴趣的:(图形学基础知识)