橡皮筋算法绘制多边形,利用扫描线算法(增量求交)填充多边形。增加菜单交互实现更改颜色、依据坐标轴平移、依据多边形点旋转、清屏、伸缩变化等附加操作。
图(一)
图(一)改色
图(一)改色+线加宽
图(一)改色+线加宽+旋转
图(一)改色+线加宽+旋转+x轴压缩
图(一)改色+线加宽+旋转+y轴压缩
代码如下:
datastruct.h:
#ifndef DATA_H_
#define DATA_H_
#include<vector>
using namespace std;
class point //点类,存储了一个点的两坐标值
{
public:
int x;
int y;
};
class polygon //多边形类,存了一个多边形
{
public:
vector<point> p; //多边形的顶点
};
#endif
typedef struct XET
{
float x;
float dx,ymax;
struct XET* next;
}AET,NET;
main.cpp:
#include<stdio.h>
#include<GLUT/glut.h>
#include"datastruct.h"
#include<OpenGL/gl.h>
#include<OpenGL/glu.h>
#include<math.h>
static const int screenwidth = 1000; //自定义窗口宽度
static const int screenheight = 1000; //自定义窗口高度
vector<point> p; //多边形点集向量
vector<polygon> s; //多边形类向量,用来保存已经画完的多边形
int move_x,move_y; //鼠标当前坐标值,在鼠标移动动态画线时使用
bool select = false; //多边形封闭状态判断变量
float red=1.0,green=0.0,blue=0.0; //用此来改变线段的颜色
int menu = 0, submenu = 0, changemenu = 0; // 设置菜单的变量
int R = 0;//设置旋转时的第一顶点下标
float lineWidth;
int MaxY=0,MinY=1000;
int POINTNUM;
int colorNum=0;
void menufunc(int data); // 显示主菜单函数
void submenufunc(int data); //颜色子菜单函数
void changemenufunc(int data); //操作子菜单函数
void close(); // 菜单函数,用于封闭多边形
void fill(); // 菜单函数,用于填充多边形
void changeblue(); // 菜单函数,用于改变颜色
void changered(); // 菜单函数,用于改变颜色
void changegreen(); // 菜单函数,用于改变颜色
void init()
{
glClearColor(1.0,1.0,1.0,0.0);
glMatrixMode(GL_PROJECTION); //表示给物体加物理贴图,或透视效果
gluOrtho2D(0.0,screenwidth,0.0,screenheight);
}
/**
使用说明:
1)按上下左右键平移
2)W: y轴伸缩1.2倍 w y轴伸缩0.8倍
3)S: x轴伸缩1.2倍 s x轴伸缩0.8倍
4)F10: 边增粗
5)F9: 边变细
6) 鼠标右键菜单进行选择: 1.封闭、2.填充、3.退步、4.清屏、5.改色
7)z 键实现旋转功能,绕多边形点集的第一个点进行旋转,按n键旋转点变换
**/
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
void fillColor(int mn){
int i;
for(int k=0;k<mn;k++){
//k表示多变行的个数
POINTNUM = s[k].p.size();
for( i = 0;i < POINTNUM; i++ )
{
if( s[k].p[i].y > MaxY )
{
MaxY = s[k].p[i].y;
}
if( s[k].p[i].y < MinY )
{
MinY = s[k].p[i].y;
}
}
//初始化AET表,即初始化活跃边表
AET *pAET = new AET;
pAET->next = NULL;
//初始化NET表,即初始化边表
NET *pNET[1024];
for( i = MinY;i <= MaxY; i++ )
{
pNET[i] = new NET;
pNET[i]->next = NULL;
}
//glClear(GL_COLOR_BUFFER_BIT); //赋值的窗口显示.
//glColor3f(0.0,0.0,0.0); //设置直线的颜色红色
glBegin(GL_POINTS);
//扫描并建立NET
for( i = MinY; i <= MaxY; i++ )
{
for( int j = 0;j < POINTNUM; j++ )
{
//当扫描线与绘制的点y坐标相等
if( s[k].p[j].y == i)
{
//加入下顶点左边
if( s[k].p[ (j-1+POINTNUM) % POINTNUM ].y > s[k].p[j].y )
{
NET *p=new NET;
p->x = s[k].p[j].x;
//取当前最高扫描线号,及最高值
p->ymax = s[k].p[ (j-1+POINTNUM) % POINTNUM ].y;
//下面表示的是斜率需要用浮点型来计算float()
//增量1/k
p->dx = ( float(s[k].p[ (j-1+POINTNUM)%POINTNUM ].x-s[k].p[j].x )) /float( ( s[k].p[ (j-1+POINTNUM) % POINTNUM ].y - s[k].p[j].y ));
//将结点加入新建的NET表中
p->next = pNET[i]->next;
pNET[i]->next = p;
}
//加入下顶点右边
if( s[k].p[ (j+1+POINTNUM ) % POINTNUM].y > s[k].p[j].y )
{
NET *p = new NET;
p->x = s[k].p[j].x;
//取当前最高扫描线号,及最高值
p->ymax = s[k].p[ (j+1+POINTNUM) % POINTNUM ].y;
//增量1/k
p->dx = ( float(s[k].p[(j+1+POINTNUM) % POINTNUM ].x-s[k].p[j].x )) / float( s[k].p[ (j+1+POINTNUM) % POINTNUM ].y- s[k].p[j].y );
//将结点加入新建的NET表中
p->next = pNET[i]->next;
pNET[i]->next = p;
}
}
}
}
//建立并更新活性边表AET
for( i = MinY; i <= MaxY; i++ ) //for循环中按下面的流程而不是《计算机图形学》徐长青第二版P38中Polygonfill算法中的while循环中的流程,
{
// 这样可以处理书中的边界问题,无需开始时进行边缩短
//计算新的交点x,更新AET********************************************************/
NET *p = pAET->next;
while( p != NULL )
{
p->x = p->x + p->dx;
p = p->next;
}
//更新后新AET先排序/
AET *tq = pAET;
p = pAET->next;
tq->next = NULL;
while( p != NULL )
{
while( tq->next != NULL && p->x >= tq->next->x )
{
tq = tq->next;
}
NET *s = p->next;
p->next = tq->next;
tq->next = p;
p = s;
tq = pAET;
}
//先AET表中删除ymax==i的结点/
AET *q = pAET;
p = q->next;
while( p != NULL )
{
if( p->ymax == i)
{
q->next = p->next;
delete p;
p = q->next;
}
else
{
q = q->next;
p = q->next;
}
}
//扫描线遇到端点,将NET中的新点加入AET,并用插入法按X值递增排序
p = pNET[i]->next;
q = pAET;
while( p != NULL )
{
while( q->next != NULL && p->x >= q->next->x)
{
q = q->next;
}
NET *s = p->next;
p->next = q->next;
q->next = p;
p = s;
q = pAET;
}
//配对填充颜色
p = pAET->next;
while( p != NULL && p->next != NULL )
{
for(float j = p->x;j <= p->next->x; j++)
{
glVertex2i(static_cast<int>(j),i);
} // pDC.MoveTo( static_cast<int>(p->x), i ); 用画直线来替换上面的设置像素点颜色,速度更快
p = p->next->next;//考虑端点情况
}
}
glEnd();
//exam = false;
NET *phead = NULL;
NET *pnext = NULL;
//释放边表
//释放活跃边表
phead = pAET;
while( phead != NULL )
{
pnext = phead->next;
delete phead;
phead = pnext;
}
}
glFlush();
}
void lineSegment()
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(red,green,blue); //设定颜色,既是线段颜色也是填充色
int i, j; //两个循环控制变量,在下面的向量循环和数组循环中将被多次调用。
//if(exam){
if(!s.empty()) //看多边形类向量是否为空,即判断除了当前正在画的多边形是否还有曾经已经画好的多边形
{
for( i = 0; i < s.size(); i++) //对多边形类向量循环,该向量中的每个元素代表一个多边形
{
int k = s[i].p.size(); //将一个多边形的点的个数,后面划线会用到
// s[i].line(); //生成多边形的边
for(j = 0; j < s[i].p.size(); j++) //画多边形
{
glBegin(GL_LINES); //将当前的点与后一个点连线
glVertex2i(s[i].p[j].x, s[i].p[j].y);
glVertex2i(s[i].p[(j+1)%k].x, s[i].p[(j+1)%k].y);//,通过取模操作来避免越界问题,该思路来源于循环队列.
glEnd();
}
// paint(s[i].edge); //为当前的多边形填充颜色
// s[i].edge.clear(); //清空当前多边形的边向量
}
}
i = 0;
j = p.size() - 1;
while(i < j) //循环画图,将当前正在画的多边形
{
glBegin(GL_LINES); //将已经确定的点连接起来
glVertex2i(p[i].x, p[i].y);
glVertex2i(p[i+1].x, p[i+1].y);
glEnd();
i++;
}
if(!p.empty())
{
// int i = p.size() - 1; //将确定的最后一个点与当前鼠标所在位置连线,即动态画线
glBegin(GL_LINES);
glVertex2i(p[j].x, p[j].y);
glVertex2i(move_x, move_y);
glEnd();
}else{
//按下F1以后将线进行重新绘制,便展现了清屏的效果
glBegin(GL_LINES);
glVertex2i(0, 0);
glVertex2i(0, 0);
glEnd();
}
//}
if(colorNum>0)
fillColor(colorNum);
if(select) //判断右键是否被点下
{
select = false; //将状态值置为假
if(!p.empty())
{
glBegin(GL_LINES); //将多边形封闭
glVertex2i(p[j].x, p[j].y);
glVertex2i(p[0].x, p[0].y);
glEnd();
polygon sq;
//将封闭了的多边形保存到多边形类中
for(i = 0; i < p.size(); i++)
sq.p.push_back(p[i]);
s.push_back(sq); //将绘成的多边形存入多边形类向量中
// paint(sq.edge); //给当前画完的多边形填色
}
p.clear();
}
glFlush();
}
void myMouse(int button, int state ,int x, int y) //鼠标点击事件响应函数
{
if(state == GLUT_DOWN && button == GLUT_LEFT_BUTTON)//当鼠标左键被点击
{
point v; //申请一个点类变量,点类为自定义类,在zl.h中定义
v.x = x; //将点击处的点坐标,即x和y的值存入v中
v.y = screenheight - y;
p.push_back(v); //将点信息存入多边形点集向量p中
//
//exam = true;
glutPostRedisplay(); //重绘窗口
}
if(state == GLUT_DOWN && button == GLUT_RIGHT_BUTTON) //当鼠标右键被点击
{
select = true;
glutPostRedisplay();
}
}
void myPassiveMotion(int x, int y) //鼠标移动事件响应函数
{
move_x = x; //保存当前鼠标所在的坐标的值
move_y = screenheight - y;
glutPostRedisplay();
}
//比例/缩放(用键盘的f7键控制y的缩放,fn8键控制x的缩放)
void Scale(GLfloat Sx,GLfloat Sy)
{
//以500 500 为中心缩放
glTranslatef(500, 500, 0);
glScaled(Sx, Sy, 0);
glTranslatef(-500, -500, 0);
}
void Translate(GLfloat Tx,GLfloat Ty)//平移
glTranslated(Tx, Ty, 0.0)
}
void Rotate(int angle,double x,double y,double z)//旋转
{
glTranslatef(p[R].x, p[R].y, 0);
glRotatef(angle, x, y, z);
glTranslatef(-p[R].x, -p[R].y, 0);
}
void processSpqcialKeys(int key, int x, int y){
//按下按键改变线条及多边形特征
if(key == 27){
exit(0);
}
else if(key == 87) //对应W
{
Scale(1.0, 1.2);
}
else if(key == 119) //对应w
{
Scale(1.0, 0.8);
}
else if(key == 83) //对应S
{
Scale(1.2, 1.0);
}
else if(key == 115) //对应s
{
Scale(0.8, 1.0);
}
else if(key == 122) //对应z
{
Rotate(30,0,0,1);
}
else if(key == 90) //对应Z
{
Rotate(-30,0,0,1);
}
else if(key == 110) //对应n
{
R+=1;
R%=sizeof(p)/sizeof(p[0]);
}
else if(key == GLUT_KEY_F10)//按F10边加粗
{
lineWidth = lineWidth+2;
glLineWidth(lineWidth);
}
else if(key == GLUT_KEY_F9)//按F11边变细
{
lineWidth = lineWidth-2;
glLineWidth(lineWidth);
}
else if(key == GLUT_KEY_UP)//向上平移
{
Translate(0.0, 100.0);
}
else if(key == GLUT_KEY_DOWN)//向下平移
{
Translate(0.0, -100.0);
}
else if(key == GLUT_KEY_LEFT)//向左平移
{
Translate(-100.0, 0.0);
}
else if(key == GLUT_KEY_RIGHT)//向右平移
{
Translate(100.0, 0.0);
}
glutPostRedisplay();
}
void pside()//撤销一个刚画的边
{
p.pop_back();
}
void stri()//直接撤销一个三角形
{
s.pop_back();
}
void clear()//清屏函数 将多边形全部删除
{
printf("%lu ",s.size());
//将的多边形全部删除
while(!s.empty()){
s.pop_back();
}
printf("%lu",p.size());
while(!p.empty()){
//将最后一个多变形删除
p.pop_back();
}
//exam = true;
colorNum =0;
//glTranslatef(0, 0, 0);
}
void close()// 菜单相关函数,用于封闭图形
{
select = true;
glutPostRedisplay();
}
void fill()// 菜单相关函数,用于填充多边形
{
if (!s.empty() && s.size() > colorNum)
colorNum = s.size();
// 重新显示窗口
glutPostRedisplay();
}
// 主菜单
void menufunc(int data) {
switch (data) {
case 1:
// 调用封闭函数
close();
break;
case 2:
//填充
fill();
break;
case 3:
//清屏
clear();
break;
case 4:
//去边
pside();
break;
case 5:
//去三角
stri();
break;
}
}
// 第一个子菜单
void submenufunc(int data) {
switch (data) {
case 1:
changered();
printf("submenu's item 1 is triggered.\n");
break;
case 2:
changegreen();
printf("submenu's item 2 is triggered.\n");
break;
case 3:
changeblue();
printf("submenu's item 3 is triggered.\n");
break;
}
}
//
void changemenufunc(int data) {
switch (data) {
case 1:
close();
printf("changemenu's item 1 is triggered.\n");
break;
case 2:
fill();
printf("changemenu's item 2 is triggered.\n");
break;
case 3:
clear();
printf("changemenu's item 3 is triggered.\n");
break;
case 4:
pside();
printf("changemenu's item 4 is triggered.\n");
break;
case 5:
stri();
printf("changemenu's item 5 is triggered.\n");
break;
}
}
void changeblue() {
red = 0.0; green = 0.0; blue = 1.0; }
void changered() {
red = 1.0; green = 0.0; blue = 0.0; }
void changegreen() {
red = 0.0; green = 1.0; blue = 0.0; }
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(50,100);
glutInitWindowSize(screenwidth,screenheight);
glutCreateWindow("扫描线填充算法");
init();
glutMouseFunc(myMouse); //鼠标点击消息监控,即监控鼠标是否被点击,若被点击就调用myMouse函数
glutDisplayFunc(lineSegment);
glutPassiveMotionFunc(myPassiveMotion); //鼠标移动消息监控,即监控鼠标是否移动,若移动就调用myPassiveMotion函数
glutSpecialFunc(processSpqcialKeys); //通过按特殊按键改变多边形线条的大小
//构建子菜单1的内容
submenu = glutCreateMenu(submenufunc);
glutAddMenuEntry("红色", 1);
glutAddMenuEntry("绿色", 2);
glutAddMenuEntry("蓝色", 3);
//构建子菜单2的内容
changemenu = glutCreateMenu(changemenufunc);
glutAddMenuEntry("封闭", 1);
glutAddMenuEntry("填充", 2);
glutAddMenuEntry("清屏", 3);
glutAddMenuEntry("去一个边", 4);
//glutAddMenuEntry("去三角", 5);
//构建主菜单的内容
menu = glutCreateMenu(menufunc);
glutAddSubMenu("修改操作", changemenu);
glutAddSubMenu("改变颜色", submenu);
//点击鼠标右键时显示菜单
glutAttachMenu(GLUT_RIGHT_BUTTON);
glutMainLoop();
return 0;
}
#pragma clang diagnostic pop
这里我调用的是Xcode自带的OpenGL库和 gult库,其中平移、旋转没有对顶点集坐标进行更改,而是直接调用gl相关函数,对坐标轴操作,这样会有瑕疵,如若需要更改请参考书籍自行修改。以上代码仅供学习参考。
转载:https://blog.csdn.net/weixin_43275631/article/details/116739277
查看评论