phyepuck.cpp
1 /********************************************************************************
2  * WorldSim -- library for robot simulations *
3  * Copyright (C) 2012-2013 *
4  * Gianluca Massera <emmegian@yahoo.it> *
5  * Fabrizio Papi <erkito87@gmail.com> *
6  * *
7  * This program is free software; you can redistribute it and/or modify *
8  * it under the terms of the GNU General Public License as published by *
9  * the Free Software Foundation; either version 2 of the License, or *
10  * (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License *
18  * along with this program; if not, write to the Free Software *
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
20  ********************************************************************************/
21 
22 #include "phyepuck.h"
23 #include "phybox.h"
24 #include "phycylinder.h"
25 #include "phyfixed.h"
26 #include "phyballandsocket.h"
27 #include "physphere.h"
28 #include "physuspension.h"
29 #include "phycompoundobject.h"
30 #include "phycone.h"
31 #include "mathutils.h"
32 #include <cmath>
33 
34 namespace farsa {
35 
36 // All measures have been taken on the LARAL e-puck or the official specifications. Lengths are in
37 // meters, weights in kilograms.
38 const real PhyEpuck::batteryplacex = 0.043f;
39 const real PhyEpuck::batteryplacey = 0.055f;
40 const real PhyEpuck::batteryplacez = 0.033f;
41 const real PhyEpuck::batterym = 0.042f;
42 const real PhyEpuck::batteryplacedistancefromground = 0.0015f;
43 const real PhyEpuck::bodyr = 0.035f;
44 const real PhyEpuck::bodyh = 0.021f;
45 const real PhyEpuck::wholebodym = 0.078f;
46 const real PhyEpuck::wheelr = 0.0205f;
47 const real PhyEpuck::wheelh = 0.003f;
48 const real PhyEpuck::wheelm = 0.010f;
49 const real PhyEpuck::axletrack = 0.053f;
50 const real PhyEpuck::passivewheelr = 0.005f;
51 const real PhyEpuck::passivewheelm = 0.005f;
52 
53 PhyEpuck::PhyEpuck(World* world, QString name, const wMatrix& transformation) :
54  WObject(world, name, transformation, false),
55  m_body(NULL),
56  m_wheels(),
57  m_wheelsTransformation(),
58  m_wheelJoints(),
59  m_wheelsCtrl(NULL),
60  m_proximityIR(NULL),
61  m_groundIR(NULL),
62  m_kinematicSimulation(false),
63  m_leftWheelVelocity(0.0f),
64  m_rightWheelVelocity(0.0f)
65 {
66  // --- reference frame
67  // X
68  // ^
69  // | ------
70  // | | |
71  // | -------------------
72  // | | battery |
73  // | | |
74  // | -------------------
75  // | | |
76  // | ------
77  // |
78  // +---------------------> Y
79  // The front of the robot is towards -Y (positive speeds of the wheel make the robot move towards -Y)
80  // and the camera is in -Y
81 
82  // Creating a material for the e-puck
83  world->materials().createMaterial("epuckMaterial");
84  world->materials().setProperties("epuckMaterial", "default", 0.0f, 0.0f, 0.01f, 0.01f, true);
85  world->materials().createMaterial("epuckTire");
86  world->materials().setProperties("epuckTire", "default", 1.2f, 0.9f, 0.01f, 0.01f, true);
87 
88  // Now creating the body of the e-puck. It is made up of two pieces: a box modelling the battery
89  // pack and a short cylinder modelling the upper part (where the electronic board is). We put the
90  // frame of reference of the body (which is equal to that of the whole robot) on the ground to have
91  // move stability and to simplify moving the robot (no need to apply displacements to put it on a
92  // plane)
93  {
94  QVector<PhyObject*> bodyObjs;
96  // First we create the battery pack
98  tm.w_pos.z = (batteryplacez / 2.0f) + batteryplacedistancefromground;
99  obj->setMatrix(tm);
100  bodyObjs << obj;
101  // Then we create the upper part
102  obj = new PhyCylinder(bodyr, bodyh, world);
103  tm = wMatrix::yaw(toRad(90.0f));
104  tm.w_pos.z = batteryplacez + batteryplacedistancefromground + (bodyh / 2.0f);
105  obj->setMatrix( tm );
106  bodyObjs << obj;
107  // Finally we create the compound object actually modelling the body
108  m_body = new PhyCompoundObject(bodyObjs, world, "base");
109  m_body->setMass(batterym + wholebodym);
110  m_body->setMaterial("epuckMaterial");
111  m_body->setOwner(this, false);
112  m_body->setMatrix(transformation);
113  }
114 
115  // Creating all the wheels. We first create the two motorized wheels and then the two passive ones.
116  // The position of the wheels and their indexes are as follows (remember that the front of the
117  // robot is towards -Y):
118  //
119  // X
120  // ^
121  // | ------
122  // | | 0 |
123  // | -------------------
124  // | | battery |
125  // | | 3 2 |
126  // | -------------------
127  // | | 1 |
128  // | ------
129  // |
130  // +---------------------> Y
131 
132  // Creating the first motorized wheel and its joint
133  {
134  // The matrix is relative to the base. First creating the wheel
136  tm.w_pos = wVector(axletrack / 2.0f, 0.0f, wheelr);
137  PhyObject* wheel = new PhyCylinder(wheelr, wheelh, world, "motorWheel");
138  wheel->setMass(wheelm);
139  wheel->setColor(Qt::blue);
140  wheel->setMaterial("epuckTire");
141  wheel->setOwner(this, false);
142  wheel->setMatrix(tm * m_body->matrix());
143  m_wheels.append(wheel);
144  m_wheelsTransformation.append(tm);
145 
146  // Now creating the joint
147  PhyJoint* joint = new PhySuspension(m_body->matrix().x_ax, wheel->matrix().w_pos, m_body->matrix().z_ax, m_body, wheel);
148  joint->dofs()[0]->disableLimits();
149  joint->setOwner(this, false);
150  joint->updateJointInfo();
151  m_wheelJoints.append(joint);
152  }
153 
154  // Creating the second motorized wheel and its joint
155  {
156  // The matrix is relative to the base. First creating the wheel
158  tm.w_pos = wVector(-axletrack / 2.0f, 0.0f, wheelr);
159  PhyObject* wheel = new PhyCylinder(wheelr, wheelh, world, "motorWheel");
160  wheel->setMass(wheelm);
161  wheel->setColor(Qt::blue);
162  wheel->setMaterial("epuckTire");
163  wheel->setOwner(this, false);
164  wheel->setMatrix(tm * m_body->matrix());
165  m_wheels.append(wheel);
166  m_wheelsTransformation.append(tm);
167 
168  // Now creating the joint
169  PhyJoint* joint = new PhySuspension(m_body->matrix().x_ax, wheel->matrix().w_pos, m_body->matrix().z_ax, m_body, wheel);
170  joint->dofs()[0]->disableLimits();
171  joint->setOwner(this, false);
172  joint->updateJointInfo();
173  m_wheelJoints.append(joint);
174  }
175 
176  // Creating the first passive wheel and its joint
177  {
178  // The matrix is relative to the base. First creating the wheel
180  tm.w_pos = wVector(0.0f, (batteryplacey / 2.0f) - passivewheelr, passivewheelr);
181  PhyObject* wheel = new PhySphere(passivewheelr, world, "passiveWheel");
182  wheel->setMass(passivewheelm);
183  wheel->setColor(Qt::blue);
184  wheel->setMaterial("epuckTire");
185  wheel->setOwner(this, false);
186  wheel->setMatrix(tm * m_body->matrix());
187  m_wheels.append(wheel);
188  m_wheelsTransformation.append(tm);
189 
190  // Now creating the joint
191  PhyJoint* joint = new PhyBallAndSocket(wheel->matrix().w_pos, m_body, wheel);
192  joint->setOwner(this, false);
193  joint->updateJointInfo();
194  m_wheelJoints.append(joint);
195  }
196 
197  // Creating the second passive wheel and its joint
198  {
199  // The matrix is relative to the base. First creating the wheel
201  tm.w_pos = wVector(0.0f, -((batteryplacey / 2.0f) - passivewheelr), passivewheelr);
202  PhyObject* wheel = new PhySphere(passivewheelr, world, "passiveWheel");
203  wheel->setMass(passivewheelm);
204  wheel->setColor(Qt::blue);
205  wheel->setMaterial("epuckTire");
206  wheel->setOwner(this, false);
207  wheel->setMatrix(tm * m_body->matrix());
208  m_wheels.append(wheel);
209  m_wheelsTransformation.append(tm);
210 
211  // Now creating the joint
212  PhyJoint* joint = new PhyBallAndSocket(wheel->matrix().w_pos, m_body, wheel);
213  joint->setOwner(this, false);
214  joint->updateJointInfo();
215  m_wheelJoints.append(joint);
216  }
217 
218  // Now we can create the motor controller
219  QVector<PhyDOF*> motors;
220  motors << m_wheelJoints[0]->dofs()[0] << m_wheelJoints[1]->dofs()[0];
221  m_wheelsCtrl = new WheelMotorController(motors, world);
222  m_wheelsCtrl->setOwner(this, false);
223  // The min and max speed in rad/s have been derived for the e-puck wheel considering as maximum linear
224  // displacement 14 cm/s (that for the give wheel radius is approximately 1.1 ratations per second)
225  const real maxAngularSpeed = 1.1 * 2.0 * PI_GRECO;
226  m_wheelsCtrl->setSpeedLimits(-maxAngularSpeed, -maxAngularSpeed, maxAngularSpeed, maxAngularSpeed);
227 
228  // Connecting wheels speed signals to be able to move the robot when in kinematic
229  connect(m_wheelJoints[0]->dofs()[0], SIGNAL(changedDesiredVelocity(real)), this, SLOT(setRightWheelDesideredVelocity(real)));
230  connect(m_wheelJoints[1]->dofs()[0], SIGNAL(changedDesiredVelocity(real)), this, SLOT(setLeftWheelDesideredVelocity(real)));
231 
232  // Creating the proximity IR sensors
233  QVector<SingleIR> sensors;
234 
235 #ifdef __GNUC__
236  #warning QUI DECIDERE PER I RANGE/APERTURE E LE POSIZIONI
237 #endif
238  // Adding the sensors. The epuck has 8 proximity infrared sensors
239  for (unsigned int i = 0; i < 8; i++) {
240  const double curAngle = double(i) * (360.0 / 8.0) + ((360.0 / 8.0) / 2.0);
241  const double radius = bodyr;
242 
243  wMatrix mtr = wMatrix::yaw(deg2rad(curAngle + 90.0)) * wMatrix::pitch(deg2rad(90.0f));
244  mtr.w_pos.x = radius * cos(deg2rad(curAngle));
245  mtr.w_pos.y = radius * sin(deg2rad(curAngle));
247 
248  sensors.append(SingleIR(m_body, mtr, 0.01, 0.05, 10.0, 5));
249  }
250  m_proximityIR = new SimulatedIRProximitySensorController(world, sensors);
251 
252  // Now creating the three ground IR sensors
253  sensors.clear();
254  wMatrix mtr = wMatrix::yaw(deg2rad(180.0));
255 
256  // Adding the sensors. The e-puck has 3 ground infrared sensors in the front part
257  mtr.w_pos = wVector(-batteryplacex / 2.0f, -batteryplacey / 2.0f, batteryplacedistancefromground);
258  sensors.append(SingleIR(m_body, mtr, 0.0, mtr.w_pos.z + 0.005, 0.0, 1));
259  mtr.w_pos = wVector(0.0f, -batteryplacey / 2.0f, batteryplacedistancefromground);
260  sensors.append(SingleIR(m_body, mtr, 0.0, mtr.w_pos.z + 0.005, 0.0, 1));
262  sensors.append(SingleIR(m_body, mtr, 0.0, mtr.w_pos.z + 0.005, 0.0, 1));
263  m_groundIR = new SimulatedIRGroundSensorController(world, sensors);
264 
265  world->pushObject(this);
266 }
267 
269 {
270  foreach (PhyJoint* j, m_wheelJoints) {
271  delete j;
272  }
273  foreach (PhyObject* w, m_wheels) {
274  delete w;
275  }
276  delete m_wheelsCtrl;
277  delete m_body;
278  delete m_proximityIR;
279  delete m_groundIR;
280 }
281 
283 {
284  // Updating motors
285  if (m_wheelsCtrl->isEnabled()) {
286  m_wheelsCtrl->update();
287  }
288 
289  if (m_kinematicSimulation) {
290  // In kinematic mode, we have to manually move the robot depending on the wheel velocities
291  wMatrix mtr = matrix();
292 
293  // To compute the actual movement of the robot I assume the movement is on the plane on which
294  // the two wheels lie. This means that I assume that both wheels are in contact with a plane
295  // at every time. Then I compute the instant centre of rotation and move the robot rotating
296  // it around the axis passing through the instant center of rotation and perpendicular to the
297  // plane on which the wheels lie (i.e. around the local XY plane)
298 
299  // First of all computing the linear speed of the two wheels
300  const real leftSpeed = m_leftWheelVelocity * wheelr;
301  const real rightSpeed = m_rightWheelVelocity * wheelr;
302 
303  // If the speed of the two wheels is very similar, simply moving the robot forward (doing the
304  // computations would probably lead to invalid matrices)
305  if (fabs(leftSpeed - rightSpeed) < 0.00001f) {
306  // The front of the robot is towards -y
307  mtr.w_pos += -mtr.y_ax.scale(rightSpeed * world()->timeStep());
308  } else {
309  // The first thing to do is to compute the instant centre of rotation. We do the
310  // calculation in the robot local frame of reference. We proceed as follows (with
311  // uppercase letters we indicate vectors and points):
312  //
313  // +------------------------->
314  // | X axis
315  // | ^
316  // | |\
317  // | | \A
318  // | | \
319  // | L| ^
320  // | | |\
321  // | | | \
322  // | | R| \
323  // | | | \
324  // | O+--->----+C
325  // | D
326  // |
327  // |
328  // v Y axis (the robot moves forward towards -Y)
329  //
330  // In the picture L and R are the velocity vectors of the two wheels, D is the vector
331  // going from the center of the left wheel to the center of the right wheel, A is the
332  // vector going from the tip of L to the tip of R and C is the instant centre of
333  // rotation. D is fixed an parallel to the X axis, while L and R are always parallel
334  // to the Y axis. Also, for the moment, we take the frame of reference origin at O.
335  // The construction shown in the picture allows to find the instant center of rotation
336  // for any L and R except when they are equal. In this case C is "at infinite" and the
337  // robot moves forward of backward without rotation. All the vectors lie on the local
338  // XY plane. To find C we proceed in the following way. First of all we note that
339  // A = R - L. If a and b are two real numbers, then we can say that C is at the
340  // instersection of L + aA with bD, that is we need a and b such that:
341  // L + aA = bD.
342  // If we take the cross product on both sides we get:
343  // (L + aA) x D = bD x D => (L + aA) x D = 0
344  // This means that we need to find a such that L + aA and D are parallel. As D is parallel
345  // to the X axis, its Y component is 0, so we can impose that L + aA has a 0 Y component,
346  // too, that is:
347  // (Ly) + a(Ay) = 0 => a = -(Ly) / (Ay)
348  // Once we have a, we can easily compute C:
349  // C = L + aA
350  // In the following code we use the same names we have used in the description above. Also
351  // note that the actual origin of the frame of reference is not in O: it is in the middle
352  // between the left and right wheel (we need to take this into account when computing the
353  // point around which the robot rotates)
354  const wVector L(0.0, -leftSpeed, 0.0);
355  const wVector A(axletrack, -(rightSpeed - leftSpeed), 0.0);
356  const real a = -(L.y / A.y);
357  const wVector C = L + A.scale(a);
358 
359  // Now we have to compute the angular velocity of the robot. This is simply equal to v/r where
360  // v is the linear velocity at a point that is distant r from C. We use the velocity of one of
361  // the wheel: which weels depends on its distance from C. We take the wheel which is furthest from
362  // C to avoid having r = 0 (which would lead to an invalid value of the angular velocity). Distances
363  // are signed (they are taken along the X axis)
364  const real distLeftWheel = -C.x;
365  const real distRightWheel = -(C.x - A.x); // A.x is equal to D.x
366  const real angularVelocity = (fabs(distLeftWheel) > fabs(distRightWheel)) ? (-leftSpeed / distLeftWheel) : (-rightSpeed / distRightWheel);
367 
368  // The angular displacement is simply the angular velocity multiplied by the timeStep. Here we
369  // also compute the center of rotation in world coordinates (we also need to compute it in the frame
370  // of reference centered between the wheels, not in O) and the axis of rotation (that is simply the
371  // z axis of the robot in world coordinates)
372  const real angularDisplacement = angularVelocity * world()->timeStep();
373  const wVector centerOfRotation = matrix().transformVector(C - wVector(axletrack / 2.0, 0.0, 0.0));
374  const wVector axisOfRotation = matrix().z_ax;
375 
376  // Rotating the robot transformation matrix to obtain the new position
377  mtr = mtr.rotateAround(axisOfRotation, centerOfRotation, angularDisplacement);
378  }
379 
380  // This will also update the position of all pieces
381  setMatrix(mtr);
382  }
383 }
384 
386 {
387  // Updating sensors
388  if (m_proximityIR->isEnabled()) {
389  m_proximityIR->update();
390  }
391  if (m_groundIR->isEnabled()) {
392  m_groundIR->update();
393  }
394 
395  // Updating the transformation matrix of the robot. It is coincident with the matrix of the body
396  tm = m_body->matrix();
397 }
398 
399 void PhyEpuck::setProximityIRSensorsGraphicalProperties(bool drawSensor, bool drawRay, bool drawRealRay)
400 {
401  m_proximityIR->setGraphicalProperties(drawSensor, drawRay, drawRealRay);
402 }
403 
404 void PhyEpuck::setGroundIRSensorsGraphicalProperties(bool drawSensor, bool drawRay, bool drawRealRay)
405 {
406  m_groundIR->setGraphicalProperties(drawSensor, drawRay, drawRealRay);
407 }
408 
410 {
411  if (m_kinematicSimulation == k) {
412  return;
413  }
414 
415  m_kinematicSimulation = k;
416  if (m_kinematicSimulation) {
417  // First disabling all joints...
418  for (int i = 0; i < m_wheelJoints.size(); i++) {
419  m_wheelJoints[i]->enable(false);
420  }
421 
422  // ... then setting all objects to kinematic behaviour
423  m_body->setKinematic(true);
424  for (int i = 0; i < m_wheels.size(); i++) {
425  m_wheels[i]->setKinematic(true);
426  }
427  } else {
428  // First setting all objects to dynamic behaviour...
429  m_body->setKinematic(false);
430  for (int i = 0; i < m_wheels.size(); i++) {
431  m_wheels[i]->setKinematic(false);
432  }
433 
434  // ... then enabling all joints (if the corresponding part is enabled)
435  for (int i = 0; i < m_wheelJoints.size(); i++) {
436  m_wheelJoints[i]->enable(true);
437  m_wheelJoints[i]->updateJointInfo();
438  }
439  }
440 }
441 
443 {
444  m_leftWheelVelocity = velocity;
445 }
446 
448 {
449  m_rightWheelVelocity = velocity;
450 }
451 
453 {
454  wMatrix tm = matrix();
455  m_body->setMatrix(tm);
456  for (int i = 0; i < m_wheels.size(); i++) {
457  m_wheels[i]->setMatrix(m_wheelsTransformation[i] * tm);
458  }
459  foreach (PhyJoint* j, m_wheelJoints) {
460  j->updateJointInfo();
461  }
462 }
463 
464 } // end namespace farsa