arena.cpp
1 /********************************************************************************
2  * FARSA Experiments Library *
3  * Copyright (C) 2007-2012 *
4  * Stefano Nolfi <stefano.nolfi@istc.cnr.it> *
5  * Onofrio Gigliotta <onofrio.gigliotta@istc.cnr.it> *
6  * Gianluca Massera <emmegian@yahoo.it> *
7  * Tomassino Ferrauto <tomassino.ferrauto@istc.cnr.it> *
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  * This program is distributed in the hope that it will be useful, *
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17  * GNU General Public License for more details. *
18  * *
19  * You should have received a copy of the GNU General Public License *
20  * along with this program; if not, write to the Free Software *
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
22  ********************************************************************************/
23 
24 #include "arena.h"
25 #include "configurationhelper.h"
26 #include "phybox.h"
27 #include "logger.h"
28 #include "robots.h"
29 #include "utilitiesexceptions.h"
30 #include "randomgenerator.h"
31 
32 namespace farsa {
33 
34 // This anonymous namespace contains some constants used throughout the code
35 namespace {
41  const real defaultHeight = 0.3f;
42 
48  const real planeThickness = 0.1f;
49 
55  const real defaultSmallCylinderRadius = 0.0125f;
56 
62  const real defaultBigCylinderRadius = 0.027f;
63 
69  const real targetAreasHeight = 0.01f;
70 
76  const real targetAreasProtrusion = 0.005f;
77 
83  const real defaultLightBulbRadius = 0.01f;
84 }
85 
89  enum KinematicCollisionHandlers {
90  SimpleCollisions,
93  CircleCollisions,
97  UnknownCollisionHandler
98  };
99 
100 Arena::KinematicCollisionHandlers Arena::stringToCollisionHandler(QString str)
101 {
102  str = str.toLower();
103 
104  if (str == "simplecollisions") {
105  return SimpleCollisions;
106  } else if (str == "circlecollisions") {
107  return CircleCollisions;
108  } else {
109  return UnknownCollisionHandler;
110  }
111 }
112 
113 QString Arena::collisionHandlerToString(Arena::KinematicCollisionHandlers h)
114 {
115  switch (h) {
116  case SimpleCollisions:
117  return "SimpleCollisions";
118  case CircleCollisions:
119  return "CircleCollisions";
120  case UnknownCollisionHandler:
121  default:
122  return "UnknownCollisionHandler";
123  }
124 }
125 
126 Arena::Arena(ConfigurationParameters& params, QString prefix) :
127  ParameterSettableInConstructor(params, prefix),
129  m_z(ConfigurationHelper::getDouble(params, prefix + "z", 0.0)),
130  m_smallCylinderRadius(ConfigurationHelper::getDouble(params, prefix + "smallCylinderRadius", defaultSmallCylinderRadius)),
131  m_bigCylinderRadius(ConfigurationHelper::getDouble(params, prefix + "bigCylinderRadius", defaultBigCylinderRadius)),
132  m_lightBulbRadius(ConfigurationHelper::getDouble(params, prefix + "lightBulbRadius", defaultLightBulbRadius)),
133  m_collisionHandler(stringToCollisionHandler(ConfigurationHelper::getString(params, prefix + "collisionHandler", "SimpleCollisions"))),
134  m_objects2DList(QVector<PhyObject2DWrapper*>()),
135  m_plane(createPlane(params, prefix, m_z, this)),
136  m_robotResourceWrappers(),
137  m_kinematicRobotCollisions(),
138  m_world(NULL)
139 {
140  if (m_collisionHandler == UnknownCollisionHandler) {
141  ConfigurationHelper::throwUserConfigError(prefix + "collisionHandler", params.getValue(prefix + "collisionHandler"), "Invalid collision handler value. It must be either SimpleCollisions or CircleCollisions");
142  }
143 
144  // The list of resources we observe here
145  usableResources(QStringList() << "world");
146 }
147 
149 {
150  // Removing all wrappers
151  for (int i = 0; i < m_objects2DList.size(); i++) {
152  delete m_objects2DList[i];
153  }
154 }
155 
157 {
158  Logger::error("NOT IMPLEMENTED (Arena::save)");
159  abort();
160 }
161 
162 void Arena::describe(QString type)
163 {
164  // The parent class is ParameterSettableInConstructor, no need to call
165  // its describe function (which does nothing)
166 
167  Descriptor d = addTypeDescription(type, "The class modelling an arena in which robots can live");
168  d.describeReal("z").def(0.0).help("The z coordinate of the plane in the world frame of reference", "This is the z coordinate of the upper part of the plane in the world frame of reference");
169  d.describeReal("smallCylinderRadius").def(defaultSmallCylinderRadius).limits(0.0001, +Infinity).help("The radius of small cylinders", "This is the value to use for the radius of small cylinders. The default value is the one used in evorobot. If you change this, be careful which sample files you use.");
170  d.describeReal("bigCylinderRadius").def(defaultBigCylinderRadius).limits(0.0001, +Infinity).help("The radius of big cylinders", "This is the value to use for the radius of big cylinders. The default value is the one used in evorobot. If you change this, be careful which sample files you use.");
171  d.describeReal("lightBulbRadius").def(defaultLightBulbRadius).limits(0.0001, +Infinity).help("The radius of light bulbs");
172  d.describeEnum( "collisionHandler" ).def("SimpleCollisions").values( QStringList() << "SimpleCollisions" << "CircleCollisions" ).help("The type of collision handler to use", "This can be either SimpleCollisions (which puts robots back to their position before collision) or CircleCollisions (which handles collisions between circular objects like robots and cylinders computing the results based on objects masses)");
173  d.describeReal("planeWidth").def(2.5).limits(0.01, +Infinity).help("The width of the arena");
174  d.describeReal("planeHeight").def(2.5).limits(0.01, +Infinity).help("The height of the arena");
175 }
176 
177 void Arena::addRobots(QStringList robots)
178 {
179  // First of all adding robots to the list of usable resources. This will call the resourceChanged callback
180  // because robots already exist, however they are not in the m_robotResourceWrappers map so the callback will
181  // do nothing (we take care of creating the wrappers here)
182  addUsableResources(robots);
183 
184  // Now taking the lock and generating the wrappers for all robots
185  ResourcesLocker locker(this);
186 
187  foreach(QString robot, robots) {
188  bool robotExists;
189  RobotOnPlane* r = getResource<RobotOnPlane>(robot, &robotExists);
190 
191  // The first thing to do is to check if the robot existed: in that case we have to first delete
192  // the old wrapper
193  if (m_robotResourceWrappers.contains(robot)) {
194  // This will probably crash if the robot is not in the list (this would be a bug anyway)
195  m_objects2DList.remove(m_objects2DList.indexOf(m_robotResourceWrappers[robot]));
196 
197  // Deleting the wrapper
198  delete m_robotResourceWrappers[robot];
199  }
200 
201  // If the robot actually exists, adding a wrapper, otherwise we simply add the robot to the map to
202  // remember the association
203  if (robotExists) {
204  // Creating the wrapper
205  WheeledRobot2DWrapper* wrapper = new WheeledRobot2DWrapper(this, r, r->robotHeight(), r->robotRadius());
206 
207  // Adding the wrapper to the list
208  m_objects2DList.push_back(wrapper);
209  m_robotResourceWrappers.insert(robot, wrapper);
210  } else {
211  // Adding the robot to the map with a NULL wrapper
212  m_robotResourceWrappers.insert(robot, NULL);
213  }
214  }
215 }
216 
217 const WheeledRobot2DWrapper* Arena::getRobotWrapper(QString robotName) const
218 {
219  if (m_robotResourceWrappers.contains(robotName)) {
220  return m_robotResourceWrappers[robotName];
221  } else {
222  return NULL;
223  }
224 }
225 
227 {
228  return m_plane;
229 }
230 
232 {
233  return m_plane;
234 }
235 
236 Box2DWrapper* Arena::createWall(QColor color, wVector start, wVector end, real thickness, real height)
237 {
238  ResourcesLocker locker(this);
239 
240  // Changing parameters
241  start.z = m_z;
242  end.z = m_z;
243 
244  // Creating the box, then we have to change its position and make it static
245  Box2DWrapper* b = createBox(color, (end - start).norm(), thickness, height, Box2DWrapper::Wall);
246 
247  // Moving the wall and setting the texture. We have to compute the rotation around the Z axis of the
248  // wall and the position of its center to build its transformation matrix
249  const real angle = atan2(end.y - start.y, end.x - start.x);
250  const wVector centerPosition = start + (end - start).scale(0.5);
251  wMatrix mtr = wMatrix::roll(angle);
252  mtr.w_pos = centerPosition + wVector(0.0, 0.0, b->phyObject()->matrix().w_pos.z);
253  b->phyObject()->setMatrix(mtr);
254  b->setTexture("tile2");
255 
256  return b;
257 }
258 
260 {
261  return createCylinder(color, m_smallCylinderRadius, height, Cylinder2DWrapper::SmallCylinder);
262 }
263 
265 {
266  return createCylinder(color, m_bigCylinderRadius, height, Cylinder2DWrapper::BigCylinder);
267 }
268 
269 Cylinder2DWrapper* Arena::createCylinder(real radius, QColor color, real height)
270 {
271  return createCylinder(color, radius, height, Cylinder2DWrapper::Cylinder);
272 }
273 
275 {
276  ResourcesLocker locker(this);
277 
278  // Creating the cylinder, then we only have to change its z position and set the material to nonCollidable
279  Cylinder2DWrapper* c = createCylinder(color, radius, targetAreasHeight, Cylinder2DWrapper::CircularTargetArea);
280 
281  wMatrix mtr = c->phyObject()->matrix();
282  mtr.w_pos = wVector(0.0, 0.0, m_z - (targetAreasHeight / 2.0) + targetAreasProtrusion);
283  c->phyObject()->setMatrix(mtr);
284  c->phyObject()->setMaterial("nonCollidable");
285 
286  return c;
287 }
288 
290 {
291  ResourcesLocker locker(this);
292 
293  // Creating the box, then we only have to change its z position and set the material to nonCollidable
294  Box2DWrapper* b = createBox(color, width, depth, targetAreasHeight, Box2DWrapper::RectangularTargetArea);
295 
296  wMatrix mtr = b->phyObject()->matrix();
297  mtr.w_pos = wVector(0.0, 0.0, m_z - (targetAreasHeight / 2.0) + targetAreasProtrusion);
298  b->phyObject()->setMatrix(mtr);
299  b->phyObject()->setMaterial("nonCollidable");
300 
301  return b;
302 }
303 
305 {
306  ResourcesLocker locker(this);
307 
308  Sphere2DWrapper* s = createSphere(color, m_lightBulbRadius, Sphere2DWrapper::LightBulb);
309 
310  // The light bulb is always kinematic, we don't need to change anything here
311  return s;
312 }
313 
315 {
316  int index = m_objects2DList.indexOf(obj);
317 
318  if ((index == -1) || (dynamic_cast<farsa::WheeledRobot2DWrapper*>(obj) != NULL)) {
319  return false;
320  }
321 
322  // Deleting the physical object
323  delete obj->phyObject();
324 
325  // Deleting the object
326  delete m_objects2DList[index];
327  m_objects2DList.remove(index);
328 
329  return true;
330 }
331 
333 {
334  switch (m_collisionHandler) {
335  case SimpleCollisions:
336  simpleCollisionsPreAdvance();
337  break;
338  case CircleCollisions:
339  circleCollisionsPreAdvance();
340  break;
341  default:
342  // We should never get here
343  throwUserRuntimeError("Invalid Arena Collision Handler!!!");
344  break;
345  }
346 }
347 
349 {
350  switch (m_collisionHandler) {
351  case SimpleCollisions:
352  simpleCollisionsHandle();
353  break;
354  case CircleCollisions:
355  circleCollisionsHandle();
356  break;
357  default:
358  // We should never get here
359  throwUserRuntimeError("Invalid Arena Collision Handler!!!");
360  break;
361  }
362 }
363 
364 QVector<PhyObject2DWrapper*> Arena::getKinematicRobotCollisions(WheeledRobot2DWrapper* robot) const
365 {
366  return getKinematicRobotCollisionsSet(robot).toList().toVector();
367 }
368 
369 QVector<PhyObject2DWrapper*> Arena::getKinematicRobotCollisions(QString robotResourceName) const
370 {
371  return getKinematicRobotCollisionsSet(robotResourceName).toList().toVector();
372 }
373 
374 QSet<PhyObject2DWrapper*> Arena::getKinematicRobotCollisionsSet(WheeledRobot2DWrapper* robot) const
375 {
376  return m_kinematicRobotCollisions.value(robot);
377 }
378 
379 QSet<PhyObject2DWrapper*> Arena::getKinematicRobotCollisionsSet(QString robotResourceName) const
380 {
381  if (m_robotResourceWrappers.contains(robotResourceName)) {
382  return getKinematicRobotCollisionsSet(m_robotResourceWrappers.value(robotResourceName));
383  } else {
384  throw ArenaException(QString("The arena doesn't contain the robot named %1 (perhaps you used \"robot\"? You must use the complete name, e.g. agent[0]:robot)").arg(robotResourceName).toLatin1().data());
385  }
386 }
387 
388 Cylinder2DWrapper* Arena::createCylinder(QColor color, real radius, real height, Cylinder2DWrapper::Type type)
389 {
390  ResourcesLocker locker(this);
391 
392  // Changing parameters
393  if (height < 0.0) {
394  height = defaultHeight;
395  }
396 
397  PhyCylinder* cylinder = new PhyCylinder(radius, height, m_world, "cylinder");
398  wMatrix mtr = wMatrix::yaw(-PI_GRECO / 2.0);
399  mtr.w_pos = wVector(0.0, 0.0, m_z + (height / 2.0));
400  cylinder->setMatrix(mtr);
401  cylinder->setUseColorTextureOfOwner(false);
402  cylinder->setColor(color);
403  cylinder->setTexture("");
404 
405  // Creating the wrapper
406  Cylinder2DWrapper* wrapper = new Cylinder2DWrapper(this, cylinder, type);
407 
408  // Appending the wrapper to the list
409  m_objects2DList.append(wrapper);
410 
411  return wrapper;
412 }
413 
414 Box2DWrapper* Arena::createBox(QColor color, real width, real depth, real height, Box2DWrapper::Type type)
415 {
416  ResourcesLocker locker(this);
417 
418  // Changing parameters
419  if (height < 0.0) {
420  height = defaultHeight;
421  }
422 
423  PhyBox* box = new PhyBox(width, depth, height, m_world, "box");
424 
425  // Moving the box so that it lies on the plane
426  wMatrix mtr = wMatrix::identity();
427  mtr.w_pos = wVector(0.0, 0.0, m_z + (height / 2.0));
428  box->setMatrix(mtr);
429  box->setUseColorTextureOfOwner(false);
430  box->setColor(color);
431  box->setTexture("");
432 
433  // Creating the wrapper
434  Box2DWrapper* wrapper = new Box2DWrapper(this, box, type);
435 
436  // Appending the wrapper to the list
437  m_objects2DList.append(wrapper);
438 
439  return wrapper;
440 }
441 
442 Sphere2DWrapper* Arena::createSphere(QColor color, real radius, Sphere2DWrapper::Type type)
443 {
444  ResourcesLocker locker(this);
445 
446  PhySphere* sphere = new PhySphere(radius, m_world, "sphere");
447 
448  // Moving the sphere so that it lies on the plane
449  wMatrix mtr = wMatrix::identity();
450  mtr.w_pos = wVector(0.0, 0.0, m_z + radius);
451  sphere->setMatrix(mtr);
452  sphere->setUseColorTextureOfOwner(false);
453  sphere->setColor(color);
454  sphere->setTexture("");
455 
456  // Creating the wrapper
457  Sphere2DWrapper* wrapper = new Sphere2DWrapper(this, sphere, type);
458 
459  // Appending the wrapper to the list
460  m_objects2DList.append(wrapper);
461 
462  return wrapper;
463 }
464 
465 void Arena::resourceChanged(QString resourceName, ResourceChangeType changeType)
466 {
467  if (resourceName == "world") {
468  switch (changeType) {
469  case Created:
470  case Modified:
471  m_world = getResource<World>();
472  break;
473  case Deleted:
474  m_world = NULL;
475  break;
476  }
477  } else if (m_robotResourceWrappers.contains(resourceName)) {
478  // The resource for a robot has changed status
479  switch (changeType) {
480  case Created:
481  case Modified:
482  // We have to delete the previous wrapper and add a new one
483  {
484  // Deleting the old wrapper if it exists
485  if (m_robotResourceWrappers[resourceName] != NULL) {
486  m_objects2DList.remove(m_objects2DList.indexOf(m_robotResourceWrappers[resourceName]));
487 
488  // Deleting the wrapper
489  delete m_robotResourceWrappers[resourceName];
490  }
491 
492  // Creating a new wrapper
493  RobotOnPlane* r = getResource<RobotOnPlane>();
494  WheeledRobot2DWrapper* wrapper = new WheeledRobot2DWrapper(this, r, r->robotHeight(), r->robotRadius());
495 
496  // Adding the wrapper to the list
497  m_objects2DList.push_back(wrapper);
498  m_robotResourceWrappers.insert(resourceName, wrapper);
499  }
500  break;
501  case Deleted:
502  // Simply removing the old wrapper
503  {
504  m_objects2DList.remove(m_objects2DList.indexOf(m_robotResourceWrappers[resourceName]));
505 
506  // Deleting the wrapper
507  delete m_robotResourceWrappers[resourceName];
508  m_robotResourceWrappers[resourceName] = NULL;
509  }
510  break;
511  }
512  }
513 }
514 
515 void Arena::simpleCollisionsPreAdvance()
516 {
517  ResourcesLocker locker(this);
518 
519  // Cycling through the list of robots and storing the matrix
520  foreach (WheeledRobot2DWrapper* robot, m_robotResourceWrappers) {
521  // We only act on kinematic robots
522  if ((robot == NULL) || (!robot->robotOnPlane()->isKinematic())) {
523  continue;
524  }
525  robot->storePreviousMatrix();
526  }
527 
528  // Clearing the list of collisions for each robot
529  m_kinematicRobotCollisions.clear();
530 }
531 
532 void Arena::simpleCollisionsHandle()
533 {
534  ResourcesLocker locker(this);
535 
536  // Here we use a naive approach: we check collision of every object with every robot. This is not
537  // the best possible algorithm (complexity is O(n^2)...), but it's the way things are done in evorobot
538 
539  // Cycling through the list of robots
540  foreach (WheeledRobot2DWrapper* robot, m_robotResourceWrappers) {
541  // We only act on kinematic robots
542  if ((robot == NULL) || (!robot->robotOnPlane()->isKinematic())) {
543  continue;
544  }
545 
546  bool collides = false;
547  // Checking if the robot collides
548  foreach (PhyObject2DWrapper* obj, m_objects2DList) {
549  // Not checking the collision of the robot with itself (obviously...) and
550  // not checking collisions with non-collidable objects
551  if ((obj == robot) || ((obj->phyObject() != NULL) && (obj->phyObject()->isCollidable() == false))) {
552  continue;
553  }
554 
555  double distance;
556  double angle;
557 
558  // Getting the distance between the robot and the object. If computeDistanceAndOrientationFromRobot
559  // returns false, we have to discard the object
560  if (!obj->computeDistanceAndOrientationFromRobot(*robot, distance, angle)) {
561  continue;
562  }
563 
564  // If the distance is negative, we had a collision
565  if (distance < 0.0) {
566  // Checking if this collision was already handled
567  if (m_kinematicRobotCollisions.contains(robot) && m_kinematicRobotCollisions[robot].contains(obj)) {
568  continue;
569  }
570 
571  collides = true;
572 
573  m_kinematicRobotCollisions[robot].insert(obj);
574 
575  // If obj is a robot, registering the collision for him, too
576  WheeledRobot2DWrapper* otherRobot = dynamic_cast<WheeledRobot2DWrapper*>(obj);
577  if (otherRobot != NULL) {
578  m_kinematicRobotCollisions[otherRobot].insert(robot);
579  }
580  }
581  }
582 
583  // If the robot collides with something else, moving it back to its previous position
584  if (collides) {
585  robot->wObject()->setMatrix(robot->previousMatrix());
586  }
587  }
588 }
589 
590 void Arena::circleCollisionsPreAdvance()
591 {
592  ResourcesLocker locker(this);
593 
594  // Cycling through the list of robots and storing the matrix
595  foreach (WheeledRobot2DWrapper* robot, m_robotResourceWrappers) {
596  // We only act on kinematic robots
597  if ((robot == NULL) || (!robot->robotOnPlane()->isKinematic())) {
598  continue;
599  }
600  robot->storePreviousMatrix();
601  }
602 
603  // Clearing the list of collisions for each robot
604  m_kinematicRobotCollisions.clear();
605 }
606 
607 void Arena::circleCollisionsHandle()
608 {
609  ResourcesLocker locker(this);
610 
611  // Here we use a naive approach: we check collision of every object with every robot like in the other implementation
612  // (simpleCollisions). If the robot collides with a non-static object, we move the object forward and the robot a bit
613  // backward. The percentage of the robot displacement which is transferred to the object is defined by the constant
614  // below (not a parameter because this part should probably be re-written from scratch)
615  const real robotDisplacementFractionToObject = 0.5;
616  // If the robot collides with another robot we move both robots backward to resolve the collision, if it collides with
617  // a static object, we move only the colliding robot bacward. In both cases we add some noise to the position and
618  // orientation of the robot after the collision. The percentage of noise is defined by the constant below
619  const real noiseOnPosition = 0.05f; // Absolute, added to k, see below
620  const real noiseOnOrientation = PI_GRECO * 0.005f; // Absolute maximum angular displacement
621 
622  // Cycling through the list of robots
623  foreach (WheeledRobot2DWrapper* robot, m_robotResourceWrappers) {
624  // We only act on kinematic robots
625  if ((robot == NULL) || (!robot->robotOnPlane()->isKinematic())) {
626  continue;
627  }
628 
629  // The vectors with distances and angles of colliding objects (these should all be negative values)
630  QVector<double> distances;
631  QVector<double> angles;
632  QVector<PhyObject2DWrapper*> collidingObjs;
633 
634  // Checking if the robot collides
635  bool collides = false;
636  foreach (PhyObject2DWrapper* obj, m_objects2DList) {
637  // Not checking the collision of the robot with itself (obviously...) and
638  // not checking collisions with non-collidable objects
639  if ((obj == robot) || ((obj->phyObject() != NULL) && (obj->phyObject()->isCollidable() == false))) {
640  continue;
641  }
642 
643  double distance;
644  double angle;
645 
646  // Getting the distance between the robot and the object. If computeDistanceAndOrientationFromRobot
647  // returns false, we have to discard the object
648  if (!obj->computeDistanceAndOrientationFromRobot(*robot, distance, angle)) {
649  continue;
650  }
651 
652  // If the distance is negative, we had a collision
653  if (distance < 0.0) {
654  // Checking if this collision was already handled
655  if (m_kinematicRobotCollisions.contains(robot) && m_kinematicRobotCollisions[robot].contains(obj)) {
656  continue;
657  }
658 
659  collides = true;
660 
661  m_kinematicRobotCollisions[robot].insert(obj);
662  collidingObjs.append(obj);
663  distances.append(distance);
664  angles.append(angle);
665 
666  // If obj is a robot, registering the collision for him, too
667  WheeledRobot2DWrapper* otherRobot = dynamic_cast<WheeledRobot2DWrapper*>(obj);
668  if (otherRobot != NULL) {
669  m_kinematicRobotCollisions[otherRobot].insert(robot);
670  }
671  }
672  }
673 
674  // If the robot collides with something else we have to check if it only collided with movable objects (we then
675  // move both the objects and the robot) or with static objects (we thus move only the robot)
676  if (collides) {
677  PhyObject2DWrapper* firstStaticObj = NULL;
678  double distanceStaticObj = 0.0;
679  double angleStaticObj = 0.0;
680  for (int i = 0; (i < distances.size()) && (firstStaticObj == NULL); i++) {
681  PhyObject2DWrapper* obj = collidingObjs[i];
682  if (obj->getStatic()) {
683  // We get here also if the other object is a robot (getStatic() returns true for robots...)
684  firstStaticObj = obj;
685  distanceStaticObj = distances[i];
686  angleStaticObj = angles[i];
687  break;
688  }
689  }
690 
691  if (firstStaticObj == NULL) {
692  const wVector robotDisplacement = robot->position() - robot->previousMatrix().w_pos;
693  const wVector objectDisplacement = robotDisplacement.scale(robotDisplacementFractionToObject);
694 
695  // Moving robot back (the hypothesis is that the robot can rotate freely but not move forward as
696  // expected in case no obstacle was in place)
697  wMatrix robotMtr = robot->wObject()->matrix();
698  robotMtr.w_pos -= objectDisplacement;
699  robot->wObject()->setMatrix(robotMtr);
700 
701  // Now moving objects
702  foreach (PhyObject2DWrapper* obj, collidingObjs) {
703  wMatrix objectMtr = obj->wObject()->matrix();
704  objectMtr.w_pos += objectDisplacement;
705  obj->wObject()->setMatrix(objectMtr);
706  }
707  } else {
708  if (dynamic_cast<WheeledRobot2DWrapper*>(firstStaticObj) != NULL) {
709  WheeledRobot2DWrapper* otherRobot = (WheeledRobot2DWrapper*) firstStaticObj;
710 
711  // Here we move both robots back (along the direction of their velocity) of a quantity which is sufficient
712  // to remove the collision. We then add a small random noise to both position and orientation. To compute
713  // how much to move robots, we can write:
714  // r1 + r2 = || (c1 - k*v1) - (c2 - k*v2) ||
715  // where r1 and r2 are the radii of the two robots, c1 and c2 are the positions on the plane of the centers
716  // of the two robots at the moment of the collision, v1 and v2 are the velocities of the two robots in the
717  // current timestep and k is the factor multiplying the two velocities that tells how much to move the robots
718  // backward. This means that we should find k so that when the robots are moved back along the respective
719  // velocity vectors by k*v1 and k*v2, the distance between their centers should be equal to r1 + r2. The
720  // solution to the equation above gives two values of k. Only the positive one (which should be between 0 and
721  // 1, if the robots were not colliding in the previous timestep) whould be taken (the negative one corrsponds
722  // to the robots being moved forward to eliminate the collision).
723  const real r1 = robot->getRadius();
724  const real r2 = otherRobot->getRadius();
725  const real r = r1 + r2;
726  const wVector c1 = robot->wObject()->matrix().w_pos;
727  const wVector c2 = otherRobot->wObject()->matrix().w_pos;
728  const wVector v1 = c1 - robot->previousMatrix().w_pos;
729  const wVector v2 = c2 - otherRobot->previousMatrix().w_pos;
730  const real dcx = c1.x - c2.x;
731  const real dcy = c1.y - c2.y;
732  const real dvx = v1.x - v2.x;
733  const real dvy = v1.y - v2.y;
734  const real a = dvx * dvx + dvy * dvy;
735  const real b = dvx * dcx + dvy * dcy;
736  const real c = dcx * dcx + dcy * dcy - r * r;
737  if (a < 0.000001) {
738  // If we get here, both velocities were zero, so we don't do anything
739  continue;
740  }
741  const real sqrtDelta = sqrt(b*b - a*c);
742  if (isnan(sqrtDelta)) {
743  // If we get here there was some error, doing nothing
744  qDebug() << "Negative delta when computing collisions, skipping";
745 
746  continue;
747  }
748  const real k1 = (b + sqrtDelta) / a;
749  const real k2 = (b - sqrtDelta) / a;
750  // k is how much I have to move the robots backwards. k1 and k2 must always have different signs, because
751  // the positive one is how much I have to move robots backwards to remove the collision, while the negative
752  // one is how much I have to move them forward. I simply take the bigger of the two, which will be positive.
753  // It could happend that the biggest is 0 (or nearly so): for example if robots are already very near and
754  // they move at a very slow velocity when colliding. This is not a problem, robots will not be moved in this
755  // step and they will be moved back if they move more in subsequent steps
756  const real k = (k1 > k2) ? k1 : k2;
757 
758  // Sanity check. Here we have to allow small negative values (which will be ignored) that are possible in
759  // the case described above (close robots moving at slow speed)
760  if (k < -0.000001) {
761  Logger::warning(QString("Wrong factor when resolving collisions, setting a value to NaN. r1 = %1, r2 = %2, r = %3, c1 = %4, c2 = %5, v1 = %6, v2 = %7, dcx = %8, dcy = %9, dvx = %10, dvy = %11, a = %12, b = %13, c = %14, sqrtDelta = %15, k1 = %16, k2 = %17, k = %18").arg(r1).arg(r2).arg(r).arg(c1).arg(c2).arg(v1).arg(v2).arg(dcx).arg(dcy).arg(dvx).arg(dvy).arg(a).arg(b).arg(c).arg(sqrtDelta).arg(k1).arg(k2).arg(k));
762 
763 #if defined(__GNUC__) && defined(DEVELOPER_WARNINGS)
764  #warning For the moment we don t kill the simulation, just for testing purpouse. We set the position of one robot to NaN
765 #endif
766  //qFatal("Wrong factor when resolving collisions in %s at line %d", __FILE__, __LINE__);
767  wMatrix robotMtr = robot->wObject()->matrix();
768  robotMtr.w_pos.x = sqrt(-1.0);
769  robot->wObject()->setMatrix(robotMtr);
770  continue;
771  } else if (k < 0.0) {
772  continue;
773  }
774 
775  // Moving robots back
776  // First robot
777  wMatrix robotMtr = robot->wObject()->matrix();
778  wVector robotPos = robotMtr.w_pos - v1.scale(k + globalRNG->getDouble(0.0, noiseOnPosition));
779  robotMtr = robotMtr.rotateAround(wVector::Z(), robotMtr.w_pos, globalRNG->getDouble(-noiseOnOrientation, noiseOnOrientation));
780  robotMtr.w_pos = robotPos;
781  robot->wObject()->setMatrix(robotMtr);
782  // Second robot
783  robotMtr = otherRobot->wObject()->matrix();
784  robotPos = robotMtr.w_pos - v2.scale(k + globalRNG->getDouble(0.0, noiseOnPosition));
785  robotMtr = robotMtr.rotateAround(wVector::Z(), robotMtr.w_pos, globalRNG->getDouble(-noiseOnOrientation, noiseOnOrientation));
786  robotMtr.w_pos = robotPos;
787  otherRobot->wObject()->setMatrix(robotMtr);
788  } else if (dynamic_cast<Box2DWrapper*>(firstStaticObj) != NULL) {
789 #if defined(__GNUC__) && defined(DEVELOPER_WARNINGS)
790  #warning COMPLETARE QUESTO (COLLISIONI CON BOX)
791 #endif
792  Box2DWrapper* obj = (Box2DWrapper*) firstStaticObj;
793 
794 // Qui per il muro bisogna usare l'angolo tra muro e robot per poter calcolare di quanto si deve spostare il robot indietro.
795 // muro
796 
797 // distanceStaticObj
798 // angleStaticObj
799 
800  robot->wObject()->setMatrix(robot->previousMatrix());
801  } else if (dynamic_cast<Cylinder2DWrapper*>(firstStaticObj) != NULL) {
802  // Computing the robot displacement
803  const wVector robotDisplacement = robot->position() - robot->previousMatrix().w_pos;
804 
805  // Now computing how much the robot should be moved backward and adding some noise as a portion of the robot displacement. The
806  // minus sign when computing k is because the distance is negative in case of collisions. We check the robotDisplacement.norm()
807  // is not too small to avoid numerical problems. If it is, simply moveing to the previous location (no noise on position since
808  // it would be tiny)
809  const real robotDisplacementNorm = robotDisplacement.norm();
810  if (robotDisplacementNorm < 0.0001) {
811  // Moving back the robot and adding noise on orientation only
812  wMatrix robotMtr = robot->wObject()->matrix();
813  robotMtr = robotMtr.rotateAround(wVector::Z(), robotMtr.w_pos, globalRNG->getDouble(-noiseOnOrientation, noiseOnOrientation));
814  robotMtr.w_pos = robot->previousMatrix().w_pos;
815  robot->wObject()->setMatrix(robotMtr);
816  } else {
817  const real k = -distanceStaticObj / robotDisplacementNorm;
818 
819  // Moving back the robot and adding noise
820  wMatrix robotMtr = robot->wObject()->matrix();
821  const wVector robotPos = robotMtr.w_pos - robotDisplacement.scale(k + globalRNG->getDouble(0.0, noiseOnPosition));
822  robotMtr = robotMtr.rotateAround(wVector::Z(), robotMtr.w_pos, globalRNG->getDouble(-noiseOnOrientation, noiseOnOrientation));
823  robotMtr.w_pos = robotPos;
824  robot->wObject()->setMatrix(robotMtr);
825  }
826  } else {
827  qDebug() << "Unknown object type when resolving collisions, skipping";
828  }
829  }
830  }
831  }
832 }
833 
834 Box2DWrapper* Arena::createPlane(ConfigurationParameters& params, QString prefix, real z, Arena* arena)
835 {
836  // First of all getting the dimensions of the plane
837  const real planeWidth = ConfigurationHelper::getDouble(params, prefix + "planeWidth", 2.5);
838  const real planeHeight = ConfigurationHelper::getDouble(params, prefix + "planeHeight", 2.5);
839 
840  // Also getting the world from the ResourcesUser associated with param
841  SimpleResourcesUser* r = params.getResourcesUserForResource("world");
842  if (r == NULL) {
843  ConfigurationHelper::throwUserMissingResourceError("world", "We need a world to create the arena!");
844  }
845  World* world = r->getResource<World>("world");
846 
847  PhyBox* plane = new PhyBox(planeWidth, planeHeight, planeThickness, world, "plane");
848  plane->setPosition(wVector(0.0, 0.0, z - (planeThickness / 2.0)));
849  plane->setStatic(true);
850  plane->setColor(Qt::white);
851  plane->setTexture("tile2");
852 
853  // Also resizing world to comfortably fit the arena if it is smaller than necessary
854  wVector worldMinPoint;
855  wVector worldMaxPoint;
856  world->size(worldMinPoint, worldMaxPoint);
857  worldMinPoint.x = min(-planeWidth, worldMinPoint.x);
858  worldMinPoint.y = min(-planeHeight, worldMinPoint.y);
859  worldMinPoint.z = min(-planeThickness, worldMinPoint.z);
860  worldMaxPoint.x = max(planeWidth, worldMaxPoint.x);
861  worldMaxPoint.y = max(planeHeight, worldMaxPoint.y);
862  worldMaxPoint.z = max(+6.0, worldMaxPoint.z);
863  world->setSize(worldMinPoint, worldMaxPoint);
864 
865  // Creating the wrapper and returning it
866  Box2DWrapper* ret = new Box2DWrapper(arena, plane, Box2DWrapper::Plane);
867  arena->m_objects2DList.append( ret );
868  return ret;
869 }
870 
871 Box2DWrapper* Arena::createPlane2(ConfigurationParameters& params, QString prefix, real z, Arena* arena)
872 {
873  // First of all getting the dimensions of the plane
874  const real planeWidth = ConfigurationHelper::getDouble(params, prefix + "planeWidth", 2.5);
875  const real planeHeight = ConfigurationHelper::getDouble(params, prefix + "planeHeight", 2.5);
876 
877  // the plane will be composed by many boxes near to each other
878  const real cellSize = 0.20f;
879  int numCellW = ceil(planeWidth/cellSize);
880  int numCellH = ceil(planeHeight/cellSize);
881 
882  // Also getting the world from the ResourcesUser associated with param
883  SimpleResourcesUser* r = params.getResourcesUserForResource("world");
884  if (r == NULL) {
885  ConfigurationHelper::throwUserMissingResourceError("world", "We need a world to create the arena!");
886  }
887  World* world = r->getResource<World>("world");
888 
889  Box2DWrapper* ret = NULL;
890  for( int w=0; w<numCellW; w++ ) {
891  for( int h=0; h<numCellH; h++ ) {
892  PhyBox* plane = new PhyBox(cellSize, cellSize, planeThickness, world, "cellPlane");
893  plane->setPosition(wVector( w*cellSize-planeWidth/2.0, h*cellSize-planeHeight/2.0, z - (planeThickness / 2.0)));
894  plane->setStatic(true);
895  plane->setColor(Qt::white);
896  plane->setTexture("tile2");
897 
898  if ( w==0 || h==0 ) {
899  // use this as reference for plane
900  ret = new Box2DWrapper(arena, plane, Box2DWrapper::Plane);
901  }
902  // Appending the wrapper to the list
903  arena->m_objects2DList.append( new Box2DWrapper(arena, plane, Box2DWrapper::Plane) );
904  }
905  }
906  // Also resizing world to comfortably fit the arena
907  world->setSize(wVector(-planeWidth, -planeHeight, -planeThickness), wVector(planeWidth, planeHeight, +6.0));
908  // return one of the cell created as reference for the plane
909  return ret;
910 }
911 
912 } // end namespace farsa
void usableResources(QStringList resources)
friend friend class ResourcesLocker
static wMatrix identity()
Sphere2DWrapper * createLightBulb(QColor color)
Creates a LightBulb.
Definition: arena.cpp:304
static void throwUserMissingResourceError(QString resourceName, QString description)
Arena(ConfigurationParameters &params, QString prefix)
Constructor.
Definition: arena.cpp:126
FARSA_UTIL_API RandomGenerator * globalRNG
void addRobots(QStringList robots)
Adds robots to the list of 2D objects.
Definition: arena.cpp:177
The subclass of PhyObject2DWrapper wrapping a box.
The exception thrown at runtime by the arena.
Definition: arena.h:551
void setTexture(QString textureName)
Set the texture to use when rendering this.
The subclass of PhyObject2DWrapper wrapping a wheeled robot.
The base class for robots that move on a plane.
Definition: robots.h:108
static const double Infinity
Cylinder2DWrapper * createSmallCylinder(QColor color, real height=-1.0)
Creates a small cylinder.
Definition: arena.cpp:259
virtual PhyCylinder * phyObject()
Returns a pointer to the wrapped PhyObject.
const WheeledRobot2DWrapper * getRobotWrapper(QString robotName) const
Returns a pointer to the wrapper of a robot given the robot resource name.
Definition: arena.cpp:217
Box2DWrapper * getPlane()
Returns the plane of the arena.
Definition: arena.cpp:226
FARSA_DEPRECATED QVector< PhyObject2DWrapper * > getKinematicRobotCollisions(WheeledRobot2DWrapper *robot) const
Returns the list of objects that collided with the given kinematic robot at the previous timestep...
Definition: arena.cpp:364
FARSA_UTIL_TEMPLATE const T max(const T &t1, const U &t2)
Cylinder2DWrapper * createCircularTargetArea(real radius, QColor color)
Creates a circular target area.
Definition: arena.cpp:274
static wMatrix roll(real ang)
static void throwUserConfigError(QString paramName, QString paramValue, QString description)
virtual real robotRadius() const =0
Returns the radius of the robot.
const wMatrix & matrix() const
void addUsableResources(QStringList resources)
void FARSA_UTIL_TEMPLATE throwUserRuntimeError(QString reason)
static void error(QString msg)
The subclass of PhyObject2DWrapper wrapping a cylinder.
Cylinder2DWrapper * createBigCylinder(QColor color, real height=-1.0)
Creates a big cylinder.
Definition: arena.cpp:264
A class wrapping a PhyObject to add methods suitable for wheeled robots simulations.
Box2DWrapper * createWall(QColor color, wVector start, wVector end, real thickness, real height=-1.0)
Creates a wall.
Definition: arena.cpp:236
virtual real robotHeight() const =0
Returns the height of the robot.
Box2DWrapper * createRectangularTargetArea(real width, real depth, QColor color)
Creates a rectangular target area.
Definition: arena.cpp:289
virtual void save(ConfigurationParameters &params, QString prefix)
Saves the actual status of parameters into the ConfigurationParameters object passed.
Definition: arena.cpp:156
static double getDouble(ConfigurationParameters &params, QString paramPath, double def=0)
static Descriptor addTypeDescription(QString type, QString shortHelp, QString longHelp=QString(""))
QString getValue(QString path, bool alsoMatchParents=false) const
float real
Type
The possible type of wrapped objects.
static wMatrix yaw(real ang)
virtual ~Arena()
Destructor.
Definition: arena.cpp:148
void handleKinematicRobotCollisions()
Checks collisions of kinematic robots.
Definition: arena.cpp:348
FARSA_UTIL_TEMPLATE const T min(const T &t1, const U &t2)
void setMatrix(const wMatrix &newm)
virtual PhyObject * phyObject()=0
Returns a pointer to the wrapped PhyObject.
bool delete2DObject(PhyObject2DWrapper *obj)
Deletes an object.
Definition: arena.cpp:314
The subclass of PhyObject2DWrapper wrapping a sphere.
double getDouble(double min, double max)
Cylinder2DWrapper * createCylinder(real radius, QColor color, real height=-1.0)
Creates a cylinder.
Definition: arena.cpp:269
virtual PhyBox * phyObject()
Returns a pointer to the wrapped PhyObject.
QSet< PhyObject2DWrapper * > getKinematicRobotCollisionsSet(WheeledRobot2DWrapper *robot) const
Returns the set of objects that collided with the given kinematic robot at the previous timestep...
Definition: arena.cpp:374
static void warning(QString msg)
void prepareToHandleKinematicRobotCollisions()
Call this before advancing the world to prepare stuffs needed by a call to handleKinematicRobotCollis...
Definition: arena.cpp:332
static void describe(QString type)
Adds to Factory::typeDescriptions() the descriptions of all parameters and subgroups.
Definition: arena.cpp:162