Sign in to follow this  
OpenGL_Guru

gluUnProject and multiple buttons

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
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
Quote:
Original post by swiftcoder
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.
[lol] That is absolutely correct! Sorry for forgetting the obvious and leading you astray, OpenGL_Guru. [embarrass]

Share this post


Link to post
Share on other sites
Quote:
Original post by Kalidor
Quote:
Original post by swiftcoder
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.
[lol] That is absolutely correct! Sorry for forgetting the obvious and leading you astray, OpenGL_Guru. [embarrass]


thanks all... the code that i posted in the previous post is what i am implementing now and it works great. i push the matrix, loadidentity and and then send the mouse coordinates to my gluUnProject code where i set the projection matrix to ortho mode.. i will repeat it here...

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

setting the projection matrix to ortho mode allows me to check pixel cooridates against the buttons that i drew also in ortho mode. As swiftcoder suggested i shouldnt need to call gluUnProject if i am already in ortho mode..is this what you mean? just want to make sure. retrieving the pixel coordinates and checking against the buttons as it stands now to see if there is any match and this works perfectly. i had to do some testing to make sure that i was getting back the correct results. my ortho code is only accessible right now from the class its in and from a class that inherits the class that its in. right now main.cpp doesnt have access to it but i guess your suggestion might be easier if i did [smile]. thanks swift, i will update you on what happens. after all this i should post the .exe so you can just test it out if someone has some room to host.

I have one last question. i am drawing my 2d buttons and slider bars etc in ortho as stated but now i have to come to the problem of resizing the screen. when i call glGetIntegerv(GL_VIEWPORT, view); (which is in my setOrthoProj() code i will get the new w & h of the screen..view[2] and view[3]. currently i am hardcoding all my buttons, sliders and stuff assuming that i am at 1280x1024 and i know thats a no-no but i just didnt think about it until now. is there a way to manipulate the ortho projection drawing of this stuff without having to test the window coorindates every time? it would just be easier i think to draw the gui in pixel coordinates rather than being confused by drawing a button with respect to the viewing ratio of the screen. i hope that makes sense. any other way i could do this? thanks!!

Share this post


Link to post
Share on other sites
Quote:
Original post by OpenGL_Guru
As swiftcoder suggested i shouldnt need to call gluUnProject if i am already in ortho mode..is this what you mean?
If you're drawing your GUI in screenspace (an orthographic projection from (0,0) to (w,h)) than you don't need to unproject the points whether or not you have an orthographic projection set. This is because the mouse coordinates are already in screen coordinates, so they're in the same space that the GUI was drawn in. However if you set up an orthographic projection to render your GUI in relative positions (like I had mentioned in an earlier post) you will still need to unproject the mouse coordinates to get them in the same space as the GUI.
Quote:
Original post by OpenGL_Guru
I have one last question. i am drawing my 2d buttons and slider bars etc in ortho as stated but now i have to come to the problem of resizing the screen. when i call glGetIntegerv(GL_VIEWPORT, view); (which is in my setOrthoProj() code i will get the new w & h of the screen..view[2] and view[3]. currently i am hardcoding all my buttons, sliders and stuff assuming that i am at 1280x1024 and i know thats a no-no but i just didnt think about it until now. is there a way to manipulate the ortho projection drawing of this stuff without having to test the window coorindates every time? it would just be easier i think to draw the gui in pixel coordinates rather than being confused by drawing a button with respect to the viewing ratio of the screen. i hope that makes sense. any other way i could do this? thanks!!
This is where relative coordinates are useful. If you want your GUI elements to be a constant pixel size and position, then you set an orthographic projection which corresponds to screen space (like you are doing now). If you want them to be a constant size on screen then you specify a constant sized orthographic projection and specify the GUI elements relative to that size. So if you don't want to change the hardcoding to 1280x1024 (you should though) and you want to support resolution changes while keeping the GUI elements the same size on screen, then just always specify the orthographic projection from (0,0) to (1280,1024). Again, keep in mind that if you use relative coordinates you will need to unproject the mouse coordinates.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this