contrib/brl/bbas/bgui3d/bgui3d_project2d_tableau.cxx
Go to the documentation of this file.
00001 // This is brl/bbas/bgui3d/bgui3d_project2d_tableau.cxx
00002 #include "bgui3d_project2d_tableau.h"
00003 //:
00004 // \file
00005 
00006 #include <vcl_iostream.h>
00007 #include <vcl_cassert.h>
00008 #include <vgui/vgui_gl.h>
00009 #include <vgui/vgui_glu.h>
00010 #include <vgui/vgui_menu.h>
00011 #include <vgui/vgui_command.h>
00012 #include <vnl/vnl_double_3x3.h>
00013 #include <vnl/vnl_double_3x4.h>
00014 #include <vnl/vnl_double_4x4.h>
00015 #include <vnl/vnl_det.h>
00016 #include <vgl/vgl_point_3d.h>
00017 #include <vgl/algo/vgl_rotation_3d.h>
00018 
00019 #include <vpgl/vpgl_perspective_camera.h>
00020 #include "bgui3d_algo.h"
00021 
00022 #include <Inventor/actions/SoGetBoundingBoxAction.h>
00023 #include <Inventor/SbLinear.h>
00024 
00025 
00026 //: Default Constructor - don't use this, use bgui3d_project2d_tableau_new.
00027 bgui3d_project2d_tableau::bgui3d_project2d_tableau()
00028  : bgui3d_tableau(NULL), draw_headlight_(true)
00029 {
00030   // Use the identity camera
00031   this->set_camera(vpgl_proj_camera<double>());
00032 }
00033 
00034 
00035 //: Constructor - don't use this, use bgui3d_project2d_tableau_new.
00036 bgui3d_project2d_tableau::bgui3d_project2d_tableau( const vpgl_proj_camera<double>& camera,
00037                                                     SoNode * scene_root )
00038 : bgui3d_tableau(scene_root), draw_headlight_(true)
00039 {
00040   this->set_camera(camera);
00041 }
00042 
00043 
00044 //: Destructor
00045 bgui3d_project2d_tableau::~bgui3d_project2d_tableau()
00046 {
00047 }
00048 
00049 
00050 vcl_string bgui3d_project2d_tableau::type_name() const
00051 {
00052   return "bgui3d_project2d_tableau";
00053 }
00054 
00055 
00056 //: Set the scene camera
00057 // creates a graphics camera from a vpgl camera (either perspective or affine)
00058 bool
00059 bgui3d_project2d_tableau::set_camera(const vpgl_proj_camera<double>& cam)
00060 {
00061   vnl_double_3x4 camera = cam.get_matrix();
00062 
00063   vnl_double_3x3 K; // internal parameters
00064   vnl_double_3x3 R; // camera rotation
00065   vnl_double_3 t; // camera translation
00066 
00067   // Code for affine camera
00068   if ( camera[2][0] == 0 && camera[2][1] ==  0 && camera[2][2] == 0 )
00069   {
00070     vnl_double_3x4 ncamera = camera;
00071     ncamera /= ncamera[2][3];
00072 
00073     // Decompose the camera as:
00074     // | a b c | | 1 0 0 0 | |   r1  0 |
00075     // |   d e |*| 0 1 0 0 |*|   r2  0 |
00076     // |     1 | | 0 0 0 1 | |   r3  0 |
00077     //                       | 0 0 0 1 |
00078     K.fill( 0.0 );
00079     vnl_double_3 r1, r2, r3, a1;
00080     r2[0] = ncamera[1][0]; r2[1] = ncamera[1][1]; r2[2] = ncamera[1][2];
00081     K[1][1] = vcl_sqrt( r2[0]*r2[0]+r2[1]*r2[1]+r2[2]*r2[2] );
00082     r2 /= vcl_sqrt( r2[0]*r2[0]+r2[1]*r2[1]+r2[2]*r2[2] );
00083     a1[0] = ncamera[0][0]; a1[1] = ncamera[0][1]; a1[2] = ncamera[0][2];
00084     r3 = vnl_cross_3d( a1, r2 );
00085     r3 /= vcl_sqrt( r3[0]*r3[0]+r3[1]*r3[1]+r3[2]*r3[2] );
00086     r1 = vnl_cross_3d( r2, r3 );
00087     K[0][0] = a1[0]*r1[0] + a1[1]*r1[1] + a1[2]*r1[2];
00088     if ( K[0][0] < 0 ) {
00089       K[0][0] *= -1;
00090       r1 *= -1;
00091     }
00092     K[0][1] = a1[0]*r2[0] + a1[1]*r2[1] + a1[2]*r2[2];
00093     if ( K[1][1] < 0 ) {
00094       K[1][1] *= -1;
00095       r2 *= -1;
00096       K[0][1] *= -1;
00097     }
00098     K[0][2] = ncamera[0][3];
00099     K[1][2] = ncamera[1][3];
00100     K[2][2] = 1.0;
00101 
00102     // Check recomposition
00103     vnl_double_3x4 mcamera( 0.0 );
00104     vnl_double_4x4 rcamera( 0.0 );
00105     mcamera[0][0] = mcamera[1][1] = mcamera[2][3] = 1;
00106     rcamera[0][0] = r1[0]; rcamera[0][1] = r1[1]; rcamera[0][2] = r1[2];
00107     rcamera[1][0] = r2[0]; rcamera[1][1] = r2[1]; rcamera[1][2] = r2[2];
00108     rcamera[2][0] = r3[0]; rcamera[2][1] = r3[1]; rcamera[2][2] = r3[2];
00109     rcamera[3][3] = 1.0;
00110     assert( (K*mcamera*rcamera - ncamera).fro_norm() > 1e-4 );
00111     // if this fails email tpollard@dam.brown.edu for help
00112 
00113     t.fill( 0.0 );
00114     R[0][0] = r1[0];  R[0][1] = r1[1];  R[0][2] = r1[2];
00115     R[1][0] = r2[0];  R[1][1] = r2[1];  R[1][2] = r2[2];
00116     R[2][0] = r3[0];  R[2][1] = r3[1];  R[2][2] = r3[2];
00117   }
00118   // Code for perspective camera:
00119   else {
00120     // make the camera right-handed
00121     vnl_double_3x4 cmr = camera;
00122     if (vnl_det(vnl_double_3x3(cmr.extract(3,3))) < 0)
00123       cmr *= -1.0;
00124     if (!bgui3d_decompose_camera(cmr, K, R, t)) {
00125       vcl_cerr << "decomposition error\n\n";
00126       return false;
00127     }
00128   }
00129 
00130   // The model matrix is the cameras rotation and translation
00131   vnl_double_4x4 M(0.0); // unused?! - FIXME
00132   M.update(R.as_ref());        // upper 3x3 part
00133   M.set_column(3, t.as_ref()); // last column, upper 3 elements
00134   M(3,3) = 1.0; // and the rest of the 4th row is zero
00135 
00136   // set M to model_matrix_ for OpenGL use
00137   vnl_matrix_fixed<double,4,4> mm(model_matrix_); // mm unused?! - FIXME
00138   mm = M.transpose();
00139 
00140   // The inverse of the model matrix
00141   vnl_double_4x4 Mi(0.0);
00142   Mi.update(R.transpose().as_ref()); // upper 3x3 part
00143   Mi.set_column(3, (-R.transpose()*t).as_ref()); // last column, upper 3 elements
00144   Mi(3,3) = 1.0; // and the rest of the 4th row is zero
00145 
00146   // Apply the inverse of the model matrix to the camera
00147   camera_z_ = camera * Mi;
00148 
00149   // The resulting left 3x3 submatrix must be upper triangle
00150   // check this condition and then force it to be exactly true.
00151   assert(vcl_fabs(camera_z_(1,0))<1e-10);
00152   assert(vcl_fabs(camera_z_(2,0))<1e-10);
00153   assert(vcl_fabs(camera_z_(2,1))<1e-10);
00154   camera_z_(1,0) = camera_z_(2,0) = camera_z_(2,1) = 0.0;
00155 
00156   return true;
00157 }
00158 
00159 
00160 //: Get the scene camera
00161 // creates a vpgl camera (either perspective or affine) from the graphics camera
00162 vcl_auto_ptr<vpgl_proj_camera<double> >
00163 bgui3d_project2d_tableau::camera() const
00164 {
00165   vnl_matrix<double> mm(4,4,16,model_matrix_);
00166   vgl_rotation_3d<double> R(mm.extract(3,3).transpose());
00167   vgl_point_3d<double> t(-mm[3][0], -mm[3][1], -mm[3][2]);
00168   t = R.inverse()*t;
00169   if (camera_z_[2][2] != 0) {
00170     vpgl_calibration_matrix<double> K(camera_z_.extract(3,3));
00171     return vcl_auto_ptr<vpgl_proj_camera<double> >
00172         ( new vpgl_perspective_camera<double>(K,t,R) );
00173   }
00174   else
00175     // FIXME - construct a vpgl_affine_camera
00176     return vcl_auto_ptr<vpgl_proj_camera<double> >
00177       ( new vpgl_proj_camera<double>(camera_z_*mm.transpose()) );
00178 }
00179 
00180 
00181 //: Draw a headlight in the direction of the camera
00182 static void draw_headlight()
00183 {
00184   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
00185   GLfloat light0_pos[4]   = {  0.0, 0.0, -1.0, 0.0 };
00186   GLfloat light0_diff[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
00187   GLfloat light0_amb[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
00188   GLfloat light0_spec[4] = { 0.1f, 0.1f, 0.1f, 1.0f };
00189 
00190   glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
00191   glLightfv(GL_LIGHT0, GL_AMBIENT,  light0_amb);
00192   glLightfv(GL_LIGHT0, GL_DIFFUSE,  light0_diff);
00193   glLightfv(GL_LIGHT0, GL_SPECULAR,  light0_spec);
00194 
00195   glEnable(GL_LIGHT0);
00196 }
00197 
00198 
00199 //: Handle vgui events
00200 bool
00201 bgui3d_project2d_tableau::handle(const vgui_event& e)
00202 {
00203   // Handle draw events
00204   if ( e.type == vgui_DRAW || e.type == vgui_DRAW_OVERLAY ) {
00205     // Store state, but don't rely on GL stacks since they might be full
00206     double P[16] , M[16];
00207     glGetDoublev(GL_PROJECTION_MATRIX, P);
00208     glGetDoublev(GL_MODELVIEW_MATRIX, M);
00209 
00210     if (this->setup_projection()) {
00211       glMatrixMode(GL_MODELVIEW);
00212       glLoadIdentity();
00213       if (draw_headlight_)
00214         draw_headlight();
00215       else
00216         glDisable(GL_LIGHT0);
00217       glLoadMatrixd(model_matrix_);
00218       if ( e.type == vgui_DRAW )
00219         return this->render();
00220       if ( e.type == vgui_DRAW_OVERLAY )
00221         return this->render_overlay();
00222     }
00223 
00224     // Restore the OpenGL state
00225     glMatrixMode(GL_PROJECTION);
00226     glLoadMatrixd(P);
00227     glMatrixMode(GL_MODELVIEW);
00228     glLoadMatrixd(M);
00229   }
00230 
00231   return bgui3d_tableau::handle(e);
00232 }
00233 
00234 
00235 //: setup the OpenGL projection matrix
00236 bool
00237 bgui3d_project2d_tableau::setup_projection()
00238 {
00239   // Get current modelview matrix state and combine with projection state
00240   // This moves the 2D panning and zooming into the projection stage
00241   // The modelview matrix will now be used for 3D orientation and translation
00242   double M[16];
00243   glGetDoublev(GL_MODELVIEW_MATRIX, M);
00244   glMatrixMode(GL_PROJECTION);
00245   glMultMatrixd(M);
00246 
00247   // Compute a bounding box around the world to determine near and far
00248   // clipping planes
00249   SoGetBoundingBoxAction sbba(get_viewport_region());
00250   sbba.apply(scene_root_);
00251 
00252   SbXfBox3f xbox = sbba.getXfBoundingBox();
00253   SbMatrix mat;
00254   mat.makeIdentity();
00255   for (int i=0; i<4; ++i)
00256     for (int j=0; j<4; ++j)
00257       mat[i][j] = float(model_matrix_[4*i+j]);
00258   xbox.transform(mat);
00259   mat = xbox.getTransform();
00260   SbBox3f box = xbox.project();
00261 
00262   double nearval = box.getMin()[2];
00263   double farval =  box.getMax()[2];
00264 
00265   // Compute the missing 3rd row of the matrix such that
00266   // nearval and farval map to -1 and 1 in depth
00267 
00268   // Assume that the camera has been rotated to point down the
00269   // -z axis.  Thus the bottom row of the camera is [0 0 W1 W2]
00270   assert(!camera_z_(2,0) && !camera_z_(2,1));
00271   // actually map between -1+epsilon and 1-epsilon to be safe from clipping
00272   double epsilon = 0.01;
00273   double denom = (farval - nearval)/(1.0-epsilon);
00274   double w1 = camera_z_[2][2];
00275   double w2 = camera_z_[2][3];
00276   double z2 = -(w1*(farval+nearval) + 2*w2)/denom;
00277   double z3 = (2*w1*farval*nearval + w2*(farval+nearval))/denom;
00278 
00279   double P[4][4] = {
00280     { camera_z_(0,0),               0,   0,               0},
00281     { camera_z_(0,1),  camera_z_(1,1),   0,               0},
00282     { camera_z_(0,2),  camera_z_(1,2),  z2,  camera_z_(2,2)},
00283     { camera_z_(0,3),  camera_z_(1,3),  z3,  camera_z_(2,3)}
00284   };
00285 
00286   // Compose this projection with the panning and zooming from vgui
00287   glMultMatrixd((double *)P);
00288 
00289   return true;
00290 }
00291 
00292 
00293 //----------------------------------------------------------------------------
00294 //: A vgui command used to toggle animation
00295 class bgui3d_headlight_command : public vgui_command
00296 {
00297  public:
00298   bgui3d_headlight_command(bgui3d_project2d_tableau* tab) : bgui3d_project2d_tab(tab) {}
00299   void execute()
00300   {
00301     bool headlight = bgui3d_project2d_tab->is_headlight();
00302     bgui3d_project2d_tab->set_headlight(!headlight);
00303   }
00304 
00305   bgui3d_project2d_tableau *bgui3d_project2d_tab;
00306 };
00307 
00308 //----------------------------------------------------------------------------
00309 //: Builds a popup menu
00310 void bgui3d_project2d_tableau::get_popup(const vgui_popup_params& /*params*/, // unused - FIXME
00311                                          vgui_menu &menu)
00312 {
00313   vcl_string headlight_item;
00314   if ( this->is_headlight() )
00315     headlight_item = "Disable Headlight";
00316   else
00317     headlight_item = "Enable Headlight";
00318 
00319   menu.add(headlight_item, new bgui3d_headlight_command(this));
00320 }