Revision: 8623 http://playerstage.svn.sourceforge.net/playerstage/?rev=8623&view=rev Author: rtv Date: 2010-04-26 13:50:18 +0000 (Mon, 26 Apr 2010)
Log Message: ----------- beautifying fasr2 code Modified Paths: -------------- code/stage/trunk/examples/ctrl/fasr2.cc code/stage/trunk/libstage/model_fiducial.cc code/stage/trunk/worlds/fasr2.world Modified: code/stage/trunk/examples/ctrl/fasr2.cc =================================================================== --- code/stage/trunk/examples/ctrl/fasr2.cc 2010-04-24 22:47:52 UTC (rev 8622) +++ code/stage/trunk/examples/ctrl/fasr2.cc 2010-04-26 13:50:18 UTC (rev 8623) @@ -1,290 +1,195 @@ +#include <pthread.h> + #include "stage.hh" using namespace Stg; +// generic planner implementation #include "astar/astar.h" -const bool verbose = false; +static const bool verbose = false; // navigation control params -const double cruisespeed = 0.4; -const double avoidspeed = 0.05; -const double avoidturn = 0.5; -const double minfrontdistance = 0.7; -const double stopdist = 0.5; -const unsigned int avoidduration = 10; -const unsigned int PAYLOAD = 1; +static const double cruisespeed = 0.4; +static const double avoidspeed = 0.05; +static const double avoidturn = 0.5; +static const double minfrontdistance = 0.7; +static const double stopdist = 0.5; +static const unsigned int avoidduration = 10; +static const unsigned int PAYLOAD = 1; -//const int TRAIL_LENGTH_MAX = 500; -typedef enum { - MODE_WORK=0, - MODE_DOCK, - MODE_UNDOCK, - MODE_QUEUE -} nav_mode_t; - - - -// abstract base class -class Queue +static unsigned int +MetersToCell( stg_meters_t m, stg_meters_t size_m, unsigned int size_c ) { -public: - Queue() {}; - virtual ~Queue() {} - - virtual int Clear() = 0; - virtual int Position( const char* id ) = 0; - virtual int Join( const char* id ) = 0; - virtual int Leave( const char* id ) = 0; -}; - -// on-host implementation -#include <list> - -class Task -{ -public: - Model* source; - Model* sink; - unsigned int participants; - - Task( Model* source, Model* sink ) - : source(source), sink(sink), participants(0) - {} -}; - -class LocalQueue : public Queue -{ -public: - std::list<std::string> list; - //pthread_mutex_t* mutex; - - LocalQueue() : - Queue(), - list() - //mutex( NULL ) - { - //pthread_mutex_init( &mutex, NULL ); - } - - virtual int Clear() - { - list.clear(); - return 0; - } - - virtual int Position( const char* id ) - { - std::list<std::string>::iterator it( std::find( list.begin(), list.end(), std::string(id) )); - - if( it == list.end() ) - return -1; - else - return std::distance( list.begin(), it ); - } - - virtual int Join( const char* id ) - { - list.push_back( id ); - return 0; - } - - virtual int Leave( const char* id ) - { - list.erase( std::remove( list.begin(), list.end(), std::string(id))); - return 0; - } - - virtual void Print( const char* prefix ) - { - printf( "%s queue:\n", prefix ); - FOR_EACH( it, list ) - printf( "\t%s\n", it->c_str() ); - } -}; - - -class Node; - -class Edge -{ -public: - Node* to; - double cost; - - Edge( Node* to, double cost=1.0 ) - : to(to), cost(cost) {} -}; - -class Node -{ -public: - Pose pose; - double value; - std::vector<Edge*> edges; - - Node( Pose pose ) - : pose(pose), value(0), edges() {} - - ~Node() - { - FOR_EACH( it, edges ) - { delete *it; } - } - - void AddEdge( Edge* edge ) - { - assert(edge); - edges.push_back( edge ); - } - - void Draw() const; -}; - - -class Graph -{ -public: - std::vector<Node*> nodes; - - Graph(){} - ~Graph() { FOR_EACH( it, nodes ){ delete *it; }} - - void AddNode( Node* node ){ nodes.push_back( node ); } - - void PopFront() - { - const std::vector<Node*>::iterator& it = nodes.begin(); - delete *it; - nodes.erase( it ); - } - - void Draw() const - { - glPointSize(3); - FOR_EACH( it, nodes ){ (*it)->Draw(); } - } - - bool GoodDirection( const Pose& pose, stg_meters_t range, stg_radians_t& heading_result ) - { - // find the node with the lowest value within range of the given - // pose and return the absolute heading from pose to that node - - if( nodes.empty() ) - return 0; // a null guess - - - Node* best_node = NULL; - - // find the closest node - FOR_EACH( it, nodes ) - { - Node* node = *it; - double dist = hypot( node->pose.x - pose.x, node->pose.y - pose.y ); - - // if it's in range, and either its the first we have found, - // or it has a lower value than the current best - if( dist < range && - ( best_node == NULL || node->value < best_node->value )) - { - best_node = node; - } - } - - if( best_node == NULL ) - { - printf( "FASR warning: no nodes in range" ); - return false; - } - - //else - heading_result = atan2( best_node->pose.y - pose.y, - best_node->pose.x - pose.x ); - - // add a little bias - heading_result = normalize( heading_result + 0.25 ); - - return true; - } -}; - - -class GraphVis : public Visualizer -{ -public: - Graph& graph; - - GraphVis( Graph& graph ) - : Visualizer( "graph", "vis_graph" ), graph(graph) {} - virtual ~GraphVis(){} - - virtual void Visualize( Model* mod, Camera* cam ) - { - - glPushMatrix(); - - Gl::pose_inverse_shift( mod->GetGlobalPose() ); - - //mod->PushColor( 1,0,0,1 ); - - Color c = mod->GetColor(); - c.a = 0.4; - - mod->PushColor( c ); - graph.Draw(); - mod->PopColor(); - - glPopMatrix(); - } -}; - - - -void Node::Draw() const -{ - // print value - //char buf[32]; - //snprintf( buf, 32, "%.0f", value ); - //Gl::draw_string( pose.x, pose.y+0.2, 0.1, buf ); - - //glBegin( GL_POINTS ); - //glVertex2f( pose.x, pose.y ); - //glEnd(); - - glBegin( GL_LINES ); - FOR_EACH( it, edges ) - { - glVertex2f( pose.x, pose.y ); - glVertex2f( (*it)->to->pose.x, (*it)->to->pose.y ); - } - glEnd(); -} - -unsigned int MetersToCell( stg_meters_t m, stg_meters_t size_m, unsigned int size_c ) -{ m += size_m / 2.0; // shift to local coords m /= size_m / (float)size_c; // scale return (unsigned int)floor(m); // quantize } -stg_meters_t CellToMeters( unsigned int c, stg_meters_t size_m, unsigned int size_c ) +static stg_meters_t +CellToMeters( unsigned int c, stg_meters_t size_m, unsigned int size_c ) { stg_meters_t cell_size = size_m/(float)size_c; stg_meters_t m = c * cell_size; // scale m -= size_m / 2.0; // shift to local coords - return m + cell_size/2.0; // offset to cell center } -#include <pthread.h> - class Robot { +public: + class Task + { + public: + Model* source; + Model* sink; + unsigned int participants; + + Task( Model* source, Model* sink ) + : source(source), sink(sink), participants(0) + {} + }; + + static std::vector<Task> tasks; + private: - static std::vector<Robot*> robots; + class Node; + + class Edge + { + public: + Node* to; + double cost; + + Edge( Node* to, double cost=1.0 ) + : to(to), cost(cost) {} + }; + + class Node + { + public: + Pose pose; + double value; + std::vector<Edge*> edges; + + Node( Pose pose, double value=0 ) + : pose(pose), value(value), edges() {} + + ~Node() + { + FOR_EACH( it, edges ) + { delete *it; } + } + + void AddEdge( Edge* edge ) + { + assert(edge); + edges.push_back( edge ); + } + + void Draw() const; + }; + + class Graph + { + public: + std::vector<Node*> nodes; + + Graph(){} + ~Graph() { FOR_EACH( it, nodes ){ delete *it; }} + + void AddNode( Node* node ){ nodes.push_back( node ); } + + void PopFront() + { + const std::vector<Node*>::iterator& it = nodes.begin(); + delete *it; + nodes.erase( it ); + } + + void Draw() const + { + glPointSize(3); + FOR_EACH( it, nodes ){ (*it)->Draw(); } + } + + bool GoodDirection( const Pose& pose, stg_meters_t range, stg_radians_t& heading_result ) + { + // find the node with the lowest value within range of the given + // pose and return the absolute heading from pose to that node + + if( nodes.empty() ) + return 0; // a null guess + + + Node* best_node = NULL; + + // find the closest node + FOR_EACH( it, nodes ) + { + Node* node = *it; + double dist = hypot( node->pose.x - pose.x, node->pose.y - pose.y ); + + // if it's in range, and either its the first we have found, + // or it has a lower value than the current best + if( dist < range && + ( best_node == NULL || node->value < best_node->value )) + { + best_node = node; + } + } + + if( best_node == NULL ) + { + printf( "FASR warning: no nodes in range" ); + return false; + } + //else + heading_result = atan2( best_node->pose.y - pose.y, + best_node->pose.x - pose.x ); + + // add a little bias to one side (the left) - creates two lanes + // of robot traffic + heading_result = normalize( heading_result + 0.25 ); + + return true; + } + }; + + class GraphVis : public Visualizer + { + public: + Graph& graph; + + GraphVis( Graph& graph ) + : Visualizer( "graph", "vis_graph" ), graph(graph) {} + virtual ~GraphVis(){} + + virtual void Visualize( Model* mod, Camera* cam ) + { + + glPushMatrix(); + + Gl::pose_inverse_shift( mod->GetGlobalPose() ); + + //mod->PushColor( 1,0,0,1 ); + + Color c = mod->GetColor(); + c.a = 0.4; + + mod->PushColor( c ); + graph.Draw(); + mod->PopColor(); + + glPopMatrix(); + } + }; + + +private: long int wait_started_at; ModelPosition* pos; @@ -294,7 +199,6 @@ unsigned int task; - //Model *source, *sink; Model* fuel_zone; Model* pool_zone; int avoidcount, randcount; @@ -303,8 +207,14 @@ double charger_bearing; double charger_range; double charger_heading; - nav_mode_t mode; - + + enum { + MODE_WORK=0, + MODE_DOCK, + MODE_UNDOCK, + MODE_QUEUE + } mode; + stg_radians_t docked_angle; static pthread_mutex_t planner_mutex; @@ -318,8 +228,8 @@ unsigned int node_interval; unsigned int node_interval_countdown; - static unsigned int map_width; - static unsigned int map_height; + static const unsigned int map_width; + static const unsigned int map_height; static uint8_t* map_data; static Model* map_model; @@ -329,817 +239,741 @@ bool force_recharge; - static std::vector<LocalQueue> queues; - public: - static std::vector<Task> tasks; Robot( ModelPosition* pos, - Model* fuel, - Model* pool ) - : - wait_started_at(-1), - pos(pos), - laser( (ModelLaser*)pos->GetUnusedModelOfType( "laser" )), - ranger( (ModelRanger*)pos->GetUnusedModelOfType( "ranger" )), - fiducial( (ModelFiducial*)pos->GetUnusedModelOfType( "fiducial" )), - task(random() % tasks.size() ), // choose a task at random - fuel_zone(fuel), - pool_zone(pool), - avoidcount(0), - randcount(0), - work_get(0), - work_put(0), - charger_ahoy(false), - charger_bearing(0), - charger_range(0), - charger_heading(0), - mode(MODE_WORK), - docked_angle(0), - goal(tasks[task].source), - cached_goal_pose(), - graph(), - graphvis( graph ), - last_node( NULL ), - node_interval( 20 ), - node_interval_countdown( node_interval ), - fiducial_sub(false), - laser_sub(false), - ranger_sub(false), - force_recharge( false ) + Model* fuel, + Model* pool ) + : + wait_started_at(-1), + pos(pos), + laser( (ModelLaser*)pos->GetUnusedModelOfType( "laser" )), + ranger( (ModelRanger*)pos->GetUnusedModelOfType( "ranger" )), + fiducial( (ModelFiducial*)pos->GetUnusedModelOfType( "fiducial" )), + task(random() % tasks.size() ), // choose a task at random + fuel_zone(fuel), + pool_zone(pool), + avoidcount(0), + randcount(0), + work_get(0), + work_put(0), + charger_ahoy(false), + charger_bearing(0), + charger_range(0), + charger_heading(0), + mode(MODE_WORK), + docked_angle(0), + goal(tasks[task].source), + cached_goal_pose(), + graph(), + graphvis( graph ), + last_node( NULL ), + node_interval( 20 ), + node_interval_countdown( node_interval ), + fiducial_sub(false), + laser_sub(false), + ranger_sub(false), + force_recharge( false ) { - // add myself to the static list of all robots - robots.push_back( this ); - - // need at least these models to get any work done - // (pos must be good, as we used it in the initialization list) - assert( laser ); - assert( fiducial ); - assert( ranger ); - assert( goal ); + // need at least these models to get any work done + // (pos must be good, as we used it in the initialization list) + assert( laser ); + assert( fiducial ); + assert( ranger ); + assert( goal ); - // match the color of my destination - pos->SetColor( tasks[task].source->GetColor() ); + // match the color of my destination + pos->SetColor( tasks[task].source->GetColor() ); + + tasks[task].participants++; + + EnableLaser(true); - tasks[task].participants++; + // we access all other data from the position callback + pos->AddCallback( Model::CB_UPDATE, (stg_model_callback_t)UpdateCallback, this ); + pos->Subscribe(); - // pos->GetUnusedModelOfType( "laser" ); + pos->AddVisualizer( &graphvis, true ); - // PositionUpdate() checks to see if we reached source or sink - pos->AddCallback( Model::CB_UPDATE, (stg_model_callback_t)UpdateCallback, this ); - pos->Subscribe(); - - - // no other callbacks - we access all other data from the position - // callback - EnableLaser(true); - - pos->AddVisualizer( &graphvis, true ); - - if( map_data == NULL ) - { - map_data = new uint8_t[map_width*map_height*2]; + if( map_data == NULL ) + { + map_data = new uint8_t[map_width*map_height*2]; - // MUST clear the data, since Model::Rasterize() only enters - // non-zero pixels - memset( map_data, 0, sizeof(uint8_t) * map_width * map_height); + // MUST clear the data, since Model::Rasterize() only enters + // non-zero pixels + memset( map_data, 0, sizeof(uint8_t) * map_width * map_height); - // get the map - map_model = pos->GetWorld()->GetModel( "cave" ); - assert(map_model); - Geom g = map_model->GetGeom(); + // get the map + map_model = pos->GetWorld()->GetModel( "cave" ); + assert(map_model); + Geom g = map_model->GetGeom(); - map_model->Rasterize( map_data, - map_width, - map_height, - g.size.x/(float)map_width, - g.size.y/(float)map_height ); + map_model->Rasterize( map_data, + map_width, + map_height, + g.size.x/(float)map_width, + g.size.y/(float)map_height ); - // fix the node costs for astar: 0=>1, 1=>9 + // fix the node costs for astar: 0=>1, 1=>9 - unsigned int sz = map_width * map_height; - for( unsigned int i=0; i<sz; i++ ) - { - if( map_data[i] == 0 ) - map_data[i] = 1; - else if( map_data[i] == 1 ) - map_data[i] = 9; - else - printf( "FASR: bad value %d in map at index %d\n", (int)map_data[i], (int)i ); + unsigned int sz = map_width * map_height; + for( unsigned int i=0; i<sz; i++ ) + { + if( map_data[i] == 0 ) + map_data[i] = 1; + else if( map_data[i] == 1 ) + map_data[i] = 9; + else + printf( "FASR: bad value %d in map at index %d\n", (int)map_data[i], (int)i ); - assert( (map_data[i] == 1) || (map_data[i] == 9) ); - } - } + assert( (map_data[i] == 1) || (map_data[i] == 9) ); + } + } - //( goal ); - //puts(""); + //( goal ); + //puts(""); } - - + + void Enable( Stg::Model* model, bool& sub, bool on ) + { + if( on && !sub ) + { + sub = true; + model->Subscribe(); + } + + if( !on && sub ) + { + sub = false; + model->Unsubscribe(); + } + } + void EnableRanger( bool on ) { - if( on && !ranger_sub ) - { - ranger_sub = true; - ranger->Subscribe(); - } - - if( !on && ranger_sub ) - { - ranger_sub = false; - ranger->Unsubscribe(); - } + Enable( ranger, ranger_sub, on ); } void EnableLaser( bool on ) { - if( on && !laser_sub ) - { - laser_sub = true; - laser->Subscribe(); - } - - if( !on && laser_sub ) - { - laser_sub = false; - laser->Unsubscribe(); - } + Enable( laser, laser_sub, on ); } void EnableFiducial( bool on ) { - if( on && !fiducial_sub ) - { - fiducial_sub = true; - fiducial->Subscribe(); - } - - if( !on && fiducial_sub ) - { - fiducial_sub = false; - fiducial->Unsubscribe(); - } + Enable( fiducial, fiducial_sub, on ); } void Plan( Pose sp ) { - // change my color to that of my destination - //pos->SetColor( dest->GetColor() ); - - Pose pose = pos->GetPose(); - //Pose sp = dest->GetPose(); - Geom g = map_model->GetGeom(); - - point_t start( MetersToCell(pose.x, g.size.x, map_width), - MetersToCell(pose.y, g.size.y, map_height) ); - point_t goal( MetersToCell(sp.x, g.size.x, map_width), - MetersToCell(sp.y, g.size.y, map_height) ); - - //printf( "searching from (%.2f, %.2f) [%d, %d]\n", pose.x, pose.y, start.x, start.y ); - //printf( "searching to (%.2f, %.2f) [%d, %d]\n", sp.x, sp.y, goal.x, goal.y ); + // change my color to that of my destination + //pos->SetColor( dest->GetColor() ); + + Pose pose = pos->GetPose(); + Geom g = map_model->GetGeom(); + + point_t start( MetersToCell(pose.x, g.size.x, map_width), + MetersToCell(pose.y, g.size.y, map_height) ); + point_t goal( MetersToCell(sp.x, g.size.x, map_width), + MetersToCell(sp.y, g.size.y, map_height) ); + + //printf( "searching from (%.2f, %.2f) [%d, %d]\n", pose.x, pose.y, start.x, start.y ); + //printf( "searching to (%.2f, %.2f) [%d, %d]\n", sp.x, sp.y, goal.x, goal.y ); + + // astar() is not reentrant, so we protect it thus + pthread_mutex_lock( &planner_mutex ); + + std::vector<point_t> path; + bool result = astar( map_data, + map_width, + map_height, + start, + goal, + path ); - // astar() is not reentrant, so we protect it thus - pthread_mutex_lock( &planner_mutex ); - - std::vector<point_t> path; - bool result = astar( map_data, - map_width, - map_height, - start, - goal, - path ); - - pthread_mutex_unlock( &planner_mutex ); + pthread_mutex_unlock( &planner_mutex ); - if( ! result ) - printf( "FASR warning: plan failed to find path from (%.2f,%.2f) to (%.2f,%.2f)\n", - pose.x, pose.y, sp.x, sp.y ); + if( ! result ) + printf( "FASR warning: plan failed to find path from (%.2f,%.2f) to (%.2f,%.2f)\n", + pose.x, pose.y, sp.x, sp.y ); - graph.nodes.clear(); + graph.nodes.clear(); - unsigned int dist = 0; - //FOR_EACH( it, path ) - for( std::vector<point_t>::const_reverse_iterator rit = path.rbegin(); - rit != path.rend(); - ++rit ) - { - //printf( "%d, %d\n", it->x, it->y ); + unsigned int dist = 0; + + for( std::vector<point_t>::const_reverse_iterator rit = path.rbegin(); + rit != path.rend(); + ++rit ) + { + //printf( "%d, %d\n", it->x, it->y ); + + Node* node = new Node( Pose( CellToMeters(rit->x, g.size.x, map_width ), + CellToMeters(rit->y, g.size.y, map_height), + 0, 0 ), + dist++ ); // value stored at node - Node* node = new Node( Pose( CellToMeters(rit->x, g.size.x, map_width ), - CellToMeters(rit->y, g.size.y, map_height), - 0, 0 ) ); + graph.AddNode( node ); - node->value = dist++; + if( last_node ) + last_node->AddEdge( new Edge( node ) ); - graph.AddNode( node ); - - if( last_node ) - last_node->AddEdge( new Edge( node ) ); - - last_node = node; - } + last_node = node; + } } void Dock() { - const stg_meters_t creep_distance = 0.5; + const stg_meters_t creep_distance = 0.5; - if( charger_ahoy ) - { - // drive toward the charger - double a_goal = normalize( charger_bearing ); - - // helps to align with the way the charger is pointing - double orient = normalize( M_PI - (charger_bearing - charger_heading) ); - - a_goal = normalize( a_goal - 2.0 * orient ); + if( charger_ahoy ) + { + // drive toward the charger + // orient term helps to align with the way the charger is pointing + const double orient = normalize( M_PI - (charger_bearing - charger_heading) ); + const double a_goal = normalize( charger_bearing - 2.0 * orient ); - if( charger_range > creep_distance ) - { - if( !ObstacleAvoid() ) - { - pos->SetXSpeed( cruisespeed ); - pos->SetTurnSpeed( a_goal ); - } - } - else // we are very close! - { - pos->SetTurnSpeed( a_goal ); - pos->SetXSpeed( 0.02 ); // creep towards it + if( charger_range > creep_distance ) + { + if( !ObstacleAvoid() ) + { + pos->SetXSpeed( cruisespeed ); + pos->SetTurnSpeed( a_goal ); + } + } + else // we are very close! + { + pos->SetTurnSpeed( a_goal ); + pos->SetXSpeed( 0.02 ); // creep towards it - if( charger_range < 0.08 ) // close enough - { - pos->Stop(); - docked_angle = pos->GetPose().a; - EnableLaser( false ); - } + if( charger_range < 0.08 ) // close enough + { + pos->Stop(); + docked_angle = pos->GetPose().a; + EnableLaser( false ); + } - if( pos->Stalled() ) // touching - pos->SetXSpeed( -0.01 ); // back off a bit - } - } - else - { - //printf( "FASR: %s docking but can't see a charger\n", pos->Token() ); - pos->Stop(); - EnableFiducial( false ); - mode = MODE_WORK; // should get us back on track eventually - } + if( pos->Stalled() ) // touching + pos->SetXSpeed( -0.01 ); // back off a bit + } + } + else + { + //printf( "FASR: %s docking but can't see a charger\n", pos->Token() ); + pos->Stop(); + EnableFiducial( false ); + mode = MODE_WORK; // should get us back on track eventually + } - // if the battery is charged, go back to work - if( Full() ) - { - //printf( "fully charged, now back to work\n" ); - mode = MODE_UNDOCK; - EnableRanger(true); // enable the sonar to see behind us - EnableLaser(true); - EnableFiducial(true); - force_recharge = false; - } + // if the battery is charged, go back to work + if( Full() ) + { + //printf( "fully charged, now back to work\n" ); + mode = MODE_UNDOCK; + EnableRanger(true); // enable the sonar to see behind us + EnableLaser(true); + EnableFiducial(true); + force_recharge = false; + } } void SetTask( unsigned int t ) { - //tasks[task].participants--; - task = t; - tasks[task].participants++; + task = t; + tasks[task].participants++; } void UnDock() { - const stg_meters_t back_off_distance = 0.2; - const stg_meters_t back_off_speed = -0.02; - const stg_radians_t undock_rotate_speed = 0.3; - const stg_meters_t wait_distance = 0.2; - const unsigned int BACK_SENSOR_FIRST = 10; - const unsigned int BACK_SENSOR_LAST = 13; + const stg_meters_t back_off_distance = 0.2; + const stg_meters_t back_off_speed = -0.02; + const stg_radians_t undock_rotate_speed = 0.3; + const stg_meters_t wait_distance = 0.2; + const unsigned int BACK_SENSOR_FIRST = 10; + const unsigned int BACK_SENSOR_LAST = 13; - // make sure the required sensors are running - assert( ranger_sub ); - assert( fiducial_sub ); + // make sure the required sensors are running + assert( ranger_sub ); + assert( fiducial_sub ); - if( charger_range < back_off_distance ) - { - pos->SetXSpeed( back_off_speed ); + if( charger_range < back_off_distance ) + { + pos->SetXSpeed( back_off_speed ); - pos->Say( "" ); + pos->Say( "" ); - // stay put while anything is close behind - const std::vector<ModelRanger::Sensor>& sensors = ranger->GetSensors(); + // stay put while anything is close behind + const std::vector<ModelRanger::Sensor>& sensors = ranger->GetSensors(); - for( unsigned int s = BACK_SENSOR_FIRST; s <= BACK_SENSOR_LAST; ++s ) - if( sensors[s].range < wait_distance) - { - pos->Say( "Waiting..." ); - pos->SetXSpeed( 0.0 ); - return; - } - } - else - { // we've backed up enough + for( unsigned int s = BACK_SENSOR_FIRST; s <= BACK_SENSOR_LAST; ++s ) + if( sensors[s].range < wait_distance) + { + pos->Say( "Waiting..." ); + pos->SetXSpeed( 0.0 ); + return; + } + } + else + { // we've backed up enough - double heading_error = normalize( pos->GetPose().a - (docked_angle + M_PI ) ); + double heading_error = normalize( pos->GetPose().a - (docked_angle + M_PI ) ); - if( fabs( heading_error ) > 0.05 ) - { - // turn - pos->SetXSpeed( 0 ); - pos->SetTurnSpeed( undock_rotate_speed * sgn(-heading_error) ); - } - else - { - // we're pointing the right way, so we're done - mode = MODE_WORK; - - // if we're not working on a drop-off - if( pos->GetFlagCount() == 0 ) - { - // pick a new task at random - SetTask( random() % tasks.size() ); - SetGoal( tasks[task].source ); - } + if( fabs( heading_error ) > 0.05 ) + { + // turn + pos->SetXSpeed( 0 ); + pos->SetTurnSpeed( undock_rotate_speed * sgn(-heading_error) ); + } else - SetGoal( tasks[task].sink ); + { + // we're pointing the right way, so we're done + mode = MODE_WORK; + + // if we're not working on a drop-off + if( pos->GetFlagCount() == 0 ) + { + // pick a new task at random + SetTask( random() % tasks.size() ); + SetGoal( tasks[task].source ); + } + else + SetGoal( tasks[task].sink ); - EnableFiducial(false); - EnableRanger(false); - } - } + EnableFiducial(false); + EnableRanger(false); + } + } } bool ObstacleAvoid() { - bool obstruction = false; - bool stop = false; + bool obstruction = false; + bool stop = false; - // find the closest distance to the left and right and check if - // there's anything in front - double minleft = 1e6; - double minright = 1e6; + // find the closest distance to the left and right and check if + // there's anything in front + double minleft = 1e6; + double minright = 1e6; - // Get the data - const std::vector<ModelLaser::Sample>& scan = laser->GetSamples(); + // Get the data + const std::vector<ModelLaser::Sample>& scan = laser->GetSamples(); uint32_t sample_count = scan.size(); - for (uint32_t i = 0; i < sample_count; i++) - { - if( verbose ) printf( "%.3f ", scan[i].range ); + for (uint32_t i = 0; i < sample_count; i++) + { + if( verbose ) printf( "%.3f ", scan[i].range ); - if( (i > (sample_count/4)) - && (i < (sample_count - (sample_count/4))) - && scan[i].range < minfrontdistance) - { - if( verbose ) puts( " obstruction!" ); - obstruction = true; - } + if( (i > (sample_count/4)) + && (i < (sample_count - (sample_count/4))) + && scan[i].range < minfrontdistance) + { + if( verbose ) puts( " obstruction!" ); + obstruction = true; + } - if( scan[i].range < stopdist ) - { - if( verbose ) puts( " stopping!" ); - stop = true; - } + if( scan[i].range < stopdist ) + { + if( verbose ) puts( " stopping!" ); + stop = true; + } - if( i > sample_count/2 ) - minleft = std::min( minleft, scan[i].range ); - else - minright = std::min( minright, scan[i].range ); - } + if( i > sample_count/2 ) + minleft = std::min( minleft, scan[i].range ); + else + minright = std::min( minright, scan[i].range ); + } - if( verbose ) - { - puts( "" ); - printf( "minleft %.3f \n", minleft ); - printf( "minright %.3f\n ", minright ); - } + if( verbose ) + { + puts( "" ); + printf( "minleft %.3f \n", minleft ); + printf( "minright %.3f\n ", minright ); + } - if( obstruction || stop || (avoidcount>0) ) - { - if( verbose ) printf( "Avoid %d\n", avoidcount ); + if( obstruction || stop || (avoidcount>0) ) + { + if( verbose ) printf( "Avoid %d\n", avoidcount ); - pos->SetXSpeed( stop ? 0.0 : avoidspeed ); + pos->SetXSpeed( stop ? 0.0 : avoidspeed ); - /* once we start avoiding, select a turn direction and stick - with it for a few iterations */ - if( avoidcount < 1 ) - { - if( verbose ) puts( "Avoid START" ); - avoidcount = random() % avoidduration + avoidduration; + /* once we start avoiding, select a turn direction and stick + with it for a few iterations */ + if( avoidcount < 1 ) + { + if( verbose ) puts( "Avoid START" ); + avoidcount = random() % avoidduration + avoidduration; - if( minleft < minright ) - { - pos->SetTurnSpeed( -avoidturn ); - if( verbose ) printf( "turning right %.2f\n", -avoidturn ); - } - else - { - pos->SetTurnSpeed( +avoidturn ); - if( verbose ) printf( "turning left %2f\n", +avoidturn ); - } - } + if( minleft < minright ) + { + pos->SetTurnSpeed( -avoidturn ); + if( verbose ) printf( "turning right %.2f\n", -avoidturn ); + } + else + { + pos->SetTurnSpeed( +avoidturn ); + if( verbose ) printf( "turning left %2f\n", +avoidturn ); + } + } - avoidcount--; + avoidcount--; - return true; // busy avoding obstacles - } + return true; // busy avoding obstacles + } - return false; // didn't have to avoid anything + return false; // didn't have to avoid anything } void SetGoal( Model* goal ) { - if( goal != this->goal ) - { - this->goal = goal; - Plan( goal->GetPose() ); - pos->SetColor( goal->GetColor() ); - } + if( goal != this->goal ) + { + this->goal = goal; + Plan( goal->GetPose() ); + pos->SetColor( goal->GetColor() ); + } } void Work() { - if( ! ObstacleAvoid() ) - { - if( verbose ) puts( "Cruise" ); - - //avoidcount = 0; - - Pose pose = pos->GetPose(); - - double a_goal = 0; + if( ! ObstacleAvoid() ) + { + if( verbose ) + puts( "Cruise" ); + + if( Hungry() ) + SetGoal( fuel_zone ); + + const Pose pose = pos->GetPose(); + double a_goal = 0; + + // if the graph fails to offer advice or the goal has moved a + // ways since last time we planned + if( (graph.GoodDirection( pose, 4.0, a_goal ) == 0) || + (goal->GetPose().Distance2D( cached_goal_pose ) > 2.0) ) + { + //printf( "%s replanning from (%.2f,%.2f) to %s at (%.2f,%.2f) in Work()\n", + // pos->Token(), + // pose.x, pose.y, + // goal->Token(), + // goal->GetPose().x, + // goal->GetPose().y ); + Plan( goal->GetPose() ); + cached_goal_pose = goal->GetPose(); + } - // dtor( ( pos->GetFlagCount() ) ? have[y][x] : need[y][x] ); // map - //atan2( gp.y - pose.y, gp.x - pose.x ); // crow flies - // use direction of lowest value node within range in graph - - - // Model* goal = fuel_zone; - - if( Hungry() ) - SetGoal( fuel_zone ); - - // if the graph fails to offer advice or the goal has moved a - // ways since last time we planned - if( (graph.GoodDirection( pose, 4.0, a_goal ) == 0) || - (goal->GetPose().Distance2D( cached_goal_pose ) > 2.0) ) - { - //printf( "%s replanning from (%.2f,%.2f) to %s at (%.2f,%.2f) in Work()\n", - // pos->Token(), - // pose.x, pose.y, - // goal->Token(), - // goal->GetPose().x, - // goal->GetPose().y ); - Plan( goal->GetPose() ); - cached_goal_pose = goal->GetPose(); - } - - // if we are low on juice - find the direction to the recharger instead - if( Hungry() ) - { - EnableFiducial(true); - - //puts( "hungry - using refuel map" ); + // if we are low on juice - find the direction to the recharger instead + if( Hungry() ) + { + EnableFiducial(true); - // use the refuel map - //a_goal = dtor( refuel[y][x] ); + while( graph.GoodDirection( pose, 4.0, a_goal ) == 0 ) + { + printf( "%s replanning in Work()\n", pos->Token() ); + } - while( graph.GoodDirection( pose, 4.0, a_goal ) == 0 ) - { - printf( "%s replanning in Work()\n", pos->Token() ); - } + if( charger_ahoy ) // I see a charger while hungry! + mode = MODE_DOCK; + } - if( charger_ahoy ) // I see a charger while hungry! - mode = MODE_DOCK; - } - - double a_error = normalize( a_goal - pose.a ); - pos->SetTurnSpeed( a_error ); - - //double a_error_size = fabs(a_error); - - //if( a_error_size < 1.0 ) - //pos->SetXSpeed( (1.0 - a_error_size) * cruisespeed ); - //else - //pos->SetXSpeed( 0.0 ); - pos->SetXSpeed( cruisespeed ); - } + const double a_error = normalize( a_goal - pose.a ); + pos->SetTurnSpeed( a_error ); + pos->SetXSpeed( cruisespeed ); + } } bool Hungry() { - return( force_recharge || pos->FindPowerPack()->ProportionRemaining() < 0.2 ); + return( force_recharge || pos->FindPowerPack()->ProportionRemaining() < 0.2 ); } bool Full() { - return( pos->FindPowerPack()->ProportionRemaining() > 0.95 ); + return( pos->FindPowerPack()->ProportionRemaining() > 0.95 ); } // static callback wrapper static int UpdateCallback( ModelLaser* laser, Robot* robot ) { - return robot->Update(); + return robot->Update(); } int Update( void ) { - if( strcmp( pos->Token(), "position:0") == 0 ) - { - static int seconds = 0; + if( strcmp( pos->Token(), "position:0") == 0 ) + { + static int seconds = 0; - int timenow = pos->GetWorld()->SimTimeNow() / 1e6; + int timenow = pos->GetWorld()->SimTimeNow() / 1e6; - if( timenow - seconds > 5 ) - { - seconds = timenow; + if( timenow - seconds > 5 ) + { + seconds = timenow; - // report the task participation - printf( "time: %d sec\n", seconds ); + // report the task participation + printf( "time: %d sec\n", seconds ); - unsigned int sz = tasks.size(); - for( unsigned int i=0; i<sz; ++i ) - printf( "\t task[%u] %3u (%s)\n", - i, tasks[i].participants, tasks[i].source->Token() ); - } - } + unsigned int sz = tasks.size(); + for( unsigned int i=0; i<sz; ++i ) + printf( "\t task[%u] %3u (%s)\n", + i, tasks[i].participants, tasks[i].source->Token() ); + } + } - Pose pose = pos->GetPose(); + Pose pose = pos->GetPose(); #if 0 - // when countdown reaches zero - if( --node_interval_countdown == 0 ) - { - // reset countdown - node_interval_countdown = node_interval; + // when countdown reaches zero + if( --node_interval_countdown == 0 ) + { + // reset countdown + node_interval_countdown = node_interval; - Node* node = new Node( pose ); - graph.AddNode( node ); + Node* node = new Node( pose ); + graph.AddNode( node ); - if( last_node ) - last_node->AddEdge( new Edge( node ) ); + if( last_node ) + last_node->AddEdge( new Edge( node ) ); - last_node = node; + last_node = node; - // limit the number of nodes - while( graph.nodes.size() > TRAIL_LENGTH_MAX ) - graph.PopFront(); - } + // limit the number of nodes + while( graph.nodes.size() > TRAIL_LENGTH_MAX ) + graph.PopFront(); + } #endif - // assume we can't see the charger - charger_ahoy = false; + // assume we can't see the charger + charger_ahoy = false; - // if the fiducial is enabled - if( fiducial_sub ) - { - std::vector<ModelFiducial::Fiducial>& fids = fiducial->GetFiducials(); + // if the fiducial is enabled + if( fiducial_sub ) + { + std::vector<ModelFiducial::Fiducial>& fids = fiducial->GetFiducials(); - if( fids.size() > 0 ) - { - ModelFiducial::Fiducial* closest = NULL; + if( fids.size() > 0 ) + { + ModelFiducial::Fiducial* closest = NULL; - for( unsigned int i = 0; i < fids.size(); i++ ) - { - //printf( "fiducial %d is %d at %.2f m %.2f radians\n", - // i, f->id, f->range, f->bearing ); + for( unsigned int i = 0; i < fids.size(); i++ ) + { + //printf( "fiducial %d is %d at %.2f m %.2f radians\n", + // i, f->id, f->range, f->bearing ); - ModelFiducial::Fiducial* f = &fids[i]; + ModelFiducial::Fiducial* f = &fids[i]; - // find the closest - if( f->id == 2 && ( closest == NULL || f->range < closest->range )) // I see a charging station - closest = f; - } + // find the closest + if( f->id == 2 && ( closest == NULL || f->range < closest->range )) // I see a charging station + closest = f; + } - if( closest ) - { // record that I've seen it and where it is - charger_ahoy = true; + if( closest ) + { // record that I've seen it and where it is + charger_ahoy = true; - //printf( "AHOY %s\n", pos->Token() ); + //printf( "AHOY %s\n", pos->Token() ); - charger_bearing = closest->bearing; - charger_range = closest->range; - charger_heading = closest->geom.a; - } - } - } + charger_bearing = closest->bearing; + charger_range = closest->range; + charger_heading = closest->geom.a; + } + } + } - //printf( "Pose: [%.2f %.2f %.2f %.2f]\n", - // pose.x, pose.y, pose.z, pose.a ); + //printf( "Pose: [%.2f %.2f %.2f %.2f]\n", + // pose.x, pose.y, pose.z, pose.a ); - //pose.z += 0.0001; - //pos->SetPose( pose ); + //pose.z += 0.0001; + //pos->SetPose( pose ); - if( goal == tasks[task].source ) - { - // if we're close to the source we get a flag - Pose sourcepose = goal->GetPose(); - Geom sourcegeom = goal->GetGeom(); + if( goal == tasks[task].source ) + { + // if we're close to the source we get a flag + Pose sourcepose = goal->GetPose(); + Geom sourcegeom = goal->GetGeom(); - stg_meters_t dest_dist = hypot( sourcepose.x-pose.x, sourcepose.y-pose.y ); + stg_meters_t dest_dist = hypot( sourcepose.x-pose.x, sourcepose.y-pose.y ); - // when we get close enough, we start waiting - if( dest_dist < sourcegeom.size.x/2.0 ) // nearby - if( wait_started_at < 0 && pos->GetFlagCount() == 0 ) - { - wait_started_at = pos->GetWorld()->SimTimeNow() / 1e6; // usec to seconds - //printf( "%s begain waiting at %ld seconds\n", pos->Token(), wait_started_at ); - } + // when we get close enough, we start waiting + if( dest_dist < sourcegeom.size.x/2.0 ) // nearby + if( wait_started_at < 0 && pos->GetFlagCount() == 0 ) + { + wait_started_at = pos->GetWorld()->SimTimeNow() / 1e6; // usec to seconds + //printf( "%s begain waiting at %ld seconds\n", pos->Token(), wait_started_at ); + } - if( wait_started_at > 0 ) - { - long int waited = (pos->GetWorld()->SimTimeNow() / 1e6) - wait_started_at; + if( wait_started_at > 0 ) + { + //long int waited = (pos->GetWorld()->SimTimeNow() / 1e6) - wait_started_at; - - // leave with small probability - if( drand48() < 0.0005 ) - { - //printf( "%s abandoning task %s after waiting %ld seconds\n", - // pos->Token(), goal->Token(), waited ); + // leave with small probability + if( drand48() < 0.0005 ) + { + //printf( "%s abandoning task %s after waiting %ld seconds\n", + // pos->Token(), goal->Token(), waited ); - force_recharge = true; // forces hungry to return true - tasks[task].participants--; - wait_started_at = -1; - return 0; - } - } + force_recharge = true; // forces hungry to return true + tasks[task].participants--; + wait_started_at = -1; + return 0; + } + } - // when we get onto the square - if( dest_dist < sourcegeom.size.x/2.0 && - pos->GetFlagCount() < PAYLOAD ) - { + // when we get onto the square + if( dest_dist < sourcegeom.size.x/2.0 && + pos->GetFlagCount() < PAYLOAD ) + { - // transfer a chunk from source to robot - pos->PushFlag( goal->PopFlag() ); + // transfer a chunk from source to robot + pos->PushFlag( goal->PopFlag() ); - if( pos->GetFlagCount() == 1 && wait_started_at > 0 ) - { - // stop waiting, since we have received our first flag - wait_started_at = -1; - } + if( pos->GetFlagCount() == 1 && wait_started_at > 0 ) + { + // stop waiting, since we have received our first flag + wait_started_at = -1; + } - if( pos->GetFlagCount() == PAYLOAD ) - SetGoal( tasks[task].sink ); // we're done working - } - } - else if( goal == tasks[task].sink ) - { - // if we're close to the sink we lose a flag - Pose sinkpose = goal->GetPose(); - Geom sinkgeom = goal->GetGeom(); + if( pos->GetFlagCount() == PAYLOAD ) + SetGoal( tasks[task].sink ); // we're done working + } + } + else if( goal == tasks[task].sink ) + { + // if we're close to the sink we lose a flag + Pose sinkpose = goal->GetPose(); + Geom sinkgeom = goal->GetGeom(); - if( hypot( sinkpose.x-pose.x, sinkpose.y-pose.y ) < sinkgeom.size.x/2.0 && - pos->GetFlagCount() > 0 ) - { - //puts( "dropping" ); - // transfer a chunk between robot and goal - goal->PushFlag( pos->PopFlag() ); + if( hypot( sinkpose.x-pose.x, sinkpose.y-pose.y ) < sinkgeom.size.x/2.0 && + pos->GetFlagCount() > 0 ) + { + //puts( "dropping" ); + // transfer a chunk between robot and goal + goal->PushFlag( pos->PopFlag() ); - if( pos->GetFlagCount() == 0 ) - SetGoal( tasks[task].source ); // we're done dropping off - } - } - else - { - assert( goal == fuel_zone || goal == pool_zone ); - } + if( pos->GetFlagCount() == 0 ) + SetGoal( tasks[task].source ); // we're done dropping off + } + } + else + { + assert( goal == fuel_zone || goal == pool_zone ); + } - //printf( "diss: %.2f\n", pos->FindPowerPack()->GetDissipated() ); - - // if( laser->power_pack && laser->power_pack->charging ) - // printf( "model %s power pack @%p is charging\n", - // laser->Token(), laser->power_pack ); - - //if( laser->GetSamples(NULL) == NULL ) - //return 0; - - //assert( laser->GetSamples().size() > 0 ); - - switch( mode ) - { - case MODE_DOCK: - //puts( "DOCK" ); - Dock(); - break; + switch( mode ) + { + case MODE_DOCK: + //puts( "DOCK" ); + Dock(); + break; - case MODE_WORK: - //puts( "WORK" ); - Work(); - break; + case MODE_WORK: + //puts( "WORK" ); + Work(); + break; - case MODE_UNDOCK: - //puts( "UNDOCK" ); - UnDock(); - break; + case MODE_UNDOCK: + //puts( "UNDOCK" ); + UnDock(); + break; - //case MODE_QUEUE: - //Queue(); - - default: - printf( "unrecognized mode %u\n", mode ); - } + default: + printf( "unrecognized mode %u\n", mode ); + } - return 0; // run again + return 0; // run again } - - - void JoinQueue( unsigned int q ) - { - //puts( "joing queue" ); - - if( queues[q].Position( pos->Token() ) < 0 ) - { - queues[q].Join( pos->Token() ); - queues[q].Print( "q0"); - } - } - - - void LeaveQueue( unsigned int q ) - { - queues[q].Leave( pos->Token() ); - } - - static int FlagIncr( Model* mod, Robot* robot ) - { - printf( "model %s collected flag\n", mod->Token() ); - return 0; + { + printf( "model %s collected flag\n", mod->Token() ); + return 0; } static int FlagDecr( Model* mod, Robot* robot ) { - printf( "model %s dropped flag\n", mod->Token() ); - return 0; + printf( "model %s dropped flag\n", mod->Token() ); + return 0; } }; +void Robot::Node::Draw() const +{ + // print value + //char buf[32]; + //snprintf( buf, 32, "%.0f", value ); + //Gl::draw_string( pose.x, pose.y+0.2, 0.1, buf ); + + //glBegin( GL_POINTS ); + //glVertex2f( pose.x, pose.y ); + //glEnd(); + + glBegin( GL_LINES ); + FOR_EACH( it, edges ) + { + glVertex2f( pose.x, pose.y ); + glVertex2f( (*it)->to->pose.x, (*it)->to->pose.y ); + } + glEnd(); +} + + // STATIC VARS pthread_mutex_t Robot::planner_mutex = PTHREAD_MUTEX_INITIALIZER; -unsigned int Robot::map_width( 50 ); -unsigned int Robot::map_height( 50 ); +const unsigned int Robot::map_width( 50 ); +const unsigned int Robot::map_height( 50 ); uint8_t* Robot::map_data( NULL ); Model* Robot::map_model( NULL ); -std::vector<LocalQueue> Robot::queues(100); -std::vector<Robot*> Robot::robots; -std::vector<Task> Robot::tasks; +std::vector<Robot::Task> Robot::tasks; void split( const std::string& text, const std::string& separators, std::vector<std::string>& words) { - int n = text.length(); + const int n = text.length(); int start, stop; start = text.find_first_not_of(separators); while ((start >= 0) && (start < n)) - { - stop = text.find_first_of(separators, start); - if ((stop < 0) || (stop > n)) stop = n; - words.push_back(text.substr(start, stop - start)); - start = text.find_first_not_of(separators, stop+1); - } + { + stop = text.find_first_of(separators, start); + if ((stop < 0) || (stop > n)) stop = n; + words.push_back(text.substr(start, stop - start)); + start = text.find_first_not_of(separators, stop+1); + } } // Stage calls this when the model starts up -extern "C" int Init( Model* mod, CtrlArgs* args ) +extern "C" int Init( ModelPosition* mod, CtrlArgs* args ) { - //printf( "%s args: %s\n", mod->Token(), args->worldfile.c_str() ); - - // tokenize the argument string into words - std::vector<std::string> words; - split( args->worldfile, std::string(" \t"), words ); - - // printf( "words size %u\n", words.size() ); - //FOR_EACH( it, words ) - // printf( "word: %s\n", it->c_str() ); - //puts( "" ); - - // expecting a task color name as the 1th argument - assert( words.size() > 1 ); - // some init for only the first controller if( Robot::tasks.size() == 0 ) - { - srandom( time(NULL) ); - - World* w = mod->GetWorld(); - for( unsigned int s=1; s<words.size(); s++ ) - Robot::tasks.push_back( Task( w->GetModel( words[s] + "_source"), - w->GetModel( words[s] + "_sink") ) ); - } - - new Robot( (ModelPosition*)mod, - mod->GetWorld()->GetModel( "fuel_zone" ), - mod->GetWorld()->GetModel( "pool_zone" ) ); - + { + srandom( time(NULL) ); + + // tokenize the worldfile ctrl argument string into words + std::vector<std::string> words; + split( args->worldfile, std::string(" \t"), words ); + // expecting at least one task color name + assert( words.size() > 1 ); + + const World* w = mod->GetWorld(); + + // make an array of tasks by reading the controller arguments + for( unsigned int s=1; s<words.size(); s++ ) + Robot::tasks.push_back( Robot::Task( w->GetModel( words[s] + "_source"), + w->GetModel( words[s] + "_sink") ) ); + } + + new Robot( mod, + mod->GetWorld()->GetModel( "fuel_zone" ), + mod->GetWorld()->GetModel( "pool_zone" ) ); + return 0; //ok } Modified: code/stage/trunk/libstage/model_fiducial.cc =================================================================== --- code/stage/trunk/libstage/model_fiducial.cc 2010-04-24 22:47:52 UTC (rev 8622) +++ code/stage/trunk/libstage/model_fiducial.cc 2010-04-26 13:50:18 UTC (rev 8623) @@ -11,7 +11,7 @@ // /////////////////////////////////////////////////////////////////////////// -#define DEBUG 0 +#undef DEBUG #include "stage.hh" #include "option.hh" @@ -252,7 +252,7 @@ max_range_anon = wf->ReadLength( wf_entity, "range_max", max_range_anon ); max_range_id = wf->ReadLength( wf_entity, "range_max_id", max_range_id ); fov = wf->ReadAngle ( wf_entity, "fov", fov ); - ignore_zloc = wf->ReadInt ( wf_entity, "ignore_zloc", ignore_zloc); + ignore_zloc = wf->ReadInt ( wf_entity, "ignore_zloc", ignore_zloc); } Modified: code/stage/trunk/worlds/fasr2.world =================================================================== --- code/stage/trunk/worlds/fasr2.world 2010-04-24 22:47:52 UTC (rev 8622) +++ code/stage/trunk/worlds/fasr2.world 2010-04-26 13:50:18 UTC (rev 8623) @@ -189,7 +189,7 @@ gui_move 0 ) -zone( color "gray5" +zone( color "wheat" pose [ -19.459 -42.499 0 0 ] name "pool_zone" size [ 1.000 1.000 0.010 ] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------------ _______________________________________________ Playerstage-commit mailing list Playerstage-commit@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/playerstage-commit