計算機圖形學(三)掃描線多邊形填充演算法講解與原始碼
如果喜歡轉載請標明出處:
並非菜鳥菜鳥的部落格
在這裡先說下演算法的實現過程
本人覺得這個演算法實現起來還是有點難度的!很多人都不願意去看太多描述性的文字,所以對這個演算法的過程是什麼大概也不知道,那麼我在這裡簡要的說一些!
演算法實現過程中應用兩個資料結構:
1、邊表(ET:Edge Table)
用來對除水平邊外的所有邊進行登記,來建立邊的記錄。邊的記錄定義為:
掃描線 y 對應的ET表
第一項:某邊的最大y值(ymax)。注意要進行奇異點處理:對於非極值點應該ymax=ymax-1。
第二項:某邊的最小的y對應的x值。
第三項:某邊斜率的倒數:1/m。
第四項:指標。用來指向同一條掃描線相交的其它邊,如果其它邊不存在,則該項置空。
2、活動邊表(AET:Active Edge Table)
ET表建立以後,就可以開始掃描轉換了。對不同的掃描線,與之相交的邊線也是不同的,當對某一條掃描線進行掃描轉換時,我們只需要考慮與它相交的那些邊線,為此需要建立一個只與當前掃描線相交的邊記錄連結串列,稱之為活動邊表。
下邊說下演算法的實現:
1、根據給定的多邊形頂點座標,建立ET 表。
2、AET表初始化,每個桶置空。
3、for(y=ymin;y<= ymax;y++)
{
合併當前掃描線y的ET表;
將y桶中每個記錄按x項升序排列;
在當前y值下,將兩兩記錄的x值之間的象素進行填充;
刪除y=ymax的邊記錄;
修改邊記錄x=x+1/m;
}
那麼先來一個效果圖
那麼現在來看下關鍵的程式碼部分
先看下兩關鍵的資料結構
-
<span style="white-space:pre"> </span>struct Edge
-
{
-
double Ymax;
-
double X;
-
double Dx;
-
};
-
struct EDGE
-
{
-
CPoint Up;
-
CPoint Down;
-
Edge EG;
-
};
這兩個資料結構是我們要用到的
看一下我們怎麼講多邊形儲存起來的
-
void CPolyFill::BuildEDGEs()
-
{
-
if(m_pEDGEs)
-
{
-
delete[] m_pEDGEs; m_pEDGEs = NULL;
-
}
-
m_pEDGEs = new EDGE[m_PtNum];
-
for(int i = 0; i < m_PtNum-1; i++)
-
{
-
if (m_Pts[i].y > m_Pts[i+1].y)
-
{
-
m_pEDGEs[i].Up = m_Pts[i];
-
m_pEDGEs[i].Down = m_Pts[i+1];
-
}
-
else
-
{
-
m_pEDGEs[i].Up = m_Pts[i+1];
-
m_pEDGEs[i].Down = m_Pts[i];
-
}
-
m_pEDGEs[i].EG.Ymax = m_pEDGEs[i].Up.y ;
-
m_pEDGEs[i].EG.X = m_pEDGEs[i].Down.x;
-
m_pEDGEs[i].EG.Dx = double((m_pEDGEs[i].Up.x - m_pEDGEs[i].Down.x))/(m_pEDGEs[i].Up.y - m_pEDGEs[i].Down.y);
-
}
-
if (m_Pts[0].y > m_Pts[m_PtNum-1].y)
-
{
-
m_pEDGEs[m_PtNum-1].Up = m_Pts[0];
-
m_pEDGEs[m_PtNum-1].Down = m_Pts[m_PtNum-1];
-
}
-
else
-
{
-
m_pEDGEs[m_PtNum-1].Up = m_Pts[m_PtNum-1];
-
m_pEDGEs[m_PtNum-1].Down = m_Pts[0];
-
}
-
m_pEDGEs[m_PtNum-1].EG.Ymax = m_pEDGEs[m_PtNum-1].Up.y ;
-
m_pEDGEs[m_PtNum-1].EG.X = m_pEDGEs[m_PtNum-1].Down.x;
-
m_pEDGEs[m_PtNum-1].EG.Dx = double((m_pEDGEs[m_PtNum-1].Up.x - m_pEDGEs[m_PtNum-1].Down.x))/(m_pEDGEs[m_PtNum-1].Up.y - m_pEDGEs[m_PtNum-1].Down.y);
-
}
現在多邊形已經被儲存起來了
那麼接下來該做的就是建立邊表
-
void CPolyFill::CreateET()
-
{
-
GetMinMaxY(MinY,MaxY);
-
if(m_pET)
-
{
-
delete [] m_pET;
-
m_pET = NULL;
-
}
-
m_pET = new CArray <Edge,Edge>[MaxY- MinY+1];
-
// Add EDGE to ET
-
for(int i = 0; i < m_PtNum; i++)
-
{
-
int scanline = m_pEDGEs[i].Down.y - MinY;
-
m_pET[scanline].Add(m_pEDGEs[i].EG);
-
}
-
// 多邊形的邊排序: Sort according to Xmin
-
for (int n = MinY; n < MaxY; n++)
-
{
-
int index = n-MinY;
-
int sz = m_pET[index].GetSize();
-
for (int i = 0; i < sz-1; i++)
-
{
-
for (int k = i+1; k < sz; k++)
-
{
-
if (m_pET[index][i].X > m_pET[index][k].X)
-
{
-
Edge t = m_pET[index][i];
-
m_pET[index][i] = m_pET[index][k];
-
m_pET[index][k] = t;
-
}
-
}
-
}
-
}
-
}
然後建立活動邊表
-
void CPolyFill::InitAET()
-
{
-
if(m_pAET)
-
{
-
delete [] m_pAET;
-
m_pAET = NULL;
-
}
-
m_pAET = new CArray<Edge,Edge>[MaxY- MinY+1];
-
}
現在準備工作已經做好了,開始進入演算法的主要部分
-
void CPolyFill::FillPolygon(CDC *pDC)
-
{
-
int nRand = rand();
-
float fMap = (float)255/RAND_MAX;
-
int ColorR = (UINT)(float)nRand*fMap + 0.5f;
-
nRand = rand();
-
fMap = (float)255/RAND_MAX;
-
int ColorG = (UINT)(float)nRand*fMap + 0.5f;
-
for (m_CurrentScanLine = MinY; m_CurrentScanLine < MaxY; m_CurrentScanLine++)
-
{
-
MoveNewEdgeFromET(); // 加入新邊
-
RemoveEdges(); // 刪除舊邊
-
SortAET(); // 按照邊的交點的X值排列
-
FillScanLine(pDC); // 按照配對填充當前掃描行
-
UpdateDelteX(); // 更新下條掃描線的交點X座標
-
}
-
}
一些關鍵的函式如下
-
// 加入新邊
-
void CPolyFill::MoveNewEdgeFromET()
-
{
-
int index = m_CurrentScanLine - MinY;
-
for (int i = 0; i < m_pET[index].GetSize(); i++)
-
{
-
m_pAET[index].Add(m_pET[index][i]);
-
}
-
}
-
// 刪除舊邊
-
void CPolyFill::RemoveEdges()
-
{
-
int index = m_CurrentScanLine- MinY;
-
for(int i = 0; i < m_pAET[index].GetSize(); i++)
-
{
-
if (m_CurrentScanLine == m_pAET[index][i].Ymax)
-
{
-
m_pAET[index].RemoveAt(i);
-
i--;
-
}
-
}
-
}
-
// 排序
-
void CPolyFill::SortAET()
-
{
-
// Sort according to Xmin
-
int index = m_CurrentScanLine-MinY;
-
int sz = m_pAET[index].GetSize();
-
for (int i = 0; i < sz-1; i++)
-
{
-
for (int k = i+1; k < sz; k++)
-
{
-
if (m_pAET[index][i].X > m_pAET[index][k].X)
-
{
-
Edge t = m_pAET[index][i];
-
m_pAET[index][i] = m_pAET[index][k];
-
m_pAET[index][k] = t;
-
}
-
}
-
}
-
}
-
// 配對填充當前行
-
void CPolyFill::FillScanLine(CDC *pDC)
-
{
-
int mY;
-
mY =MaxY-MinY;
-
int index = m_CurrentScanLine-MinY;
-
for (int i = 0; i < m_pAET[index].GetSize()-1; i += 2)
-
{
-
// int i =0;
-
for (int x0 = m_pAET[index][i].X+0.99; x0 < int(m_pAET[index][i+1].X); x0++)
-
{
-
pDC->SetPixel(x0,m_CurrentScanLine, RGB(ColorR, ColorG,255-MulDiv(m_CurrentScanLine, 255, mY)));
-
}
-
}
-
}
-
// 更新交點橫座標。
-
void CPolyFill::UpdateDelteX()
-
{
-
// Sort according to Xmin
-
int index = m_CurrentScanLine-MinY;
-
int sz = m_pAET[index].GetSize();
-
for (int i = 0; i < sz; i++)
-
{
-
m_pAET[index][i].X += m_pAET[index][i].Dx;
-
m_pAET[index+1].Add(m_pAET[index][i]);
-
}
-
}