EigenOpt 1.0.0
Loading...
Searching...
No Matches
example_pixel_robot.cpp
Go to the documentation of this file.
1
59#include <piksel/baseapp.hpp>
60#include <Eigen/Dense>
63#include <memory>
64#include <deque>
65
67// Shorten some names.
68using Eigen::VectorXd;
69using Eigen::MatrixXd;
70using Eigen::ArrayXd;
72
73// Define some colors.
74const glm::vec4 BLACK = glm::vec4(0, 0, 0, 1);
75const glm::vec4 WHITE = glm::vec4(1, 1, 1, 1);
76const glm::vec4 RED = glm::vec4(0.8, 0, 0, 1);
77const glm::vec4 ORANGE = glm::vec4(0.8, 0.4, 0, 1);
78const glm::vec4 LIGHT_GRAY = glm::vec4(0.8f, 0.8f, 0.8f, 1);
79const glm::vec4 DARK_GRAY = glm::vec4(0.2f, 0.2f, 0.2f, 1.0f);
80
81
82// Simple class that represents a mobile robot.
83class Robot {
84public:
85 // New robot at the given coordinates.
86 Robot(double x, double y, double theta=0.0);
87
88 // Update the pose of the robot given the wheels velocities.
89 void update(VectorXd phi_dot);
90
91 // Draw the robot in the environment.
92 void draw(piksel::Graphics& g);
93
94 // Return the position of the "head" of the robot.
95 VectorXd head() const;
96
97 // Return the jacobian for the "head" of the robot.
98 MatrixXd jacobian() const;
99
100 static constexpr double wheel_radius = 5; // Radius of the wheels.
101 static constexpr double wheels_distance = 24; // Distance between the wheels.
102 static constexpr double half_length = 12; // Distance from the center to the "head" of the robot.
103 static constexpr double phi_max = 30; // Maximum wheel rotation speed.
104 static constexpr double dt = 0.02; // Simulation time step.
105private:
106 MatrixXd Omega; // Transforms from wheel velocity to linear and angular speeds.
107 VectorXd position; // Current position of the robot.
108 double theta; // Current orientation of the robot.
109 std::deque<std::pair<double, double>> trace; // List of past position, to show a trail.
110 static constexpr unsigned int trace_length = 1000; // Number of positions kept in memory.
111 static constexpr unsigned int trace_subsample = 3; // Subsampling factor to reduce the amount of lines to be drawn.
112};
113
114
115Robot::Robot(
116 double x,
117 double y,
118 double theta
119) : position(2)
120 , theta(theta)
121 , Omega(2, 2)
122{
123 position(0) = x;
124 position(1) = y;
125 Omega(0, 0) = wheel_radius / 2;
126 Omega(0, 1) = wheel_radius / 2;
127 Omega(1, 0) = wheel_radius / (2 * wheels_distance);
128 Omega(1, 1) = -wheel_radius / (2 * wheels_distance);
129 trace.push_back({x, y});
130}
131
132
133void Robot::update(VectorXd phi_dot) {
134 // Enfore rotation speed limits.
135 static ArrayXd max_phi = ArrayXd::Constant(2, phi_max);
136 phi_dot = (phi_dot.array().max(-max_phi)).min(max_phi);
137
138 // Obtain the kinematic twist of the robot.
139 VectorXd vw = Omega * phi_dot;
140
141 // Update the position and orientation of the robot.
142 position(0) += dt * vw(0) * std::cos(theta);
143 position(1) += dt * vw(0) * std::sin(theta);
144 theta += dt * vw(1);
145
146 // Store the current position in the buffer.
147 trace.push_back({position(0), position(1)});
148 if(trace.size() > trace_length)
149 trace.pop_front();
150}
151
152
153void Robot::draw(piksel::Graphics& g) {
154 // Draw a curve showing the trajectory followed by the robot.
155 if(trace.size() > trace_subsample) {
156 g.stroke(LIGHT_GRAY);
157 for(unsigned int i=0; i<trace.size()-trace_subsample; i+=trace_subsample) {
158 g.line(trace[i].first, trace[i].second, trace[i+trace_subsample].first, trace[i+trace_subsample].second);
159 }
160 g.stroke(BLACK);
161 }
162
163 // Draw the robot: a simple ellipse, plus a red dot showing the "head".
164 g.translate(position(0), position(1));
165 g.rotate(theta);
166 g.fill(WHITE);
167 g.ellipse(0, 0, 2*half_length, wheels_distance);
168 g.fill(RED);
169 g.ellipse(half_length, 0, 5, 5);
170 g.resetMatrix();
171}
172
173
174VectorXd Robot::head() const {
175 VectorXd p(position);
176 p(0) += half_length * std::cos(theta);
177 p(1) += half_length * std::sin(theta);
178 return p;
179}
180
181
182MatrixXd Robot::jacobian() const {
183 MatrixXd M(2, 2);
184 double c = std::cos(theta);
185 double s = std::sin(theta);
186 M << c, -half_length*s,
187 s, half_length*c;
188 return M * Omega;
189}
190
191
192// A simple circular obstacle to be avoided by the robot.
193class Obstacle {
194public:
195 // Create a new obstacle given its position and size.
196 Obstacle(double x, double y, double size);
197
198 // Draw the obstacle in the environment.
199 void draw(piksel::Graphics& g);
200
201 // Calculate the "vector-distance" to a given point.
202 VectorXd distance(const VectorXd& p) { return p - position; };
203
204 // Distance to maintain from the center of the obstacle.
205 double radius() const { return size / 2 + safety_distance; }
206
207 // Safety factor to ensure that the robot won't collide with an obstacle.
208 static constexpr double safety_distance = 15;
209private:
210 VectorXd position; // Position of the obstacle.
211 double size; // Diameter of the obstacle.
212};
213
214
215Obstacle::Obstacle(double x, double y, double size)
216: size(size)
217, position(2)
218{
219 position(0) = x;
220 position(1) = y;
221}
222
223
224void Obstacle::draw(piksel::Graphics& g) {
225 g.fill(ORANGE);
226 g.ellipse(position(0), position(1), size, size);
227}
228
229
230// Interactive app to simulate the robot and its environment.
231class App : public piksel::BaseApp {
232public:
233 // Create a new app.
234 App() : piksel::BaseApp(1000, 1000) {}
235
236 // Called once at startup.
237 void setup();
238
239 // Called at each animation step.
240 void draw(piksel::Graphics& g);
241
242 // Called whenever the mouse is moved.
243 void mouseMoved(int x, int y);
244
245 // Called whenever a mouse button is pressed.
246 void mousePressed(int);
247private:
248 VectorXd target; // Current mouse position.
249 std::unique_ptr<Robot> bot; // Our intrepid robot.
250 std::vector<std::unique_ptr<Obstacle>> obstacles; // List of obstacles to be avoided.
251 std::unique_ptr<QPSolver> solver; // Solver that will compute the control law.
252 bool paused; // Flag to pause/resume the simulation.
253};
254
255
256void App::setup() {
257 // Create a new robot.
258 bot = std::make_unique<Robot>(500, 500);
259
260 // Add a bunch of obstacles to the environment.
261 for(unsigned int x=0; x<=1000; x+=200) {
262 for(unsigned int y=0; y<=1000; y+=200) {
263 auto new_obstacle = std::make_unique<Obstacle>(x, y, 80);
264 if(new_obstacle->distance(bot->head()).norm() > new_obstacle->radius()) {
265 obstacles.push_back(std::move(new_obstacle));
266 }
267 }
268 }
269
270 // MORE OBSTACLES >=)
271 for(unsigned int x=100; x<=1000; x+=200) {
272 for(unsigned int y=100; y<=1000; y+=200) {
273 auto new_obstacle = std::make_unique<Obstacle>(x, y, 50);
274 if(new_obstacle->distance(bot->head()).norm() > new_obstacle->radius()) {
275 obstacles.push_back(std::move(new_obstacle));
276 }
277 }
278 }
279
280 // Instanciate the solver.
281 solver = std::make_unique<QPSolver>(2, 2, 1e-9);
282
283 // Auxiliary variables for the simulation.
284 target = VectorXd::Zero(2);
285 paused = true;
286}
287
288
289void App::mouseMoved(int x, int y) {
290 // Whenever the mouse is moved, record the new position so that the robot can
291 // try to follow the mouse.
292 target(0) = x;
293 target(1) = y;
294}
295
296
297void App::mousePressed(int) {
298 // Resume/pause the simulation whenever the mouse is clicked.
299 paused = !paused;
300}
301
302
303void App::draw(piksel::Graphics& g) {
304 // Draw the obstacles and the robot.
305 g.background(DARK_GRAY);
306 for(auto& obstacle : obstacles)
307 obstacle->draw(g);
308 bot->draw(g);
309
310 // If the simulation is paused, do nothing more.
311 if(paused) {
312 return;
313 }
314
315 // Time to use the solver! The objective would be to follow the target using a
316 // simple proportional control law.
317 double control_gain = 10;
318 MatrixXd J = bot->jacobian();
319 VectorXd r = control_gain * (target - bot->head());
320 solver->updateObjective(J, r);
321
322 // Add one avoidance constraint per obstacle, plus 4 constraints to set limits
323 // on the wheel rotation.
324 MatrixXd C(obstacles.size() + 4, 2);
325 VectorXd d(obstacles.size() + 4);
326
327 // For each obstacle, add one avoidance constraint
328 const double avoidance_gain = 200;
329 for(unsigned int i=0; i<obstacles.size(); i++) {
330 VectorXd distance = obstacles[i]->distance(bot->head());
331 C.row(i) = - 2 * distance.transpose() * J;
332 d(i) = avoidance_gain * (distance.norm() - obstacles[i]->radius());
333 }
334
335 // Set wheel constraints.
336 C.bottomRows(4).topRows(2).setIdentity();
337 C.bottomRows(2) = -MatrixXd::Identity(2, 2);
338 d.tail(4) = VectorXd::Constant(4, Robot::phi_max);
339
340 // Try to solve the optimization. Upon success, send the command to the robot.
341 VectorXd phi_dot;
342 if(solver->updateInequalities(C, d) && solver->solve(phi_dot)) {
343 bot->update(phi_dot);
344 }
345}
346
347
348int main(int argc, char** argv) {
349 // Run the simulation.
350 App app;
351 app.start();
352 return 0;
353}
Quadratic Programming solver using active set and null-space projections.
int main(int argc, char **argv)