1. 程式人生 > >計算機圖形學常用演算法實現4 多邊形掃描轉換演算法-邊界標誌演算法

計算機圖形學常用演算法實現4 多邊形掃描轉換演算法-邊界標誌演算法

程式碼是在winform中執行的。
看書上這個演算法寫起來輕描淡寫的,實際上實現起來還是有很多難點的,難點如下:
1.無法判斷經過某個點的時候是不是應該變號。
2.掃描演算法畫直線的時候,可能同一行有多個點相鄰的情況,如果遇到這樣的點就變號結果會出現錯誤。
3.兩條相鄰邊的路徑可能經過同一個點,尤其是dy>>dx的時候,不加以判斷還是會出錯。
解決方法如下:
1.借用掃描線演算法的思路,如果經過某個點的兩條邊的另外一個定點分別位於該點兩邊,則保留該定點,否則捨去該定點。
2.掃描線演算法進行掃描的時候,如果存在某處x++,y不變時,填充顏色,但是不標記該點,只有y變化才能標記,這樣也就是讓同一條直線每一行只能有一個點被標記。
3.不用bool而是改用int作為標記陣列,每次標記進行++,如果某點的值為2,表示兩條直線都經過了該點,遇到這裡的時候變兩次符號(也就是不變號)。

經過以上改良之後,結果雖然是實現了,但是執行速度很明顯感覺得到沒有掃描線演算法好~
程式碼如下:
1.變數宣告

int xmin, ymin, xmax, ymax;
private Point[] polygon = new Point[10000];
private int indexOfPolygon=0;
private int[,] map = new int[1024, 768];//作為標記用
void initMap()
{
   for (int i = 0; i < 1024; i++)
       for (int j = 0; j < 768; j++)
           map[i, j] = 0;
}

2.修改後的DDA畫線

void DDADrawLine(Point p1, Point p2)
{
    Point pp = new Point();
    if (p1.X > p2.X)
    {
        pp = p1;
        p1 = p2;
        p2 = pp;
    }
    Graphics g = this.CreateGraphics();
    Brush p = new SolidBrush(Color.Red);
    int dx = p2.X - p1.X;
    int dy = p2.Y - p1.Y;
    float x, y;
    float k;
    bool key;
    if (Math.Abs(dx) >= Math.Abs(dy))
    {    
        k = (float)dy / dx;
        x = p1.X;
        y = p1.Y;
        key = true;
        for (int i = 0; i <= Math.Abs(dx); i++)
        {
            if (key)//key的作用是確保一條直線同一行只標記一個
            {
                map[(int)x, (int)(y + 0.5)] ++;
                key = !key;
            }
            g.FillRectangle(p, new RectangleF(x, (int)(y + 0.5), 1, 1));                  
            if ((int)(y + 0.5) != (int)(y + k + 0.5)&& (int)(y + k + 0.5)!=p2.Y)
                key = true;
            y += k;
            x++;
        }
    }
    else if (Math.Abs(dx) < Math.Abs(dy))
    {
        k = (float)dx / dy;
        x = p1.X;
        y = p1.Y;
        for (int i = 0; i <= Math.Abs(dy); i++)
        {//dx<dy的時候不需要key標記,因為不會出現上述情況
            if((int)y!=p2.Y)
                map[(int)(x + 0.5), (int)y] ++;
            g.FillRectangle(p, new RectangleF((int)(x + 0.5), y, 1, 1));
            if (p1.Y < p2.Y)
            {
                y++;
                x += k;
            }
            else
            {
                y--;
                x -= k;
            }
        }
    }
}

3.掃描轉換

void edgeMarkFill()
 {
 	   for (int i = 0; i < indexOfPolygon; i++)
        {
            if (xmin > polygon[i].X)
                xmin = polygon[i].X;
            if (ymin > polygon[i].Y)
                ymin = polygon[i].Y;
            if (xmax < polygon[i].X)
                xmax = polygon[i].X;
            if (ymax < polygon[i].Y)
                ymax = polygon[i].Y;
        }
     for (int i = 0; i < indexOfPolygon; i++)//解決方案1的實現
     {
         if (polygon[(i - 1 + indexOfPolygon) % indexOfPolygon].Y <= polygon[i].Y && polygon[(i + 1) % indexOfPolygon].Y <= polygon[i].Y || polygon[(i - 1 + indexOfPolygon) % indexOfPolygon].Y >= polygon[i].Y && polygon[(i + 1) % indexOfPolygon].Y >= polygon[i].Y)
             map[polygon[i].X, polygon[i].Y] = 0;
         else
             map[polygon[i].X, polygon[i].Y] = 1;
         if(polygon[i].Y==polygon[i+1].Y)//特殊處理直線段,直線只保留一個點
             map[polygon[i].X, polygon[i].Y] = 1;
     }
     bool inSide;
     Graphics g = this.CreateGraphics();
     Brush p = new SolidBrush(Color.Red);
     for (int i = ymin; i <=ymax; i++)
     {
         inSide = false;
         for (int j=xmin; j <= xmax; j++)
         {
             if (map[j, i]%2==1)//標記偶數次也就是沒標記
                 inSide = !inSide;
             if(inSide)
                 g.FillRectangle(p, new RectangleF(j, i, 1, 1));
         }
     }
 }

效果截圖:
在這裡插入圖片描述

完整程式碼:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace cgdemo2
{
    public partial class Form1 : Form
    {
        private Point[] polygon = new Point[10000];
        private int indexOfPolygon=0;
        private int[,] map = new int[1024, 768];
        bool isClick=false;
        int xmin, ymin, xmax, ymax;
        public Form1()
        {

            InitializeComponent();
        }
        void initMap()
        {

            for (int i = 0; i < 1024; i++)
                for (int j = 0; j < 768; j++)
                    map[i, j] = 0;
        }
        void DDADrawLine(Point p1, Point p2)
        {
            Point pp = new Point();
            if (p1.X > p2.X)
            {
                pp = p1;
                p1 = p2;
                p2 = pp;
            }
            Graphics g = this.CreateGraphics();
            Brush p = new SolidBrush(Color.Red);
            int dx = p2.X - p1.X;
            int dy = p2.Y - p1.Y;
            float x, y;
            float k;
            bool key;
            if (Math.Abs(dx) >= Math.Abs(dy))
            {    
                k = (float)dy / dx;
                x = p1.X;
                y = p1.Y;
                key = true;
                for (int i = 0; i <= Math.Abs(dx); i++)
                {
                    if (key)
                    {
                        map[(int)x, (int)(y + 0.5)] ++;
                        key = !key;
                    }
                    g.FillRectangle(p, new RectangleF(x, (int)(y + 0.5), 1, 1));                  
                    if ((int)(y + 0.5) != (int)(y + k + 0.5)&& (int)(y + k + 0.5)!=p2.Y)
                        key = true;
                    y += k;
                    x++;
                }
            }
            else if (Math.Abs(dx) < Math.Abs(dy))
            {
                k = (float)dx / dy;
                x = p1.X;
                y = p1.Y;
                for (int i = 0; i <= Math.Abs(dy); i++)
                {
                    if((int)y!=p2.Y)
                        map[(int)(x + 0.5), (int)y] ++;
                    g.FillRectangle(p, new RectangleF((int)(x + 0.5), y, 1, 1));
                    if (p1.Y < p2.Y)
                    {
                        y++;
                        x += k;
                    }
                    else
                    {
                        y--;
                        x -= k;
                    }
                }
            }
        }
        void edgeMarkFill()
        {
            for (int i = 0; i < indexOfPolygon; i++)
            {
                if (polygon[(i - 1 + indexOfPolygon) % indexOfPolygon].Y <= polygon[i].Y && polygon[(i + 1) % indexOfPolygon].Y <= polygon[i].Y || polygon[(i - 1 + indexOfPolygon) % indexOfPolygon].Y >= polygon[i].Y && polygon[(i + 1) % indexOfPolygon].Y >= polygon[i].Y)
                    map[polygon[i].X, polygon[i].Y] = 0;
                else
                    map[polygon[i].X, polygon[i].Y] = 1;
                if(polygon[i].Y==polygon[i+1].Y)
                    map[polygon[i].X, polygon[i].Y] = 1;
            }
            bool inSide;
            Graphics g = this.CreateGraphics();
            Brush p = new SolidBrush(Color.Red);
            for (int i = ymin; i <=ymax; i++)
            {
                inSide = false;
                for (int j=xmin; j <= xmax; j++)
                {
                    if (map[j, i]%2==1)
                        inSide = !inSide;
                    if(inSide)
                        g.FillRectangle(p, new RectangleF(j, i, 1, 1));
                }
            }
        }
        void debug()
        {
            Point p1 = new Point(100, 200);
            Point p2 = new Point(200, 200);
            Point p3 = new Point(200, 300);
            Point p4 = new Point(300, 200);
            Point p5 = new Point(200, 100);
            indexOfPolygon = 5;
            polygon[0] = p1;
            polygon[1] = p2;
            polygon[2] = p3;
            polygon[3] = p4;
            polygon[4] = p5;
            initMap();
            //BresenhamDrawLine(p2, p3);
            //DDADrawLine(p2, p3);
            xmin = 0x3f3f3f;
            ymin = 0x3f3f3f;
            xmax = -1;
            ymax = -1;
            for (int i = 0; i < indexOfPolygon; i++)
                DDADrawLine(polygon[i], polygon[(i + 1) % indexOfPolygon]);
            for (int i = 0; i < indexOfPolygon; i++)
            {
                if (xmin > polygon[i].X)
                    xmin = polygon[i].X;
                if (ymin > polygon[i].Y)
                    ymin = polygon[i].Y;
                if (xmax < polygon[i].X)
                    xmax = polygon[i].X;
                if (ymax < polygon[i].Y)
                    ymax = polygon[i].Y;
            }
            edgeMarkFill();
        }
        private void Form1_MouseClick(object sender, MouseEventArgs e)
        {
            //debug();
            Graphics g = this.CreateGraphics();
            Pen p = new Pen(Brushes.Black);
            RectangleF rec = new RectangleF(e.Location.X - 1, e.Location.Y - 1, 3, 3);
            g.DrawEllipse(p, rec);
            if (isClick == false)
            {
                xmin = 0x3f3f3f;
                ymin = 0x3f3f3f;
                xmax = -1;
                ymax = -1;
                indexOfPolygon = 0;
                polygon[indexOfPolygon] = e.Location;
                if (xmin > polygon[indexOfPolygon].X)
                    xmin = polygon[indexOfPolygon].X;
                if (ymin > polygon[indexOfPolygon].Y)
                    ymin = polygon[indexOfPolygon].Y;
                if (xmax < polygon[indexOfPolygon].X)
                    xmax = polygon[indexOfPolygon].X;
                if (ymax < polygon[indexOfPolygon].Y)
                    ymax = polygon[indexOfPolygon].Y;
                indexOfPolygon++;
                isClick = true;
            }
            else
            {
                if (Math.Sqrt((e.Location.X - polygon[0].X) * (e.Location.X - polygon[0].X) + (e.Location.Y - polygon[0].Y) * (e.Location.Y - polygon[0].Y)) < 8)
                {
                    initMap();
                    for (int i = 0; i < indexOfPolygon; i++)
                        DDADrawLine(polygon[i], polygon[(i + 1) % indexOfPolygon]);
                    edgeMarkFill();
                    isClick = false;
                }
                else
                {
                    polygon[indexOfPolygon] = e.Location;
                    if (xmin > polygon[indexOfPolygon].X)
                        xmin = polygon[indexOfPolygon].X;
                    if (ymin > polygon[indexOfPolygon].Y)
                        ymin = polygon[indexOfPolygon].Y;
                    if (xmax < polygon[indexOfPolygon].X)
                        xmax = polygon[indexOfPolygon].X;
                    if (ymax < polygon[indexOfPolygon].Y)
                        ymax = polygon[indexOfPolygon].Y;
                    indexOfPolygon++;
                }
            }
        }
    }
}