1 // This is brl/bbas/bgui3d/bgui3d_project2d_tableau.cxx
2 #include <iostream>
3 #include "bgui3d_project2d_tableau.h"
4 //:
5 // \file
6 
7 #ifdef _MSC_VER
8 #  include "vcl_msvc_warnings.h"
9 #endif
10 #include <cassert>
11 #include "vgui/vgui_gl.h"
12 #include "vgui/vgui_glu.h"
13 #include "vgui/vgui_menu.h"
14 #include "vgui/vgui_command.h"
15 #include "vnl/vnl_double_3x3.h"
16 #include "vnl/vnl_double_3x4.h"
17 #include "vnl/vnl_double_4x4.h"
18 #include "vnl/vnl_det.h"
19 #include "vgl/vgl_point_3d.h"
20 #include <vgl/algo/vgl_rotation_3d.h>
21 
22 #include "vpgl/vpgl_perspective_camera.h"
23 #include "bgui3d_algo.h"
24 
25 #include <Inventor/actions/SoGetBoundingBoxAction.h>
26 #include <Inventor/SbLinear.h>
27 
28 
29 //: Default Constructor - don't use this, use bgui3d_project2d_tableau_new.
bgui3d_project2d_tableau()30 bgui3d_project2d_tableau::bgui3d_project2d_tableau()
31  : bgui3d_tableau(NULL), draw_headlight_(true)
32 {
33   // Use the identity camera
34   this->set_camera(vpgl_proj_camera<double>());
35 }
36 
37 
38 //: Constructor - don't use this, use bgui3d_project2d_tableau_new.
bgui3d_project2d_tableau(const vpgl_proj_camera<double> & camera,SoNode * scene_root)39 bgui3d_project2d_tableau::bgui3d_project2d_tableau( const vpgl_proj_camera<double>& camera,
40                                                     SoNode * scene_root )
41 : bgui3d_tableau(scene_root), draw_headlight_(true)
42 {
43   this->set_camera(camera);
44 }
45 
46 
47 //: Destructor
~bgui3d_project2d_tableau()48 bgui3d_project2d_tableau::~bgui3d_project2d_tableau()
49 {
50 }
51 
52 
type_name() const53 std::string bgui3d_project2d_tableau::type_name() const
54 {
55   return "bgui3d_project2d_tableau";
56 }
57 
58 
59 //: Set the scene camera
60 // creates a graphics camera from a vpgl camera (either perspective or affine)
61 bool
set_camera(const vpgl_proj_camera<double> & cam)62 bgui3d_project2d_tableau::set_camera(const vpgl_proj_camera<double>& cam)
63 {
64   vnl_double_3x4 camera = cam.get_matrix();
65 
66   vnl_double_3x3 K; // internal parameters
67   vnl_double_3x3 R; // camera rotation
68   vnl_double_3 t; // camera translation
69 
70   // Code for affine camera
71   if ( camera[2][0] == 0 && camera[2][1] ==  0 && camera[2][2] == 0 )
72   {
73     vnl_double_3x4 ncamera = camera;
74     ncamera /= ncamera[2][3];
75 
76     // Decompose the camera as:
77     // | a b c | | 1 0 0 0 | |   r1  0 |
78     // |   d e |*| 0 1 0 0 |*|   r2  0 |
79     // |     1 | | 0 0 0 1 | |   r3  0 |
80     //                       | 0 0 0 1 |
81     K.fill( 0.0 );
82     vnl_double_3 r1, r2, r3, a1;
83     r2[0] = ncamera[1][0]; r2[1] = ncamera[1][1]; r2[2] = ncamera[1][2];
84     K[1][1] = std::sqrt( r2[0]*r2[0]+r2[1]*r2[1]+r2[2]*r2[2] );
85     r2 /= std::sqrt( r2[0]*r2[0]+r2[1]*r2[1]+r2[2]*r2[2] );
86     a1[0] = ncamera[0][0]; a1[1] = ncamera[0][1]; a1[2] = ncamera[0][2];
87     r3 = vnl_cross_3d( a1, r2 );
88     r3 /= std::sqrt( r3[0]*r3[0]+r3[1]*r3[1]+r3[2]*r3[2] );
89     r1 = vnl_cross_3d( r2, r3 );
90     K[0][0] = a1[0]*r1[0] + a1[1]*r1[1] + a1[2]*r1[2];
91     if ( K[0][0] < 0 ) {
92       K[0][0] *= -1;
93       r1 *= -1;
94     }
95     K[0][1] = a1[0]*r2[0] + a1[1]*r2[1] + a1[2]*r2[2];
96     if ( K[1][1] < 0 ) {
97       K[1][1] *= -1;
98       r2 *= -1;
99       K[0][1] *= -1;
100     }
101     K[0][2] = ncamera[0][3];
102     K[1][2] = ncamera[1][3];
103     K[2][2] = 1.0;
104 
105     // Check recomposition
106     vnl_double_3x4 mcamera( 0.0 );
107     vnl_double_4x4 rcamera( 0.0 );
108     mcamera[0][0] = mcamera[1][1] = mcamera[2][3] = 1;
109     rcamera[0][0] = r1[0]; rcamera[0][1] = r1[1]; rcamera[0][2] = r1[2];
110     rcamera[1][0] = r2[0]; rcamera[1][1] = r2[1]; rcamera[1][2] = r2[2];
111     rcamera[2][0] = r3[0]; rcamera[2][1] = r3[1]; rcamera[2][2] = r3[2];
112     rcamera[3][3] = 1.0;
113     assert( (K*mcamera*rcamera - ncamera).fro_norm() > 1e-4 );
114     // if this fails email tpollard@dam.brown.edu for help
115 
116     t.fill( 0.0 );
117     R[0][0] = r1[0];  R[0][1] = r1[1];  R[0][2] = r1[2];
118     R[1][0] = r2[0];  R[1][1] = r2[1];  R[1][2] = r2[2];
119     R[2][0] = r3[0];  R[2][1] = r3[1];  R[2][2] = r3[2];
120   }
121   // Code for perspective camera:
122   else {
123     // make the camera right-handed
124     vnl_double_3x4 cmr = camera;
125     if (vnl_det(vnl_double_3x3(cmr.extract(3,3))) < 0)
126       cmr *= -1.0;
127     if (!bgui3d_decompose_camera(cmr, K, R, t)) {
128       std::cerr << "decomposition error\n\n";
129       return false;
130     }
131   }
132 
133   // The model matrix is the cameras rotation and translation
134   vnl_double_4x4 M(0.0); // unused?! - FIXME
135   M.update(R.as_ref());        // upper 3x3 part
136   M.set_column(3, t.as_ref()); // last column, upper 3 elements
137   M(3,3) = 1.0; // and the rest of the 4th row is zero
138 
139   // set M to model_matrix_ for OpenGL use
140   vnl_matrix_fixed<double,4,4> mm(model_matrix_); // mm unused?! - FIXME
141   mm = M.transpose();
142 
143   // The inverse of the model matrix
144   vnl_double_4x4 Mi(0.0);
145   Mi.update(R.transpose().as_ref()); // upper 3x3 part
146   Mi.set_column(3, (-R.transpose()*t).as_ref()); // last column, upper 3 elements
147   Mi(3,3) = 1.0; // and the rest of the 4th row is zero
148 
149   // Apply the inverse of the model matrix to the camera
150   camera_z_ = camera * Mi;
151 
152   // The resulting left 3x3 submatrix must be upper triangle
153   // check this condition and then force it to be exactly true.
154   assert(std::fabs(camera_z_(1,0))<1e-10);
155   assert(std::fabs(camera_z_(2,0))<1e-10);
156   assert(std::fabs(camera_z_(2,1))<1e-10);
157   camera_z_(1,0) = camera_z_(2,0) = camera_z_(2,1) = 0.0;
158 
159   return true;
160 }
161 
162 
163 //: Get the scene camera
164 // creates a vpgl camera (either perspective or affine) from the graphics camera
165 std::unique_ptr<vpgl_proj_camera<double> >
camera() const166 bgui3d_project2d_tableau::camera() const
167 {
168   vnl_matrix<double> mm(4,4,16,model_matrix_);
169   vgl_rotation_3d<double> R(mm.extract(3,3).transpose());
170   vgl_point_3d<double> t(-mm[3][0], -mm[3][1], -mm[3][2]);
171   t = R.inverse()*t;
172   if (camera_z_[2][2] != 0) {
173     vpgl_calibration_matrix<double> K(camera_z_.extract(3,3));
174     return std::unique_ptr<vpgl_proj_camera<double> >
175         ( new vpgl_perspective_camera<double>(K,t,R) );
176   }
177   else
178     // FIXME - construct a vpgl_affine_camera
179     return std::unique_ptr<vpgl_proj_camera<double> >
180       ( new vpgl_proj_camera<double>(camera_z_*mm.transpose()) );
181 }
182 
183 
184 //: Draw a headlight in the direction of the camera
draw_headlight()185 static void draw_headlight()
186 {
187   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
188   GLfloat light0_pos[4]   = {  0.0, 0.0, -1.0, 0.0 };
189   GLfloat light0_diff[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
190   GLfloat light0_amb[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
191   GLfloat light0_spec[4] = { 0.1f, 0.1f, 0.1f, 1.0f };
192 
193   glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
194   glLightfv(GL_LIGHT0, GL_AMBIENT,  light0_amb);
195   glLightfv(GL_LIGHT0, GL_DIFFUSE,  light0_diff);
196   glLightfv(GL_LIGHT0, GL_SPECULAR,  light0_spec);
197 
198   glEnable(GL_LIGHT0);
199 }
200 
201 
202 //: Handle vgui events
203 bool
handle(const vgui_event & e)204 bgui3d_project2d_tableau::handle(const vgui_event& e)
205 {
206   // Handle draw events
207   if ( e.type == vgui_DRAW || e.type == vgui_DRAW_OVERLAY ) {
208     // Store state, but don't rely on GL stacks since they might be full
209     double P[16] , M[16];
210     glGetDoublev(GL_PROJECTION_MATRIX, P);
211     glGetDoublev(GL_MODELVIEW_MATRIX, M);
212 
213     if (this->setup_projection()) {
214       glMatrixMode(GL_MODELVIEW);
215       glLoadIdentity();
216       if (draw_headlight_)
217         draw_headlight();
218       else
219         glDisable(GL_LIGHT0);
220       glLoadMatrixd(model_matrix_);
221       if ( e.type == vgui_DRAW )
222         return this->render();
223       if ( e.type == vgui_DRAW_OVERLAY )
224         return this->render_overlay();
225     }
226 
227     // Restore the OpenGL state
228     glMatrixMode(GL_PROJECTION);
229     glLoadMatrixd(P);
230     glMatrixMode(GL_MODELVIEW);
231     glLoadMatrixd(M);
232   }
233 
234   return bgui3d_tableau::handle(e);
235 }
236 
237 
238 //: setup the OpenGL projection matrix
239 bool
setup_projection()240 bgui3d_project2d_tableau::setup_projection()
241 {
242   // Get current modelview matrix state and combine with projection state
243   // This moves the 2D panning and zooming into the projection stage
244   // The modelview matrix will now be used for 3D orientation and translation
245   double M[16];
246   glGetDoublev(GL_MODELVIEW_MATRIX, M);
247   glMatrixMode(GL_PROJECTION);
248   glMultMatrixd(M);
249 
250   // Compute a bounding box around the world to determine near and far
251   // clipping planes
252   SoGetBoundingBoxAction sbba(get_viewport_region());
253   sbba.apply(scene_root_);
254 
255   SbXfBox3f xbox = sbba.getXfBoundingBox();
256   SbMatrix mat;
257   mat.makeIdentity();
258   for (int i=0; i<4; ++i)
259     for (int j=0; j<4; ++j)
260       mat[i][j] = float(model_matrix_[4*i+j]);
261   xbox.transform(mat);
262   mat = xbox.getTransform();
263   SbBox3f box = xbox.project();
264 
265   double nearval = box.getMin()[2];
266   double farval =  box.getMax()[2];
267 
268   // Compute the missing 3rd row of the matrix such that
269   // nearval and farval map to -1 and 1 in depth
270 
271   // Assume that the camera has been rotated to point down the
272   // -z axis.  Thus the bottom row of the camera is [0 0 W1 W2]
273   assert(!camera_z_(2,0) && !camera_z_(2,1));
274   // actually map between -1+epsilon and 1-epsilon to be safe from clipping
275   double epsilon = 0.01;
276   double denom = (farval - nearval)/(1.0-epsilon);
277   double w1 = camera_z_[2][2];
278   double w2 = camera_z_[2][3];
279   double z2 = -(w1*(farval+nearval) + 2*w2)/denom;
280   double z3 = (2*w1*farval*nearval + w2*(farval+nearval))/denom;
281 
282   double P[4][4] = {
283     { camera_z_(0,0),               0,   0,               0},
284     { camera_z_(0,1),  camera_z_(1,1),   0,               0},
285     { camera_z_(0,2),  camera_z_(1,2),  z2,  camera_z_(2,2)},
286     { camera_z_(0,3),  camera_z_(1,3),  z3,  camera_z_(2,3)}
287   };
288 
289   // Compose this projection with the panning and zooming from vgui
290   glMultMatrixd((double *)P);
291 
292   return true;
293 }
294 
295 
296 //----------------------------------------------------------------------------
297 //: A vgui command used to toggle animation
298 class bgui3d_headlight_command : public vgui_command
299 {
300  public:
bgui3d_headlight_command(bgui3d_project2d_tableau * tab)301   bgui3d_headlight_command(bgui3d_project2d_tableau* tab) : bgui3d_project2d_tab(tab) {}
execute()302   void execute()
303   {
304     bool headlight = bgui3d_project2d_tab->is_headlight();
305     bgui3d_project2d_tab->set_headlight(!headlight);
306   }
307 
308   bgui3d_project2d_tableau *bgui3d_project2d_tab;
309 };
310 
311 //----------------------------------------------------------------------------
312 //: Builds a popup menu
get_popup(const vgui_popup_params &,vgui_menu & menu)313 void bgui3d_project2d_tableau::get_popup(const vgui_popup_params& /*params*/, // unused - FIXME
314                                          vgui_menu &menu)
315 {
316   std::string headlight_item;
317   if ( this->is_headlight() )
318     headlight_item = "Disable Headlight";
319   else
320     headlight_item = "Enable Headlight";
321 
322   menu.add(headlight_item, new bgui3d_headlight_command(this));
323 }
324