计算机图形学(三)扫描线多边形填充算法讲解与源代码

如果喜欢转载请标明出处:

并非菜鸟菜鸟的博客

源代码下载:点击打开链接

在这里先说下算法的实现过程

 

本人觉得这个算法实现起来还是有点难度的!很多人都不愿意去看太多描述性的文字,所以对这个算法的过程是什么大概也不知道,那么我在这里简要的说一些!

算法实现过程中应用两个数据结构:

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.  
  9. for(int i = 0; i < m_PtNum-1; i++)

  10. {

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

  12. {

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

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

  15. }

  16. else

  17. {

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

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

  20. }

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

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

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

  24. }

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

  27. {

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

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

  30. }

  31. else

  32. {

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

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

  35. }

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

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

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

  39. }

现在多边形已经被存储起来了

 

那么接下来该做的就是建立边表

 

 
  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.  
  10. for (m_CurrentScanLine = MinY; m_CurrentScanLine < MaxY; m_CurrentScanLine++)

  11. {

  12. MoveNewEdgeFromET(); // 加入新边

  13. RemoveEdges(); // 删除旧边

  14. SortAET(); // 按照边的交点的X值排列

  15. FillScanLine(pDC); // 按照配对填充当前扫描行

  16. UpdateDelteX(); // 更新下条扫描线的交点X坐标

  17. }

  18. }


一些关键的函数如下

 

 

 
  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. // 删除旧边

  12. void CPolyFill::RemoveEdges()

  13. {

  14. int index = m_CurrentScanLine- MinY;

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

  16. {

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

  18. {

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

  20. i--;

  21. }

  22. }

  23. }

  24.  
  25. // 排序

  26. void CPolyFill::SortAET()

  27. {

  28. // Sort according to Xmin

  29. int index = m_CurrentScanLine-MinY;

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

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

  32. {

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

  34. {

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

  36. {

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

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

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

  40. }

  41. }

  42. }

  43. }

  44.  
  45.  
  46.  
  47. // 配对填充当前行

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

  49. {

  50. int mY;

  51. mY =MaxY-MinY;

  52.  
  53. int index = m_CurrentScanLine-MinY;

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

  55. {

  56. // int i =0;

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

  58. {

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

  61. }

  62. }

  63. }

  64. // 更新交点横坐标。

  65. void CPolyFill::UpdateDelteX()

  66. {

  67. // Sort according to Xmin

  68. int index = m_CurrentScanLine-MinY;

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

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

  71. {

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

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

  74. }

  75. }