00001 /** 00002 \file Robots/LoBot/control/LoGoal.C 00003 \brief This file defines the non-inline member functions of the 00004 lobot::Goal class. 00005 */ 00006 00007 // //////////////////////////////////////////////////////////////////// // 00008 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00009 // by the University of Southern California (USC) and the iLab at USC. // 00010 // See http://iLab.usc.edu for information about this project. // 00011 // //////////////////////////////////////////////////////////////////// // 00012 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00013 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00014 // in Visual Environments, and Applications'' by Christof Koch and // 00015 // Laurent Itti, California Institute of Technology, 2001 (patent // 00016 // pending; application number 09/912,225 filed July 23, 2001; see // 00017 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00018 // //////////////////////////////////////////////////////////////////// // 00019 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00020 // // 00021 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00022 // redistribute it and/or modify it under the terms of the GNU General // 00023 // Public License as published by the Free Software Foundation; either // 00024 // version 2 of the License, or (at your option) any later version. // 00025 // // 00026 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00027 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00028 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00029 // PURPOSE. See the GNU General Public License for more details. // 00030 // // 00031 // You should have received a copy of the GNU General Public License // 00032 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00033 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00034 // Boston, MA 02111-1307 USA. // 00035 // //////////////////////////////////////////////////////////////////// // 00036 // 00037 // Primary maintainer for this file: mviswana usc edu 00038 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Robots/LoBot/control/LoGoal.C $ 00039 // $Id: LoGoal.C 13812 2010-08-21 04:04:10Z mviswana $ 00040 // 00041 00042 //------------------------------ HEADERS -------------------------------- 00043 00044 // lobot headers 00045 #include "Robots/LoBot/control/LoGoal.H" 00046 #include "Robots/LoBot/control/LoMetrics.H" 00047 #include "Robots/LoBot/control/LoTurnArbiter.H" 00048 #include "Robots/LoBot/control/LoSpinArbiter.H" 00049 00050 #include "Robots/LoBot/LoApp.H" 00051 #include "Robots/LoBot/slam/LoMap.H" 00052 #include "Robots/LoBot/slam/LoSlamParams.H" 00053 00054 #include "Robots/LoBot/config/LoConfigHelpers.H" 00055 00056 #include "Robots/LoBot/thread/LoShutdown.H" 00057 #include "Robots/LoBot/thread/LoUpdateLock.H" 00058 #include "Robots/LoBot/thread/LoPause.H" 00059 00060 #include "Robots/LoBot/misc/LoExcept.H" 00061 #include "Robots/LoBot/misc/LoRegistry.H" 00062 #include "Robots/LoBot/misc/singleton.hh" 00063 00064 #include "Robots/LoBot/util/LoGL.H" 00065 #include "Robots/LoBot/util/LoMath.H" 00066 00067 // OpenGL headers 00068 #ifdef INVT_HAVE_LIBGLU 00069 #include <GL/glu.h> 00070 #endif 00071 00072 #ifdef INVT_HAVE_LIBGL 00073 #include <GL/gl.h> 00074 #endif 00075 00076 // Standard C++ headers 00077 #include <iomanip> 00078 #include <sstream> 00079 #include <numeric> 00080 #include <algorithm> 00081 #include <functional> 00082 #include <iterator> 00083 #include <utility> 00084 00085 //----------------------------- NAMESPACE ------------------------------- 00086 00087 namespace lobot { 00088 00089 //-------------------------- KNOB TWIDDLING ----------------------------- 00090 00091 namespace { 00092 00093 // Retrieve settings from goal section of config file 00094 template<typename T> 00095 inline T conf(const std::string& key, const T& default_value) 00096 { 00097 return get_conf<T>(LOBE_GOAL, key, default_value) ; 00098 } 00099 00100 // Overload for retrieving pairs 00101 template<typename T> 00102 inline std::pair<T, T> 00103 conf(const std::string& key, const std::pair<T, T>& default_value) 00104 { 00105 return get_conf<T>(LOBE_GOAL, key, default_value) ; 00106 } 00107 00108 /// This local class encapsulates various parameters that can be used to 00109 /// tweak different aspects of the goal behaviour. 00110 class Params : public singleton<Params> { 00111 /// This behaviour can be configured to actively steer the robot to 00112 /// each goal or to simply monitor the robot's current location to 00113 /// check if the robot is at a goal or not. When set, this flag will 00114 /// make the goal behaviour actively seek each goal in its list. By 00115 /// default, this flag is off, i.e., the behaviour only monitors the 00116 /// robot's location without affecting its steering in any way. 00117 bool m_seek_mode ; 00118 00119 /// What should we do when we reach a goal? The options are: 00120 /// 00121 /// - continue on to the next goal 00122 /// - pause robot and wait for user to start it moving again 00123 /// 00124 /// This setting specifies which of the above two actions to use. By 00125 /// default, the goal behaviour implements the "continue" action, 00126 /// i.e., when the robot reaches a goal, it simply carries on to the 00127 /// next one. However, by setting this flag, the behaviour will pause 00128 /// the robot and wait for the user to explicitly signal resumption of 00129 /// normal operations. 00130 bool m_pause ; 00131 00132 /// By default, when the final goal in the goal list is reached, the 00133 /// goal behaviour will maintain that goal. Thus, if another behaviour 00134 /// causes the robot to move away from the final goal in the goal 00135 /// list, the goal behaviour will redirect the robot back to that 00136 /// goal. 00137 /// 00138 /// However, by setting this flag, we can have the goal behaviour loop 00139 /// back to the first goal in the list and start over. 00140 bool m_loop ; 00141 00142 /// In loop mode, instead of starting over at the first goal, we can 00143 /// have the goal behaviour backtrack over the goal list, i.e., seek 00144 /// each of the goals in reverse. This flag enables backtracking. It 00145 /// is off by default. 00146 bool m_backtrack ; 00147 00148 /// When configured to actively steer the robot towards goals, the 00149 /// goal behaviour directs the robot towards the current goal by 00150 /// implementing the VFF method described by Borenstein and Koren (see 00151 /// "Real-time Obstacle Avoidance for Fast Mobile Robots," IEEE 00152 /// Transactions on Systems, Man, and Cybernetics 19(5):1179--1187, 00153 /// Sep/Oct 1989). 00154 /// 00155 /// The following setting specifies the size of the active window 00156 /// within the certainty grid (i.e., occupancy map). This size is 00157 /// specified in terms of the number of cells occupied by the active 00158 /// window. 00159 /// 00160 /// NOTE: Actually, this setting specifies half of the active window's 00161 /// size. That is, internally, this number is doubled. 00162 int m_vff_window_size ; 00163 00164 /// The VFF method mentioned above computes a repulsive force vector 00165 /// for each cell within the active window and an attractive force 00166 /// vector for the goal. The sum of all the repulsive forces and the 00167 /// single attractive force result in a final steering vector. 00168 /// 00169 /// This setting specifies the force constants to use for the 00170 /// individual force vectors. It should be a pair of numbers. The 00171 /// first number is the repulsive force constant and the second one is 00172 /// the attractive force constant. 00173 std::pair<float, float> m_vff_forces ; 00174 00175 /// Usually, steering control is effected using the turn arbiter, 00176 /// which veers the robot in different directions while it moves, 00177 /// i.e., smooth car-like turns. However, the lgmd_extricate_tti 00178 /// behaviour also supports spin-style steering, i.e., momentarily 00179 /// stopping the robot and then turning it cw/ccw in-place. This flag 00180 /// turns on spin-style steering. By default, the behaviour uses the 00181 /// normal car-like steering mode. 00182 bool m_spin_style_steering ; 00183 00184 /// The number of milliseconds between successive iterations of this 00185 /// behaviour. 00186 /// 00187 /// WARNING: The ability to change a behaviour's update frequency is a 00188 /// very powerful feature whose misuse or abuse can wreak havoc! Be 00189 /// sure to use reasonable values for this setting. 00190 int m_update_delay ; 00191 00192 /// The location and size (within the Robolocust main window) of the 00193 /// goal behaviour's visualization. 00194 typedef Drawable::Geometry Geom ; // just for properly aligning accessors 00195 Drawable::Geometry m_geometry ; 00196 00197 /// In addition to rendering its steering votes, the goal behaviour 00198 /// can also render the goal list on the Robolocust map. This flag 00199 /// turns on goal list rendering. It is off by default. 00200 bool m_render_goals ; 00201 00202 /// Private constructor because this is a singleton. 00203 Params() ; 00204 friend class singleton<Params> ; 00205 00206 public: 00207 /// Accessing the various parameters 00208 //@{ 00209 static bool seek_mode() {return instance().m_seek_mode ;} 00210 static bool pause() {return instance().m_pause ;} 00211 static bool loop() {return instance().m_loop ;} 00212 static bool backtrack() {return instance().m_backtrack ;} 00213 static bool render_goals() {return instance().m_render_goals ;} 00214 static bool spin_style_steering(){return instance().m_spin_style_steering;} 00215 static int update_delay() {return instance().m_update_delay ;} 00216 static Geom geometry() {return instance().m_geometry ;} 00217 static int vff_window_size() {return instance().m_vff_window_size ;} 00218 static float vff_rep_force() {return instance().m_vff_forces.first ;} 00219 static float vff_att_force() {return instance().m_vff_forces.second ;} 00220 //@} 00221 } ; 00222 00223 // Parameter initialization 00224 Params::Params() 00225 : m_seek_mode(conf("actively_seek_goals", false)), 00226 m_pause(conf("pause_at_goal", false)), 00227 m_loop(conf("loop", false)), 00228 m_backtrack(conf("backtrack", false)), 00229 m_vff_forces(conf("vff_forces", std::make_pair(100.0f, 1000.0f))), 00230 m_spin_style_steering(conf("spin_style_steering", false)), 00231 m_update_delay(clamp(conf("update_delay", 1750), 500, 10000)), 00232 m_geometry(conf<std::string>("geometry", "0 0 10 10")), 00233 m_render_goals(conf("render_goals", false)) 00234 { 00235 const float R = clamp(robot_conf("size", 400), 150, 1000) ; 00236 const float s = SlamParams::map_cell_size() ; 00237 const int n = round(R/s)/2 ; // num cells occupied by half robot 00238 m_vff_window_size = clamp(conf("vff_window_size", 3*n/2), n + 1, 4 * n) ; 00239 00240 m_vff_forces.first = clamp(m_vff_forces.first, 1.0f, 1e6f) ; 00241 m_vff_forces.second = clamp(m_vff_forces.second, 1.0f, 1e6f) ; 00242 } 00243 00244 } // end of local anonymous namespace encapsulating above helpers 00245 00246 //-------------------------- INITIALIZATION ----------------------------- 00247 00248 Goal::Goal() 00249 : base(Params::update_delay(), LOBE_GOAL, Params::geometry()), 00250 m_goal(-1), m_turn_dir(0) 00251 { 00252 start(LOBE_GOAL) ; 00253 } 00254 00255 Goal::Target::Target(float L, float R, float B, float T) 00256 : m_left(L), m_right(R), m_bottom(B), m_top(T), 00257 m_center_x((L + R)/2), m_center_y((B + T)/2) 00258 {} 00259 00260 void Goal::pre_run() 00261 { 00262 if (! App::robot()) 00263 throw behavior_error(MOTOR_SYSTEM_MISSING) ; 00264 00265 Map* map = App::map() ; 00266 if (! map) 00267 throw behavior_error(MAPPING_DISABLED) ; 00268 00269 // Read in goal list 00270 float L, R, B, T ; 00271 SlamParams::map_extents(&L, &R, &B, &T) ; 00272 std::istringstream goals(conf<std::string>("goals", "")) ; 00273 for(;;) 00274 { 00275 float l, r, b, t ; 00276 goals >> l >> r >> b >> t ; 00277 if (! goals) 00278 break ; 00279 m_goals.push_back(Target(clamp(l, L, R), clamp(r, l, R), 00280 clamp(b, B, T), clamp(t, b, T))) ; 00281 } 00282 if (m_goals.empty()) 00283 throw behavior_error(NO_GOALS) ; 00284 00285 // Start off seeking the first goal 00286 m_goal = 0 ; 00287 00288 // Easy way to implement backtracking: simply append the goals in 00289 // reverse to the goal list. 00290 if (Params::loop() && Params::backtrack()) 00291 std::copy(m_goals.rbegin() + 1, m_goals.rend() - 1, 00292 std::back_inserter(m_goals)) ; 00293 00294 // Init VFF mask 00295 const int W = Params::vff_window_size() ; 00296 const float S = SlamParams::map_cell_size() ; 00297 const float F = -Params::vff_rep_force() ; 00298 00299 int x, y ; 00300 float dx, dy ; 00301 m_vff_mask.reserve(sqr(W * 2 + 1)) ; 00302 for (y = W, dy = y * S; y >= -W; --y, dy -= S) 00303 for (x = -W, dx = x * S; x <= W; ++x, dx += S) 00304 { 00305 float d2 = sqr(dx) + sqr(dy) ; 00306 float d = sqrtf(d2) ; 00307 m_vff_mask.push_back(Vector((x == 0) ? 0 : F/d2 * dx/d, 00308 (y == 0) ? 0 : F/d2 * dy/d)) ; 00309 } 00310 00311 // Add goal rendering callback to map object 00312 if (Params::render_goals()) 00313 map->add_hook(RenderHook( 00314 render_goals, reinterpret_cast<unsigned long>(this))) ; 00315 } 00316 00317 //---------------------- THE BEHAVIOUR'S ACTION ------------------------- 00318 00319 // Override from base class because we want to monitor the Pause state. 00320 // Every time the application resumes from a paused state, we want to 00321 // send a message to the metrics log stating that goal seeking has begun. 00322 // When a goal is reached, the action method will log that event. 00323 // 00324 // DEVNOTE: In the while loop below, we use the App::map() function's 00325 // returned pointer without checking it. This is okay because pre_run() 00326 // would have thrown an exception if it were not. 00327 void Goal::run() 00328 { 00329 try 00330 { 00331 App::wait_for_init() ; 00332 pre_run() ; 00333 00334 const Map* M = App::map() ; 00335 bool paused_previously = Pause::is_set() ; 00336 while (! Shutdown::signaled()) 00337 { 00338 bool paused_now = Pause::is_set() ; 00339 if (! paused_now) { 00340 if (paused_previously) 00341 log("seeking goal", m_goal, m_goals[m_goal], M->current_pose()); 00342 action() ; 00343 } 00344 paused_previously = paused_now ; 00345 usleep(m_update_delay) ; 00346 } 00347 00348 post_run() ; 00349 } 00350 catch (uhoh& e) 00351 { 00352 LERROR("behaviour %s encountered an error: %s", name.c_str(), e.what()) ; 00353 return ; 00354 } 00355 } 00356 00357 // To drive towards the current goal, we turn based on the difference 00358 // between the current bearing and the steering vector computed by the 00359 // VFF technique. This steering vector takes into account the local 00360 // obstacle configuration as well as the goal location. 00361 void Goal::action() 00362 { 00363 const Map* map = App::map() ; 00364 const Pose P = map->current_pose() ; 00365 00366 int turn_dir = 0 ; 00367 Vector Ft, Fr, R ; 00368 00369 const Target goal = m_goals[m_goal] ; 00370 if (goal.at(P.x(), P.y())) 00371 { 00372 log("reached goal", m_goal, goal, P) ; 00373 00374 if (Params::pause()) 00375 Pause::set() ; 00376 00377 ++m_goal ; 00378 if (m_goal == static_cast<int>(m_goals.size())) { 00379 if (Params::loop()) 00380 m_goal = 0 ; 00381 else // maintain final goal in list 00382 --m_goal ; 00383 } 00384 } 00385 else if (Params::seek_mode()) // not yet at goal and configured to seek it 00386 { 00387 // We use Borenstein and Koren's VFF method to steer the robot 00388 // towards the goal. The first step is to obtain the VFF active 00389 // window, which is a subportion of the occupancy map centered at 00390 // the robot's current position. 00391 std::vector<unsigned char> vff_window = 00392 map->submap(Params::vff_window_size()) ; 00393 00394 // The lobot::Map class uses zero to indicate the presence of an 00395 // obstacle and 255 for free space. We need to flip that so that 00396 // the repulsive force computations work correctly. 00397 std::transform(vff_window.begin(), vff_window.end(), vff_window.begin(), 00398 std::bind1st(std::minus<unsigned char>(), 255)) ; 00399 00400 // Now, compute the repulsive force vector by convolving the VFF 00401 // vector mask with the certainty values in the active window. 00402 Fr = std::inner_product(m_vff_mask.begin(), m_vff_mask.end(), 00403 vff_window.begin(), Vector(0, 0)) ; 00404 00405 // Compute the target/goal's attractive force vector... 00406 Ft = normalized(Vector(goal.x() - P.x(), goal.y() - P.y())) 00407 * Params::vff_att_force() ; 00408 00409 // Compute the steering vector... 00410 R = Ft + Fr ; 00411 00412 // Follow the VFF steering vector by determining the amount of 00413 // error between it and the robot's current heading. 00414 const int T = TurnArbiter::turn_max() ; 00415 const float current_heading = 00416 (P.bearing() < 180) ? P.bearing() : P.bearing() - 360 ; 00417 const int heading_error = round(direction(R) - current_heading) ; 00418 if (Params::spin_style_steering()) 00419 { 00420 turn_dir = heading_error ; 00421 SpinArbiter::instance().vote(base::name, 00422 new SpinArbiter::Vote(turn_dir)) ; 00423 } 00424 else 00425 { 00426 turn_dir = clamp(heading_error, -T, T) ; 00427 TurnArbiter::instance().vote(base::name, 00428 new TurnArbiter::Vote(turn_vote_centered_at(turn_dir))) ; 00429 } 00430 } 00431 00432 // Record VFF vectors for visualization 00433 viz_lock() ; 00434 m_attractive = Ft ; 00435 m_repulsive = Fr ; 00436 m_steering = R ; 00437 m_turn_dir = turn_dir ; 00438 viz_unlock() ; 00439 } 00440 00441 // Helper to send appropriate messages to metrics log 00442 void 00443 Goal:: 00444 log(const char* msg, int index, const Goal::Target& goal, const Pose& p) const 00445 { 00446 int n = index + 1 ; 00447 const int N = m_goals.size()/2 + 1 ; 00448 if (Params::loop() && Params::backtrack() && n > N) 00449 n = N - (n - N) ; 00450 00451 Metrics::Log() << std::setw(Metrics::opw()) << std::left 00452 << (std::string(msg) + " #" + to_string(n)) 00453 << goal << " from/at" << Metrics::newl() 00454 << dup_string(" ", 6 + Metrics::opw()) << p ; 00455 } 00456 00457 //--------------------------- VISUALIZATION ----------------------------- 00458 00459 #ifdef INVT_HAVE_LIBGL 00460 00461 // Helper function to convert the magnitude of a vector into a string, 00462 // prefixing the supplied single character label to the mag string. 00463 static std::string mag(char label, const Vector& v) 00464 { 00465 using namespace std ; 00466 00467 std::ostringstream v_str ; 00468 v_str << label << ": " 00469 << setw(8) << fixed << setprecision(1) << magnitude(v) ; 00470 return v_str.str() ; 00471 } 00472 00473 void Goal::render_me() 00474 { 00475 // Make local copy so that goal behaviour's thread isn't held up 00476 // waiting for visualization thread to complete. 00477 viz_lock() ; 00478 Vector A = m_attractive ; 00479 Vector R = m_repulsive ; 00480 Vector T = m_steering ; 00481 int turn = m_turn_dir ; 00482 viz_unlock() ; 00483 00484 // Draw principal axes (to make it easier to understand what's going 00485 // on with the force vectors and turn command). 00486 unit_view_volume() ; 00487 glColor3f(1, 1, 1) ; 00488 glBegin(GL_LINES) ; 00489 glVertex2i(0, -1) ; 00490 glVertex2i(0, 1) ; 00491 glVertex2i(-1, 0) ; 00492 glVertex2i( 1, 0) ; 00493 glEnd() ; 00494 00495 // Render the VFF vectors 00496 if (is_zero(T.i) && is_zero(T.j)) 00497 ; // no steering took place 00498 else 00499 { 00500 glColor3f(0, 1, 0) ; 00501 draw_vector(normalized(A)) ; 00502 00503 glColor3f(1, 0, 0) ; 00504 draw_vector(normalized(R)) ; 00505 00506 glColor3f(0.15f, 0.75f, 0.85f) ; 00507 draw_vector(normalized(T)) ; 00508 00509 glColor3f(1, 1, 1) ; 00510 draw_vector(Vector(0.75f * cos(turn), 0.75f * sin(turn))) ; 00511 } 00512 00513 // Label the visualization so that it is easy to tell which behaviour 00514 // is being visualized. Also label the speed value and, if applicable, 00515 // the distance reading that resulted in the chosen speed. 00516 restore_view_volume() ; 00517 text_view_volume() ; 00518 glColor3f(0, 1, 1) ; 00519 draw_label(3, 12, "Goal") ; 00520 00521 glColor3f(0, 1, 0) ; 00522 draw_label(3, m_geometry.height - 28, mag('A', A).c_str()) ; 00523 00524 glColor3f(1, 0, 0) ; 00525 draw_label(3, m_geometry.height - 16, mag('R', R).c_str()) ; 00526 00527 glColor3f(0.15f, 0.75f, 0.85f) ; 00528 draw_label(3, m_geometry.height - 4, mag('T', T).c_str()) ; 00529 restore_view_volume() ; 00530 } 00531 00532 void Goal::render_goals(unsigned long client_data) 00533 { 00534 (reinterpret_cast<Goal*>(client_data))->render_goals() ; 00535 } 00536 00537 void Goal::render_goals() 00538 { 00539 // Setup 2D "view volume" to match real/physical coordinate system 00540 // except that the whole thing is rotated 90 degrees ccw so that our 00541 // notion of "up" matches that of the robot's. 00542 float L, R, B, T ; // map extents 00543 SlamParams::map_extents(&L, &R, &B, &T) ; 00544 setup_view_volume(T, B, L, R) ; 00545 00546 // Now we're ready to draw the goal locations... 00547 glPushAttrib(GL_POINT_BIT | GL_LINE_BIT) ; 00548 glPointSize(5) ; 00549 glColor3f(0, 0, 1) ; 00550 glBegin(GL_POINTS) ; 00551 std::for_each(m_goals.begin(), m_goals.end(), render_goal) ; 00552 glEnd() ; 00553 00554 // For current goal being sought, we also draw a dashed box around it 00555 glEnable(GL_LINE_STIPPLE) ; 00556 glLineStipple(1, 0xCCCC) ; 00557 glColor3f(1, 0, 0) ; 00558 glBegin(GL_LINE_LOOP) ; 00559 const Target& current = m_goals[m_goal] ; // WARNING: m_goal: no viz lock 00560 glVertex2f(current.T(), current.L()) ; 00561 glVertex2f(current.B(), current.L()) ; 00562 glVertex2f(current.B(), current.R()) ; 00563 glVertex2f(current.T(), current.R()) ; 00564 glEnd() ; 00565 glPopAttrib() ; 00566 00567 // Reset GL transformations so next drawable won't get screwed 00568 restore_view_volume() ; 00569 } 00570 00571 void Goal::render_goal(const Goal::Target& t) 00572 { 00573 glVertex2f(t.y(), t.x()) ;//coord sys 90 degree ccw rotation ==> swap x & y 00574 } 00575 00576 #endif 00577 00578 //----------------------------- CLEAN-UP -------------------------------- 00579 00580 Goal::~Goal(){} 00581 00582 //----------------------------------------------------------------------- 00583 00584 } // end of namespace encapsulating this file's definitions 00585 00586 /* So things look consistent in everyone's emacs... */ 00587 /* Local Variables: */ 00588 /* indent-tabs-mode: nil */ 00589 /* End: */