wheeledexperimenthelper.cpp
1 /********************************************************************************
2  * FARSA Experiments Library *
3  * Copyright (C) 2007-2012 *
4  * Gianluca Massera <emmegian@yahoo.it> *
5  * Stefano Nolfi <stefano.nolfi@istc.cnr.it> *
6  * Tomassino Ferrauto <tomassino.ferrauto@istc.cnr.it> *
7  * Onofrio Gigliotta <onofrio.gigliotta@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 "wheeledexperimenthelper.h"
25 #include "logger.h"
26 #include "phybox.h"
27 #include "phycylinder.h"
28 #include "robots.h"
29 #include "arena.h"
30 
31 namespace farsa {
32 
33 // This anonymous namespace contains helper functions used in this file
34 namespace {
47  wVector computeWallVertex(PhyBox* wall, unsigned int vertexID)
48  {
49  const wVector centerOnPlane = wall->matrix().w_pos - wall->matrix().z_ax.scale(wall->sideZ() / 2.0);
50  const wVector halfSide1Vector = wall->matrix().x_ax.scale(wall->sideX() / 2.0);
51  const wVector halfSide2Vector = wall->matrix().y_ax.scale(wall->sideY() / 2.0);
52 
53  wVector vertex;
54  switch(vertexID % 4) {
55  case 0:
56  vertex = centerOnPlane + halfSide1Vector + halfSide2Vector;
57  break;
58  case 1:
59  vertex = centerOnPlane + halfSide1Vector - halfSide2Vector;
60  break;
61  case 2:
62  vertex = centerOnPlane - halfSide1Vector + halfSide2Vector;
63  break;
64  case 3:
65  vertex = centerOnPlane - halfSide1Vector - halfSide2Vector;
66  break;
67  default:
68  break;
69  }
70 
71  return vertex;
72  }
73 
74 
84  double getAngleWithXAxis(const wMatrix& mtr, const wVector& v)
85  {
86  // Normalizing v
87  const wVector vdir = v.scale(1.0 / v.norm());
88 
89  // To get the angle (unsigned), computing the acos of the dot product of the two vectors
90  const double unsignedAngle = acos(mtr.x_ax % vdir);
91 
92  // Now choosing the right sign. To do this we first compute the cross product of the x axis and
93  // the vertex direction, then we see if it has the same direction of Z or not
94  const double s = mtr.z_ax % (mtr.x_ax * vdir);
95  return (s < 0.0) ? -unsignedAngle : unsignedAngle;
96  }
97 
126  void computeLinearViewFieldOccupiedRangeForCylinder(const wMatrix& cameraMtr, const wMatrix& objMtr, double height, double radius, double& minAngle, double& maxAngle, double& distance)
127  {
128  // The center of the lower base of the cylinder. The local x axis points towards the ground (cylinders
129  // are created this way by the arena...)
130  const wVector baseCenter = objMtr.w_pos + objMtr.x_ax.scale(height / 2.0);
131 
132  // We have to translate the camera to lie on the same plane of the cylinder base. We translate it along
133  // its local upvector (Z axis) until it reaches the plane containing the base of the cylinder. Of course
134  // this only works if the camera Z axis is not paraller to the plane with the base of the cylinder. In
135  // that case all computations would be invalid, so we don't do anything
136  wMatrix mtr = cameraMtr;
137  if (fabs(mtr.z_ax % objMtr.x_ax) < 0.0001) {
138  distance = -1.0;
139  return;
140  }
141  mtr.w_pos = mtr.w_pos + mtr.z_ax.scale((baseCenter.z - mtr.w_pos.z) / mtr.z_ax.z);
142 
143  // First of all we have to calculate the angle between the vector from the center of the
144  // camera to the center of the cylinder and the vector from the center of the camera and
145  // tangent to the circumference. We know that the tangent is always perpendicular to the
146  // radius to the contact point, so we can simply take the asin(radius/distance to center
147  // of cylinder)
148  const wVector centerDir = baseCenter - mtr.w_pos;
149  distance = centerDir.norm();
150  const double deltaAngle = asin(radius / distance);
151 
152  // Now computing the angle of the cylinder center
153  const double centerAngle = getAngleWithXAxis(mtr, centerDir);
154 
155  // Finally we can compute the min and max angle
156  minAngle = centerAngle - deltaAngle;
157  if (minAngle < -PI_GRECO) {
158  minAngle += 2.0 * PI_GRECO;
159  }
160  maxAngle = centerAngle + deltaAngle;
161  if (maxAngle > PI_GRECO) {
162  maxAngle -= 2.0 * PI_GRECO;
163  }
164  }
165 
184  void computeDistanceAndOrientationFromRobotToCylinder(const wVector& robotPosition, double robotOrientation, double robotRadius, double radius, const wVector& position, double& distance, double& angle)
185  {
186  // Computing the distance. We have to remove both the robot radius and the object radius
187  distance = (position - robotPosition).norm() - robotRadius - radius;
188 
189  // Now computing the angle between the robot and the object
190  angle = atan2(position.y - robotPosition.y, position.x - robotPosition.x) - robotOrientation;
191  }
192 }
193 
195  m_arena(arena)
196 {
197  // Nothing to do here
198 }
199 
201 {
202  // Nothing to do here
203 }
204 
206 {
207  return phyObject();
208 }
209 
211 {
212  return phyObject();
213 }
214 
216 {
217  if (phyObject() != NULL) {
218  phyObject()->setStatic(s);
219  }
220 }
221 
223 {
224  if (phyObject() != NULL) {
225  return phyObject()->getStatic();
226  } else {
227  return true;
228  }
229 }
230 
232 {
233  setPosition(pos.x, pos.y);
234 }
235 
237 {
238  return wObject()->matrix().w_pos;
239 }
240 
241 void PhyObject2DWrapper::setTexture(QString textureName)
242 {
243  wObject()->setTexture(textureName);
244 }
245 
247 {
248  return wObject()->texture();
249 }
250 
252 {
253  wObject()->setColor(color);
254 }
255 
257 {
258  return wObject()->color();
259 }
260 
262 {
264 }
265 
267 {
268  return wObject()->useColorTextureOfOwner();
269 }
270 
271 Box2DWrapper::Box2DWrapper(Arena* arena, PhyBox* box, Type type) :
272  PhyObject2DWrapper(arena),
273  m_box(box),
274  m_vertexes(QVector<wVector>() << computeWallVertex(m_box, 0) << computeWallVertex(m_box, 1) << computeWallVertex(m_box, 2) << computeWallVertex(m_box, 3)),
275  m_centerOnPlane(m_box->matrix().w_pos - m_box->matrix().z_ax.scale(m_box->sideZ() / 2.0)),
276  m_type(((type != Plane) && (type != Wall) && (type != RectangularTargetArea)) ? Box : type)
277 {
278  if (m_type != Box) {
279  m_box->setStatic(true);
280  }
281 }
282 
284 {
285  // Nothing to do here
286 }
287 
289 {
290  return m_box;
291 }
292 
294 {
295  return m_box;
296 }
297 
299 {
300  return m_type;
301 }
302 
304 {
305  // Only Boxes can be made non-static
306  if (m_type != Box) {
307  return;
308  }
309 
310  phyObject()->setStatic(s);
311 }
312 
313 void Box2DWrapper::setPosition(real x, real y)
314 {
315  // Planes and Walls cannot be moved
316  if ((m_type == Plane) || (m_type == Wall)) {
317  return;
318  }
319 
320  wVector pos = phyObject()->matrix().w_pos;
321 
322  pos.x = x;
323  pos.y = y;
324 
325  phyObject()->setPosition(pos);
326 
327  // We also have to recompute the vertexes and center
328  for (unsigned int i = 0; i < 4; i++) {
329  m_vertexes[i] = computeWallVertex(m_box, i);
330  }
331  m_centerOnPlane = m_box->matrix().w_pos - m_box->matrix().z_ax.scale(m_box->sideZ() / 2.0);
332 }
333 
334 void Box2DWrapper::computeLinearViewFieldOccupiedRange(const wMatrix& cameraMtr, double& minAngle, double& maxAngle, double& distance) const
335 {
336  // If this is a Plane or a RectangularTargetArea, we simply return a negative distance (they are not visible with
337  // a linear camera)
338  if ((m_type == Plane) || (m_type == RectangularTargetArea)) {
339  distance = -1.0;
340  return;
341  }
342 
343  // We have to translate the camera to lie on the same plane of the vertex. We translate it along
344  // its local upvector (Z axis) until it reaches the plane containing the base of the wall. Of course
345  // this only works if the camera Z axis is not paraller to the plane with the base of the wall. In
346  // that case all computations would be invalid, so we don't do anything
347  wMatrix mtr = cameraMtr;
348  if (fabs(mtr.z_ax % m_box->matrix().z_ax) < 0.0001) {
349  distance = -1.0;
350  return;
351  }
352  mtr.w_pos = mtr.w_pos + mtr.z_ax.scale((m_centerOnPlane.z - mtr.w_pos.z) / mtr.z_ax.z);
353 
354  // First of all computing the angle for every vertex
355  QVector<double> angles(4);
356 
357  for (int i = 0; i < 4; i++) {
358  // Computing the vector giving the direction to the vertex
359  const wVector vdir = m_vertexes[i] - mtr.w_pos;
360 
361  // Now computing the angle
362  angles[i] = getAngleWithXAxis(mtr, vdir);
363  }
364 
365  // Now finding the min and max angle (their indexes). We have to take into account the fact that the
366  // angle with the minimum value could be the upper limit and viceversa because the object could be
367  // behind the camera. However we know that, as the camera is outside the wall, the maximum possible
368  // angular sector of the view filed occupied by the wall is 180°. This means that also the angular
369  // distance of one vertex with the center of the wall must be less than 180°. So, if we compute this
370  // distance and get a value greater than 180°, we have to take (360° - computed_angular_distance)
371  // and invert min with max.
372  const wVector centerDir = m_centerOnPlane - mtr.w_pos;
373  const double centerAngle = getAngleWithXAxis(mtr, centerDir);
374  int minAngleID = 0;
375  int maxAngleID = 0;
376 
377  // These two are the angular distances of the current min and max angles from the center. Their initial
378  // value is the lowest possible
379  double minDelta = 0.0;
380  double maxDelta = 0.0;
381 
382  for (int i = 0; i < 4; i++) {
383  const double curDelta = fabs(angles[i] - centerAngle);
384 
385  // Checking if the vertex and the center are behind the camera
386  if (curDelta > PI_GRECO) {
387  const double actualDelta = (2.0 * PI_GRECO) - curDelta;
388  if (angles[i] > centerAngle) {
389  // This is a candidate minimum angle
390  if (actualDelta > minDelta) {
391  minAngleID = i;
392  minDelta = actualDelta;
393  }
394  } else {
395  // This is a candidate maximum angle
396  if (actualDelta > maxDelta) {
397  maxAngleID = i;
398  maxDelta = actualDelta;
399  }
400  }
401  } else {
402  if (angles[i] < centerAngle) {
403  // This is a candidate minimum angle
404  if (curDelta > minDelta) {
405  minAngleID = i;
406  minDelta = curDelta;
407  }
408  } else {
409  // This is a candidate maximum angle
410  if (curDelta > maxDelta) {
411  maxAngleID = i;
412  maxDelta = curDelta;
413  }
414  }
415  }
416  }
417 
418  // Filling the minAngle and maxAngle parameters
419  minAngle = angles[minAngleID];
420  maxAngle = angles[maxAngleID];
421 
422 #ifdef __GNUC__
423  #warning QUESTO MODO DI CALCOLARE LA DISTANZA È SBAGLIATO (E NON NE CAPISCO IL SENSO), MA È QUELLO USATO IN EVOROBOT, QUINDI PER IL MOMENTO LO USO (ANCHE PERCHÉ USARE LA DISTANZA PER L OCCLUSIONE NON VA BENE COMUNQUE)
424 #endif
425  // Now computing distance. This way of calculating the distance is plainly wrong (and I can't
426  // see why it is written this way), but it is the method used by Evorobot, so for the moment
427  // using it (moreover using distance for occlusion is not correct)
428  distance = ((mtr.w_pos - m_vertexes[minAngleID]).norm() + (mtr.w_pos - m_vertexes[maxAngleID]).norm()) / 2.0;
429 }
430 
431 bool Box2DWrapper::computeDistanceAndOrientationFromRobot(const WheeledRobot2DWrapper& robot, double& distance, double& angle) const
432 {
433 #ifdef __GNUC__
434  #warning I CALCOLI PER DISTANZA E ORIENTAMENTO IN EVOROBOT SONO A DIR POCO FANTASIOSI, QUI HO CERCATO DI FARE QUALCOSA DI PIÙ SENSATO...
435 #endif
436  // Only doing computations for walls and boxes
437  if ((m_type != Wall) && (m_type != Box)) {
438  return false;
439  }
440 
441  // Taking the robot position and setting z to lie on the same plane of the vertexes
442  const wVector robotPosition(robot.position().x, robot.position().y, m_vertexes[0].z);
443 
444  // Now computing the robot position in the box frame of reference
445  const wVector relRobotPosition = m_box->matrix().untransformVector(robotPosition);
446 
447  // Now we can find the point in the rectangle that is nearest to the robot position. As we work in the box
448  // frame of reference, the vertex are easy to compute. They are (discarding z):
449  // (+m_box->sideX() / 2.0, +m_box->sideY() / 2.0)
450  // (+m_box->sideX() / 2.0, -m_box->sideY() / 2.0)
451  // (-m_box->sideX() / 2.0, +m_box->sideY() / 2.0)
452  // (-m_box->sideX() / 2.0, -m_box->sideY() / 2.0)
453  // Finding the nearest point is just a matter of separately computing x and y. Note that if the robot is inside
454  // the box, the distance will be 0.0 and will become negative once we subtract the robot radius
455  real nearestX;
456  if (relRobotPosition.x < -m_box->sideX() / 2.0) {
457  nearestX = -m_box->sideX() / 2.0;
458  } else if (relRobotPosition.x > +m_box->sideX() / 2.0) {
459  nearestX = +m_box->sideX() / 2.0;
460  } else {
461  nearestX = relRobotPosition.x;
462  }
463  real nearestY;
464  if (relRobotPosition.y < -m_box->sideY() / 2.0) {
465  nearestY = -m_box->sideY() / 2.0;
466  } else if (relRobotPosition.y > +m_box->sideY() / 2.0) {
467  nearestY = +m_box->sideY() / 2.0;
468  } else {
469  nearestY = relRobotPosition.y;
470  }
471 
472  // Although distance is independent of the frame of reference, we convert the nearest point to the global frame
473  // of reference because we only have the robot orientation in that frame
474  const wVector nearestPoint = m_box->matrix().transformVector(wVector(nearestX, nearestY, relRobotPosition.z));
475 
476  // Now we can easily compute the distance and orientation. For the distance we have to remove the robot radius
477  distance = (nearestPoint - robotPosition).norm() - robot.getRadius();
478  const real robotOrientation = (dynamic_cast<const RobotOnPlane*>(robot.wObject()))->orientation(m_arena->getPlane());
479  angle = atan2(nearestPoint.y - robotPosition.y, nearestPoint.x - robotPosition.x) - robotOrientation;
480 
481  return true;
482 }
483 
485  PhyObject2DWrapper(arena),
486  m_cylinder(cylinder),
487  m_type(((type != SmallCylinder) && (type != BigCylinder) && (type != CircularTargetArea)) ? Cylinder : type)
488 {
489  if (m_type == CircularTargetArea) {
490  m_cylinder->setStatic(true);
491  }
492 }
493 
495 {
496  // Nothing to do here
497 }
498 
500 {
501  return m_cylinder;
502 }
503 
505 {
506  return m_cylinder;
507 }
508 
510 {
511  // CircularTargetArea cannot be made non-static
512  if (m_type == CircularTargetArea) {
513  return;
514  }
515 
516  phyObject()->setStatic(s);
517 }
518 
520 {
521  return m_type;
522 }
523 
524 void Cylinder2DWrapper::setPosition(real x, real y)
525 {
526  wVector pos = phyObject()->matrix().w_pos;
527 
528  pos.x = x;
529  pos.y = y;
530 
531  phyObject()->setPosition(pos);
532 }
533 
534 void Cylinder2DWrapper::computeLinearViewFieldOccupiedRange(const wMatrix& cameraMtr, double& minAngle, double& maxAngle, double& distance) const
535 {
536  // If this is a CircularTargetArea, we simply return a negative distance (it is not visible with
537  // a linear camera)
538  if (m_type == CircularTargetArea) {
539  distance = -1.0;
540  return;
541  }
542 
543  computeLinearViewFieldOccupiedRangeForCylinder(cameraMtr, m_cylinder->matrix(), m_cylinder->height(), m_cylinder->radius(), minAngle, maxAngle, distance);
544 }
545 
546 bool Cylinder2DWrapper::computeDistanceAndOrientationFromRobot(const WheeledRobot2DWrapper& robot, double& distance, double& angle) const
547 {
548  // If this is a CircularTargetArea, we simply return a negative distance
549  if (m_type == CircularTargetArea) {
550  return false;
551  }
552 
553  const double robotOrientation = (dynamic_cast<const RobotOnPlane*>(robot.wObject()))->orientation(m_arena->getPlane());
554  computeDistanceAndOrientationFromRobotToCylinder(robot.position(), robotOrientation, robot.getRadius(), m_cylinder->radius(), m_cylinder->matrix().w_pos, distance, angle);
555 
556  return true;
557 }
558 
559 WheeledRobot2DWrapper::WheeledRobot2DWrapper(Arena* arena, RobotOnPlane* robot, double height, double radius) :
560  PhyObject2DWrapper(arena),
561  m_robot(robot),
562  m_height(height),
563  m_radius(radius),
564  m_previousMatrix((dynamic_cast<WObject*>(robot))->matrix())
565 {
566 }
567 
569 {
570 }
571 
573 {
574  return m_robot;
575 }
576 
578 {
579  return m_robot;
580 }
581 
583 {
584  return dynamic_cast<WObject*>(m_robot);
585 }
586 
588 {
589  return dynamic_cast<const WObject*>(m_robot);
590 }
591 
593 {
594  return NULL;
595 }
596 
598 {
599  return NULL;
600 }
601 
603 {
604  return WheeledRobot;
605 }
606 
608 {
609  wVector pos = wObject()->matrix().w_pos;
610 
611  pos.x = x;
612  pos.y = y;
613 
614  wObject()->setPosition(pos);
615 }
616 
617 void WheeledRobot2DWrapper::computeLinearViewFieldOccupiedRange(const wMatrix& cameraMtr, double& minAngle, double& maxAngle, double& distance) const
618 {
619  wMatrix mtr = wObject()->matrix();
620 
621  mtr.w_pos.z += m_height / 2.0;
622 
623  computeLinearViewFieldOccupiedRangeForCylinder(cameraMtr, mtr, m_height, m_radius, minAngle, maxAngle, distance);
624 }
625 
626 bool WheeledRobot2DWrapper::computeDistanceAndOrientationFromRobot(const WheeledRobot2DWrapper& robot, double& distance, double& angle) const
627 {
628  if (this == &robot) {
629  return false;
630  }
631 
632  const double robotOrientation = (dynamic_cast<const RobotOnPlane*>(robot.wObject()))->orientation(m_arena->getPlane());
633  computeDistanceAndOrientationFromRobotToCylinder(robot.position(), robotOrientation, robot.getRadius(), m_radius, position(), distance, angle);
634 
635  return true;
636 }
637 
638 } // end namespace farsa