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(wVector robotPosition, double robotOrientation, double robotRadius, double radius, wVector position, double& distance, double& angle)
185  {
186  // Setting to 0.0 the z coordinate of positions
187  robotPosition.z = 0.0;
188  position.z = 0.0;
189 
190  // Computing the distance. We have to remove both the robot radius and the object radius
191  distance = (position - robotPosition).norm() - robotRadius - radius;
192 
193  // Now computing the angle between the robot and the object
194  angle = atan2(position.y - robotPosition.y, position.x - robotPosition.x) - robotOrientation;
195  }
196 }
197 
199  m_arena(arena)
200 {
201  // Nothing to do here
202 }
203 
205 {
206  // Nothing to do here
207 }
208 
210 {
211  return phyObject();
212 }
213 
215 {
216  return phyObject();
217 }
218 
220 {
221  if (phyObject() != NULL) {
222  phyObject()->setStatic(s);
223  }
224 }
225 
227 {
228  if (phyObject() != NULL) {
229  return phyObject()->getStatic();
230  } else {
231  return true;
232  }
233 }
234 
236 {
237  setPosition(pos.x, pos.y);
238 }
239 
241 {
242  return wObject()->matrix().w_pos;
243 }
244 
245 void PhyObject2DWrapper::setTexture(QString textureName)
246 {
247  wObject()->setTexture(textureName);
248 }
249 
251 {
252  return wObject()->texture();
253 }
254 
256 {
257  wObject()->setColor(color);
258 }
259 
261 {
262  return wObject()->color();
263 }
264 
266 {
268 }
269 
271 {
272  return wObject()->useColorTextureOfOwner();
273 }
274 
275 Box2DWrapper::Box2DWrapper(Arena* arena, PhyBox* box, Type type) :
276  PhyObject2DWrapper(arena),
277  m_box(box),
278  m_vertexes(QVector<wVector>() << computeWallVertex(m_box, 0) << computeWallVertex(m_box, 1) << computeWallVertex(m_box, 2) << computeWallVertex(m_box, 3)),
279  m_centerOnPlane(m_box->matrix().w_pos - m_box->matrix().z_ax.scale(m_box->sideZ() / 2.0)),
280  m_type(((type != Plane) && (type != Wall) && (type != RectangularTargetArea)) ? Box : type)
281 {
282  if (m_type != Box) {
283  m_box->setStatic(true);
284  }
285 }
286 
288 {
289  // Nothing to do here
290 }
291 
293 {
294  return m_box;
295 }
296 
298 {
299  return m_box;
300 }
301 
303 {
304  return m_type;
305 }
306 
308 {
309  // Only Boxes can be made non-static
310  if (m_type != Box) {
311  return;
312  }
313 
314  phyObject()->setStatic(s);
315 }
316 
317 void Box2DWrapper::setPosition(real x, real y)
318 {
319  // Planes and Walls cannot be moved
320  if ((m_type == Plane) || (m_type == Wall)) {
321  return;
322  }
323 
324  wVector pos = phyObject()->matrix().w_pos;
325 
326  pos.x = x;
327  pos.y = y;
328 
329  phyObject()->setPosition(pos);
330 
331  // We also have to recompute the vertexes and center
332  for (unsigned int i = 0; i < 4; i++) {
333  m_vertexes[i] = computeWallVertex(m_box, i);
334  }
335  m_centerOnPlane = m_box->matrix().w_pos - m_box->matrix().z_ax.scale(m_box->sideZ() / 2.0);
336 }
337 
338 void Box2DWrapper::computeLinearViewFieldOccupiedRange(const wMatrix& cameraMtr, double& minAngle, double& maxAngle, double& distance) const
339 {
340  // If this is a Plane or a RectangularTargetArea, we simply return a negative distance (they are not visible with
341  // a linear camera)
342  if ((m_type == Plane) || (m_type == RectangularTargetArea)) {
343  distance = -1.0;
344  return;
345  }
346 
347  // We have to translate the camera to lie on the same plane of the vertex. We translate it along
348  // its local upvector (Z axis) until it reaches the plane containing the base of the wall. Of course
349  // this only works if the camera Z axis is not paraller to the plane with the base of the wall. In
350  // that case all computations would be invalid, so we don't do anything
351  wMatrix mtr = cameraMtr;
352  if (fabs(mtr.z_ax % m_box->matrix().z_ax) < 0.0001) {
353  distance = -1.0;
354  return;
355  }
356  mtr.w_pos = mtr.w_pos + mtr.z_ax.scale((m_centerOnPlane.z - mtr.w_pos.z) / mtr.z_ax.z);
357 
358  // First of all computing the angle for every vertex
359  QVector<double> angles(4);
360 
361  for (int i = 0; i < 4; i++) {
362  // Computing the vector giving the direction to the vertex
363  const wVector vdir = m_vertexes[i] - mtr.w_pos;
364 
365  // Now computing the angle
366  angles[i] = getAngleWithXAxis(mtr, vdir);
367  }
368 
369  // Now finding the min and max angle (their indexes). We have to take into account the fact that the
370  // angle with the minimum value could be the upper limit and viceversa because the object could be
371  // behind the camera. However we know that, as the camera is outside the wall, the maximum possible
372  // angular sector of the view filed occupied by the wall is 180°. This means that also the angular
373  // distance of one vertex with the center of the wall must be less than 180°. So, if we compute this
374  // distance and get a value greater than 180°, we have to take (360° - computed_angular_distance)
375  // and invert min with max.
376  const wVector centerDir = m_centerOnPlane - mtr.w_pos;
377  const double centerAngle = getAngleWithXAxis(mtr, centerDir);
378  int minAngleID = 0;
379  int maxAngleID = 0;
380 
381  // These two are the angular distances of the current min and max angles from the center. Their initial
382  // value is the lowest possible
383  double minDelta = 0.0;
384  double maxDelta = 0.0;
385 
386  for (int i = 0; i < 4; i++) {
387  const double curDelta = fabs(angles[i] - centerAngle);
388 
389  // Checking if the vertex and the center are behind the camera
390  if (curDelta > PI_GRECO) {
391  const double actualDelta = (2.0 * PI_GRECO) - curDelta;
392  if (angles[i] > centerAngle) {
393  // This is a candidate minimum angle
394  if (actualDelta > minDelta) {
395  minAngleID = i;
396  minDelta = actualDelta;
397  }
398  } else {
399  // This is a candidate maximum angle
400  if (actualDelta > maxDelta) {
401  maxAngleID = i;
402  maxDelta = actualDelta;
403  }
404  }
405  } else {
406  if (angles[i] < centerAngle) {
407  // This is a candidate minimum angle
408  if (curDelta > minDelta) {
409  minAngleID = i;
410  minDelta = curDelta;
411  }
412  } else {
413  // This is a candidate maximum angle
414  if (curDelta > maxDelta) {
415  maxAngleID = i;
416  maxDelta = curDelta;
417  }
418  }
419  }
420  }
421 
422  // Filling the minAngle and maxAngle parameters
423  minAngle = angles[minAngleID];
424  maxAngle = angles[maxAngleID];
425 
426 #ifdef __GNUC__
427  #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)
428 #endif
429  // Now computing distance. This way of calculating the distance is plainly wrong (and I can't
430  // see why it is written this way), but it is the method used by Evorobot, so for the moment
431  // using it (moreover using distance for occlusion is not correct)
432  distance = ((mtr.w_pos - m_vertexes[minAngleID]).norm() + (mtr.w_pos - m_vertexes[maxAngleID]).norm()) / 2.0;
433 }
434 
435 bool Box2DWrapper::computeDistanceAndOrientationFromRobot(const WheeledRobot2DWrapper& robot, double& distance, double& angle) const
436 {
437 #ifdef __GNUC__
438  #warning I CALCOLI PER DISTANZA E ORIENTAMENTO IN EVOROBOT SONO A DIR POCO FANTASIOSI, QUI HO CERCATO DI FARE QUALCOSA DI PIÙ SENSATO...
439 #endif
440  // Only doing computations for walls and boxes
441  if ((m_type != Wall) && (m_type != Box)) {
442  return false;
443  }
444 
445  // Taking the robot position and setting z to lie on the same plane of the vertexes
446  const wVector robotPosition(robot.position().x, robot.position().y, m_vertexes[0].z);
447 
448  // Now computing the robot position in the box frame of reference
449  const wVector relRobotPosition = m_box->matrix().untransformVector(robotPosition);
450 
451  // Now we can find the point in the rectangle that is nearest to the robot position. As we work in the box
452  // frame of reference, the vertex are easy to compute. They are (discarding z):
453  // (+m_box->sideX() / 2.0, +m_box->sideY() / 2.0)
454  // (+m_box->sideX() / 2.0, -m_box->sideY() / 2.0)
455  // (-m_box->sideX() / 2.0, +m_box->sideY() / 2.0)
456  // (-m_box->sideX() / 2.0, -m_box->sideY() / 2.0)
457  // Finding the nearest point is just a matter of separately computing x and y. Note that if the robot is inside
458  // the box, the distance will be 0.0 and will become negative once we subtract the robot radius
459  real nearestX;
460  if (relRobotPosition.x < -m_box->sideX() / 2.0) {
461  nearestX = -m_box->sideX() / 2.0;
462  } else if (relRobotPosition.x > +m_box->sideX() / 2.0) {
463  nearestX = +m_box->sideX() / 2.0;
464  } else {
465  nearestX = relRobotPosition.x;
466  }
467  real nearestY;
468  if (relRobotPosition.y < -m_box->sideY() / 2.0) {
469  nearestY = -m_box->sideY() / 2.0;
470  } else if (relRobotPosition.y > +m_box->sideY() / 2.0) {
471  nearestY = +m_box->sideY() / 2.0;
472  } else {
473  nearestY = relRobotPosition.y;
474  }
475 
476  // Although distance is independent of the frame of reference, we convert the nearest point to the global frame
477  // of reference because we only have the robot orientation in that frame
478  const wVector nearestPoint = m_box->matrix().transformVector(wVector(nearestX, nearestY, relRobotPosition.z));
479 
480  // Now we can easily compute the distance and orientation. For the distance we have to remove the robot radius
481  distance = (nearestPoint - robotPosition).norm() - robot.getRadius();
482  const real robotOrientation = (dynamic_cast<const RobotOnPlane*>(robot.wObject()))->orientation(m_arena->getPlane());
483  angle = atan2(nearestPoint.y - robotPosition.y, nearestPoint.x - robotPosition.x) - robotOrientation;
484 
485  return true;
486 }
487 
489  PhyObject2DWrapper(arena),
490  m_cylinder(cylinder),
491  m_type(((type != SmallCylinder) && (type != BigCylinder) && (type != CircularTargetArea)) ? Cylinder : type)
492 {
493  if (m_type == CircularTargetArea) {
494  m_cylinder->setStatic(true);
495  }
496 }
497 
499 {
500  // Nothing to do here
501 }
502 
504 {
505  return m_cylinder;
506 }
507 
509 {
510  return m_cylinder;
511 }
512 
514 {
515  // CircularTargetArea cannot be made non-static
516  if (m_type == CircularTargetArea) {
517  return;
518  }
519 
520  phyObject()->setStatic(s);
521 }
522 
524 {
525  return m_type;
526 }
527 
528 void Cylinder2DWrapper::setPosition(real x, real y)
529 {
530  wVector pos = phyObject()->matrix().w_pos;
531 
532  pos.x = x;
533  pos.y = y;
534 
535  phyObject()->setPosition(pos);
536 }
537 
538 void Cylinder2DWrapper::computeLinearViewFieldOccupiedRange(const wMatrix& cameraMtr, double& minAngle, double& maxAngle, double& distance) const
539 {
540  // If this is a CircularTargetArea, we simply return a negative distance (it is not visible with
541  // a linear camera)
542  if (m_type == CircularTargetArea) {
543  distance = -1.0;
544  return;
545  }
546 
547  computeLinearViewFieldOccupiedRangeForCylinder(cameraMtr, m_cylinder->matrix(), m_cylinder->height(), m_cylinder->radius(), minAngle, maxAngle, distance);
548 }
549 
550 bool Cylinder2DWrapper::computeDistanceAndOrientationFromRobot(const WheeledRobot2DWrapper& robot, double& distance, double& angle) const
551 {
552  // If this is a CircularTargetArea, we simply return a negative distance
553  if (m_type == CircularTargetArea) {
554  return false;
555  }
556 
557  const double robotOrientation = (dynamic_cast<const RobotOnPlane*>(robot.wObject()))->orientation(m_arena->getPlane());
558  computeDistanceAndOrientationFromRobotToCylinder(robot.position(), robotOrientation, robot.getRadius(), m_cylinder->radius(), m_cylinder->matrix().w_pos, distance, angle);
559 
560  return true;
561 }
562 
563 WheeledRobot2DWrapper::WheeledRobot2DWrapper(Arena* arena, RobotOnPlane* robot, double height, double radius) :
564  PhyObject2DWrapper(arena),
565  m_robot(robot),
566  m_height(height),
567  m_radius(radius),
568  m_previousMatrix((dynamic_cast<WObject*>(robot))->matrix())
569 {
570 }
571 
573 {
574 }
575 
577 {
578  return m_robot;
579 }
580 
582 {
583  return m_robot;
584 }
585 
587 {
588  return dynamic_cast<WObject*>(m_robot);
589 }
590 
592 {
593  return dynamic_cast<const WObject*>(m_robot);
594 }
595 
597 {
598  return NULL;
599 }
600 
602 {
603  return NULL;
604 }
605 
607 {
608  return WheeledRobot;
609 }
610 
612 {
613  wVector pos = wObject()->matrix().w_pos;
614 
615  pos.x = x;
616  pos.y = y;
617 
618  wObject()->setPosition(pos);
619 }
620 
621 void WheeledRobot2DWrapper::computeLinearViewFieldOccupiedRange(const wMatrix& cameraMtr, double& minAngle, double& maxAngle, double& distance) const
622 {
623  wMatrix mtr = wObject()->matrix();
624 
625  mtr.w_pos.z += m_height / 2.0;
626 
627  computeLinearViewFieldOccupiedRangeForCylinder(cameraMtr, mtr, m_height, m_radius, minAngle, maxAngle, distance);
628 }
629 
630 bool WheeledRobot2DWrapper::computeDistanceAndOrientationFromRobot(const WheeledRobot2DWrapper& robot, double& distance, double& angle) const
631 {
632  if (this == &robot) {
633  return false;
634  }
635 
636  const double robotOrientation = (dynamic_cast<const RobotOnPlane*>(robot.wObject()))->orientation(m_arena->getPlane());
637  computeDistanceAndOrientationFromRobotToCylinder(robot.position(), robotOrientation, robot.getRadius(), m_radius, position(), distance, angle);
638 
639  return true;
640 }
641 
642 wVector positionOnPlane(const Box2DWrapper* plane, real x, real y)
643 {
644  wVector pos = plane->position();
645 
646  pos.x = x;
647  pos.y = y;
648  pos.z += plane->phyObject()->sideZ() / 2.0f;
649 
650  return pos;
651 }
652 
653 void orientationOnPlane(const Box2DWrapper* plane, real angle, wMatrix& mtr)
654 {
655  wMatrix rotatedMtr = plane->phyObject()->matrix();
656 
657  // Now rotating the matrix around the Z axis
658  rotatedMtr = rotatedMtr.rotateAround(rotatedMtr.z_ax, rotatedMtr.w_pos, angle);
659 
660  // Setting the position of the rotated matrix to be the same as the original one
661  rotatedMtr.w_pos = mtr.w_pos;
662 
663  // Now overwriting the matrix
664  mtr = rotatedMtr;
665 }
666 
667 real angleBetweenXAxes(const wMatrix& mtr1, const wMatrix& mtr2)
668 {
669  // Taking the two x axes. We can take the x axis of mtr1 as is, while we need to project the X axis
670  // of mtr2 onto the XY plane of mtr1. To do so we simply project the x axis of mtr2 on the z axis of
671  // mtr1 and subtract this vector from the original x axis of mtr2
672  const wVector& x1 = mtr1.x_ax;
673  const wVector x2 = mtr2.x_ax - mtr1.z_ax.scale(mtr2.x_ax % mtr1.z_ax);
674 
675  // Now normalizing both axes
676  const wVector normX1 = x1.scale(1.0 / x1.norm());
677  const wVector normX2 = x2.scale(1.0 / x2.norm());
678 
679  // To get the angle (unsigned), computing the acos of the dot product of the two vectors
680  const double unsignedAngle = acos(normX1 % normX2);
681 
682  // Now choosing the right sign. To do this we first compute the cross product of the two x axes and
683  // then we see if it has the same direction of the z axis of the first matrix or not
684  const double s = mtr1.z_ax % (normX1 * normX2);
685  return (s < 0.0) ? -unsignedAngle : unsignedAngle;
686 }
687 
688 } // end namespace farsa