In the Qt Forums I found this question and sparkled my curiosity.
I found a very simple example in Qt to display a cube and modified the cube logic to create a cube with a side length of 1 unit.
Then I tried to click on the model and show the coordinates of the area where I clicked.
When there are no rotations involved, it seems that the picking works fine. But! If I un-comment the rotations (or one of them) in paintGL
I end up with "wrong" values.
For example:
gluUnProject
method says that I clicked on the point { -0.49075, 0.234, 0.5 }
, which seems ok.{ -0.501456, 0.157555, -0.482942 }
, which seems wrong. The x
coordinate is off its range of -0.5, 0.5
.I think the coordinates transformation is okay. I've been researching through Google and the people always use the same piece of code. Moreover, the color information of the pixel matches the color of the face where I clicked.
So, can anyone tell me why when a rotation is involved I get wrong coordinates? I think I'm failing in some basic 3D understanding, but I'm not able to realize where it is.
Here's the code:
main.cpp:
#include <QApplication>
#include "GLCube.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
GLCube w;
w.resize(800,600);
w.show();
return a.exec();
}
GLCube.h
#ifndef GLCUBE_H
#define GLCUBE_H
#include <QtOpenGL>
#include <QGLWidget>
class GLCube : public QGLWidget{
Q_OBJECT // must include this if you use Qt signals/slots
public:
GLCube(QWidget *parent = NULL)
: QGLWidget(parent) {
}
protected:
// Set up the rendering context, define display lists etc.:
void initializeGL();
// draw the scene:
void paintGL();
// setup viewport, projection etc.:
void resizeGL (int width, int height);
virtual void mousePressEvent(QMouseEvent *pme);
};
#endif
GLCube.cpp:
#include "GLCube.h"
#include <cmath>
#include <iostream>
#include <iomanip>
#include "GL/glu.h"
namespace {
float ver[8][3] =
{
{ 0.5, -0.5, 0.5 },
{ -0.5, -0.5, 0.5 },
{ -0.5, -0.5, -0.5 },
{ 0.5, -0.5, -0.5 },
{ 0.5, 0.5, 0.5 },
{ -0.5, 0.5, 0.5 },
{ -0.5, 0.5, -0.5 },
{ +0.5, 0.5, -0.5 }
};
GLfloat color[8][4] =
{
{0.0,0.0,0.0, 1.0},
{1.0,0.0,0.0, 1.0 },
{1.0,1.0,0.0, 1.0 },
{0.0,1.0,0.0, 1.0 },
{0.0,0.0,1.0, 1.0 },
{1.0,0.0,1.0, 1.0 },
{1.0,1.0,1.0, 1.0 },
{0.0,1.0,1.0, 1.0 },
};
void quad(int a,int b,int c,int d, int col)
{
glPointSize( 5 );
glBegin(GL_POINTS);
glColor4fv(color[1]);
glVertex3fv(ver[a]);
glColor4fv(color[2]);
glVertex3fv(ver[b]);
glColor4fv(color[3]);
glVertex3fv(ver[c]);
glColor4fv(color[4]);
glVertex3fv(ver[d]);
glEnd();
glBegin(GL_LINES);
glColor4fv(color[1]);
glVertex3fv(ver[a]);
glVertex3fv(ver[b]);
glColor4fv(color[1]);
glVertex3fv(ver[b]);
glVertex3fv(ver[c]);
glColor4fv(color[1]);
glVertex3fv(ver[c]);
glVertex3fv(ver[d]);
glColor4fv(color[1]);
glVertex3fv(ver[d]);
glVertex3fv(ver[a]);
glEnd();
glBegin(GL_QUADS);
glColor4fv(/*color[a]*/ color[col] );
glVertex3fv(ver[a]);
// glColor3fv(color[b]);
glVertex3fv(ver[b]);
// glColor3fv(color[c]);
glVertex3fv(ver[c]);
// glColor3fv(color[d]);
glVertex3fv(ver[d]);
glEnd();
}
void colorcube()
{
// glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
quad( 3, 2, 1, 0, 1 ); // bottom
quad( 7, 6, 5, 4, 2); // top
quad( 6, 2, 1, 5, 3 ); // front
quad( 7, 3, 0, 4, 5 ); // back
quad( 7, 6, 2, 3, 6 ); // left
quad( 4, 5, 1, 0, 7); // right
}
}
/*
* Sets up the OpenGL rendering context, defines display lists, etc.
* Gets called once before the first time resizeGL() or paintGL() is called.
*/
void GLCube::initializeGL(){
//activate the depth buffer
glEnable(GL_DEPTH_TEST);
qglClearColor(Qt::black);
glEnable(GL_CULL_FACE);
}
/*
* Sets up the OpenGL viewport, projection, etc. Gets called whenever the widget has been resized
* (and also when it is shown for the first time because all newly created widgets get a resize event automatically).
*/
void GLCube::resizeGL (int width, int height){
glViewport( 0, 0, (GLint)width, (GLint)height );
/* create viewing cone with near and far clipping planes */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum( -1.0, 1.0, -1.0, 1.0, 15.0, 30.0);
glMatrixMode( GL_MODELVIEW );
}
/*
* Renders the OpenGL scene. Gets called whenever the widget needs to be updated.
*/
void GLCube::paintGL(){
//delete color and depth buffer
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0f,0.0f,-20.0f); //move along z-axis
glRotatef(30.0,0.0,1.0,0.0); //rotate 30 degress around y-axis
glRotatef(15.0,1.0,0.0,0.0); //rotate 15 degress around x-axis
colorcube();
// originalcube();
}
void GLCube::mousePressEvent(QMouseEvent *pme) {
GLint viewport[4];
GLdouble modelview[16];
GLdouble projection[16];
glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
glGetDoublev( GL_PROJECTION_MATRIX, projection );
glGetIntegerv( GL_VIEWPORT, viewport );
const int x = pme->x();
const int y = viewport[3] - pme->y();
qDebug() << "HERE: " << x << y;
GLfloat color[4];
glReadPixels( x, y, 1, 1, GL_RGBA, GL_FLOAT, color);
GLenum error = glGetError();
std::cout << "RETRIEVED COLOR:" << color[0] << ", " << color[1] << ", " << color[2] << ", " << color[3] << std::endl;
printf( "\tERROR: %s (Code: %u)\n", gluErrorString(error), error );
if(GL_NO_ERROR != error) throw;
GLdouble depthScale;
glGetDoublev( GL_DEPTH_SCALE, &depthScale );
std::cout << "DEPTH SCALE: " << depthScale << std::endl;
GLfloat z;
glReadPixels( x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z );
error = glGetError();
std::cout << "X: " << x << ", Y: " << y << ", RETRIEVED Z: " << z << std::endl;
printf( "\tERROR: %s (Code: %u)\n", gluErrorString(error), error );
if(GL_NO_ERROR != error) throw;
std::cout << std::endl << std::endl;
GLdouble posX, posY, posZ;
GLint result;
result = gluUnProject( x, y, z, modelview, projection, viewport, &posX, &posY, &posZ);
error = glGetError();
std::cout << "3D point with POS: " << posX << " " << posY << " " << posZ << std::endl;
printf( "\tERROR: %s (Code: %u)\n", gluErrorString(error), error );
std::cout << "\tglUnProject: " << (( GL_FALSE == result ) ? "FALSE" : "TRUE") << std::endl;
if(GL_NO_ERROR != error) throw;
}
NOTE:
I'm running this in Windows 8.1 64 bit, Qt 4.8 and MinGW.
Also, both glReadPixels
and gluUnProject
exit without errors (ERROR CODE = GL_NO_ERROR
)
And glut
is not available. Only what OpenGL
, QtOpenGL
and/or glu
offer.
It is not really wrong, you are reading the depth buffer to figure out the window-space Z value to use for reverse projection.
The problem is that there is limited precision available in the depth buffer, and that introduces a bit of inaccuracy. In reality you cannot expect the range of unprojected values to be perfectly [-0.5,0.5]. You are going to have to introduce a small epsilon here, so your effective range would then be something like [-0.5015,0.5015].
You could probably lessen the impact by increasing the precision of your depth buffer and/or decreasing the range between the near and far clip planes. The depth buffer is generally 24-bit fixed-point by default, but a 32-bit fixed-point or floating-point depth buffer might slightly improve your situation. However, you are never going to completely eliminate this problem.