扫描线算法(Scan-Line Filling)
扫描线填充的算法的基本思想是用水平扫描线从上到下(或者从下往上)扫描由多条首尾相连的线段构成的多边形,每根扫描线与多边形的某些边产生一系列交点。将这些交点按照x坐标排序,将排序后的点两两成对,作为线段的两个端点,以所填颜色画水平直线。多边形完成一次扫描,颜色也就填充完毕了
步骤:
- 计算扫描线和多边形的交点,求交
- 对于得到的交点按照X值从小到大进行排序
- 对于排序后的交点两两组成水平线段,用画线段的方式进行颜色的填充
- 判断是否完成多边形的扫描,如果完成了就结束算法,如果没有玩长城就改变扫描线重复执行步骤
算法的重点是如何使用尽量少的计算量将扫描线和多边形的交点求出来,还需要考虑交点是线段端点的特殊情况。如果对于每一条扫描线,每次都按照线段求交算法进行计算的话计算量比较大。
可以注意到:
1.每次只有相关的几条边可能与扫描线存在交点,所以不是所有的边都需要进行求交的计算;
2.相邻的扫描线与同一条直线段的交点的关系与直线段的斜率有关
所以根据这两个特点,可以通过维护一张活动边表(AET)来减少计算量,假设当前的扫描线和多边形的某一条边的交点已经通过直线段的求交计算了出来,交点为(x,y),那么下一条扫描线和这条边的交点就不再需要进行求交计算,可以通过与直线斜率的关系得到下一个下一条扫描线的新交点坐标(x+▲x,y+1)
对于多边形顶点的处理
在对多边形的边进行求交的过程中,在两条边的相连处的顶点可能会出现特殊情况,扫描线与两边各求一个交点,在顶点位置会出现两个交点。
对于多边形来说,相邻的两条边产生顶点可能会有四种情况:左顶点(y1<y2<y3),右顶点(y1>y2>y3),上顶点(y2>y1&&y2>y3),下顶点(y2<y1&&y2<y3)
对于左顶点和右顶点的修正方式是删除这条边的终点,通过修改以顶点为终点的那条边的区间,将顶点排除(将ymax修改为ymax-1)
对于上顶点和下顶点的可以不进行修改(也能保证交点奇偶个数平衡);或者将交点计算做0个,修改两条边的区间,把交点从两个边中取出
水平边的处理
水平边和扫描线重合会产生很多的交点,通常的方式是直接将水平边画出,不对其进行求交计算
避免过度填充
多边形的边界和扫描线会产生两个交点,如果对于两个交点都进行填充的话容易导致填充范围变大,因此多半采用左闭右开的填充方法
活动边表AET
扫描线算法的重点就是维护“活动边”组成的表,称为活动边表。
每条边和扫描线都有个交点,扫描线填充算法只关注交点的x坐标。一条边不会一直在活动边表中,如果扫描线和边没有交点了,就把它从活动边表中删除(是否有交点就是看扫描线的y是否大于这条边两个顶点y的坐标值,所以边的结构体中需要对于y的最大值进行记录)。每一条扫描线都有自己对应的活动边表,该扫描线与多边形有几个交点,该表中就有几个串起来的边结构体
为了方便对于活动边表的建立,对每一条扫描线建立一个新边表(NET),存放该扫描线第一次出现的边,当处理到某一条赛描线的时候,就把这条扫描线的新边表中所有边逐一插入到活动边表中
新边表的规则:如果某条边的较低端点(y坐标较小的点)的y坐标与扫描线y相等,则该边就是扫描线y的新边,应该加入扫描线y的新边表(该操作通常咋算法开始之前完成建立)
#include<iostream>
#include<gl/glut.h>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
const int window_width = 800, window_height = 500;
const int maxn = 100000;
struct point
{
float x, y;
point()
{
}
point(int tempx, int tempy)
:x(tempx), y(tempy) {
}
};
vector<point> vertice;
typedef struct XET
{
float x;
float dx;
float ymax;//float
XET* next;
}AET, NET;
void draw_a_point(int x, int y);
void PolyScan();
void mymouse(int button, int state, int x, int y);
void display();
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(100, 50);
glutInitWindowSize(window_width, window_height);
glutCreateWindow("扫描线填充");
glMatrixMode(GL_PROJECTION);
glLoadIdentity();