`
java-mans
  • 浏览: 11391659 次
文章分类
社区版块
存档分类
最新评论

OpenGL橡皮筋技术与拾取技术的实现

 
阅读更多

计算机图形学课程链接

3.4橡皮筋技术基于鼠标的实现

1. 鼠标响应函数
MousePlot(GLint button, GLint action, GLint xMouse, GLint yMouse)函数是鼠标响应函数,它包含了四个参数:参数button的取值是GLUT定义的三个鼠标按键符号常量GLUT_LEFT_BUTTON,GLUT_MIDDLE_BUTTON,GLUT_RIGHT_BUTTON,分别表示鼠标的左键、中键和右键。参数action的取值也是符号常量,它可以为GLUT_DOWN或GLUT_UP,以确定鼠标按键的行为是按下还是松开状态。坐标(xMouse,yMouse)用于指定当前鼠标在窗口中相对于窗口左上角点的位置坐标,xMouse表示鼠标位置到窗口左边界的像素距离,yMouse表示鼠标位置到窗口上边界的像素距离。由于OpenGL程序绘制图形时的坐标原点在左下角,因此处理y坐标时应该用窗口高度减去yMouse值。当鼠标响应函数开始工作后,由GLUT框架确定当前鼠标操作时响应函数四个参数的取值。
此外,GLUT还提供了两个用于处理鼠标移动的注册函数,一个是一个或多个鼠标按键被按下时在窗口内移动的注册函数,另一个是鼠标按键没有被按下时在窗口内移动的注册函数:
glutMotionFunc(MouseMove);
glutPassiveMotionFunc(PassiveMouseMove);
它们分别定义了一个鼠标移动响应函数,以获得鼠标在窗口中移动时的位置。这两个函数均包含两个参数:
void MouseMove(GLint xMouse, GLint yMouse);
void PassiveMouseMove(GLint xMouse, GLint yMouse);
其中,坐标(xMouse,yMouse)用于指定当前时刻鼠标在窗口中相对于窗口左上角点的位置坐标,单位为像素。

2. 利用鼠标实现橡皮筋技术
橡皮筋技术的关键在于控制图形随着用户的操作(鼠标移动)而不断发生变化,此时需要擦除原有的图形同时生成新的图形。橡皮筋技术的实现方法有两种:其一是利用颜色的异或操作,对原有图形并不是擦除,而是再绘制一条同样的直线并与原图形进行异或操作,此时原图形会从屏幕上消失;另一种是利用双缓存技术,绘制图形时分别绘制到两个缓存,交替显示。这里我们采用双缓存技术实现橡皮筋技术。

程序3-1 OpenGL中利用鼠标实现橡皮筋技术的例子

#include <gl/glut.h>
 
int iPointNum = 0;                     //已确定点的数目
int x1=0,x2=0,y1=0,y2=0;               //确定的点坐标
int winWidth = 400, winHeight = 300;     //窗口的宽度和高度
 
void Initial(void)
{
       glClearColor(1.0f, 1.0f, 1.0f, 1.0f);        
}
 
void ChangeSize(int w, int h)
{
       winWidth = w;       winHeight = h;
       glViewport(0, 0, w, h);                 //指定窗口显示区域
       glMatrixMode(GL_PROJECTION);      //设置投影参数
       glLoadIdentity();
       gluOrtho2D(0.0,winWidth,0.0,winHeight);
}
 
void Display(void)
{
       glClear(GL_COLOR_BUFFER_BIT);
       glColor3f(1.0f, 0.0f, 0.0f);
       if(iPointNum >= 1)       {
              glBegin(GL_LINES);              //绘制直线段
                     glVertex2i(x1,y1);
                     glVertex2i(x2,y2);
              glEnd();
       }
       glutSwapBuffers();                    //交换缓冲区
}
 
void MousePlot(GLint button, GLint action, GLint xMouse, GLint yMouse)
{
       if(button == GLUT_LEFT_BUTTON && action == GLUT_DOWN)       {
              if(iPointNum = = 0 || iPointNum = = 2){
                     iPointNum = 1;
                     x1 = xMouse;         y1 = winHeight - yMouse;
              }
              else {
                     iPointNum = 2;
                     x2 = xMouse;         y2 = winHeight - yMouse;
                     glutPostRedisplay();                  //指定窗口重新绘制
              }
       }
       if(button == GLUT_RIGHT_BUTTON && action == GLUT_DOWN){
              iPointNum = 0;
              glutPostRedisplay();
       }
}
 
void PassiveMouseMove (GLint xMouse, GLint yMouse)
{
       if(iPointNum == 1)       {
              x2 = xMouse;
              y2 = winHeight - yMouse;    
              glutPostRedisplay();
       }    
}
 
int main(int argc, char* argv[])
{
       glutInit(&argc, argv);
       glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);   //使用双缓存及RGB模型
       glutInitWindowSize(400,300);
       glutInitWindowPosition(100,100);
       glutCreateWindow("橡皮筋技术");
       glutDisplayFunc(Display);
       glutReshapeFunc(ChangeSize);                //指定窗口在整形回调函数
       glutMouseFunc(MousePlot);                  //指定鼠标响应函数
       glutPassiveMotionFunc(PassiveMouseMove);    //指定鼠标移动响应函数
       Initial();                                   
       glutMainLoop();                               
       return 0;
}


基于键盘的实现

1. 键盘响应函数
GLUT还提供了对键盘的输入响应,键盘输入注册函数为:
glutKeyboardFunc(Key);
它指定了程序在运行状态时,按下键盘上的任意一个键都会调用Key函数,该函数包含了三个参数:
void Key(unsigned char key, int x, int y);
其中,参数key的取值是一个字符值或者对应的ASCII编码,而(x,y)则是按下键盘时窗口中当前鼠标光标相对于窗口左上角的位置坐标。

2. 利用键盘实现橡皮筋技术
对于程序3-1,可以用键盘上的“p”键代替鼠标左键,此时需要在main函数中加入键盘输入注册函数,并用下面的代码代替MousePlot函数,可以实现同样的效果。

程序3-2 OpenGL中利用键盘实现橡皮筋技术的例子

void Key(unsigned char key, int x, int y)
{
 switch(key){
  case 'p':
   if(iPointNum = = 0 || iPointNum = = 2) {
    iPointNum = 1;
    x1 = x; y1 = winHeight - y;
   }
   else {
    iPointNum = 2;
    x2 = x; y2 = winHeight - y;
    glutPostRedisplay();
   }
   break;
  default: break;
 } 
}


另外,还可以用下面的函数指定功能键、方向键及其他特殊键的回调函数:
glutSpecialKeyFucn(SpecialKeys);
被指定的函数有三个参数:
void SpecialKeys(int key, int x, int y);
其中,参数key的取值为按下特定键对应的GLUT_KEY_常量,(x,y)则是按下键盘时窗口中当前鼠标光标相对于窗口左上角的位置坐标。

3.5 拾取操作的实现

OpenGL中采用一种比较复杂的方式实现了拾取操作,即选择模式。选择模式是一种绘制模式,它基本思想是在一次拾取操作时,系统根据拾取操作的参数(如鼠标位置)生成一个特定视景体,然后由系统重新绘制场景中的所有图元,但这些图元并不会绘制到颜色缓存中,系统跟踪有哪些图元绘制到了这个特定的视景体中,并将这些对象的标识符保存到拾取缓冲区数组中。
在OpenGL中实现拾取操作主要包括以下步骤。

1.设置拾取缓冲区
拾取时,在特定的视景体中绘制每个对象都会产生一个命中消息,命中消息将存放在一个名字堆栈中,这个名字堆栈就是拾取缓冲区。函数:
void glSelectBuffer(GLsizei n, GLunint *buff);
指定了一个具有n个元素的整形数组buffer作为拾取缓冲区。对于每个命中消息,都会在拾取缓冲区数组中添加一条记录,每条记录包含了以下的信息:
(1)命中发生时堆栈中的名称序号;
(2)拾取图元所有顶点的最大和最小窗口z坐标。这两个值的范围都位于[0,1]内,他们都乘以232-1,然后四舍五入为最接近的无符号整数。
(3)命中发生时堆栈中的内容,最下面的名称排在最前面。

2.进入选择模式
在定义了拾取缓冲区后,需要激活选择模式。选择模式的指定采用函数:
GLint glRenderMode(GLenum mode);
其中,参数mode值可以为GL_RENDER(默认值)、GL_SELECT或GL_FEEDBACK,分别指定应用程序处于渲染模式、选择模式和反馈模式。应用程序一直处于当前模式下,直到调用本函数改变为其他模式为止。

3.名字堆栈操作
在选择模式下,需要对名字堆栈进行一系列操作,包括初始化、压栈、弹栈以及栈顶元素操作等。
void glInitNames();//初始化名字堆栈,其初始状态为空
void glPushName(GLuint name);//将一个名字压入堆栈,其中name是标识图元的一个无符号整数值
void glLoad Name(GLuint name);//将名字堆栈的栈顶元素替换为name
void glPopName();//将栈顶元素弹出

4.设置合适的变换过程
拾取操作可以通过矩形拾取窗口来实现,我们可以用下面的函数调用:
gluPickMatrix(xPick, yPick, widthPick, heightPick, *vp);
其中参数xPick和yPick指定相对于显示区域左下角的拾取窗口中心的双精度浮点屏幕坐标值。当使用鼠标进行选择操作时,xPick和yPick由鼠标位置确定,但要注意y坐标的反转。参数widthPick和heightPick指定拾取窗口的双精度浮点宽高值。参数vp指定了一个包含当前显示区域的坐标位置和尺寸等参数的整型数组,该参数可以通过函数glGetIntegerv来获得。这个函数可以设置一个用于拾取操作的观察空间。

5.为每个图元分配名字并绘制
为了标识图元,在图元绘制过程中需要用一个整型值指定图元的名称,并在选择模式下,将这个名字压入到名字堆栈中。为了节省名字堆栈的空间,应该在图元绘制完成后,将其名字从堆栈中弹出。

6.切换回渲染模式
在选择模式下,所有的图元绘制完成后,应该再次调用函数glRenderMode选择渲染模式,在帧缓冲存储器中绘制图元,并返回被选中图元的个数。

7.分析选择缓冲区中的数据
拾取操作完成之后,可以根据选择缓冲区中的内容进行分析,以确定拾取的图元。

程序3-3 OpenGL实现的拾取操作的例子

#include <gl/glut.h>
#include "stdio.h"
const GLint pickSize = 32;
int winWidth = 400, winHeight = 300;
void Initial(void)
{
 glClearColor(1.0f, 1.0f, 1.0f, 1.0f);         
}
void DrawRect(GLenum mode)
{
 if(mode == GL_SELECT) glPushName(1); //压入堆栈
 glColor3f(1.0f,0.0f,0.0f);
 glRectf(60.0f,50.0f,150.0f,150.0f);
 
if(mode == GL_SELECT) glPushName(2); //压入堆栈
glColor3f(0.0f,1.0f,0.0f);
glRectf(230.0f,50.0f,330.0f,150.0f);
if(mode == GL_SELECT) glPushName(3); //压入堆栈
glColor3f(0.0f,0.0f,1.0f);
glRectf(140.0f,140.0f,240.0f,240.0f);
}
void ProcessPicks(GLint nPicks, GLuint pickBuffer[])
{
 GLint i;
 GLuint name, *ptr;
printf("选中的数目为%d个\n",nPicks);
ptr=pickBuffer;
for(i=0;i<nPicks; i++){
 name=*ptr;    //选中图元在堆栈中的位置
 ptr+=3;       //跳过名字和深度信息
 ptr+=name-1;  //根据位置信息获得选中的图元名字
 if(*ptr==1) printf("你选择了红色图元\n");
 if(*ptr==2) printf("你选择了绿色图元\n");
 if(*ptr==3) printf("你选择了蓝色图元\n");
 ptr++;
}
 printf("\n\n");
}
void ChangeSize(int w, int h)
{
winWidth = w;
winHeight = h;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION); 
glLoadIdentity();
gluOrtho2D(0.0,winWidth,0.0,winHeight);
}
void Display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
DrawRect(GL_RENDER);
glFlush();
}
void MousePlot(GLint button, GLint action, GLint xMouse, GLint yMouse)
{
GLuint pickBuffer[pickSize];
GLint nPicks, vp[4];
if(button == GLUT_LEFT_BUTTON && action == GLUT_DOWN){
 glSelectBuffer(pickSize,pickBuffer); //设置选择缓冲区
glRenderMode(GL_SELECT); //激活选择模式
glInitNames();   //初始化名字堆栈
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glGetIntegerv(GL_VIEWPORT, vp);
//定义一个10×10的选择区域
gluPickMatrix(GLdouble(xMouse), GLdouble(vp[3]-yMouse),10.0,10.0,vp);
gluOrtho2D(0.0,winWidth,0.0,winHeight);
DrawRect(GL_SELECT);
//恢复投影变换
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glFlush();
//获得选择集并输出
nPicks = glRenderMode(GL_RENDER);
ProcessPicks(nPicks, pickBuffer);
glutPostRedisplay();
}
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);  
glutInitWindowSize(400,300);                  
glutInitWindowPosition(100,100);              
glutCreateWindow("拾取操作");                   
glutDisplayFunc(Display);
glutReshapeFunc(ChangeSize);
glutMouseFunc(MousePlot);
Initial();                                    
glutMainLoop();                               
return 0;
}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics