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