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 #ifdef __GNUC__
34  #warning QUESTE COSTANTI DEVONO PROBABILMENTE DIVENTARE DEI PARAMETRI
35 #endif
36 namespace {
42  const real defaultHeight = 0.3f;
43 
49  const real planeThickness = 0.1;
50 
56  const real smallCylinderRadius = 0.03;
57 
63  const real bigCylinderRadius = 0.06;
64 
70  const real targetAreasHeight = 0.001;
71 
77  const real targetAreasProtrusion = 0.0005;
78 }
79 
80 Arena::Arena(ConfigurationParameters& params, QString prefix) :
81  ParameterSettableInConstructor(params, prefix),
83  m_z(ConfigurationHelper::getDouble(params, prefix + "z", 0.0)),
84  m_objects2DList(QVector<PhyObject2DWrapper*>()),
85  m_plane(createPlane(params, prefix, m_z, this)),
86  m_robotResourceWrappers(),
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("planeWidth").def(2.5).limits(0.01, +Infinity).help("The width of the arena");
115  d.describeReal("planeHeight").def(0.0).limits(0.01, +Infinity).help("The height of the arena");
116 }
117 
118 void Arena::addRobots(QStringList robots)
119 {
120  // First of all adding robots to the list of usable resources. This will call the resourceChanged callback
121  // because robots already exist, however they are not in the m_robotResourceWrappers map so the callback will
122  // do nothing (we take care of creating the wrappers here)
123  addUsableResources(robots);
124 
125  // Now taking the lock and generating the wrappers for all robots
126  ResourcesLocker locker(this);
127 
128  foreach(QString robot, robots) {
129  bool robotExists;
130  RobotOnPlane* r = getResource<RobotOnPlane>(robot, &robotExists);
131 
132  // The first thing to do is to check if the robot existed: in that case we have to first delete
133  // the old wrapper
134  if (m_robotResourceWrappers.contains(robot)) {
135  // This will probably crash if the robot is not in the list (this would be a bug anyway)
136  m_objects2DList.remove(m_objects2DList.indexOf(m_robotResourceWrappers[robot]));
137 
138  // Deleting the wrapper
139  delete m_robotResourceWrappers[robot];
140  }
141 
142  // If the robot actually exists, adding a wrapper, otherwise we simply add the robot to the map to
143  // remember the association
144  if (robotExists) {
145  // Creating the wrapper
146  WheeledRobot2DWrapper* wrapper = new WheeledRobot2DWrapper(this, r, r->robotHeight(), r->robotRadius());
147 
148  // Adding the wrapper to the list
149  m_objects2DList.push_back(wrapper);
150  m_robotResourceWrappers.insert(robot, wrapper);
151  } else {
152  // Adding the robot to the map with a NULL wrapper
153  m_robotResourceWrappers.insert(robot, NULL);
154  }
155  }
156 }
157 
158 const WheeledRobot2DWrapper* Arena::getRobotWrapper(QString robotName) const
159 {
160  if (m_robotResourceWrappers.contains(robotName)) {
161  return m_robotResourceWrappers[robotName];
162  } else {
163  return NULL;
164  }
165 }
166 
168 {
169  return m_plane;
170 }
171 
172 Box2DWrapper* Arena::createWall(QColor color, wVector start, wVector end, real thickness, real height)
173 {
174  ResourcesLocker locker(this);
175 
176  // Changing parameters
177  start.z = m_z;
178  end.z = m_z;
179 
180  // Creating the box, then we have to change its position and make it static
181  Box2DWrapper* b = createBox(color, (end - start).norm(), thickness, height, Box2DWrapper::Wall);
182 
183  // Moving the wall and setting the texture. We have to compute the rotation around the Z axis of the
184  // wall and the position of its center to build its transformation matrix
185  const real angle = atan2(end.y - start.y, end.x - start.x);
186  const wVector centerPosition = start + (end - start).scale(0.5);
187  wMatrix mtr = wMatrix::roll(angle);
188  mtr.w_pos = centerPosition + wVector(0.0, 0.0, b->phyObject()->matrix().w_pos.z);
189  b->phyObject()->setMatrix(mtr);
190  b->setTexture("tile2");
191 
192  return b;
193 }
194 
196 {
197  return createCylinder(color, smallCylinderRadius, height, Cylinder2DWrapper::SmallCylinder);
198 }
199 
200 Cylinder2DWrapper* Arena::createBigCylinder(QColor color, real height)
201 {
202  return createCylinder(color, bigCylinderRadius, height, Cylinder2DWrapper::BigCylinder);
203 }
204 
206 {
207  ResourcesLocker locker(this);
208 
209  // Creating the cylinder, then we only have to change its z position and set the material to nonCollidable
210  Cylinder2DWrapper* c = createCylinder(color, radius, targetAreasHeight, Cylinder2DWrapper::CircularTargetArea);
211 
212  wMatrix mtr = c->phyObject()->matrix();
213  mtr.w_pos = wVector(0.0, 0.0, m_z - (targetAreasHeight / 2.0) + targetAreasProtrusion);
214  c->phyObject()->setMatrix(mtr);
215  c->phyObject()->setMaterial("nonCollidable");
216 
217  return c;
218 }
219 
220 Box2DWrapper* Arena::createRectangularTargetArea(real width, real depth, QColor color)
221 {
222  ResourcesLocker locker(this);
223 
224  // Creating the box, then we only have to change its z position and set the material to nonCollidable
225  Box2DWrapper* b = createBox(color, width, depth, targetAreasHeight, Box2DWrapper::RectangularTargetArea);
226 
227  wMatrix mtr = b->phyObject()->matrix();
228  mtr.w_pos = wVector(0.0, 0.0, m_z - (targetAreasHeight / 2.0) + targetAreasProtrusion);
229  b->phyObject()->setMatrix(mtr);
230  b->phyObject()->setMaterial("nonCollidable");
231 
232  return b;
233 }
234 
236 {
237  int index = m_objects2DList.indexOf(obj);
238 
239  if ((index == -1) || (dynamic_cast<farsa::WheeledRobot2DWrapper*>(obj) != NULL)) {
240  return false;
241  }
242 
243  // Deleting the object
244  delete m_objects2DList[index];
245  m_objects2DList.remove(index);
246 
247  return true;
248 }
249 
251 {
252  ResourcesLocker locker(this);
253 
254  // Cycling through the list of robots and storing the matrix
255  foreach (WheeledRobot2DWrapper* robot, m_robotResourceWrappers) {
256  // We only act on kinematic robots
257  if (!robot->robotOnPlane()->isKinematic()) {
258  continue;
259  }
260  robot->storePreviousMatrix();
261  }
262 }
263 
265 {
266  ResourcesLocker locker(this);
267 
268  // Here we use a naive approach: we check collision of every object with every robot. This is not
269  // the best possible algorithm (complexity is O(n^2)...), but it's the way things are done in evorobot
270 
271  // Cycling through the list of robots
272  foreach (WheeledRobot2DWrapper* robot, m_robotResourceWrappers) {
273  // We only act on kinematic robots
274  if (!robot->robotOnPlane()->isKinematic()) {
275  continue;
276  }
277 
278  bool collides = false;
279  // Checking if the robot collides
280  foreach (PhyObject2DWrapper* obj, m_objects2DList) {
281  // Not checking the collision of the robot with itself (obviously...)
282  if (obj == robot) {
283  continue;
284  }
285 
286  double distance;
287  double angle;
288 
289  // Getting the distance between the robot and the object. If computeDistanceAndOrientationFromRobot
290  // returns false, we have to discard the object
291  if (!obj->computeDistanceAndOrientationFromRobot(*robot, distance, angle)) {
292  continue;
293  }
294 
295  // If the distance is negative, we had a collision
296  if (distance < 0.0) {
297  collides = true;
298  break;
299  }
300  }
301 
302  // If the robot collides with something else, moving it back to its previous position
303  if (collides) {
304  robot->wObject()->setMatrix(robot->previousMatrix());
305  }
306  }
307 }
308 
309 Cylinder2DWrapper* Arena::createCylinder(QColor color, real radius, real height, Cylinder2DWrapper::Type type)
310 {
311  ResourcesLocker locker(this);
312 
313  // Changing parameters
314  if (height < 0.0) {
315  height = defaultHeight;
316  }
317 
318  PhyCylinder* cylinder = new PhyCylinder(radius, height, m_world, "cylinder");
319  wMatrix mtr = wMatrix::yaw(PI_GRECO / 2.0);
320  mtr.w_pos = wVector(0.0, 0.0, m_z + (height / 2.0));
321  cylinder->setMatrix(mtr);
322  cylinder->setUseColorTextureOfOwner(false);
323  cylinder->setColor(color);
324  cylinder->setTexture("");
325 
326  // Creating the wrapper
327  Cylinder2DWrapper* wrapper = new Cylinder2DWrapper(this, cylinder, type);
328 
329  // Appending the wrapper to the list
330  m_objects2DList.append(wrapper);
331 
332  return wrapper;
333 }
334 
335 Box2DWrapper* Arena::createBox(QColor color, real width, real depth, real height, Box2DWrapper::Type type)
336 {
337  ResourcesLocker locker(this);
338 
339  // Changing parameters
340  if (height < 0.0) {
341  height = defaultHeight;
342  }
343 
344  PhyBox* box = new PhyBox(width, depth, height, m_world, "box");
345 
346  // Moving the box so that it lies on the plane
347  wMatrix mtr = wMatrix::identity();
348  mtr.w_pos = wVector(0.0, 0.0, m_z + (height / 2.0));
349  box->setMatrix(mtr);
350  box->setUseColorTextureOfOwner(false);
351  box->setColor(color);
352  box->setTexture("");
353 
354  // Creating the wrapper
355  Box2DWrapper* wrapper = new Box2DWrapper(this, box, type);
356 
357  // Appending the wrapper to the list
358  m_objects2DList.append(wrapper);
359 
360  return wrapper;
361 }
362 
363 void Arena::resourceChanged(QString resourceName, ResourceChangeType changeType)
364 {
365  if (resourceName == "world") {
366  switch (changeType) {
367  case Created:
368  case Modified:
369  m_world = getResource<World>();
370  break;
371  case Deleted:
372  m_world = NULL;
373  break;
374  }
375  } else if (m_robotResourceWrappers.contains(resourceName)) {
376  // The resource for a robot has changed status
377  switch (changeType) {
378  case Created:
379  case Modified:
380  // We have to delete the previous wrapper and add a new one
381  {
382  // Deleting the old wrapper if it exists
383  if (m_robotResourceWrappers[resourceName] != NULL) {
384  m_objects2DList.remove(m_objects2DList.indexOf(m_robotResourceWrappers[resourceName]));
385 
386  // Deleting the wrapper
387  delete m_robotResourceWrappers[resourceName];
388  }
389 
390  // Creating a new wrapper
391  RobotOnPlane* r = getResource<RobotOnPlane>();
392  WheeledRobot2DWrapper* wrapper = new WheeledRobot2DWrapper(this, r, r->robotHeight(), r->robotRadius());
393 
394  // Adding the wrapper to the list
395  m_objects2DList.push_back(wrapper);
396  m_robotResourceWrappers.insert(resourceName, wrapper);
397  }
398  break;
399  case Deleted:
400  // Simply removing the old wrapper
401  {
402  m_objects2DList.remove(m_objects2DList.indexOf(m_robotResourceWrappers[resourceName]));
403 
404  // Deleting the wrapper
405  delete m_robotResourceWrappers[resourceName];
406  m_robotResourceWrappers[resourceName] = NULL;
407  }
408  break;
409  }
410  }
411 }
412 
413 Box2DWrapper* Arena::createPlane(ConfigurationParameters& params, QString prefix, real z, Arena* arena)
414 {
415  // First of all getting the dimensions of the plane
416  const real planeWidth = ConfigurationHelper::getDouble(params, prefix + "planeWidth", 2.5);
417  const real planeHeight = ConfigurationHelper::getDouble(params, prefix + "planeHeight", 2.5);
418 
419  // Also getting the world from the ResourcesUser associated with param
420  SimpleResourcesUser* r = params.getResourcesUserForResource("world");
421  if (r == NULL) {
422  ConfigurationHelper::throwUserMissingResourceError("world", "We need a world to create the arena!");
423  }
424  World* world = r->getResource<World>("world");
425 
426  PhyBox* plane = new PhyBox(planeWidth, planeHeight, planeThickness, world, "plane");
427  plane->setPosition(wVector(0.0, 0.0, z - (planeThickness / 2.0)));
428  plane->setStatic(true);
429  plane->setColor(Qt::white);
430  plane->setTexture("tile2");
431 
432  // Also resizing world to comfortably fit the arena
433  world->setSize(wVector(-planeWidth, -planeHeight, -planeThickness), wVector(planeWidth, planeHeight, +6.0));
434 
435  // Creating the wrapper and returning it
436  Box2DWrapper* ret = new Box2DWrapper(arena, plane, Box2DWrapper::Plane);
437  arena->m_objects2DList.append( ret );
438  return ret;
439 }
440 
441 Box2DWrapper* Arena::createPlane2(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  // the plane will be composed by many boxes near to each other
448  const real cellSize = 0.20f;
449  int numCellW = ceil(planeWidth/cellSize);
450  int numCellH = ceil(planeHeight/cellSize);
451 
452  // Also getting the world from the ResourcesUser associated with param
453  SimpleResourcesUser* r = params.getResourcesUserForResource("world");
454  if (r == NULL) {
455  ConfigurationHelper::throwUserMissingResourceError("world", "We need a world to create the arena!");
456  }
457  World* world = r->getResource<World>("world");
458 
459  Box2DWrapper* ret;
460  for( int w=0; w<numCellW; w++ ) {
461  for( int h=0; h<numCellH; h++ ) {
462  PhyBox* plane = new PhyBox(cellSize, cellSize, planeThickness, world, "cellPlane");
463  plane->setPosition(wVector( w*cellSize-planeWidth/2.0, h*cellSize-planeHeight/2.0, z - (planeThickness / 2.0)));
464  plane->setStatic(true);
465  plane->setColor(Qt::white);
466  plane->setTexture("tile2");
467 
468  if ( w==0 || h==0 ) {
469  // use this as reference for plane
470  ret = new Box2DWrapper(arena, plane, Box2DWrapper::Plane);
471  }
472  // Appending the wrapper to the list
473  arena->m_objects2DList.append( new Box2DWrapper(arena, plane, Box2DWrapper::Plane) );
474  }
475  }
476  // Also resizing world to comfortably fit the arena
477  world->setSize(wVector(-planeWidth, -planeHeight, -planeThickness), wVector(planeWidth, planeHeight, +6.0));
478  // return one of the cell created as reference for the plane
479  return ret;
480 }
481 
482 } // end namespace farsa