Jump to content
  • Advertisement
Sign in to follow this  
OpenGL_Guru

gluUnProject and multiple buttons

This topic is 4379 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi all, i have an interactive gui that i am trying to write to familiarize myself with writing a custom gui app. i see to have run into a hassle/problem using gluUnProject and coordinates systems. I think the problem also comes into effect because ive always had to do a glTranslate into the screen for me to see any 2D stuff that i want to draw...maybe i dont need to do this and just need some help in this area. I have multiple gui buttons and each are drawn to the screen in different places. here is some sample code of a button that i draw along with a border to make it look nice and easy to see. As i mentioned before i always have to translate into the screen it seems when i want to see anything 2D. this is causing me problem using gluUnProject as i will point out shortly. in main somewhere: gui->drawbutton(); ------------------------- in gui class function, draw_button():

//draw the border of the button

glPushMatrix();
    glLoadIdentity();
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    //glDisable(GL_LIGHTING);
    glColor3f(1, 0.5, 0);
    glTranslatef(-0.80, 1.1, -3.0);
    
    glBegin(GL_QUADS);
     glVertex2f(1.799, 0.0); 
     glVertex2f(1.799, 0.101);
     glVertex2f(2.301, 0.101);
     glVertex2f(2.301, 0.0);
    glEnd();
       
    //glEnable(GL_LIGHTING);
    
glPopMatrix();

//now draw button and assign the texture

glPushMatrix();
   glLoadIdentity();
   glColor4f(1, 1 ,1, 1);
   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
   glBindTexture(GL_TEXTURE_2D, texture);
   glTranslatef(-0.80, 1.1, -3.0);
   
   glEnable(GL_TEXTURE_2D);
   
   glBegin(GL_QUADS);
    glTexCoord2f(0, 0);
    glVertex2f(1.8, 0.0); 
    glTexCoord2f(0, 1);
    glVertex2f(1.8, 0.1);
    glTexCoord2f(1, 1);
    glVertex2f(2.3, 0.1);
    glTexCoord2f(1, 0);
    glVertex2f(2.3, 0.0);
  glEnd();
   
  glDisable(GL_TEXTURE_2D);
  
 glPopMatrix();   

now here is a gluUnProject function to get the coordinates that i need(i keep forgetting which is screen and world coordinates) but i think i am trying to get world coordinates. code:
void gui::convert_pixels_button(int x, int y, GLdouble *obj)
{ 
  GLdouble model[16], proj[16];
  GLint view[4];
  GLfloat z;
  glTranslatef(-0.80, 1.1, -3.0);	
  glGetDoublev(GL_MODELVIEW_MATRIX, model); 
  glGetDoublev(GL_PROJECTION_MATRIX, proj);
  glGetIntegerv(GL_VIEWPORT, view); //0, 0, w, h
 
  y = view[3] - (GLfloat) y - 1;

  glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z); //get the pixel(in 2D) w/respect to 3D Coordinates

 // if(z == 1.0)    
 //return FALSE;
  
  gluUnProject(x, y, z, model, proj, view, obj, obj+1, obj+2);

}

through trial and error i discovered that in this function as you see, if i didnt include the gltranslate function i would never get a 'hit' when trying to see if i have clicked on the button. The problem that i have is that every button that i want to draw or any little box on the screen that i want to draw i do a DIFFERENT glTranslate function for it and thus i am ending up having to make a separate function like the one above so i can get a correct 'hit'. Eventually in my glutmouse function i look to a mouseover function that sends back the callbacks so i can then do the appropriate task with whatever callback is returned. However, since i am faced with these blasted glTranslates i got to think i am doing it all wrong and dont need this and multiple functions to do the same thing seems overkill. In conclusion how can i draw all my 2D gui stuff all over the screen and just have one function with the gluUnProject function in it? i hope i have been clear and if you have any questions please let me know..and once again thanks for your help!

Share this post


Link to post
Share on other sites
Advertisement
How do you set up your orthographic projection? You should use -1.0 for near plane and 1.0 for the far plane (or use gluOrtho2D, it does the same thing). Then render your 2D objects on the z=0 plane and it will be visible without needing to translate on the z-axis. It's quite common to set it up so that it represents screen coordinates as well, then you just specify your objects' positions in screen coordinates and you don't have to do any translating at all.

Share this post


Link to post
Share on other sites
Quote:
Original post by Kalidor
How do you set up your orthographic projection? You should use -1.0 for near plane and 1.0 for the far plane (or use gluOrtho2D, it does the same thing). Then render your 2D objects on the z=0 plane and it will be visible without needing to translate on the z-axis. It's quite common to set it up so that it represents screen coordinates as well, then you just specify your objects' positions in screen coordinates and you don't have to do any translating at all.


well i definitely think then that thats my problem. the only time i seem to use ortho mode is when i am printing out 2D text to the screen. here is my ortho code:


void setOrthoProj()
{
//GLint view[4];
glGetIntegerv(GL_VIEWPORT, view);

// switch to projection mode
glMatrixMode(GL_PROJECTION);
// save previous matrix which contains the
//settings for the perspective projection
glPushMatrix();
// reset matrix
glLoadIdentity();
// set a 2D orthographic projection
gluOrtho2D(0, view[2], 0, view[3]);
// invert the y axis, down is positive
glScalef(1, -1, 1);
// mover the origin from the bottom left corner
// to the upper left corner
glTranslatef(0, -view[3], 0);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
}

void resetPerspProj()
{
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}





//set up to print 2D text

void print_text()
{
char string[40];

glPushMatrix();
glDisable(GL_LIGHTING);
glColor3f(1, 1, 1);
setOrthoProj();
sprintf(string, "%s", "This is a test ", mystring);
glRasterPos2f(view[2]/128, 30);
resetPerspProj();

glEnable(GL_LIGHTING);
glPopMatrix();
}




so it looks like i have my ortho set up to go from my width and height from the main program.. in this case w = 1280(view[2]) and h = 1024(view[3]. So should i go ahead and make it: gluOrtho2D(0, -1, 0, 1); ?? I did it previously based on resolution so everything would be the same if ever resized the screen or not.

Anyway everything else looks like i am rendering in perspective mode per my basic resize function here:


void resize(GLsizei width, GLsizei height)
{
if (height==0) // Prevent A Divide By Zero By
{ height=1;} // Making Height Equal One
glViewport(0,0,width,height); // Reset The Current Viewport
glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
glLoadIdentity(); // Reset The Projection Matrix
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,1.0f,1000.0f);
glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
glLoadIdentity(); // Reset The Modelview Matrix

//glutPostRedisplay(); //glut will do this by default
}





and...


void draw()
{
glLoadIdentity();
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // Clear The Screen And The Depth Buffer
...
....
...draw_button();
....
}




so i guess i am asking what steps i can take to keep the functionality that i have now and still just switch everything to ortho mode. should i keep my ortho functions above in main at all times?

if i kept my ortho code based on resoultion then i guess my vertex calls to draw the buttons and also the callback function to retrieve matches would also be have to set in terms of 1280 to 1024 correct?

The ortho code i gave you above is just in my text class. i have a separate text class for drawing 2D text.. nothing else. i like to keep my functions/classes short and sweet and when i ever need to expand then i do it. thanks again for your help Kalidor!

Share this post


Link to post
Share on other sites
You should definitely be using an orthographic projection for all your 2D rendering. If you want to position your 2D objects in screen space then use the screen resolution for the width and height.
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
//Now you can position your 2D objects in exact pixel coordinates
If you want to position them in relative coordinates then you can use a constant for the width and height.
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
//Now you position your 2D objects by some percentage
//glVertex2f(0.1f, 0.2f) will be placed at 10% along the
//x-axis and 20% along the y-axis
If you want the top-left to be the origin, don't bother scaling and translating the projection matrix, just swap the bottom and top parameters to glOrtho.
glOrtho(0.0, width, height, 0.0, -1.0, 1.0);
//Similar to the first example except (0,0) is at the
//top-left and +y goes down
If you have face culling enabled you will either have to send the vertices in the opposite winding, switch which faces get culled, or change which winding represents front faces.

So to recap, the general order of things in your render loop should be...
 · Set up perspective projection (or just do it on window resize
so that it is set up at the beginning of the render function)
· Render 3D scene
· Disable depth test (or just clear depth buffer if your 2D
rendering uses it)
· Push the projection matrix stack so you can easily revert to the
original perspective projection
· Set up proper orthographic projection
· Render 2D objects
· Pop the projection matrix stack

Share this post


Link to post
Share on other sites
Quote:
Original post by Kalidor
You should definitely be using an orthographic projection for all your 2D rendering. If you want to position your 2D objects in screen space then use the screen resolution for the width and height.
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
//Now you can position your 2D objects in exact pixel coordinates
If you want to position them in relative coordinates then you can use a constant for the width and height.
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
//Now you position your 2D objects by some percentage
//glVertex2f(0.1f, 0.2f) will be placed at 10% along the
//x-axis and 20% along the y-axis
If you want the top-left to be the origin, don't bother scaling and translating the projection matrix, just swap the bottom and top parameters to glOrtho.
glOrtho(0.0, width, height, 0.0, -1.0, 1.0);
//Similar to the first example except (0,0) is at the
//top-left and +y goes down
If you have face culling enabled you will either have to send the vertices in the opposite winding, switch which faces get culled, or change which winding represents front faces.

So to recap, the general order of things in your render loop should be...
 · Set up perspective projection (or just do it on window resize
so that it is set up at the beginning of the render function)
· Render 3D scene
· Disable depth test (or just clear depth buffer if your 2D
rendering uses it)
· Push the projection matrix stack so you can easily revert to the
original perspective projection
· Set up proper orthographic projection
· Render 2D objects
· Pop the projection matrix stack


thanks for your help, i really appreciate it. since i posted last i did a test using my ortho code just using glOrtho2D, which is like your first example, and then in my 2D text class quickly drew a 2D quad on the screen:
glBegin(GL_QUADS);
glVertex2f(500.0, 500.0);
glVertex2f(500.0, 600.0);
glVertex2f(600.0, 600.0);
glVertex2f(600.0, 500.0);
glEnd();





you have to remember that my res is 1280 x 1024 and it worked perfectly. i think i would rather do this(your first example) than anything else.. that way i can place them perfectly in pixel coordinates. is glOrtho2D ok though? you just put glOrtho as your example. i have one question that goes along with this now if you dont mind involving my gluUnProject.

In my main program is where i check to see if i have clicked on a certain button. before i call the convert_pixels function in my original post should i also then go into ortho mode to check or how should that work? here is the code again with the gltranslate commented out:


void gui::convert_pixels_button(int x, int y, GLdouble *obj)
{
GLdouble model[16], proj[16];
GLint view[4];
GLfloat z;
//glTranslatef(-0.80, 1.1, -3.0); //dont need this anymore
glGetDoublev(GL_MODELVIEW_MATRIX, model);
glGetDoublev(GL_PROJECTION_MATRIX, proj);
glGetIntegerv(GL_VIEWPORT, view); //0, 0, w, h

y = view[3] - (GLfloat) y - 1;

glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z); //get the pixel(in 2D) w/respect to 3D Coordinates

// if(z == 1.0)
//return FALSE;

gluUnProject(x, y, z, model, proj, view, obj, obj+1, obj+2);

}






this will give me back my x, y coordinates that will go back to main and main will then use these to send to a function whos job is to check against the x,y pair to assign a certain callback. so i need to make sure i guess that the x,y is in pixel coordinates correct? is the code now as is ok..and to reiterate before i call this function(the convert pixels) should i also change to ortho mode(in main) and then when i am done go back to perspective(in main)? thanks again for your time, rating++

Share this post


Link to post
Share on other sites
Quote:
Original post by OpenGL_Guru
...is glOrtho2D ok though? you just put glOrtho as your example...
Yep, gluOrtho2D is exactly the same as calling glOrtho with zNear and zFar set to -1.0 and 1.0, respectively.

Your gluUnProject code looks fine. You just have to remember to use the correct matrices with it (ie: the orthographic projection matrix, probably identity modelview matrix). To do that you can either store the orthographic matrix you create to use it later in that function, call that unprojection function from inside your render loop when the orthographic matrix is set, or set the same orthographic matrix before you call glGetDoublev(GL_PROJECTION_MATRIX, proj).

Share this post


Link to post
Share on other sites
Quote:
Original post by Kalidor
Quote:
Original post by OpenGL_Guru
...is glOrtho2D ok though? you just put glOrtho as your example...
Yep, gluOrtho2D is exactly the same as calling glOrtho with zNear and zFar set to -1.0 and 1.0, respectively.

Your gluUnProject code looks fine. You just have to remember to use the correct matrices with it (ie: the orthographic projection matrix, probably identity modelview matrix). To do that you can either store the orthographic matrix you create to use it later in that function, call that unprojection function from inside your render loop when the orthographic matrix is set, or set the same orthographic matrix before you call glGetDoublev(GL_PROJECTION_MATRIX, proj).


this is what i am doing in my main.cpp under the mouse function..


void mouse(int button, int button_state, int x, int y)
{
....
....
if(button_down)
{
glPushMatrix();
glLoadIdentity();
gui->convert_pixels_button(x, y, button);
glPopMatrix();

callback = gui->mouseover(button[0], button[1]);

switch(callback)
{
//do stuff here
}
}
}



my question is should i be pushing the matrix here and calling load identity? if this is correct i assume you were saying to do this inside the convert function:

....
....
glGetDoublev(GL_MODELVIEW_MATRIX, model);
setOrthoProj()
glGetDoublev(GL_PROJECTION_MATRIX, proj);
resetPerspProj();
....
....

and everything else in the function stays the same? i would rather not have any projection changes inside my main render loop in main.cpp. sorry i am asking so many questions..i just want to make sure i am doing this right.. thanks again!

Share this post


Link to post
Share on other sites
Wouldn't it be easier to use colors as button identifier instead of this whole push/pop stack of names an so on. Lighthouse3d.com has a good tutorial on picking in OpenGL

PS: Sorry, but I haven't read all previous posts, so bare with me if my post is a repost.

Share this post


Link to post
Share on other sites
The main thing that you have to remember is that the modelview and projection matrices you pass to gluUnProject must be the same as the ones used for rendering whatever it is you're checking for intersections (in this case, your GUI). Exactly how you get these matrices is a design decision and depends on the overall design of your engine. Setting the projection matrix to the same orthographic matrix used for rendering and then using glGet* is one possible way.

Another, more abstracted, way is to have various "camera" objects which encompass the view and projection matrices. These would be used for the player's current first/third person view, maybe for lights with shadowmapping, for the scope of a sniper rifle, for security cameras littered through your levels, etc. Then you can have one of these cameras be used for rendering the GUI, in which case you just pass the GUI camera's view and projection matrices directly to gluUnProject with no need to first use glGet*.

Like I said, it all depends on the overall design of your engine and how you feel it would best fit into that design. Just remember it has to use the same matrices as used for rendering.
Quote:
Original post by OpenGL_Guru
...
i would rather not have any projection changes inside my main render loop in main.cpp.
...
I'm not sure exactly what you mean here. Changing the projection matrix is just as fast as changing the modelview matrix (it is very fast). You obviously don't want to do it anymore than necessary, but don't be afraid of changing it when needed. You will be changing it to render the GUI in an orthographic projection anyway. You also don't need to change the projection matrix to use gluUnProject, you can just store the correct matrix somewhere (such as a GUI camera object) and pass that directly to the function.
Quote:
Original post by Red_falcon
Wouldn't it be easier to use colors as button identifier instead of this whole push/pop stack of names an so on.
That is certainly another way of doing picking in OpenGL, but that involves rendering the GUI a second time, and reading back the color from the framebuffer where the mouse is, which stalls the pipeline. It is more efficient and more flexible to do your own intersection tests on the CPU.

Share this post


Link to post
Share on other sites
Hang on, hang on...
As far as I can tell, you are using glUnProject() to find out which button the users cas clicked, is that correct?
But now that you are rendering your buttons in 2D screen coordinates, you don't need to do this, just check the mouse coordinates against the pixel rectangle of each button instead.
This should be much simpler and faster.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!