1. 程式人生 > >計算機圖形學(三)掃描線多邊形填充演算法講解與原始碼

計算機圖形學(三)掃描線多邊形填充演算法講解與原始碼

如果喜歡轉載請標明出處:

並非菜鳥菜鳥的部落格

在這裡先說下演算法的實現過程

本人覺得這個演算法實現起來還是有點難度的!很多人都不願意去看太多描述性的文字,所以對這個演算法的過程是什麼大概也不知道,那麼我在這裡簡要的說一些!

演算法實現過程中應用兩個資料結構:

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;

     }

那麼先來一個效果圖

那麼現在來看下關鍵的程式碼部分

先看下兩關鍵的資料結構

  1. <span style="white-space:pre"> </span>struct Edge

  2. {

  3. double Ymax;

  4. double X;

  5. double Dx;

  6. };

  7. struct EDGE

  8. {

  9. CPoint Up;

  10. CPoint Down;

  11. Edge EG;

  12. };


這兩個資料結構是我們要用到的

看一下我們怎麼講多邊形儲存起來的

  1. void CPolyFill::BuildEDGEs()

  2. {

  3. if(m_pEDGEs)

  4. {

  5. delete[] m_pEDGEs; m_pEDGEs = NULL;

  6. }

  7. m_pEDGEs = new EDGE[m_PtNum];

  8. for(int i = 0; i < m_PtNum-1; i++)

  9. {

  10. if (m_Pts[i].y > m_Pts[i+1].y)

  11. {

  12. m_pEDGEs[i].Up = m_Pts[i];

  13. m_pEDGEs[i].Down = m_Pts[i+1];

  14. }

  15. else

  16. {

  17. m_pEDGEs[i].Up = m_Pts[i+1];

  18. m_pEDGEs[i].Down = m_Pts[i];

  19. }

  20. m_pEDGEs[i].EG.Ymax = m_pEDGEs[i].Up.y ;

  21. m_pEDGEs[i].EG.X = m_pEDGEs[i].Down.x;

  22. 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);

  23. }

  24. if (m_Pts[0].y > m_Pts[m_PtNum-1].y)

  25. {

  26. m_pEDGEs[m_PtNum-1].Up = m_Pts[0];

  27. m_pEDGEs[m_PtNum-1].Down = m_Pts[m_PtNum-1];

  28. }

  29. else

  30. {

  31. m_pEDGEs[m_PtNum-1].Up = m_Pts[m_PtNum-1];

  32. m_pEDGEs[m_PtNum-1].Down = m_Pts[0];

  33. }

  34. m_pEDGEs[m_PtNum-1].EG.Ymax = m_pEDGEs[m_PtNum-1].Up.y ;

  35. m_pEDGEs[m_PtNum-1].EG.X = m_pEDGEs[m_PtNum-1].Down.x;

  36. 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);

  37. }

現在多邊形已經被儲存起來了

那麼接下來該做的就是建立邊表

  1. void CPolyFill::CreateET()

  2. {

  3. GetMinMaxY(MinY,MaxY);

  4. if(m_pET)

  5. {

  6. delete [] m_pET;

  7. m_pET = NULL;

  8. }

  9. m_pET = new CArray <Edge,Edge>[MaxY- MinY+1];

  10. // Add EDGE to ET

  11. for(int i = 0; i < m_PtNum; i++)

  12. {

  13. int scanline = m_pEDGEs[i].Down.y - MinY;

  14. m_pET[scanline].Add(m_pEDGEs[i].EG);

  15. }

  16. // 多邊形的邊排序: Sort according to Xmin

  17. for (int n = MinY; n < MaxY; n++)

  18. {

  19. int index = n-MinY;

  20. int sz = m_pET[index].GetSize();

  21. for (int i = 0; i < sz-1; i++)

  22. {

  23. for (int k = i+1; k < sz; k++)

  24. {

  25. if (m_pET[index][i].X > m_pET[index][k].X)

  26. {

  27. Edge t = m_pET[index][i];

  28. m_pET[index][i] = m_pET[index][k];

  29. m_pET[index][k] = t;

  30. }

  31. }

  32. }

  33. }

  34. }


然後建立活動邊表

  1. void CPolyFill::InitAET()

  2. {

  3. if(m_pAET)

  4. {

  5. delete [] m_pAET;

  6. m_pAET = NULL;

  7. }

  8. m_pAET = new CArray<Edge,Edge>[MaxY- MinY+1];

  9. }


現在準備工作已經做好了,開始進入演算法的主要部分

  1. void CPolyFill::FillPolygon(CDC *pDC)

  2. {

  3. int nRand = rand();

  4. float fMap = (float)255/RAND_MAX;

  5. int ColorR = (UINT)(float)nRand*fMap + 0.5f;

  6. nRand = rand();

  7. fMap = (float)255/RAND_MAX;

  8. int ColorG = (UINT)(float)nRand*fMap + 0.5f;

  9. for (m_CurrentScanLine = MinY; m_CurrentScanLine < MaxY; m_CurrentScanLine++)

  10. {

  11. MoveNewEdgeFromET(); // 加入新邊

  12. RemoveEdges(); // 刪除舊邊

  13. SortAET(); // 按照邊的交點的X值排列

  14. FillScanLine(pDC); // 按照配對填充當前掃描行

  15. UpdateDelteX(); // 更新下條掃描線的交點X座標

  16. }

  17. }


一些關鍵的函式如下

  1. // 加入新邊

  2. void CPolyFill::MoveNewEdgeFromET()

  3. {

  4. int index = m_CurrentScanLine - MinY;

  5. for (int i = 0; i < m_pET[index].GetSize(); i++)

  6. {

  7. m_pAET[index].Add(m_pET[index][i]);

  8. }

  9. }

  10. // 刪除舊邊

  11. void CPolyFill::RemoveEdges()

  12. {

  13. int index = m_CurrentScanLine- MinY;

  14. for(int i = 0; i < m_pAET[index].GetSize(); i++)

  15. {

  16. if (m_CurrentScanLine == m_pAET[index][i].Ymax)

  17. {

  18. m_pAET[index].RemoveAt(i);

  19. i--;

  20. }

  21. }

  22. }

  23. // 排序

  24. void CPolyFill::SortAET()

  25. {

  26. // Sort according to Xmin

  27. int index = m_CurrentScanLine-MinY;

  28. int sz = m_pAET[index].GetSize();

  29. for (int i = 0; i < sz-1; i++)

  30. {

  31. for (int k = i+1; k < sz; k++)

  32. {

  33. if (m_pAET[index][i].X > m_pAET[index][k].X)

  34. {

  35. Edge t = m_pAET[index][i];

  36. m_pAET[index][i] = m_pAET[index][k];

  37. m_pAET[index][k] = t;

  38. }

  39. }

  40. }

  41. }

  42. // 配對填充當前行

  43. void CPolyFill::FillScanLine(CDC *pDC)

  44. {

  45. int mY;

  46. mY =MaxY-MinY;

  47. int index = m_CurrentScanLine-MinY;

  48. for (int i = 0; i < m_pAET[index].GetSize()-1; i += 2)

  49. {

  50. // int i =0;

  51. for (int x0 = m_pAET[index][i].X+0.99; x0 < int(m_pAET[index][i+1].X); x0++)

  52. {

  53. pDC->SetPixel(x0,m_CurrentScanLine, RGB(ColorR, ColorG,255-MulDiv(m_CurrentScanLine, 255, mY)));

  54. }

  55. }

  56. }

  57. // 更新交點橫座標。

  58. void CPolyFill::UpdateDelteX()

  59. {

  60. // Sort according to Xmin

  61. int index = m_CurrentScanLine-MinY;

  62. int sz = m_pAET[index].GetSize();

  63. for (int i = 0; i < sz; i++)

  64. {

  65. m_pAET[index][i].X += m_pAET[index][i].Dx;

  66. m_pAET[index+1].Add(m_pAET[index][i]);

  67. }

  68. }