目录
基本思想
从图形的y最小值到y最大值依次进行y上的遍历,遍历的每个y=c这条直线称为扫描线。对于每条扫描线,多边形会和这条直线存在若干交点。这些交点中的之间的线段中某些是在多边形的内部,某些是在多边形的外部,且这种内外之分往往是间隔出现的。那么我们就将这些交点两两配对(保证配对两点之间的线段在多边形内部),然后对交点之间的部分进行绘制即可。易得水平线对于这个过程是没有什么意义的,所以在下面的所有分析中,水平线应当忽略不予考虑。
有序边表思想
这种算法的情况下,我们对于每一条扫描线都需要和所有的直线求交,这个过程计算较为复杂,且存在很多不必要的计算(例如上图中的3、4、5、6、7、8号直线和0号扫描线的求交运算就是不必要的)。那么对计算的简化就可以从增量计算以及非必要计算的减少两个方面去进行。可以通过有效边表算法达到上述两个方面的简化。
这种算法的情况下,我们对于每一条扫描线都需要和所有的直线求交,这个过程计算较为复杂,且存在很多不必要的计算(例如上图中的3、4、5、6、7、8号直线和0号扫描线的求交运算就是不必要的)。那么对计算的简化就可以从增量计算以及非必要计算的减少两个方面去进行。可以通过有效边表算法达到上述两个方面的简化。
首先,利用边表存储多边形所有边的有效信息。边表(NET)是一个存储每一扫描线处进行入考虑范围的变得信息的链表,大小等于扫描线范围大小。对于其中的每一项,存储下端点在标号+多边形y最小值的每一条边的信息,这些信息包含下端点x值,y增加1x增加的步长值m以及边上端点的ymax值。
基于以上静态边表,我们去遍历每一扫描线,进行动态的有效边表(AET)的维护。所谓有效边是指与当前扫描线有交点的边。对于每一扫描线,我们首先判断通过ymax参数当前边表中的边是否还有效,无效则从边表中删除,有效则完成x从与i-1号扫面线交点横坐标到与i号扫描线交点横坐标的转化,即x+=m。然后对于所有在NET[i]上挂着的所有从y最小值+i起始的边加入AET表,然后对AET表进行基于x值得排序。接下来进行AET表中x值的两两配对,对配对x之间的线段进行填充颜色的绘制即可。
特别的,对于y方向上的极小值端点,由于x值会作为两台哦不同的边进入有效边表两次,所以不会产生配对上的错误。
代码实现
Edge类的内容(边表项):
class Edge
{
public:
double x; //当前x坐标
int ymax; //最上端y坐标
double m; //斜率倒数
};
NET的产生:
//静态边表NET的产生
std::vector<std::vector<Edge>>* setNET(const vetor<pair<int,int>>& pnts)
{
int ymin = 0x3f3f3f3f, ymax = 0;
int n = pnts.size();
for (int i = 0; i < n; i++)
{
if (pnts[i].second < ymin) ymin = pnts[i].second;
if (pnts[i].second > ymax) ymax = pnts[i].second;
}
std::vector<std::vector<Edge>>& v = *new std::vector<std::vector<Edge>>(ymax - ymin + 1);
for (int i = 1; i <= n; i++)
{
double dx, dy;
dx = pnts[i % n].first - pnts[i - 1].first;
dy = pnts[i % n].second - pnts[i - 1].second;
if (dy == 0) continue;
Edge e;
e.m = dx / dy;
if (dy > 0)
{
e.ymax = pnts[i % n].second - ymin;
e.x = pnts[i - 1].first;
v[pnts[i - 1].second - ymin].push_back(e);
}
else
{
e.ymax = pnts[i - 1].second - ymin;
e.x = pnts[i % n].first;
v[pnts[i % n].second - ymin].push_back(e);
}
}
return &v;
}
AET的变化以及填充过程
bool EdgePointcmp(Edge a, Edge b)
{
return a.x < b.x;
}
void ScanLinePolygonFill(const vector<pair<int, int>>& pnts, unsigned long fillcolor)
{
CClientDC dc(mView); //如果hDC为0时使用
std::vector<std::vector<Edge>>NET = *setNET(pnts);
int n = pnts.size();
int ymin = 0x3f3f3f3f;
for (int i = 0; i < n; i++)
if (pnts[i].second < ymin) ymin = pnts[i].second;
std::vector<Edge> AET;
for (int i = 0; i < NET.size() - 1; i++)
{
for (int j = 0; j < AET.size(); j++)
{
if (AET[j].ymax <= i) AET.erase(AET.begin() + j--);
else AET[j].x += AET[j].m;
}
AET.insert(AET.end(), NET[i].begin(), NET[i].end());
std::sort(AET.begin(), AET.end(), EdgePointcmp);
for (int k = 0; k < AET.size() - 1; k += 2)
{
CPen pen(PS_SOLID, 1, fillcolor);
CPen* pOldPen = dc.SelectObject(&pen);
dc.MoveTo(AET[k].x + 2, ymin + i);
dc.LineTo(AET[k + 1].x, ymin + i);
dc.SelectObject(pOldPen);
}
}
}