Not logged in, Join Here! or Log In Below:  
 
News Articles Search    
 

 Home / 3D Theory & Graphics / moving 3Dpoints with a mouse in a perspective view? Account Manager
 
Archive Notice: This thread is old and no longer active. It is here for reference purposes. This thread was created on an older version of the flipcode forums, before the site closed in 2005. Please keep that in mind as you view this thread, as many of the topics and opinions may be outdated.
 
David Llewellyn

May 06, 2005, 09:00 AM

I've often wondered how modelling packages allow users to move around 3D points (by dragging the mouse) given an arbitrary camera angle within a perspective view.

Obviously, to do this, the screen coordinates must be mapped back to the object coordinates. Im guessing the process is something like this:

1. remove perspective transform. (some multiplication).
2. remove camera translation and rotation. (inverse transform matrix?).
3. remove object scale, translation and rotation. (another inverse transform matrix?).

I've tried this before, but my solution didn't seem to work. Does anyone know how to properly do it?

Cheers :)

 
Rui Martins

May 06, 2005, 09:36 AM

Modeling Packages define what is usually called a Work Plane.
Then every movement you do with the mouse is calculate has if it was in this plane, or in a plane parallel to that one.

Example:
Assuming a 3D PersPective view, which shows the Plane XZ (horizontal) on thr origin, something like this

  1.  
  2.    /     /  
  3.    /     /  
  4.   /     /    
  5.   /     /     /
  6. /     /     /
  7.       /     /
  8.      /     /  
  9.  


Then when you try to drag a point that is in position (X,Y,Z), you just ignore the Y and drag the point through XZ (keeping the original Y).
NOTE: This is a simple explanation for the specific case when Work plane is XZ. for any plane, you have to move the point ON the PLANE SURFACE.

How you drag is easy, just find where your mouse pick point is relative to the plane.
How ?
Well, you have a reference, which is the original position (X,Y,Z) of the point, so knowing that and it's relative position on screen (it's current projection onto the screen), having the plane set to something you know, just mark your mouse movement relative to that, and you have all the info required to derive the new position.

The plane is needed because you need to have a way to map the mouse 2D movement into 3D, which is exactly what a plane does, it limits your 3D movement in a 2D plane.

Hope this is enough to help you out.

 
David Llewellyn

May 06, 2005, 11:35 AM

"Modeling Packages define what is usually called a Work Plane.
Then every movement you do with the mouse is calculate has if it was in this plane, or in a plane parallel to that one."

Is this plane usually defined using the current camera (viewing) angle??
Its the only sensible thing I can think of :)

"having the plane set to something you know, just mark your mouse movement relative to that"

I'm not sure i understand that.. do you mean the screen coordinates of the mouse are somehow mapped onto the plane?
then the mouse's plane coordinates are added to the existing object coordinates to produce the resulting new object coordinates.

Sorry I've never tried this before, so it may take me a few goes before the ideas sink in. Cheers :)

 
Reedbeta

May 06, 2005, 03:08 PM

"Is this plane usually defined using the current camera (viewing) angle??"

No, they are usually axis aligned planes (xy, yz, or xz planes), with respect to the world or to the object's local coordinate system. Which one is used is chosen through the user-interface. For instance, if the object's local coordinate system XYZ axes are displayed with little arrows, clicking and dragging the X axis arrow will allow you to move the object only parallel to its local X axis. Other systems use the left/right/middle mouse button to select, or a control key, or a toolbar button you can click to select the axis.

The thing you must understand is that under the 'inverse perspective transformation', all points on the screen map to a RAY in world space. There is NO way to non-arbitrarily choose a point on this ray without additional information or constraints, so there is NO way move things in 3D, with complete freedom using a 2D mouse! Sorry if I am reiterating what is obvious to you, but many people seem to miss this point.

Anyway, once you figure out your work plane, basically what you do is fire a ray from the mouse's position at the start of the drag, and find the intersection point with the work plane and store the vector offset from that point to the object's local origin. During the drag, fire more rays from the mouse pointer and intersect them with the work plane; then re-add the offset you calculate to get the location that the object has been dragged to.

 
ThaRed

May 06, 2005, 03:53 PM

Here's a piece of an example I downloaded recently. I looked for the website it came from, but couldn't find it. I also looked for the author's name in the source, but alas, couldn't find that either. This example let you select and move primitives with the mouse. This is one of the files of the example (with some parts removed (ie, window init) by me). The other files were just helper stuff (object, point, list, etc)... Hope it has some code that helps. Sorry I couldn't credit the author.

The following website might also help:
http://steinsoft.net/index.php?site=Programming/Code%20Snippets/OpenGL/no8


  1.  
  2. /*
  3.  
  4.   How to drag stuff in OpenGL
  5.  
  6. */
  7.  
  8.  
  9. //*******************************************************************
  10.  
  11.  
  12. static double drag_depth = 1.0;   // Where between the front (0.0) and back (1.0) clipping planes the mouse is.
  13. static double move_dir = 1.0;
  14.  
  15. static int last_x = 0, last_y = 0; // Where the mouse was the last time we called GetMousePosition
  16.  
  17. //*******************************************************************
  18. // GetMousePosition
  19. //*******************************************************************
  20.  
  21. Point GetMousePosition(int x, int y, double depth)
  22.  {
  23.   double modelMatrix[16];
  24.   double projMatrix[16];
  25.   int    viewport[4];
  26.                
  27.   glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
  28.   glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
  29.   glGetIntegerv(GL_VIEWPORT, viewport);           // Load all the matricies and junk for gluUnproject to use.
  30.  
  31.   Point position;
  32.  
  33.   gluUnProject(x, viewport[3]-y, depth, modelMatrix, projMatrix, viewport, &position.x, &position.y, &position.z);
  34.  
  35.   last_x = x; // Remember where the mouse is
  36.   last_y = y;
  37.  
  38.   return position;
  39.  }
  40.  
  41. //*******************************************************************
  42. // GetMousePosition
  43. //*******************************************************************
  44.  
  45. Point GetMousePosition(double depth)
  46.  {
  47.   return GetMousePosition(last_x, last_y, depth); // Call GetMousePosition using the last mouse position
  48.  }
  49.  
  50. //*******************************************************************
  51. // FastDraw
  52. //*******************************************************************
  53.  
  54. void FastDraw(void) // Draws the world when selecting. Nothing ends up on the screen, so looks don't
  55.                     // matter. You just need the basic shape, and of course the object numbers.
  56.  {
  57.   glClear(GL_DEPTH_BUFFER_BIT);
  58.   glLoadIdentity();
  59.   gluLookAt(cam_pos.x, cam_pos.y, cam_pos.z,
  60.             cam_pos.x+cam_dir.x, cam_pos.y+cam_dir.y, cam_pos.z+cam_dir.z,
  61.             0.0, 1.0, 0.0);
  62.  
  63.   for(unsigned long c = 0; c < objects.Count(); ++c)
  64.    {
  65.     glLoadName(c);
  66.     objects[c]->FastDraw();
  67.    }
  68.  }
  69.  
  70. //*******************************************************************
  71. // Draw
  72. //*******************************************************************
  73.  
  74. void Draw(void) // Draws the world when rendering. This is what you see.
  75.  {
  76.   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the buffers.
  77.  
  78.   glLoadIdentity();
  79.   gluLookAt(cam_pos.x, cam_pos.y, cam_pos.z,
  80.             cam_pos.x+cam_dir.x, cam_pos.y+cam_dir.y, cam_pos.z+cam_dir.z,
  81.             0.0, 1.0, 0.0); // set up our camera
  82.  
  83.   float pos[4] = {cam_pos.x, cam_pos.y, cam_pos.z, 1.0f};
  84.   glLightfv(GL_LIGHT0, GL_POSITION, pos); // set the light position
  85.  
  86.   for(unsigned long c = 0; c < objects.Count(); ++c)  
  87.    objects[c]->Draw(); // Draw the objects
  88.  }
  89.  
  90. //*******************************************************************
  91. // Update
  92. //*******************************************************************
  93.  
  94. void Update(const double &time)
  95.  {  
  96.   for(unsigned long c = 0; c < objects.Count(); ++c)  
  97.    objects[c]->Update(time); // Update all the objects
  98.  
  99.   if(moving || looking) // If we are moving or looking.
  100.    {
  101.     cam_dir += ((mouse_pos - cam_pos) | 1) * time; // point the camera towards the mouse
  102.     cam_dir |= 1.0; // and normalize it.
  103.  
  104.     if(moving)
  105.      cam_pos += cam_dir * time * move_dir; // move us toward the mouse.
  106.  
  107.     Point p = GetMousePosition(drag_depth); // we moved so get the new mouse position.
  108.  
  109.     if(dragging)
  110.      { // we are dragging an object so move it too.
  111.       Object *o = objects.Get(selected);
  112.  
  113.       if(o)
  114.        o->position += p - mouse_pos;
  115.      }
  116.  
  117.     mouse_pos = p;
  118.    }
  119.  }
  120.  
  121. //*******************************************************************
  122. // CalcPointDepth
  123. //*******************************************************************
  124.  
  125. double CalcPointDepth(const double &x, const double &y, const double &z)
  126.  {
  127.   // this figures our where inbetween the near and far clipping planes a point is.
  128.   // use the result of this with the Pick function so that the mouse is the same
  129.   // depth into the screen as that point.
  130.  
  131.   double modelMatrix[16];
  132.   double projMatrix[16];
  133.   double junkx, junky, depth;
  134.   int    viewport[4];
  135.                
  136.   glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
  137.   glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
  138.   glGetIntegerv(GL_VIEWPORT, viewport);
  139.  
  140.   gluProject(x, y, z, modelMatrix, projMatrix, viewport, &junkx, &junky, &depth);
  141.  
  142.   return depth;
  143.  }
  144.  
  145.  
  146. //*******************************************************************
  147. // CalcPointDepth
  148. //*******************************************************************
  149.  
  150. double CalcPointDepth(const Point &p)
  151.  { // same as above function, but uses a point instead of 3 vars
  152.   return CalcPointDepth(p.x, p.y, p.z);
  153.  }
  154.  
  155. //*******************************************************************
  156. // Pick
  157. //*******************************************************************
  158.  
  159. bool Pick(int x, int y, void(*FastDraw)(void), unsigned int *result)
  160.  {
  161.   // this function will figure out what is where. it will draw using the function FastDraw
  162.   // which needs to set up the camera position, set the names, and draw the objects. the result is
  163.   // stored in result for you to used.
  164.  
  165.   if(!FastDraw || !result)
  166.    return true;
  167.  
  168.   bool failed = false; // set to true if something when wrong
  169.   unsigned int buffer[512*4]; // stores a list of the objects and their depths
  170.   int viewport[4];
  171.   double projMatrix[16];      // the projection matrix.
  172.   glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
  173.   glGetIntegerv(GL_VIEWPORT,viewport);
  174.   unsigned int selected, depth;                
  175.                
  176.   glSelectBuffer(512*4, buffer);  
  177.   glRenderMode(GL_SELECT);        
  178.   glInitNames();                  
  179.   glPushName(0);                  
  180.                
  181.   glMatrixMode(GL_PROJECTION);
  182.   glPushMatrix();
  183.   glLoadIdentity();
  184.  
  185.   // the picking matrix needs to be done first, so we just do that an multiply by the old projection
  186.   // matrix. Saves you needing to define it again, which is is annoying and might cause bugs if we did.
  187.   // something like change the fov or had an isometric view instead.
  188.  
  189.   gluPickMatrix(x, viewport[3]-y, 1, 1, viewport);
  190.   glMultMatrixd(projMatrix);  
  191.   glMatrixMode(GL_MODELVIEW);
  192.                
  193.   FastDraw(); // Draw our world.
  194.                
  195.   int hits = glRenderMode(GL_RENDER);
  196.                
  197.   if(hits > 0) // Did we find anything?
  198.    {
  199.     selected = buffer[3]; // id of the first object
  200.     depth = buffer[1];    // depth of the first object
  201.    
  202.     for(int c = 1; c < hits; c++) // go through all the rest of the objects and find the closest.
  203.      if(buffer[c*4+1] < depth)
  204.       {
  205.        selected = buffer[c*4+3];
  206.        depth = buffer[c*4+1];
  207.       }
  208.      
  209.     *result = selected;
  210.    }
  211.   else
  212.    failed = true;            
  213.    
  214.   glMatrixMode(GL_PROJECTION); // return the projection matrix to what it was before
  215.   glPopMatrix();
  216.   glMatrixMode(GL_MODELVIEW);
  217.  
  218.   return failed;
  219.  }
  220.  
  221.  
  222. //*******************************************************************
  223. // switch(message)
  224. //*******************************************************************
  225.  
  226.  
  227. LRESULT CALLBACK WndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
  228.  {
  229.   switch(message)
  230.    {
  231.     case WM_LBUTTONDOWN: // left mouse button. select an object for dragging, and starts the process of looking.
  232.      if(!Pick(LOWORD(lParam), HIWORD(lParam), &FastDraw, &selected))
  233.       {
  234.        Object *o = objects.Get(selected);
  235.      
  236.        if(o)
  237.         {
  238.          drag_depth = CalcPointDepth(o->position);  
  239.          mouse_pos  = GetMousePosition(LOWORD(lParam), HIWORD(lParam), drag_depth);
  240.          dragging = true;
  241.         }
  242.       }
  243.  
  244.      looking = true;
  245.     return 0;
  246.  
  247.     case WM_LBUTTONUP: // left mouse button up. stops looking and dragging.
  248.      dragging = false;
  249.      looking = false;
  250.     return 0;
  251.  
  252.     case WM_RBUTTONDBLCLK: // double clicked the right mouse button. reverse our movement direction
  253.      move_dir *= -1.0;
  254.  
  255.     case WM_RBUTTONDOWN: // start the process of moving.
  256.      moving = true;
  257.     return 0;
  258.  
  259.     case WM_RBUTTONUP: // stop the process of moving.
  260.      moving = false;
  261.     return 0;
  262.  
  263.     case WM_MOUSEMOVE: // mouse has moved.
  264.      if(dragging)
  265.       {
  266.        Object *o = objects.Get(selected);
  267.        
  268.        if(o)
  269.         { // if we have a valid object, move it accordingly
  270.          Point p = GetMousePosition(LOWORD(lParam), HIWORD(lParam), drag_depth);
  271.          
  272.          o->position += p - mouse_pos;
  273.          mouse_pos = p;
  274.         }
  275.       }
  276.      else // update the mouse position anyway for looking and moving.
  277.       mouse_pos = GetMousePosition(LOWORD(lParam), HIWORD(lParam), drag_depth);
  278.  
  279.     return 0;
  280.  

 
David Llewellyn

May 07, 2005, 02:52 PM

I think I've got it figured out now...I'm not sure I explained my problem clearly enough though. In usual modelling (or point picking) programs you have modelling views such as axis aligned planes (xy, yz, or xz planes) with respect to the world or to the object's local coordinate system.
However, my problem was wanting a non axis-aligned plane. I wanted an arbitrary angle for modelling at (used in perspective modelling views - Maya has an example of this).

My idea is this...

A plane that faces the current viewing camera is setup so that the selected/moving points XYZ coordinates lie exactly on the plane.

The mouse position is projected onto the plane, and the resulting XYZ point at the mouse's intersection of the plane is the new one given to the selected point.

Does that make sense?

Also, thanks to everyone who's helping out. Although I think I needed to explain my problem more clearly. Hope its clarified now, cheers :)

 
theAntiELVIS

May 07, 2005, 06:50 PM

Yes it makes sense. The Raydream modeler worked this way. So did the little mesh modeler that came with the old Flight Sim Construction Kit.

But these didn't use the plane facing the camera - they used an arbitrary plane the camera could rotate around - usually the best viewing angle was about 45 degrees to the working plane, so you had a good sense of the plane being in 3D space.

 
This thread contains 7 messages.
 
 
Hosting by Solid Eight Studios, maker of PhotoTangler Collage Maker.