1

I have a scene that I am rendering couple of cubes in it with openGL (program structure is not using GLUT,it is in win32 program structure but I just draw cubes with glutSolidCube) now I want to select these cubes by mouse by picking. this is what I am doing: first when the user clicks the mouse button on the scene I get the mouse position and trying to find its coordinates in scene coordinates (templateSkeletons is a skeleton I created out of cubes, nothing more):

if (mouse.buttonPressed(Mouse::BUTTON_LEFT))
        {
            mouse.update();
            templateSkeletons[0].selectionMode = true;
            Vector3* points;
            points = GetOGLPos();
            templateSkeletons[0].setIntersectionPoints(points[0],points[1]);
        }else
            templateSkeletons[0].selectionMode = false;

this is the GerOGLPos function that I am retrieving the coordinates in the scene (notice that I have my own camrea and its own projection matrix but I am fetching projection matrix here in this function just by calling glGetDoublev (GL_PROJECTION_MATRIX, projmatrix); is this wrong and I should get my own camrea's projection matrix ? ) :

Vector3* GetOGLPos()
  {Vector3 pointsOnLine[2];
double mvmatrix[16];
double projmatrix[16];
int viewport[4];
double dX, dY, dZ, dClickY,zz;  
glGetIntegerv(GL_VIEWPORT, viewport);   
glGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix);
glGetDoublev (GL_PROJECTION_MATRIX, projmatrix);
dClickY = double (viewport[3] - mouse.yPos()); 
// OpenGL renders with (0,0) on bottom, mouse reports with (0,0) on top
//glReadPixels( mouse.xPos(), int(dClickY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &zz );
gluUnProject ((double) mouse.xPos(), dClickY, 0.0, mvmatrix, projmatrix, viewport, &dX, &dY, &dZ);
pointsOnLine[0] = Vector3( (float) dX, (float) dY, (float) dZ );
gluUnProject ((double) mouse.xPos(), dClickY, 1.0, mvmatrix, projmatrix, viewport, &dX, &dY, &dZ);
pointsOnLine[1]  = Vector3( (float) dX, (float) dY, (float) dZ );

    return pointsOnLine;
       }

now I suppose I have two points indicating my picking ray in the scene. now when I render the cubes I try to calculate the distance of the line created by the ray and the cube, and if it is smaller than a value I change the color of the cube to know that I picked it (jointsOfSkeleton indicates each cube that is created the skeleton nothing more, in here I am testing just the cube number 6 in array ):

 if(selectionMode)
        {

            distToLine  = Vector3::PointToLineDistance3D(rayPoints[0],rayPoints[1],Vector3::Vector3(jointsOfSkeleton[6].x,
                jointsOfSkeleton[6].y,jointsOfSkeleton[6].z));
            //distToLine = sqrt(distToLine);
            if(distToLine < 0.5)
                glColor3f(1.0,0.0,0.0);

            else 
                glColor3f(1.0,1.0,1.0);
        }

when I click on irrelevant positions on the window I see the colors of cube changes, it doesn't work right, I am watching the distances on the debugger and the distances doesn't look right. and this is function I used for finding Line-point distance:

static float PointToLineDistance3D(Vector3 a, Vector3 b, Vector3 point)
{   

    Vector3 lineDirection = b - a;
    float t = (Vector3::dot(point,lineDirection) - Vector3::dot(lineDirection,a))/(Vector3::dot(lineDirection,lineDirection));
    Vector3 direction;
    direction.x = a.x + (lineDirection.x *t) - point.x;
    direction.y = a.y + (lineDirection.y *t) - point.y;
    direction.z = a.z + (lineDirection.z *t) - point.z;

    float ShortestDistance = sqrtf((direction.x*direction.x)+(direction.y*direction.y)+(direction.z*direction.z));

    return ShortestDistance;

}
genpfault
  • 51,148
  • 11
  • 85
  • 139
user667222
  • 179
  • 3
  • 16

1 Answers1

2

This is how I would write the PointToLineDistance3D:

static float PointToLineDistance3D(const Vector3 &a, const Vector3 &b, const Vector3 &point){   
    Vector3 lineDirection = Vector3::normalize(b - a), pointDirection = point - a;
    float t = Vector3::dot(pointDirection,lineDirection);
    Vector3 projection = a + (lineDirection * t);

   float ShortestDistance = (projection - point).length();
   return ShortestDistance;
}

I made the assumption that:

  • the Vector3 class has a length method, with obvious meaning,
  • there's a * operator to scale vectors,
  • there's a normalize function as well which returns ... a normalized vector (this could also be made a method to avoid constructing an extra object).

The idea is to compute the projection of point on the ray and then compute the distance between the projection and point. As you can see the algorithm is slightly different from your implementation, most notably in the computation of t. Perhaps this is where your problem lies.

In order to test case the code I provided above, I wrote a small programme using it, which build a 3x3 wall of cubes on the XY plane, the centre of the wall being <0,0,0>. I managed to have it working without problem, even when moving the camera around. The only issue was with regard to the mouse coordinate system which goes top to bottom (ie. t_the Y mouse coord increases downward_), which is the opposit of the natural OpenGL Y axis. It requires the SDL library in order to compile and run.

#include <iostream>

#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL/SDL.h>
#include <unistd.h>

#include <Vector3.h>

#define WIDTH 800
#define HEIGHT 600

GLuint box;
int highlight[2]; // position of the cube in the wall
const float cube_width = 5.0; 

Vector3 position(0,0,-25); // camera position

// build the cube display list
void setup_cube(){

  const float w = cube_width;

  float w0 = -w, h0 = -w, w1 = w, h1 = w;

  box = glGenLists(1);
  glNewList(box, GL_COMPILE);
    glBegin(GL_QUAD_STRIP);
      glVertex3f(w0, h1, w0);
      glVertex3f(w0, h0, w0 );
      glVertex3f(w1, h1, w0 );
      glVertex3f(w1, h0, w0 );
      glVertex3f(w1, h1, w1 );
      glVertex3f(w1, h0, w1 );
      glVertex3f(w0, h1, w1 );
      glVertex3f(w0, h0, w1 );
    glEnd();
    glBegin(GL_QUAD_STRIP);
      glVertex3f(w1, h1, w0 );
      glVertex3f(w1, h1, w1 );
      glVertex3f(w0, h1, w0 );
      glVertex3f(w0, h1, w1 );
      glVertex3f(w0, h0, w0 );
      glVertex3f(w0, h0, w1 );
      glVertex3f(w1, h0, w0 );
      glVertex3f(w1, h0, w1 );
    glEnd();
  glEndList();
}

void setup_scene(){

  float r = WIDTH / HEIGHT;

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glFrustum( -r, r, -1, 1, 1, 1024);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  glTranslatef(position[0],position[1],position[2]);

  glEnable(GL_CULL_FACE);
  glEnable(GL_DEPTH_TEST);
}

void draw_scene(){

  const float w = cube_width;
  int i = 0, j = 0;

  for (int i = -1; i < 2; i++) {
    for (int j = -1; j < 2; j++) {
      float x = w * 2 * i,  y = w * 2 * j;

      if (highlight[0] == i && highlight[1] == j)
         glColor3f(0.0, 1.0, 0.0);
      else 
         glColor3f(1.0, 0.0, 0.0);

      glPushMatrix ();
         glTranslatef(x,y,0);
         glCallList(box);
      glPopMatrix ();
    }
  }
}

void aim(float xm, float ym_){

  const float w = cube_width;
  float ym = HEIGHT - ym_;

  GLdouble model[16];
  GLdouble proj[16];
  GLint view[16];

  glGetDoublev(GL_MODELVIEW_MATRIX, model);
  glGetDoublev(GL_PROJECTION_MATRIX, proj);
  glGetIntegerv(GL_VIEWPORT, view);
  highlight[0] = -5;
  highlight[1] = -5;

  for (int i = -1; i < 2; i++) {
    for (int j = -1; j < 2; j++) {
      float x = w * 2 * i, y = w * 2 * j;
      double ox, oy, oz;
      Vector3 centre(x,y,0);
      gluUnProject(xm, ym, 0, model, proj, view, &ox, &oy, &oz);
      Vector3 p0(ox,oy,oz);
      gluUnProject(xm, ym, 1, model, proj, view, &ox, &oy, &oz);
      Vector3 p1(ox,oy,oz);
      float d = PointToLineDistance(p0,p1,centre);
      if (d < w) {
        highlight[0] = i;
        highlight[1] = j;
        return;
      }
    }
  }
}

int main(){

  SDL_Surface *screen;

  SDL_Init(SDL_INIT_VIDEO);

  SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
  SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
  SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
  SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
  SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

  if ( (screen=SDL_SetVideoMode( WIDTH, HEIGHT, 32, SDL_OPENGL )) == NULL ) {
    SDL_Quit();
    return -1;
  }

  setup_cube();

  while (1) {
    SDL_Event event;
    setup_scene();

    while(SDL_PollEvent(&event)){
      switch(event.type){
      case SDL_MOUSEMOTION:
        aim(event.motion.x, event.motion.y);
        break;
      case SDL_KEYDOWN: 
        {
          switch (event.key.keysym.sym){
          case SDLK_ESCAPE:
            SDL_Quit();
            exit(1);
          case SDLK_LEFT:
            position.add(Vector3(1,0,0));
            break;
          case SDLK_RIGHT:
            position.sub(Vector3(1,0,0));
            break;
          case SDLK_UP:
            position.add(Vector3(0,0,1));
            break;
          case SDLK_DOWN:
            position.sub(Vector3(0,0,1));
            break;
          case SDLK_PAGEDOWN:
            position.add(Vector3(0,1,0));
            break;
          case SDLK_PAGEUP:
            position.sub(Vector3(0,1,0));
            break;
          }
        }
      default:
        break;
      }
    }
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    draw_scene();
    SDL_GL_SwapBuffers();
    usleep(10);
  }

  return 0;
}

The source listed above displays correctly the cube aimed by the mouse pointer. It is possible to move around using the arrow and page up/down keys.

didierc
  • 14,572
  • 3
  • 32
  • 52
  • 1
    my `Vector3` class has your assumptions. but this modification in code didn't solve the problem. as I said I am using my default projection matrix. don't you think that I is the cause of problem? how can I use it instead of `glGetDoublev (GL_PROJECTION_MATRIX, projmatrix);` – user667222 Jun 06 '13 at 17:16
  • 1
    good insight: the code where you use your own matrix is not visible, but you probably install it in the OpenGL transformation pipeline by using `glLoadMatrix`. If that is the case, then you only need to provide that same matrix to `gluUnproject`, instead of the one you get with `glGetDouble`: it may or may not be the same matrix depending on what you do between rendering and the user interaction code. I reimplemented the function to verify it, and perhaps your code being optimized somehow, I could not see the similarity with mine. – didierc Jun 06 '13 at 18:37
  • yes as you said I insert my camera's projection matrix into pipeline using `glLoadMatrix` in initialization step but I dont use it in `glGetDoubl` when I projecting, I am going to modify that part and see what will happen. – user667222 Jun 06 '13 at 18:50
  • again I got the wrong coordinations and when I click other locations in the viewport the cubes get selected :( – user667222 Jun 08 '13 at 01:40
  • I also tried this `gluUnProject ((double) mymouse.x, dClickY, 1.0, templateSkeletons[0].tempMatrix, templateSkeletons[0].tempMatrixP, viewport, &dX, &dY, &dZ); pointsOnLine[1] = Vector3( (float) dX, (float) dY, (float) dZ ); Vector3 vv = g_camera.getPosition(); pointsOnLine[0].x = vv.x; pointsOnLine[0].y = vv.y; pointsOnLine[0].z = vv.z;` I mean I called `gluUbproject` once on near plane and camera position as my two point to create ray – user667222 Jun 08 '13 at 01:47
  • I am doing this `Vector3 aa = Vector3::Vector3(0.0,1.0,0.0); distToLine = Vector3::PointToLineDistance3DNEW(rayPoints[0],rayPoints[1],aa); if(distToLine < 0.1) glColor3f(1.0,0.0,0.0);` and when I click right under a cube I rendered in that coordination the cube changes to red not when I click on cube itself – user667222 Jun 08 '13 at 02:08