首页 > 代码库 > 计算机图形学 有效边表填充算法(6)

计算机图形学 有效边表填充算法(6)

作者:卿笃军

原文地址:http://blog.csdn.net/qingdujun/article/details/40154077


本文通过一个完整的实例,展示多边形有效边表填充算法。

1)创建CAET类

头文件:AET.h

// AET.h: interface for the CAET class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_AET_H__A7CAD03F_C111_4B1F_90F2_88E39668C107__INCLUDED_)
#define AFX_AET_H__A7CAD03F_C111_4B1F_90F2_88E39668C107__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

//有效边表数据结构定义如下:
class CAET  
{
public:
	CAET();
	virtual ~CAET();
	double x;
	int yMax;
	double k;       //代替1/k
	CAET *pNext;
};

#endif // !defined(AFX_AET_H__A7CAD03F_C111_4B1F_90F2_88E39668C107__INCLUDED_)
实现文件:AET.cpp

// AET.cpp: implementation of the CAET class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "FillPolygon.h"
#include "AET.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CAET::CAET()
{

}

CAET::~CAET()
{

}
2)创建CBucket类

头文件:Bucket.h

// Bucket.h: interface for the CBucket class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_BUCKET_H__F1ED22A9_3004_4C3A_8710_AD025BEC063C__INCLUDED_)
#define AFX_BUCKET_H__F1ED22A9_3004_4C3A_8710_AD025BEC063C__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "aet.h"

//桶的数据结构定义如下:
class CBucket  
{
public:
	CBucket();
	virtual ~CBucket();
	int ScanLine;      //扫描线
    CAET *p;           //桶上的边表指针
    CBucket *pNext;
};

#endif // !defined(AFX_BUCKET_H__F1ED22A9_3004_4C3A_8710_AD025BEC063C__INCLUDED_)
实现文件:Bucket.cpp

// Bucket.cpp: implementation of the CBucket class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "FillPolygon.h"
#include "Bucket.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CBucket::CBucket()
{

}

CBucket::~CBucket()
{

}
3)创建CFillPolygon类

头文件:FillPolygon.h

// FillPolygon.h: interface for the CFillPolygon class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_FILLPOLYGON_H__18C16134_AA95_46FE_B223_DD5CF4ECAEC0__INCLUDED_)
#define AFX_FILLPOLYGON_H__18C16134_AA95_46FE_B223_DD5CF4ECAEC0__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#define Number 7    //闭合多边形顶点个数,顶点存放在二维数组Point[Number]中
#include "AET.h"
#include "Bucket.h"
//有效边表法多边形填充
class CFillPolygon  
{
public:
	CFillPolygon();
	virtual ~CFillPolygon();
	
	void CreatBucket();          //建立桶结点
	void CreatEdge();            //构造边表
	void InsertEdge(CAET *edge); //插入临时边表
	CAET* EdgeSort();             //对边表进行排序
	void PolygonFill(CClientDC *dc, COLORREF rgb);          //多边形填充
	void ShowPolygon(CDC *pDC);  //将7个顶点连接起来
protected:
	CPoint Point[Number];        //多边形顶点
	COLORREF GetColor;           //调色板
	CBucket *HeadB, *CurrentB;   //桶的头结点和当前结点
	CAET Edge[Number], *HeadE, *CurrentE, *T1, *T2;//有效边表的结点
};

#endif // !defined(AFX_FILLPOLYGON_H__18C16134_AA95_46FE_B223_DD5CF4ECAEC0__INCLUDED_)
实现文件:FillPolygon.cpp

// FillPolygon.cpp: implementation of the CFillPolygon class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "EdgeTable.h"
#include "FillPolygon.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

#define ROUND(a) int(a+0.5)//四舍五入

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CFillPolygon::CFillPolygon()
{
	//设置多边形的7个顶点
    Point[0]=CPoint(550,400);//P0
    Point[1]=CPoint(350,600);//P1
    Point[2]=CPoint(250,350);//P2
    Point[3]=CPoint(350,50); //P3
    Point[4]=CPoint(500,250);//P4
    Point[5]=CPoint(600,50); //P5
    Point[6]=CPoint(800,450);//P6
}

CFillPolygon::~CFillPolygon()
{
	
}
void CFillPolygon::CreatBucket()//建立空桶结点,尚未连接边表
{
    int i,ScanMin,ScanMax;            
    ScanMax=ScanMin=Point[0].y;
    for(i=1;i<Number;i++)           //确定扫描线的最小值和最大值
    {
        if(Point[i].y<ScanMin)
        {
            ScanMin=Point[i].y;     //扫描线的最小值         
        }
        if(Point[i].y>ScanMax)
        {
            ScanMax=Point[i].y;     //扫描线的最大值
        }
    }  
    for(i=ScanMin;i<=ScanMax;i++)   //建立桶结点(和创建单链表类似)
    {
        if(ScanMin==i)              //桶头结点
        {
            HeadB=new CBucket;      //建立桶的头结点
            CurrentB=HeadB;         //CurrentB为Bucket当前结点指针
            CurrentB->ScanLine=ScanMin;
        }
        else                        //建立桶的其它结点
        {
            CurrentB->pNext=new CBucket;//新建一个桶结点
            CurrentB=CurrentB->pNext;//使CurrentB指向新建的桶结点
            CurrentB->ScanLine=i;              
        }
		CurrentB->p=NULL;           //没有连接边链表
		CurrentB->pNext=NULL;
    }
}
 
void CFillPolygon::CreatEdge()//如[P121图4-28],构造边表(边表,相当于该边“最低处”的有效边表),同时连接桶表
{
    for(int i=0;i<Number;i++)     //访问每个顶点
    {
        CurrentB=HeadB;           //从桶链表的头结点开始循环
        int j=i+1;                //边的第二个顶点,Point[i]和Point[j]构成边
        if(j==Number) j=0;        //保证多边形的闭合
        if(Point[j].y>Point[i].y) //终点比起点高
        {
            while(CurrentB->ScanLine!=Point[i].y)//在桶内寻找该边的yMin
            {
                CurrentB=CurrentB->pNext;//移到下一个桶结点
            }
            Edge[i].x=Point[i].x;           //扫描线与边的x交点
            Edge[i].yMax=Point[j].y;        //线段最高点y值
            Edge[i].k=double((Point[j].x-Point[i].x))/(Point[j].y-Point[i].y);//代表1/k       
            Edge[i].pNext=NULL;
        }
        if(Point[j].y<Point[i].y)//终点比起点低
        {
            while(CurrentB->ScanLine!=Point[j].y)
            {
                CurrentB=CurrentB->pNext;               
            }
            Edge[i].x=Point[j].x;
            Edge[i].yMax=Point[i].y;
            Edge[i].k=double((Point[i].x-Point[j].x))/(Point[i].y-Point[j].y);        
            Edge[i].pNext=NULL;
        }
		CurrentE=CurrentB->p; //获得桶上链接边表的地址
		if(CurrentB->p==NULL) //当前桶结点上没有链接边结点
		{
			CurrentE=&Edge[i];   //赋边的起始地址
			CurrentB->p=CurrentE;//第一个边结点直接连接到对应的桶中
		}
		else
		{
			while(CurrentE->pNext!=NULL)//如果当前边已连有边结点
			{
				CurrentE=CurrentE->pNext;//移动指针到当前边的最后一个边结点               
			}
			CurrentE->pNext=&Edge[i];//把当前边接上去
		}
    }
    CurrentB=NULL;
    CurrentE=NULL; 
}
 
void CFillPolygon::InsertEdge(CAET *edge)//插入临时边表
{
    T1=HeadE;
    if(T1==NULL)                //边表为空,将边表置为TempEdge
    {
        T1=edge;
        HeadE=T1;
    }
    else
    {
        while(T1->pNext!=NULL) //边表不为空,将TempEdge连在该边之后
        {
            T1=T1->pNext;
        }
        T1->pNext=edge;
    }
}
 
CAET* CFillPolygon::EdgeSort()//对边表进行排序,(x|Ymin,表示Ymin处的x值)
{  
    T1=HeadE;
    if(T1==NULL)
    {
        return NULL;
    }
    if(T1->pNext==NULL)         //如果该边表没有再连边表
    {
        return NULL;                 //桶结点只有一条边,不需要排序
    }
    else
    {
        if(T1->pNext->x<T1->x || (T1->pNext->x==T1->x && T1->pNext->k<T1->k))  //边表按x,1/k值排序
        {
            T2=T1->pNext;
            T1->pNext=T2->pNext;
            T2->pNext=T1;
            HeadE=T2;
        }
        T2=HeadE;
        T1=HeadE->pNext;    
        while(T1->pNext!=NULL) //继续两两比较相连的边表的x,1/k值,进行排序
        {
            if(T1->pNext->x<T1->x || (T1->pNext->x==T1->x && T1->pNext->k<T1->k))
            {
                T2->pNext=T1->pNext;
                T1->pNext=T1->pNext->pNext;
                T2->pNext->pNext=T1;
                T2=T2->pNext;
            }
            else
            {
                T2=T1;
                T1=T1->pNext;
            }
        }
    }
	return HeadE;
}

void CFillPolygon::ShowPolygon(CDC *pDC)  //将7个顶点连接起来
{
	pDC->Polygon(Point,7);//绘制多边形
    //输出多边形的顶点编号
    pDC->TextOut(550,410,"P0");
    pDC->TextOut(350,600,"P1");
    pDC->TextOut(230,340,"P2");
    pDC->TextOut(350,30,"P3");
    pDC->TextOut(490,220,"P4");
    pDC->TextOut(600,30,"P5");
    pDC->TextOut(805,450,"P6");
}

void CFillPolygon::PolygonFill(CClientDC *dc, COLORREF rgb)//多边形填充
{
	CAET *DelE=NULL;
	CBucket *DelB=NULL;
    HeadE=NULL;
    for(CurrentB=HeadB;CurrentB!=NULL;CurrentB=CurrentB->pNext)//访问所有桶结点
    {
        for(CurrentE=CurrentB->p;CurrentE!=NULL;CurrentE=CurrentE->pNext)//访问桶中排序前的边结点         
        {
			//创建一个临时边结点
            CAET *TempEdge=new CAET;
            TempEdge->x=CurrentE->x;
            TempEdge->yMax=CurrentE->yMax;
            TempEdge->k=CurrentE->k;
            TempEdge->pNext=NULL;           
            InsertEdge(TempEdge);   //将该边插入临时CAET表
        }
        T1=EdgeSort();              //使得边表按照x,1/k递增的顺序存放(由于CreatEdge创建时是无序的)    
        if(T1==NULL)
        {
            return;
        }
		//根据ymax抛弃扫描完的边结点(局部最高点和扫描线重合,则抛弃)
        while(CurrentB->ScanLine>=T1->yMax)//放弃该结点,CAET表指针后移(下闭上开)
        {
			DelE=T1;
            T1=T1->pNext;
            HeadE=T1;
			delete DelE;             //释放空间
			DelE=NULL;
            if(HeadE==NULL)          //T1==NULL
            {
                return;
            }
        }
        if(T1->pNext!=NULL)
        {
            T2=T1;
            T1=T2->pNext;
        }
        while(T1!=NULL)
        {
            if(CurrentB->ScanLine>=T1->yMax)//跳过一个结点
            {
                T2->pNext=T1->pNext;
                T1->pNext=NULL;
                T1=T2->pNext;
            }
            else
            {
                T2=T1;
                T1=T2->pNext;
            }
        }
        BOOL isfill=false;                 //设置一个BOOL变量isfill,初始值为假
        double xb,xe;                      //扫描线的起点和终点
        for(T1=HeadE;T1!=NULL;T1=T1->pNext)//填充扫描线和多边形相交的区间
        {
            if(isfill==false)
            {
                xb=T1->x;
                isfill=true;             //每访问一个结点,把isfill值取反一次
            }
            else                         //如果isfill值为真,则填充从当前结点的x值开始到下一结点的x值结束的区间
            {
                xe=T1->x-1;              //左闭右开
                for(double x=xb;x<=xe;x++)
                    dc->SetPixel(ROUND(x),CurrentB->ScanLine,rgb);//填充语句
                //Sleep(1);              //延时1ms,提高填充过程的可视性
                isfill=false;
            }      
        }
        for(T1=HeadE;T1!=NULL;T1=T1->pNext)//边连贯性
        {
            T1->x=T1->x+T1->k;           //x=x+1/k            
        } 
    }
	//释放边表空间
	while (HeadE!=NULL)
	{
		DelE=HeadE;
		HeadE=HeadE->pNext;
		delete DelE;
	}
	DelE=NULL;
	//释放桶表空间
	while (HeadB!=NULL)
	{
		DelB=HeadB;
		HeadB=HeadB->pNext;
		delete DelB;
	}
	DelB=NULL;
}
4)工程CEdgeTableView类
头文件:EdgeTableView.h

// EdgeTableView.h : interface of the CEdgeTableView class
//
/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EDGETABLEVIEW_H__6908D229_1F3E_40E1_BB66_41E93BA625D9__INCLUDED_)
#define AFX_EDGETABLEVIEW_H__6908D229_1F3E_40E1_BB66_41E93BA625D9__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include "FillPolygon.h"

class CEdgeTableView : public CView
{
protected: // create from serialization only
	CEdgeTableView();
	DECLARE_DYNCREATE(CEdgeTableView)

// Attributes
public:
	CEdgeTableDoc* GetDocument();

// Operations
public:
private:
	CFillPolygon *pFill;   //有效边表法多边形填充
	COLORREF rgb;          //填充颜色
// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CEdgeTableView)
	public:
	virtual void OnDraw(CDC* pDC);  // overridden to draw this view
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
	protected:
	//}}AFX_VIRTUAL

// Implementation
public:
	virtual ~CEdgeTableView();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
	//{{AFX_MSG(CEdgeTableView)
	afx_msg void OnMenuFill();
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in EdgeTableView.cpp
inline CEdgeTableDoc* CEdgeTableView::GetDocument()
   { return (CEdgeTableDoc*)m_pDocument; }
#endif

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EDGETABLEVIEW_H__6908D229_1F3E_40E1_BB66_41E93BA625D9__INCLUDED_)
实现文件:EdgeTableView.cpp

// EdgeTableView.cpp : implementation of the CEdgeTableView class
//

#include "stdafx.h"
#include "EdgeTable.h"

#include "EdgeTableDoc.h"
#include "EdgeTableView.h"

#include "FillPolygon.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CEdgeTableView

IMPLEMENT_DYNCREATE(CEdgeTableView, CView)

BEGIN_MESSAGE_MAP(CEdgeTableView, CView)
	//{{AFX_MSG_MAP(CEdgeTableView)
	ON_COMMAND(IDM_MENU_FILL, OnMenuFill)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CEdgeTableView construction/destruction

CEdgeTableView::CEdgeTableView()
{
	pFill = new CFillPolygon;  //初始化一个对象
}

CEdgeTableView::~CEdgeTableView()
{
	delete pFill;
}

BOOL CEdgeTableView::PreCreateWindow(CREATESTRUCT& cs)
{
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs

	return CView::PreCreateWindow(cs);
}

/////////////////////////////////////////////////////////////////////////////
// CEdgeTableView drawing

void CEdgeTableView::OnDraw(CDC* pDC)
{
	CEdgeTableDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	//将7个顶点连接起来
	pFill->ShowPolygon(pDC);
}

/////////////////////////////////////////////////////////////////////////////
// CEdgeTableView diagnostics

#ifdef _DEBUG
void CEdgeTableView::AssertValid() const
{
	CView::AssertValid();
}

void CEdgeTableView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);
}

CEdgeTableDoc* CEdgeTableView::GetDocument() // non-debug version is inline
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEdgeTableDoc)));
	return (CEdgeTableDoc*)m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CEdgeTableView message handlers

void CEdgeTableView::OnMenuFill() 
{
	CClientDC dc(this);
	CColorDialog ccd(rgb);
    if(ccd.DoModal()==IDOK)    //调用调色板选取前景色
    {
        rgb=ccd.GetColor();
    }
    RedrawWindow();            //刷新屏幕
    pFill->CreatBucket();             //初始化桶
    pFill->CreatEdge();               //用于建立边表 
    pFill->PolygonFill(&dc, rgb);     //多边形填充
}
5)运行效果




原文地址:http://blog.csdn.net/qingdujun/article/details/40154077

参考文献:1)计算机图形学基础教程(Visual C++版)(第2版) 孔令德 编著

2)tmljs1988,CSDN博客,多边形有效边表填充算法,http://blog.csdn.net/tmljs1988/article/details/5985914



计算机图形学 有效边表填充算法(6)