飞道的博客

Xcode 橡皮筋算法实现多边形绘制及扫描填充

204人阅读  评论(0)

橡皮筋算法绘制多边形,利用扫描线算法(增量求交)填充多边形。增加菜单交互实现更改颜色、依据坐标轴平移、依据多边形点旋转、清屏、伸缩变化等附加操作。

                                    图(一)

                                    图(一)改色

                                    图(一)改色+线加宽

                                    图(一)改色+线加宽+旋转

                                    图(一)改色+线加宽+旋转+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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场