# ClickingIn3D

From X-Plane SDK

Note: this is not necessary to build a 2-d instrument on the 3-d panel; you can use the datarefs:

sim/graphics/view/click_3d_x sim/graphics/view/click_3d_y

to recover the 2-d click location that was made to the 3-d panel.

Here are the important points:

- The input of the routine is a mouse location in 2-d, like you would get from a click callback in a window.
- This routine MUST be called while the OpenGL matrices are set up to _draw_ in 3-d!! So you cannot call this from your window - you must call it from a real 3-d drawing callback. Thus you need to save the 2-d click until the next drawing callback, then use it.
- This routine uses glGetXXXX which are not fast calls. Try to call this only once per frame and save the results. If you need to call this more than once, try to factor out common glGet calls. (However you must actually get the matrices each frame - when the camera moves they'll be different.
- The output of this click is two 3-d points. One is the location of the user's camera in the 3-d world (camera_xyz) and the other is a point along the line of site from the user's eye to the mouse (mouse_xyz). To determine what was clicked, you must intersect your 3-d geometry with this 3-d line. Make sure that you use the closest entity that is clicked, and make sure that you only click entities that are on the same side of camera_xyz as mouse_xyz!
- You will need two matrix routines: invertMatrix (to invert a 4x4 matrix) and multMatrixVec4d to transform a 4-component vector by a matrix. These are standard matrix operations, so there's a ton of sample code out there - you'll have to find some that meets your license requirements. A lot of [Mesa | http://www.mesa3d.org/license.html] is available under the BSD/X11 license and provides source for useful matrix operations.

(This snippet is based on the code used to click3-d objects, but I have not tested it since I reformatted it so there may be typos.)

int click_to_3d(GLdouble mouse_xy[2], GLdouble camera_xyz[3], GLdouble mouse_xyz[3]) { GLdouble modelM[16]; // Our current model view matrix GLdouble projM [16]; // Our current projection matrix GLdouble modelI[16]; // The inverse of the model view matrix GLint viewport[4]; // The current viewport GLdouble cam_eye[4]={0.0,0.0,0.0,1.0}; // The camera loc in eye coords GLdouble cam_mv[4]; // The camera loc in model-view coords // This code reads all of the matrix state from OpenGL. It is the ONLY code that must be run // while OpenGL is set up in the coordinate system that you want to use. glGetDoublev (GL_MODELVIEW_MATRIX ,modelM); glGetDoublev (GL_PROJECTION_MATRI ,projM); glGetIntegerv(GL_VIEWPORT ,viewport); // First we use glu to convert from "window" to world coordinates. Please note we pass a // Z as 0. Z = 0 in window coordinates means a point on the near clipping plane. So we // are converting the mouse point on the near clipping plane to world coordinates. if(gluUnProject(mouse_xy[0],mouse_xy[1],0.0,modelM,projM,viewport,&mouse_xyz[0],&mouse_xyz[1],&mouse_xyz[2])) // We also need to calculate the inverse of the modelview matrix. While unlikely, both // of these can return false, which would indicate that the OpenGL matrices are so weird // that we cannot figure out where we clicked. if(invertMatrix(modelI,modelM)) { // We take our eye point and transform it from eye to modelview coordinates. This is the // location of the viewer's eye in our current coordinate system. (Yeah we should know it // from setting up OGL, but this takes into account every transform that might be // done - so it's real easy and safe.) multMatrixVec4d(cam_mv,modelI,cam_eye); // Note for OGL nerds: we really do need a 4-component matrix for the multiply above because // the W coordinate can be used to encode translatiosn in the model-view matrix. But we do not // need to do a perspective-divide; the model-view matrix should be only composed of transforms // and rotations, not projections! camera_xyz[0] = cam_mv[0]; camera_xyz[1] = cam_mv[1]; camera_xyz[2] = cam_mv[2]; return 1; } return 0; }