1 /*
2  * Copyright (C) 2018 Open Source Robotics Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16 */
17 
18 #include <cassert>
19 #include <iostream>
20 #include <set>
21 
22 #include "plugins/robot.hh"
23 
24 #include <ignition/common/SystemPaths.hh>
25 #include <ignition/plugin/SpecializedPluginPtr.hh>
26 #include <ignition/plugin/Loader.hh>
27 
28 #ifdef HAVE_BOOST_PROGRAM_OPTIONS
29 #include <boost/program_options.hpp>
30 namespace bpo = boost::program_options;
31 #endif
32 
33 /////////////////////////////////////////////////
34 // The macro that this uses is provided as a compile definition in the
35 // examples/CMakeLists.txt file.
36 const std::string PluginLibDir = IGN_PLUGIN_EXAMPLES_LIBDIR;
37 
38 /////////////////////////////////////////////////
39 using namespace ignition::plugin::examples;
40 
41 /////////////////////////////////////////////////
42 using RobotPluginPtr = ignition::plugin::SpecializedPluginPtr<
43     Drive, ProximitySensor, GPSSensor, Compass, MapDatabase>;
44 
45 /////////////////////////////////////////////////
46 using EnvironmentPluginPtr =
47     ignition::plugin::SpecializedPluginPtr<Environment>;
48 
49 using PointPair = std::pair<ignition::math::Vector2d, ignition::math::Vector2d>;
50 
51 /////////////////////////////////////////////////
52 using IntersectionResult = std::pair<bool, ignition::math::Vector2d>;
53 
54 /////////////////////////////////////////////////
55 const IntersectionResult NoIntersection =
56     std::make_pair(false, ignition::math::Vector2d());
57 
58 /////////////////////////////////////////////////
CheckIntersection(const PointPair & _points,const Wall & _wall)59 IntersectionResult CheckIntersection(
60     const PointPair &_points,
61     const Wall &_wall)
62 {
63   std::array<double,2> A;
64   std::array<double,2> B;
65   std::array<double,2> C;
66 
67   std::size_t i = 0;
68   for (const PointPair &p : {_points, _wall})
69   {
70     A[i] = p.second.Y() - p.first.Y();
71     B[i] = p.second.X() - p.first.X();
72     C[i] = A[i]*p.first.X() + B[i]*p.first.Y();
73     ++i;
74   }
75 
76   const double det = A[0]*B[1] - A[1]*B[0];
77   if (std::abs(det) < 1e-10)
78   {
79     // Lines are parallel
80     return NoIntersection;
81   }
82 
83   const double x = (B[1]*C[0] - B[0]*C[1])/det;
84   const double y = (A[0]*C[1] - A[1]*C[0])/det;
85 
86   for (const PointPair &p : {_points, _wall})
87   {
88     if (x < std::min(p.first.X(), p.second.X()))
89       return NoIntersection;
90 
91     if (std::max(p.first.X(), p.second.X() ) < x)
92       return NoIntersection;
93   }
94 
95   return std::make_pair(true, ignition::math::Vector2d(x,y));
96 }
97 
98 /////////////////////////////////////////////////
CheckCollisions(const ignition::math::Vector2d & _x1,const ignition::math::Vector2d & _x0,const Layout & _layout)99 IntersectionResult CheckCollisions(
100     const ignition::math::Vector2d &_x1,
101     const ignition::math::Vector2d &_x0,
102     const Layout &_layout)
103 {
104   PointPair points = std::make_pair(_x1, _x0);
105 
106   for (const Wall &wall : _layout)
107   {
108     const IntersectionResult result = CheckIntersection(points, wall);
109     if (result.first)
110       return result;
111   }
112 
113   return NoIntersection;
114 }
115 
116 /////////////////////////////////////////////////
Simulate(const EnvironmentPluginPtr & _environment,const RobotPluginPtr & _robot,const double _duration,const bool _printState)117 void Simulate(const EnvironmentPluginPtr &_environment,
118               const RobotPluginPtr &_robot,
119               const double _duration,
120               const bool _printState)
121 {
122   Environment *env = _environment->QueryInterface<Environment>();
123   assert(env && "Environment interface is missing!");
124 
125   const Drive *drive = _robot->QueryInterface<Drive>();
126   assert(drive && "The robot cannot drive!");
127 
128   // Get the layout from the environment.
129   const Layout &layout = env->GenerateLayout();
130 
131   // If the robot has a MapUplink interface, send it the layout of the
132   // environment.
133   if (MapDatabase *uplink = _robot->QueryInterface<MapDatabase>())
134     uplink->ReadMap(layout);
135 
136   double time = 0.0;
137   ignition::math::Vector2d x;
138   double theta = 0.0;
139 
140   while (time < _duration)
141   {
142     // Update the proximity sensor.
143     if (ProximitySensor *proximity = _robot->QueryInterface<ProximitySensor>())
144     {
145       const double range = proximity->MaxRange();
146       const ignition::math::Vector2d x_far = x + ignition::math::Vector2d(
147             range*cos(theta), range*sin(theta));
148       IntersectionResult result = CheckCollisions(x, x_far, layout);
149 
150       if (result.first)
151       {
152         proximity->ReadProximity( (x-result.second).Length() );
153       }
154       else
155       {
156         proximity->ReadProximity(std::numeric_limits<double>::infinity());
157       }
158     }
159 
160     // Update the GPS sensor
161     if (GPSSensor *gps = _robot->QueryInterface<GPSSensor>())
162     {
163       gps->ReadGPS(x);
164     }
165 
166     // Update the compass sensor
167     if (Compass *compass = _robot->QueryInterface<Compass>())
168     {
169       compass->ReadCompass(theta);
170     }
171 
172     // Save the last state
173     ignition::math::Vector2d x_last = x;
174 
175     // Get the driving information
176     const double dt = 1.0/drive->Frequency();
177     const ignition::math::Vector3d &vel = drive->Velocity();
178 
179     // We integrate the position components with a half-step taken in the angle,
180     // which should smooth out the behavior of sharp turns.
181     const double halfAngle = theta + 0.5*vel[2]*dt;
182     const double dx = vel[0]*cos(halfAngle)*dt + vel[1]*sin(halfAngle)*dt;
183     const double dy = - vel[0]*sin(halfAngle)*dt + vel[1]*cos(halfAngle)*dt;
184 
185     x[0] += dx;
186     x[1] += dy;
187     theta += vel[2]*dt;
188 
189     const IntersectionResult collision = CheckCollisions(x, x_last, layout);
190     if (collision.first)
191     {
192       std::cout << "The robot has crashed at ("
193                 << collision.second.X() << "m, "
194                 << collision.second.Y() << "m)! Time: "
195                 << time << "s  |==|  Velocity output: "
196                 << vel << "\n" << std::endl;
197 
198       x[0] = collision.second.X();
199       x[1] = collision.second.Y();
200 
201       break;
202     }
203 
204     time += dt;
205 
206     if (_printState)
207     {
208       std::cout << "Location: (" << x[0] << "m, " << x[1] << "m) | Yaw: "
209                 << 180.0*theta/M_PI << "-degrees | Time: " << time
210                 << "s  |==|  Velocity output: " << vel << std::endl;
211     }
212   }
213 
214   std::cout << "The simulation has finished (time: " << time << "s).\n"
215             << " -- Final robot location: (" << x[0] << "m, " << x[1] << "m)\n"
216             << " -- Yaw: " << 180.0*theta/M_PI << "-degrees\n" << std::endl;
217 }
218 
219 /////////////////////////////////////////////////
main(int argc,char * argv[])220 int main(int argc, char *argv[])
221 {
222   // Create an object that can search the system paths for the plugin libraries.
223   ignition::common::SystemPaths paths;
224 
225   // Create a plugin loader
226   ignition::plugin::Loader loader;
227 
228   // Add the build directory path for the plugin libraries so the SystemPaths
229   // object will know to search through it.
230   paths.AddPluginPaths(PluginLibDir);
231 
232   std::vector<std::string> knownRobotPlugins = { "CrashBot", "CautiousBot" };
233   std::vector<std::string> knownEnvPlugins = { "BoxEnvironment" };
234 
235   std::string robotLib = knownRobotPlugins.front();
236   std::string envLib = knownEnvPlugins.front();
237   double duration = 300;
238   bool printState = false;
239 
240 #ifdef HAVE_BOOST_PROGRAM_OPTIONS
241 
242   std::string knownRobotString;
243   for (const std::string &r : knownRobotPlugins)
244     knownRobotString += r + "  ";
245 
246   std::string knownEnvString;
247   for (const std::string &e : knownEnvPlugins)
248     knownEnvString += e + "  ";
249 
250 
251   std::string usage;
252   usage +=
253       "The 'robot' example creates a very simple 2D kinematic simulation of a\n"
254       "robot, provided by a plugin, within an environment, also provided by a\n"
255       "plugin. The robot may have any combination of several sensors, defined\n"
256       "in plugins/robot.hh.\n\n"
257 
258       "Known robot plugin libraries include:  " + knownRobotString + "\n\n"
259 
260       "Known environment plugin libraries include:  " + knownEnvString + "\n\n"
261 
262       "Custom plugins can be used by passing in the custom plugin library\n"
263       "directory to the -I flag";
264 
265   bpo::options_description desc(usage);
266   desc.add_options()
267 
268       ("help,h", "Print this usage message")
269 
270       ("robot,r", bpo::value<std::string>(&robotLib),
271        std::string("Robot plugin library (default: "+robotLib+")").c_str())
272 
273       ("environment,e", bpo::value<std::string>(&envLib),
274        std::string("Environment plugin library (default: "+envLib+")").c_str())
275 
276       ("include-dirs,I", bpo::value<std::vector<std::string>>()->multitoken(),
277        "Additional directories that may contain plugin libraries")
278 
279       ("duration,t", bpo::value<double>(&duration),
280        std::string("How many seconds of simulation time to run for (default: "
281                    +std::to_string(duration)+")").c_str())
282 
283       ("print,p", "Print out the state during each iteration")
284       ;
285 
286   bpo::variables_map vm;
287   bpo::store(bpo::command_line_parser(argc, argv).options(desc).run(), vm);
288   bpo::notify(vm);
289 
290   if (vm.count("help") > 0)
291   {
292     std::cout << desc << std::endl;
293     return 1;
294   }
295 
296   if (vm.count("print") > 0)
297   {
298     printState = true;
299   }
300 
301   if (vm.count("include-dirs"))
302   {
303     const std::vector<std::string> inputDirs =
304         vm["include-dirs"].as<std::vector<std::string>>();
305 
306     for (const std::string &dir : inputDirs)
307     {
308       std::cout << "Including additional plugin directory: ["
309                 << dir << "]" << std::endl;
310       paths.AddPluginPaths(dir);
311     }
312   }
313 
314 #else
315 
316   std::cout << "boost::program_options was not found when this example was\n"
317             << "compiled, so we will default to:\n"
318             << " -- Robot:       " << robotLib <<"\n"
319             << " -- Environment: " << envLib << "\n"
320             << " -- Duration:    " << duration << "\n"
321             << std::endl;
322 
323 #endif
324 
325   std::unordered_set<std::string> robotPlugins = loader.LoadLibrary(
326         paths.FindSharedLibrary(robotLib));
327 
328   std::unordered_set<std::string> drivePlugins =
329       loader.PluginsImplementing("ignition::plugin::examples::Drive");
330 
331   RobotPluginPtr robot;
332   // Get the first plugin from the robotLib library that provides a Drive
333   // interface
334   for (const std::string &plugin : robotPlugins)
335   {
336     if (drivePlugins.find(plugin) != drivePlugins.end())
337     {
338       robot = loader.Instantiate(plugin);
339       break;
340     }
341   }
342 
343   if (robot.IsEmpty())
344   {
345     std::cerr << "The plugin library specified for the robot does not provide "
346               << "a Drive interface, which is required for the robot to work.\n"
347               << std::endl;
348     return 2;
349   }
350 
351   std::unordered_set<std::string> envPlugins = loader.LoadLibrary(
352         paths.FindSharedLibrary(envLib));
353 
354   std::unordered_set<std::string> envInterfacePlugins =
355       loader.PluginsImplementing("ignition::plugin::examples::Environment");
356 
357   EnvironmentPluginPtr environment;
358   // Get the first plugin from the envLib library that provides an Environment
359   // interface.
360   for (const std::string &plugin : envPlugins)
361   {
362     if (envInterfacePlugins.find(plugin) != envInterfacePlugins.end())
363     {
364       environment = loader.Instantiate(plugin);
365       break;
366     }
367   }
368 
369   if (environment.IsEmpty())
370   {
371     std::cerr << "The plugin library specified for the environment does not "
372               << "provide an Environment interface, which is required for the "
373               << "environment to be generated.\n" << std::endl;
374     return 3;
375   }
376 
377   Simulate(environment, robot, duration, printState);
378 }
379