扫描线填充算法(2)
活动边表示扫描线填充算法的核心,整个算法都是围绕着这张表进行处理的。要完整的定义“活动边表”,需要定义边的数据结构。每条边都和扫描线有个交点,扫描线填充算法只关注交点的x坐标。每当处理下一条扫描线时,根据xi+1/m直接计算出扫描线与边的交点x坐标,可以避免复杂的求交计算。一条边不会一直待在“活动边表”中,当扫描线与之没有交点时,要将其从“活动边表”中删除,判断是否有交点的依据就是看扫描线y是否大于这条边两个端点的y坐标值,为此,需要记录边的y坐标的最大值。
// FillPolygon.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <cstdio>
#include <malloc.h>
#include <cmath>
#include <vector>
#include <GL/glut.h>
#include <iostream>
#include <list>
using namespace std;
#pragma comment(linker,"/entry:\"mainCRTStartup\"")
#define MAX_VERTECES 100
#define WINDOWWIDTH 400
#define WINDOWHEIGHT 400
#define ACCURACY 0.1
#define max(x,y) (x)>(y)?(x):(y)
#define min(x,y) (x)<(y)?(x):(y)
#define drawLine(x1,y1,x2,y2) glBegin(GL_LINES);glVertex2i((x1),(y1));glVertex2i((x2),(y2));glEnd();
typedef struct Edge{
int ymax;
float xi, dx;
}Edge;
typedef struct Point{
GLint x, y;
}Point;
typedef struct Polygon{
Point pts[MAX_VERTECES];
int polycnt;
}Polygon;
int ymin, ymax;
GLint verteces[MAX_VERTECES][2];
GLint pointn = 0;
GLint windowwidth = WINDOWWIDTH, windowheight = WINDOWHEIGHT;
GLint finish_picking_points = GL_FALSE;
GLint begin_to_draw = GL_FALSE;
list<Edge> slNet[WINDOWHEIGHT];
list<Edge> aet;
Polygon py;
void newPloygon()
{
py.polycnt = pointn;
ymin = 10000000;
ymax = 0;
for (int i = 0; i < pointn; i++)
{
py.pts[i].x = verteces[i][0];
py.pts[i].y = verteces[i][1];
ymin = min(ymin, verteces[i][1]);
ymax = max(ymax, verteces[i][1]);
}
}
void initScanLineNewEdgeTable()
{
for (int i = 0; i < WINDOWHEIGHT; i++)
{
slNet[i].clear();
}
Edge e;
for (int i = 0; i < py.polycnt; i++)
{
const Point & ps = py.pts[i];
const Point & pe = py.pts[(i + 1) % py.polycnt];
const Point & pee = py.pts[(i + 2) % py.polycnt];
const Point & pss = py.pts[(i - 1 + py.polycnt) % py.polycnt];
if (pe.y != ps.y)
{
e.dx = (double)(pe.x - ps.x) / (double)(pe.y - ps.y);
if (pe.y > ps.y)
{
e.xi = ps.x;
if (pee.y >= pe.y)
{
e.ymax = pe.y - 1;
}
else
{
e.ymax = pe.y;
}
slNet[ps.y].push_front(e);
}
else
{
e.xi = pe.x;
if (pss.y >= ps.y)
{
e.ymax = ps.y - 1;
}
else
{
e.ymax = ps.y;
}
slNet[pe.y].push_front(e);
}
}
}
}
void HorizonEdgeFill()
{
int id1, id2;
int x1, x2, y1, y2;
for (int i = 0; i < py.polycnt; i++)
{
id1 = i;
id2 = (i + 1) % py.polycnt;
if (py.pts[id1].y == py.pts[id2].y)
{
x1 = py.pts[id1].x;
y1 = py.pts[id1].y;
x2 = py.pts[id2].x;
y2 = py.pts[id2].y;
drawLine(x1, y1, x2, y2);
}
}
glFlush();
}
bool EdgeXiComparator(Edge& e1, Edge& e2)
{
return e1.xi < e2.xi;
}
void InsertNetListToAet(int y)
{
for (list<Edge>::iterator it1 = slNet[y].begin(); it1 != slNet[y].end(); ++it1)
{
//cout << it1->xi << " " << it1->dx << " " << it1->ymax << endl;
aet.push_back(*it1);
}
aet.sort(EdgeXiComparator);
}
void FillAetScanLine(int y)
{
bool flag;
flag = true;
list<Edge>::iterator it1;
for (list<Edge>::iterator it = aet.begin(); it != aet.end(); it++)
{
it1 = it;
it1++;
if (it1 == aet.end())
{
break;
}
if (flag)
{
drawLine(it->xi, y, it1->xi, y);
}
flag = !flag;
}
glFlush();
}
//bool IsEdgeOutOfActive(Edge e, int y)
//{
// return (e.ymax == y);
//}
void ProcessScanLineFill()
{
aet.clear();
for (int y = ymin; y <= ymax; y++)
{
InsertNetListToAet(y);
FillAetScanLine(y);
//删除非活动边
for (list<Edge>::iterator it = aet.begin(); it != aet.end();)
{
if (it->ymax == y)
{
it = aet.erase(it);
}
else
{
it++;
}
}
//更新活动边表中每项的xi值,并根据xi重新排序
//更新xi
for (list<Edge>::iterator iter = aet.begin(); iter != aet.end(); iter++)
iter->xi += iter->dx;
//根据xi从小到大重新排序
aet.sort(EdgeXiComparator);
}
}
void draw()
{
initScanLineNewEdgeTable();
HorizonEdgeFill();
ProcessScanLineFill();
}
void init()
{
//glClearColor(0.0, 0.0, 0.0, 0.0);
//glShadeModel(GL_FLAT);
}
void display()
{
int k;
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.0, 1.0, 0.0);
glBegin(GL_POINTS);
for (k = 0; k < pointn; k++)
{
glVertex2i(verteces[k][0], verteces[k][1]);
}
glEnd();
if (begin_to_draw == GL_TRUE)
{
glColor3f(0.0, 1.0, 1.0);
draw();
}
glutSwapBuffers();
//glFlush();
}
void keyboard(unsigned char key, int x, int y)
{
switch (key){
case 'r':
case 'R':
pointn = 0;
finish_picking_points = GL_FALSE;
begin_to_draw = GL_FALSE;
break;
case 'b':
case 'B':
begin_to_draw = GL_FALSE;
break;
case 'n':
case 'N':
begin_to_draw = GL_TRUE;
break;
};
glutPostRedisplay();
}
//测试使用
//int xx[4] = { 0, 4, 10 };
//int yy[4] = { 0, 6, 4 };
void mouse(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
if (finish_picking_points == GL_FALSE) //如果是按下鼠标左键
{
verteces[pointn][0] = x; //记录按下的点的x坐标
verteces[pointn][1] = windowheight - y; //记录按下的点的y坐标,由于坐标系原点一个是在左上角,一个是在左下角,所以要用windowheight-y。
/*verteces[pointn][0] = xx[pointn];
verteces[pointn][1] = yy[pointn];*/
pointn++;
}
}
else if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) //如果是按下鼠标右键
{
newPloygon(); //构建多边形
finish_picking_points = GL_TRUE; //停止画点
begin_to_draw = GL_TRUE; //开始填充
}
glutPostRedisplay();
}
void reshape(int w, int h)
{
windowwidth = w;
windowheight = h;
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, (GLdouble)w, 0.0, (GLdouble)h);
}
int main(int argc, char ** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(windowwidth, windowheight);
glutInitWindowPosition(300, 300);
glutCreateWindow("扫描线算法");
//init();
glutDisplayFunc(display);
glutMouseFunc(mouse);
glutKeyboardFunc(keyboard);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}