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 
30 namespace farsa {
31 
32 // This anonymous namespace contains some constants used throughout the code
33 namespace {
39  const real defaultHeight = 0.3f;
40 
46  const real planeThickness = 0.1;
47 
53  const real defaultSmallCylinderRadius = 0.0125;
54 
60  const real defaultBigCylinderRadius = 0.027;
61 
67  const real targetAreasHeight = 0.01;
68 
74  const real targetAreasProtrusion = 0.005;
75 }
76 
77 Arena::Arena(ConfigurationParameters& params, QString prefix) :
78  ParameterSettableInConstructor(params, prefix),
80  m_z(ConfigurationHelper::getDouble(params, prefix + "z", 0.0)),
81  m_smallCylinderRadius(ConfigurationHelper::getDouble(params, prefix + "smallCylinderRadius", defaultSmallCylinderRadius)),
82  m_bigCylinderRadius(ConfigurationHelper::getDouble(params, prefix + "bigCylinderRadius", defaultBigCylinderRadius)),
83  m_objects2DList(QVector<PhyObject2DWrapper*>()),
84  m_plane(createPlane(params, prefix, m_z, this)),
85  m_robotResourceWrappers(),
86  m_kinematicRobotCollisions(),
87  m_world(NULL)
88 {
89  // The list of resources we observe here
90  usableResources(QStringList() << "world");
91 }
92 
94 {
95  // Removing all wrappers
96  for (int i = 0; i < m_objects2DList.size(); i++) {
97  delete m_objects2DList[i];
98  }
99 }
100 
102 {
103  Logger::error("NOT IMPLEMENTED (Arena::save)");
104  abort();
105 }
106 
107 void Arena::describe(QString type)
108 {
109  // The parent class is ParameterSettableInConstructor, no need to call
110  // its describe function (which does nothing)
111 
112  Descriptor d = addTypeDescription(type, "The class modelling an arena in which robots can live");
113  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");
114  d.describeReal("smallCylinderRadius").def(defaultSmallCylinderRadius).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.");
115  d.describeReal("bigCylinderRadius").def(defaultBigCylinderRadius).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.");
116  d.describeReal("planeWidth").def(2.5).limits(0.01, +Infinity).help("The width of the arena");
117  d.describeReal("planeHeight").def(2.5).limits(0.01, +Infinity).help("The height of the arena");
118 }
119 
120 void Arena::addRobots(QStringList robots)
121 {
122  // First of all adding robots to the list of usable resources. This will call the resourceChanged callback
123  // because robots already exist, however they are not in the m_robotResourceWrappers map so the callback will
124  // do nothing (we take care of creating the wrappers here)
125  addUsableResources(robots);
126 
127  // Now taking the lock and generating the wrappers for all robots
128  ResourcesLocker locker(this);
129 
130  foreach(QString robot, robots) {
131  bool robotExists;
132  RobotOnPlane* r = getResource<RobotOnPlane>(robot, &robotExists);
133 
134  // The first thing to do is to check if the robot existed: in that case we have to first delete
135  // the old wrapper
136  if (m_robotResourceWrappers.contains(robot)) {
137  // This will probably crash if the robot is not in the list (this would be a bug anyway)
138  m_objects2DList.remove(m_objects2DList.indexOf(m_robotResourceWrappers[robot]));
139 
140  // Deleting the wrapper
141  delete m_robotResourceWrappers[robot];
142  }
143 
144  // If the robot actually exists, adding a wrapper, otherwise we simply add the robot to the map to
145  // remember the association
146  if (robotExists) {
147  // Creating the wrapper
148  WheeledRobot2DWrapper* wrapper = new WheeledRobot2DWrapper(this, r, r->robotHeight(), r->robotRadius());
149 
150  // Adding the wrapper to the list
151  m_objects2DList.push_back(wrapper);
152  m_robotResourceWrappers.insert(robot, wrapper);
153  } else {
154  // Adding the robot to the map with a NULL wrapper
155  m_robotResourceWrappers.insert(robot, NULL);
156  }
157  }
158 }
159 
160 const WheeledRobot2DWrapper* Arena::getRobotWrapper(QString robotName) const
161 {
162  if (m_robotResourceWrappers.contains(robotName)) {
163  return m_robotResourceWrappers[robotName];
164  } else {
165  return NULL;
166  }
167 }
168 
170 {
171  return m_plane;
172 }
173 
175 {
176  return m_plane;
177 }
178 
179 Box2DWrapper* Arena::createWall(QColor color, wVector start, wVector end, real thickness, real height)
180 {
181  ResourcesLocker locker(this);
182 
183  // Changing parameters
184  start.z = m_z;
185  end.z = m_z;
186 
187  // Creating the box, then we have to change its position and make it static
188  Box2DWrapper* b = createBox(color, (end - start).norm(), thickness, height, Box2DWrapper::Wall);
189 
190  // Moving the wall and setting the texture. We have to compute the rotation around the Z axis of the
191  // wall and the position of its center to build its transformation matrix
192  const real angle = atan2(end.y - start.y, end.x - start.x);
193  const wVector centerPosition = start + (end - start).scale(0.5);
194  wMatrix mtr = wMatrix::roll(angle);
195  mtr.w_pos = centerPosition + wVector(0.0, 0.0, b->phyObject()->matrix().w_pos.z);
196  b->phyObject()->setMatrix(mtr);
197  b->setTexture("tile2");
198 
199  return b;
200 }
201 
203 {
204  return createCylinder(color, m_smallCylinderRadius, height, Cylinder2DWrapper::SmallCylinder);
205 }
206 
207 Cylinder2DWrapper* Arena::createBigCylinder(QColor color, real height)
208 {
209  return createCylinder(color, m_bigCylinderRadius, height, Cylinder2DWrapper::BigCylinder);
210 }
211 
213 {
214  ResourcesLocker locker(this);
215 
216  // Creating the cylinder, then we only have to change its z position and set the material to nonCollidable
217  Cylinder2DWrapper* c = createCylinder(color, radius, targetAreasHeight, Cylinder2DWrapper::CircularTargetArea);
218 
219  wMatrix mtr = c->phyObject()->matrix();
220  mtr.w_pos = wVector(0.0, 0.0, m_z - (targetAreasHeight / 2.0) + targetAreasProtrusion);
221  c->phyObject()->setMatrix(mtr);
222  c->phyObject()->setMaterial("nonCollidable");
223 
224  return c;
225 }
226 
227 Box2DWrapper* Arena::createRectangularTargetArea(real width, real depth, QColor color)
228 {
229  ResourcesLocker locker(this);
230 
231  // Creating the box, then we only have to change its z position and set the material to nonCollidable
232  Box2DWrapper* b = createBox(color, width, depth, targetAreasHeight, Box2DWrapper::RectangularTargetArea);
233 
234  wMatrix mtr = b->phyObject()->matrix();
235  mtr.w_pos = wVector(0.0, 0.0, m_z - (targetAreasHeight / 2.0) + targetAreasProtrusion);
236  b->phyObject()->setMatrix(mtr);
237  b->phyObject()->setMaterial("nonCollidable");
238 
239  return b;
240 }
241 
243 {
244  int index = m_objects2DList.indexOf(obj);
245 
246  if ((index == -1) || (dynamic_cast<farsa::WheeledRobot2DWrapper*>(obj) != NULL)) {
247  return false;
248  }
249 
250  // Deleting the physical object
251  delete obj->phyObject();
252 
253  // Deleting the object
254  delete m_objects2DList[index];
255  m_objects2DList.remove(index);
256 
257  return true;
258 }
259 
261 {
262  ResourcesLocker locker(this);
263 
264  // Cycling through the list of robots and storing the matrix
265  foreach (WheeledRobot2DWrapper* robot, m_robotResourceWrappers) {
266  // We only act on kinematic robots
267  if (!robot->robotOnPlane()->isKinematic()) {
268  continue;
269  }
270  robot->storePreviousMatrix();
271  }
272 
273  // Clearing the list of collisions for each robot
274  m_kinematicRobotCollisions.clear();
275 }
276 
278 {
279  ResourcesLocker locker(this);
280 
281  // Here we use a naive approach: we check collision of every object with every robot. This is not
282  // the best possible algorithm (complexity is O(n^2)...), but it's the way things are done in evorobot
283 
284  // Cycling through the list of robots
285  foreach (WheeledRobot2DWrapper* robot, m_robotResourceWrappers) {
286  // We only act on kinematic robots
287  if (!robot->robotOnPlane()->isKinematic()) {
288  continue;
289  }
290 
291  bool collides = false;
292  // Checking if the robot collides
293  foreach (PhyObject2DWrapper* obj, m_objects2DList) {
294  // Not checking the collision of the robot with itself (obviously...)
295  if (obj == robot) {
296  continue;
297  }
298 
299  double distance;
300  double angle;
301 
302  // Getting the distance between the robot and the object. If computeDistanceAndOrientationFromRobot
303  // returns false, we have to discard the object
304  if (!obj->computeDistanceAndOrientationFromRobot(*robot, distance, angle)) {
305  continue;
306  }
307 
308  // If the distance is negative, we had a collision
309  if (distance < 0.0) {
310  collides = true;
311 
312  m_kinematicRobotCollisions[robot].append(obj);
313  }
314  }
315 
316  // If the robot collides with something else, moving it back to its previous position
317  if (collides) {
318  robot->wObject()->setMatrix(robot->previousMatrix());
319  }
320  }
321 }
322 
323 QVector<PhyObject2DWrapper*> Arena::getKinematicRobotCollisions(WheeledRobot2DWrapper* robot) const
324 {
325  return m_kinematicRobotCollisions.value(robot);
326 }
327 
328 QVector<PhyObject2DWrapper*> Arena::getKinematicRobotCollisions(QString robotResourceName) const
329 {
330  if (m_robotResourceWrappers.contains(robotResourceName)) {
331  return getKinematicRobotCollisions(m_robotResourceWrappers.value(robotResourceName));
332  } else {
333  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).toAscii().data());
334  }
335 }
336 
337 Cylinder2DWrapper* Arena::createCylinder(QColor color, real radius, real height, Cylinder2DWrapper::Type type)
338 {
339  ResourcesLocker locker(this);
340 
341  // Changing parameters
342  if (height < 0.0) {
343  height = defaultHeight;
344  }
345 
346  PhyCylinder* cylinder = new PhyCylinder(radius, height, m_world, "cylinder");
347  wMatrix mtr = wMatrix::yaw(PI_GRECO / 2.0);
348  mtr.w_pos = wVector(0.0, 0.0, m_z + (height / 2.0));
349  cylinder->setMatrix(mtr);
350  cylinder->setUseColorTextureOfOwner(false);
351  cylinder->setColor(color);
352  cylinder->setTexture("");
353 
354  // Creating the wrapper
355  Cylinder2DWrapper* wrapper = new Cylinder2DWrapper(this, cylinder, type);
356 
357  // Appending the wrapper to the list
358  m_objects2DList.append(wrapper);
359 
360  return wrapper;
361 }
362 
363 Box2DWrapper* Arena::createBox(QColor color, real width, real depth, real height, Box2DWrapper::Type type)
364 {
365  ResourcesLocker locker(this);
366 
367  // Changing parameters
368  if (height < 0.0) {
369  height = defaultHeight;
370  }
371 
372  PhyBox* box = new PhyBox(width, depth, height, m_world, "box");
373 
374  // Moving the box so that it lies on the plane
375  wMatrix mtr = wMatrix::identity();
376  mtr.w_pos = wVector(0.0, 0.0, m_z + (height / 2.0));
377  box->setMatrix(mtr);
378  box->setUseColorTextureOfOwner(false);
379  box->setColor(color);
380  box->setTexture("");
381 
382  // Creating the wrapper
383  Box2DWrapper* wrapper = new Box2DWrapper(this, box, type);
384 
385  // Appending the wrapper to the list
386  m_objects2DList.append(wrapper);
387 
388  return wrapper;
389 }
390 
391 void Arena::resourceChanged(QString resourceName, ResourceChangeType changeType)
392 {
393  if (resourceName == "world") {
394  switch (changeType) {
395  case Created:
396  case Modified:
397  m_world = getResource<World>();
398  break;
399  case Deleted:
400  m_world = NULL;
401  break;
402  }
403  } else if (m_robotResourceWrappers.contains(resourceName)) {
404  // The resource for a robot has changed status
405  switch (changeType) {
406  case Created:
407  case Modified:
408  // We have to delete the previous wrapper and add a new one
409  {
410  // Deleting the old wrapper if it exists
411  if (m_robotResourceWrappers[resourceName] != NULL) {
412  m_objects2DList.remove(m_objects2DList.indexOf(m_robotResourceWrappers[resourceName]));
413 
414  // Deleting the wrapper
415  delete m_robotResourceWrappers[resourceName];
416  }
417 
418  // Creating a new wrapper
419  RobotOnPlane* r = getResource<RobotOnPlane>();
420  WheeledRobot2DWrapper* wrapper = new WheeledRobot2DWrapper(this, r, r->robotHeight(), r->robotRadius());
421 
422  // Adding the wrapper to the list
423  m_objects2DList.push_back(wrapper);
424  m_robotResourceWrappers.insert(resourceName, wrapper);
425  }
426  break;
427  case Deleted:
428  // Simply removing the old wrapper
429  {
430  m_objects2DList.remove(m_objects2DList.indexOf(m_robotResourceWrappers[resourceName]));
431 
432  // Deleting the wrapper
433  delete m_robotResourceWrappers[resourceName];
434  m_robotResourceWrappers[resourceName] = NULL;
435  }
436  break;
437  }
438  }
439 }
440 
441 Box2DWrapper* Arena::createPlane(ConfigurationParameters& params, QString prefix, real z, Arena* arena)
442 {
443  // First of all getting the dimensions of the plane
444  const real planeWidth = ConfigurationHelper::getDouble(params, prefix + "planeWidth", 2.5);
445  const real planeHeight = ConfigurationHelper::getDouble(params, prefix + "planeHeight", 2.5);
446 
447  // Also getting the world from the ResourcesUser associated with param
448  SimpleResourcesUser* r = params.getResourcesUserForResource("world");
449  if (r == NULL) {
450  ConfigurationHelper::throwUserMissingResourceError("world", "We need a world to create the arena!");
451  }
452  World* world = r->getResource<World>("world");
453 
454  PhyBox* plane = new PhyBox(planeWidth, planeHeight, planeThickness, world, "plane");
455  plane->setPosition(wVector(0.0, 0.0, z - (planeThickness / 2.0)));
456  plane->setStatic(true);
457  plane->setColor(Qt::white);
458  plane->setTexture("tile2");
459 
460  // Also resizing world to comfortably fit the arena if it is smaller than necessary
461  wVector worldMinPoint;
462  wVector worldMaxPoint;
463  world->size(worldMinPoint, worldMaxPoint);
464  worldMinPoint.x = min(-planeWidth, worldMinPoint.x);
465  worldMinPoint.y = min(-planeHeight, worldMinPoint.y);
466  worldMinPoint.z = min(-planeThickness, worldMinPoint.z);
467  worldMaxPoint.x = max(planeWidth, worldMaxPoint.x);
468  worldMaxPoint.y = max(planeHeight, worldMaxPoint.y);
469  worldMaxPoint.z = max(+6.0, worldMaxPoint.z);
470  world->setSize(worldMinPoint, worldMaxPoint);
471 
472  // Creating the wrapper and returning it
473  Box2DWrapper* ret = new Box2DWrapper(arena, plane, Box2DWrapper::Plane);
474  arena->m_objects2DList.append( ret );
475  return ret;
476 }
477 
478 Box2DWrapper* Arena::createPlane2(ConfigurationParameters& params, QString prefix, real z, Arena* arena)
479 {
480  // First of all getting the dimensions of the plane
481  const real planeWidth = ConfigurationHelper::getDouble(params, prefix + "planeWidth", 2.5);
482  const real planeHeight = ConfigurationHelper::getDouble(params, prefix + "planeHeight", 2.5);
483 
484  // the plane will be composed by many boxes near to each other
485  const real cellSize = 0.20f;
486  int numCellW = ceil(planeWidth/cellSize);
487  int numCellH = ceil(planeHeight/cellSize);
488 
489  // Also getting the world from the ResourcesUser associated with param
490  SimpleResourcesUser* r = params.getResourcesUserForResource("world");
491  if (r == NULL) {
492  ConfigurationHelper::throwUserMissingResourceError("world", "We need a world to create the arena!");
493  }
494  World* world = r->getResource<World>("world");
495 
496  Box2DWrapper* ret;
497  for( int w=0; w<numCellW; w++ ) {
498  for( int h=0; h<numCellH; h++ ) {
499  PhyBox* plane = new PhyBox(cellSize, cellSize, planeThickness, world, "cellPlane");
500  plane->setPosition(wVector( w*cellSize-planeWidth/2.0, h*cellSize-planeHeight/2.0, z - (planeThickness / 2.0)));
501  plane->setStatic(true);
502  plane->setColor(Qt::white);
503  plane->setTexture("tile2");
504 
505  if ( w==0 || h==0 ) {
506  // use this as reference for plane
507  ret = new Box2DWrapper(arena, plane, Box2DWrapper::Plane);
508  }
509  // Appending the wrapper to the list
510  arena->m_objects2DList.append( new Box2DWrapper(arena, plane, Box2DWrapper::Plane) );
511  }
512  }
513  // Also resizing world to comfortably fit the arena
514  world->setSize(wVector(-planeWidth, -planeHeight, -planeThickness), wVector(planeWidth, planeHeight, +6.0));
515  // return one of the cell created as reference for the plane
516  return ret;
517 }
518 
519 } // end namespace farsa