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