00001 /** 00002 \file Robots/LoBot/ui/LoLaserWindow.C 00003 00004 This file defines the non-inline member functions of the 00005 lobot::LaseWindow class used to encapsulate the GLUT-based window for 00006 the Hokuyo laser range finder's test program, which visualizes the 00007 laser range finder's measurement data with some simple 2D OpenGL. 00008 */ 00009 00010 // //////////////////////////////////////////////////////////////////// // 00011 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00012 // by the University of Southern California (USC) and the iLab at USC. // 00013 // See http://iLab.usc.edu for information about this project. // 00014 // //////////////////////////////////////////////////////////////////// // 00015 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00016 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00017 // in Visual Environments, and Applications'' by Christof Koch and // 00018 // Laurent Itti, California Institute of Technology, 2001 (patent // 00019 // pending; application number 09/912,225 filed July 23, 2001; see // 00020 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00021 // //////////////////////////////////////////////////////////////////// // 00022 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00023 // // 00024 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00025 // redistribute it and/or modify it under the terms of the GNU General // 00026 // Public License as published by the Free Software Foundation; either // 00027 // version 2 of the License, or (at your option) any later version. // 00028 // // 00029 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00030 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00031 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00032 // PURPOSE. See the GNU General Public License for more details. // 00033 // // 00034 // You should have received a copy of the GNU General Public License // 00035 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00036 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00037 // Boston, MA 02111-1307 USA. // 00038 // //////////////////////////////////////////////////////////////////// // 00039 // 00040 // Primary maintainer for this file: Manu Viswanathan <mviswana at usc dot edu> 00041 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Robots/LoBot/ui/LoLaserWindow.C $ 00042 // $Id: LoLaserWindow.C 13037 2010-03-23 01:00:53Z mviswana $ 00043 // 00044 00045 //---------------------- ALTERNATIVE DEFINITION ------------------------- 00046 00047 // In case OpenGL and/or GLUT are missing 00048 // 00049 // NOTE: Don't really need to check INVT_HAVE_LIBGL and INVT_HAVE_LIBGLU 00050 // as well because it ought to be a pretty rare/broken installation that 00051 // has GLUT but not the OpenGL libraries... 00052 #ifndef INVT_HAVE_LIBGLUT 00053 00054 #include "Robots/LoBot/ui/LoLaserWindow.H" 00055 #include "Robots/LoBot/misc/LoExcept.H" 00056 00057 namespace lobot { 00058 00059 void LaserWindow::create(const std::string&) 00060 { 00061 throw missing_libs(MISSING_OPENGL) ; 00062 } 00063 00064 // NOTE: Don't need empty definitions of the remaining member functions 00065 // because they don't get used at all if OpenGL and/or GLUT are missing, 00066 // which means that the linker won't complain about missing functions 00067 // (since the compiler doesn't generate code for these functions). 00068 00069 } 00070 00071 #else // OpenGL and GLUT available ==> the real McCoy 00072 00073 //------------------------------ HEADERS -------------------------------- 00074 00075 // lobot headers 00076 #include "Robots/LoBot/ui/LoLaserWindow.H" 00077 #include "Robots/LoBot/config/LoConfigHelpers.H" 00078 00079 #include "Robots/LoBot/misc/LoTypes.H" 00080 #include "Robots/LoBot/misc/factory.hh" 00081 #include "Robots/LoBot/util/LoMath.H" 00082 00083 // OpenGL headers 00084 #include <GL/glut.h> 00085 00086 //----------------------------- NAMESPACE ------------------------------- 00087 00088 namespace lobot { 00089 00090 //------------------------ STATIC DATA MEMBERS -------------------------- 00091 00092 // The window title of the GLUT window created by this class. 00093 std::string LaserWindow::m_title ; // static to allow passage to def. ctor 00094 00095 //-------------------------- INITIALIZATION ----------------------------- 00096 00097 // This is the only public method in this class. It is static and merely 00098 // acts as an interface for creating the singleton LaserWindow object. 00099 void LaserWindow::create(const std::string& title) 00100 { 00101 m_title = title ; 00102 instance() ; 00103 } 00104 00105 // Constructor sets up the laser range finder device and GLUT UI 00106 LaserWindow::LaserWindow() 00107 : m_lrf(new LaserRangeFinder(Params::device(), Params::baud_rate())), 00108 m_window(glutCreateWindow(m_title.c_str())), 00109 m_canvas(new GLCanvas()), 00110 m_markings(factory<LaserWindowMarkings>::create(Params::markings_type())), 00111 m_paused(false), 00112 m_drag_button(-1), 00113 m_drag_modifiers(-1) 00114 { 00115 const int M = m_lrf->get_distance_range().max() ; 00116 m_canvas->set_window(-M, M, -M, M) ; 00117 00118 m_markings->use_canvas(m_canvas) ; 00119 m_markings->set_maximum(M) ; 00120 00121 glutReshapeFunc(reshape_callback) ; 00122 glutDisplayFunc(render_callback) ; 00123 glutKeyboardFunc(keyboard_callback) ; 00124 glutMouseFunc(click_callback) ; 00125 glutMotionFunc(drag_callback) ; 00126 setup_timer() ; 00127 00128 typedef LaserWindow me ; // typing shortcut 00129 m_keymap['r'] = & me::reset_zoom_pan ; 00130 m_keymap['p'] = & me::pause ; 00131 00132 m_keymap['q'] = & me::quit ; 00133 m_keymap['Q'] = & me::quit ; 00134 m_keymap[27] = & me::quit ; // ESC (assuming ASCII encoding) 00135 00136 m_drag_prev[0] = m_drag_prev[1] = -1 ; 00137 } 00138 00139 // This method resets the graphics viewport whenever the UI window is 00140 // resized. 00141 void LaserWindow::reshape(int W, int H) 00142 { 00143 m_canvas->set_viewport(0, W, 0, H) ; 00144 } 00145 00146 //------------------ UPDATING DISTANCE MEASUREMENTS --------------------- 00147 00148 void LaserWindow::update() 00149 { 00150 m_lrf->update() ; 00151 glutPostRedisplay() ; 00152 if (! m_paused) 00153 setup_timer() ; 00154 } 00155 00156 void LaserWindow::setup_timer() 00157 { 00158 glutTimerFunc(Params::update_frequency(), timer_callback, 0) ; 00159 } 00160 00161 //------------------ RENDERING DISTANCE MEASUREMENTS -------------------- 00162 00163 // Forward declarations 00164 void draw_measurements(const LaserRangeFinder*, const range<int>&, int, 00165 const GLColor&) ; 00166 void draw_lrf(float, const GLColor&) ; 00167 00168 // This method draws the latest set of distance measurements from the 00169 // laser range finder. 00170 void LaserWindow::render() 00171 { 00172 glClear(GL_COLOR_BUFFER_BIT) ; 00173 00174 glPushMatrix() ; 00175 glRotatef(Params::lrf_direction(), 0, 0, 1) ; 00176 m_markings->render() ; 00177 draw_measurements(m_lrf, Params::angles_range(), Params::angles_step(), 00178 Params::measurements_color()) ; 00179 draw_lrf(Params::lrf_size(), Params::lrf_color()) ; 00180 glPopMatrix() ; 00181 00182 glutSwapBuffers() ; 00183 } 00184 00185 // This function draws the measurements made by the laser range finder, 00186 // showing them as rays emanating from the origin of the world coordinate 00187 // system (where the laser range finder is positioned; yes, the laser 00188 // range finder is the center of the world). 00189 // 00190 // Since drawing each and every measurement can make the resulting 00191 // picture crowded, this function only draws the distance measurements 00192 // corresponding to angles within the [min, max] range with the specified 00193 // step size. 00194 void draw_measurements(const LaserRangeFinder* lrf, 00195 const range<int>& angles, int step, 00196 const GLColor& color) 00197 { 00198 glPushAttrib(GL_COLOR_BUFFER_BIT) ; 00199 glBegin(GL_LINES) ; 00200 glColor3fv(color.rgb()) ; 00201 00202 for (float angle = angles.min(); angle <= angles.max(); angle += step) 00203 { 00204 int D = lrf->get_distance(static_cast<int>(angle)) ; 00205 if (D < 0) // didn't get a valid reading in this direction 00206 continue ; 00207 glVertex2i(0, 0) ; 00208 glVertex2f(D * cos(angle), D * sin(angle)) ; 00209 } 00210 00211 // In case the above loop missed zero degrees (i.e., straight in 00212 // front of the laser range finder... 00213 int D = lrf->get_distance(0) ; 00214 if (D > 0) { 00215 glVertex2i(0, 0) ; 00216 glVertex2i(0, D) ; 00217 } 00218 glEnd() ; 00219 glPopAttrib() ; 00220 } 00221 00222 // The laser range finder is depicted as a rectangle with a triangle on 00223 // it serving to let users know where the front of the device is. This 00224 // function expects to be passed the half-size R of a square inside of 00225 // which the entire rectangle + triangle combo is to inscribed. The 00226 // rectangle is drawn with sides R and 2R; the triangle is drawn with 00227 // height R and base length 2R. 00228 void draw_lrf(float R, const GLColor& color) 00229 { 00230 glPushAttrib(GL_COLOR_BUFFER_BIT) ; 00231 glBegin(GL_TRIANGLES) ; 00232 glColor3fv(color.rgb()) ; 00233 00234 // The triangle 00235 glVertex2f(R, 0) ; // apex 00236 glVertex2f(0, R) ; // base 00237 glVertex2f(0, -R) ; 00238 00239 // The rectangle (drawn as two triangles) 00240 glVertex2f( 0, R) ; 00241 glVertex2f(-R, R) ; 00242 glVertex2f( 0, -R) ; 00243 glVertex2f(-R, R) ; 00244 glVertex2f(-R, -R) ; 00245 glVertex2f( 0, -R) ; 00246 glEnd() ; 00247 glPopAttrib() ; 00248 } 00249 00250 //-------------------------- KEYBOARD INPUT ----------------------------- 00251 00252 // Use keymap to invoke appropriate handler for key pressed by user 00253 void LaserWindow::handle_key(unsigned char key) 00254 { 00255 KeyMap::iterator handler = m_keymap.find(key) ; 00256 if (handler == m_keymap.end()) 00257 return ; 00258 (this->*(handler->second))() ; 00259 glutPostRedisplay() ; 00260 } 00261 00262 void LaserWindow::reset_zoom_pan() 00263 { 00264 m_canvas->reset_zoom_pan() ; 00265 } 00266 00267 void LaserWindow::pause() 00268 { 00269 m_paused = ! m_paused ; 00270 if (! m_paused) 00271 setup_timer() ; 00272 } 00273 00274 void LaserWindow::quit() 00275 { 00276 exit(0) ; 00277 } 00278 00279 //---------------------------- MOUSE INPUT ------------------------------ 00280 00281 void LaserWindow::left_click(int state, int modifiers, int x, int y) 00282 { 00283 switch (state) 00284 { 00285 case GLUT_DOWN: 00286 m_drag_button = GLUT_LEFT_BUTTON ; 00287 m_drag_modifiers = modifiers ; 00288 m_drag_prev[0] = x ; 00289 m_drag_prev[1] = y ; 00290 break ; 00291 case GLUT_UP: 00292 m_drag_button = -1 ; 00293 m_drag_modifiers = -1 ; 00294 m_drag_prev[0] = -1 ; 00295 m_drag_prev[1] = -1 ; 00296 break ; 00297 } 00298 } 00299 00300 void LaserWindow::middle_click(int state, int modifiers, int x, int y) 00301 { 00302 switch (state) 00303 { 00304 case GLUT_DOWN: 00305 m_drag_button = GLUT_MIDDLE_BUTTON ; 00306 m_drag_modifiers = modifiers ; 00307 m_drag_prev[0] = x ; 00308 m_drag_prev[1] = y ; 00309 break ; 00310 case GLUT_UP: 00311 m_drag_button = -1 ; 00312 m_drag_modifiers = -1 ; 00313 m_drag_prev[0] = -1 ; 00314 m_drag_prev[1] = -1 ; 00315 break ; 00316 } 00317 } 00318 00319 void LaserWindow::right_click(int state, int modifiers, int x, int y) 00320 { 00321 switch (state) 00322 { 00323 case GLUT_DOWN: 00324 m_drag_button = GLUT_RIGHT_BUTTON ; 00325 m_drag_modifiers = modifiers ; 00326 m_drag_prev[0] = x ; 00327 m_drag_prev[1] = y ; 00328 break ; 00329 case GLUT_UP: 00330 m_drag_button = -1 ; 00331 m_drag_modifiers = -1 ; 00332 m_drag_prev[0] = -1 ; 00333 m_drag_prev[1] = -1 ; 00334 break ; 00335 } 00336 } 00337 00338 void LaserWindow::left_drag(int x, int y) 00339 { 00340 if (m_drag_modifiers & GLUT_ACTIVE_SHIFT) // zoom 00341 { 00342 const float dy = y - m_drag_prev[1] ; 00343 m_canvas->zoom_by(-dy * Params::zoom_drag_factor()) ; 00344 } 00345 else // pan 00346 { 00347 double curr_x, curr_y ; 00348 m_canvas->screen_to_world(x, y, & curr_x, & curr_y) ; 00349 00350 double prev_x, prev_y ; 00351 m_canvas->screen_to_world(m_drag_prev[0], m_drag_prev[1], 00352 & prev_x, & prev_y) ; 00353 00354 const float dx = static_cast<float>(curr_x - prev_x) ; 00355 const float dy = static_cast<float>(curr_y - prev_y) ; 00356 m_canvas->pan(-dx, -dy) ; 00357 } 00358 00359 m_drag_prev[0] = x ; 00360 m_drag_prev[1] = y ; 00361 00362 glutPostRedisplay() ; 00363 } 00364 00365 void LaserWindow::middle_drag(int x, int y) 00366 { 00367 const float dy = y - m_drag_prev[1] ; 00368 m_canvas->zoom_by(-dy * Params::zoom_drag_factor()) ; 00369 00370 m_drag_prev[0] = x ; 00371 m_drag_prev[1] = y ; 00372 00373 glutPostRedisplay() ; 00374 } 00375 00376 void LaserWindow::right_drag(int, int) 00377 { 00378 } 00379 00380 //-------------------------- GLUT CALLBACKS ----------------------------- 00381 00382 void LaserWindow::reshape_callback(int width, int height) 00383 { 00384 instance().reshape(width, height) ; 00385 } 00386 00387 void LaserWindow::render_callback() 00388 { 00389 instance().render() ; 00390 } 00391 00392 void LaserWindow::keyboard_callback(unsigned char key, int, int) 00393 { 00394 instance().handle_key(key) ; 00395 } 00396 00397 void LaserWindow::click_callback(int button, int state, int x, int y) 00398 { 00399 switch (button) 00400 { 00401 case GLUT_LEFT_BUTTON: 00402 instance().left_click(state, glutGetModifiers(), x, y) ; 00403 break ; 00404 case GLUT_MIDDLE_BUTTON: 00405 instance().middle_click(state, glutGetModifiers(), x, y) ; 00406 break ; 00407 case GLUT_RIGHT_BUTTON: 00408 instance().right_click(state, glutGetModifiers(), x, y) ; 00409 break ; 00410 } 00411 } 00412 00413 void LaserWindow::drag_callback(int x, int y) 00414 { 00415 switch (instance().m_drag_button) 00416 { 00417 case GLUT_LEFT_BUTTON: 00418 instance().left_drag(x, y) ; 00419 break ; 00420 case GLUT_MIDDLE_BUTTON: 00421 instance().middle_drag(x, y) ; 00422 break ; 00423 case GLUT_RIGHT_BUTTON: 00424 instance().right_drag(x, y) ; 00425 break ; 00426 } 00427 } 00428 00429 // We use a timer to continuously update the laser range finder 00430 void LaserWindow::timer_callback(int) 00431 { 00432 instance().update() ; 00433 } 00434 00435 //----------------------------- CLEAN-UP -------------------------------- 00436 00437 LaserWindow::~LaserWindow() 00438 { 00439 delete m_markings ; 00440 delete m_canvas ; 00441 glutDestroyWindow(m_window) ; 00442 delete m_lrf ; 00443 } 00444 00445 //-------------------------- KNOB TWIDDLING ----------------------------- 00446 00447 // Parameters initialization 00448 LaserWindow::Params::Params() 00449 : m_device(get_conf<std::string>("device", "port", "/dev/ttyACM0")), 00450 m_baud_rate(get_conf("device", "baud_rate", 115200)), 00451 m_markings_type(get_conf<std::string>("markings", "type", "rings")), 00452 m_update_frequency(clamp(get_conf("device", "update_frequency", 250), 00453 100, 60000)), 00454 m_angles_range(get_conf<int>("measurements", "angles_range", 00455 make_range(-135, 135))), 00456 m_angles_step(clamp(get_conf("measurements", "angles_step", 5), 1, 30)), 00457 m_measurements_color(get_conf<int>("measurements", "color", 00458 make_triple(0, 128, 128))), 00459 m_lrf_size(clamp(get_conf("device", "lrf_size", 100.0f), 10.0f, 250.0f)), 00460 m_lrf_direction(clamp_angle(get_conf("device", "lrf_direction", 90.0f))), 00461 m_lrf_color(get_conf<int>("device", "lrf_color", 00462 make_triple(242, 13, 26))), 00463 m_zoom_drag_factor(clamp(get_conf("zoom_pan", "zoom_drag_factor", 0.1f), 00464 0.1f, 2.5f)) 00465 {} 00466 00467 // Parameters clean-up 00468 LaserWindow::Params::~Params(){} 00469 00470 // Parameters access 00471 const std::string& LaserWindow::Params::device() 00472 { 00473 return instance().m_device ; 00474 } 00475 00476 int LaserWindow::Params::baud_rate() 00477 { 00478 return instance().m_baud_rate ; 00479 } 00480 00481 const std::string& LaserWindow::Params::markings_type() 00482 { 00483 return instance().m_markings_type ; 00484 } 00485 00486 int LaserWindow::Params::update_frequency() 00487 { 00488 return instance().m_update_frequency ; 00489 } 00490 00491 const range<int>& LaserWindow::Params::angles_range() 00492 { 00493 return instance().m_angles_range ; 00494 } 00495 00496 int LaserWindow::Params::angles_step() 00497 { 00498 return instance().m_angles_step ; 00499 } 00500 00501 const GLColor& LaserWindow::Params::measurements_color() 00502 { 00503 return instance().m_measurements_color ; 00504 } 00505 00506 float LaserWindow::Params::lrf_size() 00507 { 00508 return instance().m_lrf_size ; 00509 } 00510 00511 float LaserWindow::Params::lrf_direction() 00512 { 00513 return instance().m_lrf_direction ; 00514 } 00515 00516 const GLColor& LaserWindow::Params::lrf_color() 00517 { 00518 return instance().m_lrf_color ; 00519 } 00520 00521 float LaserWindow::Params::zoom_drag_factor() 00522 { 00523 return instance().m_zoom_drag_factor ; 00524 } 00525 00526 //----------------------------------------------------------------------- 00527 00528 } // end of namespace encapsulating this file's definitions 00529 00530 #endif // #ifndef INVT_HAVE_LIBGLUT 00531 00532 /* So things look consistent in everyone's emacs... */ 00533 /* Local Variables: */ 00534 /* indent-tabs-mode: nil */ 00535 /* End: */