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 
29 namespace farsa {
30 
31 // This anonymous namespace contains helper functions used in this file
32 namespace {
45  wVector computeWallVertex(PhyBox* wall, unsigned int vertexID)
46  {
47  const wVector centerOnPlane = wall->matrix().w_pos - wall->matrix().z_ax.scale(wall->sideZ() / 2.0);
48  const wVector halfSide1Vector = wall->matrix().x_ax.scale(wall->sideX() / 2.0);
49  const wVector halfSide2Vector = wall->matrix().y_ax.scale(wall->sideY() / 2.0);
50 
51  wVector vertex;
52  switch(vertexID % 4) {
53  case 0:
54  vertex = centerOnPlane + halfSide1Vector + halfSide2Vector;
55  break;
56  case 1:
57  vertex = centerOnPlane + halfSide1Vector - halfSide2Vector;
58  break;
59  case 2:
60  vertex = centerOnPlane - halfSide1Vector + halfSide2Vector;
61  break;
62  case 3:
63  vertex = centerOnPlane - halfSide1Vector - halfSide2Vector;
64  break;
65  default:
66  break;
67  }
68 
69  return vertex;
70  }
71 
72 
82  double getAngleWithXAxis(const wMatrix& mtr, const wVector& v)
83  {
84  // Normalizing v
85  const wVector vdir = v.scale(1.0 / v.norm());
86 
87  // To get the angle (unsigned), computing the acos of the dot product of the two vectors
88  const double unsignedAngle = acos(mtr.x_ax % vdir);
89 
90  // Now choosing the right sign. To do this we first compute the cross product of the x axis and
91  // the vertex direction, then we see if it has the same direction of Z or not
92  const double s = mtr.z_ax % (mtr.x_ax * vdir);
93  return (s < 0.0) ? -unsignedAngle : unsignedAngle;
94  }
95 }
96 
98 {
99  // Nothing to do here
100 }
101 
103 {
104  // Nothing to do here
105 }
106 
108 {
109  phyObject()->setStatic(s);
110 }
111 
113 {
114  return phyObject()->getStatic();
115 }
116 
118 {
119  setPosition(pos.x, pos.y);
120 }
121 
123 {
124  return phyObject()->matrix().w_pos;
125 }
126 
127 void PhyObject2DWrapper::setTexture(QString textureName)
128 {
129  phyObject()->setTexture(textureName);
130 }
131 
133 {
134  return phyObject()->texture();
135 }
136 
138 {
139  phyObject()->setColor(color);
140 }
141 
143 {
144  return phyObject()->color();
145 }
146 
148 {
150 }
151 
153 {
154  return phyObject()->useColorTextureOfOwner();
155 }
156 
159  m_box(box),
160  m_vertexes(QVector<wVector>() << computeWallVertex(m_box, 0) << computeWallVertex(m_box, 1) << computeWallVertex(m_box, 2) << computeWallVertex(m_box, 3)),
161  m_centerOnPlane(m_box->matrix().w_pos - m_box->matrix().z_ax.scale(m_box->sideZ() / 2.0)),
162  m_type(((type != Plane) && (type != Wall) && (type != RectangularTargetArea)) ? Box : type)
163 {
164  if (m_type != Box) {
165  m_box->setStatic(true);
166  }
167 }
168 
170 {
171  // Nothing to do here
172 }
173 
175 {
176  return m_box;
177 }
178 
180 {
181  return m_box;
182 }
183 
185 {
186  return m_type;
187 }
188 
190 {
191  // Only Boxes can be made non-static
192  if (m_type != Box) {
193  return;
194  }
195 
196  phyObject()->setStatic(s);
197 }
198 
199 void Box2DWrapper::setPosition(real x, real y)
200 {
201  // Planes and Walls cannot be moved
202  if ((m_type == Plane) || (m_type == Wall)) {
203  return;
204  }
205 
206  wVector pos = phyObject()->matrix().w_pos;
207 
208  pos.x = x;
209  pos.y = y;
210 
211  phyObject()->setPosition(pos);
212 
213  // We also have to recompute the vertexes and center
214  for (unsigned int i = 0; i < 4; i++) {
215  m_vertexes[i] = computeWallVertex(m_box, i);
216  }
217  m_centerOnPlane = m_box->matrix().w_pos - m_box->matrix().z_ax.scale(m_box->sideZ() / 2.0);
218 }
219 
220 void Box2DWrapper::computeLinearViewFieldOccupiedRange(const wMatrix& cameraMtr, double& minAngle, double& maxAngle, double& distance) const
221 {
222  // If this is a Plane or a RectangularTargetArea, we simply return a negative distance (they are not visible with
223  // a linear camera)
224  if ((m_type == Plane) || (m_type == RectangularTargetArea)) {
225  distance = -1.0;
226  return;
227  }
228 
229  // We have to translate the camera to lie on the same plane of the vertex. We translate it along
230  // its local upvector (Z axis) until it reaches the plane containing the base of the wall. Of course
231  // this only works if the camera Z axis is not paraller to the plane with the base of the wall. In
232  // that case all computations would be invalid, so we don't do anything
233  wMatrix mtr = cameraMtr;
234  if (fabs(mtr.z_ax % m_box->matrix().z_ax) < 0.0001) {
235  distance = -1.0;
236  return;
237  }
238  mtr.w_pos = mtr.w_pos + mtr.z_ax.scale((m_centerOnPlane.z - mtr.w_pos.z) / mtr.z_ax.z);
239 
240  // First of all computing the angle for every vertex
241  QVector<double> angles(4);
242 
243  for (int i = 0; i < 4; i++) {
244  // Computing the vector giving the direction to the vertex
245  const wVector vdir = m_vertexes[i] - mtr.w_pos;
246 
247  // Now computing the angle
248  angles[i] = getAngleWithXAxis(mtr, vdir);
249  }
250 
251  // Now finding the min and max angle (their indexes). We have to take into account the fact that the
252  // angle with the minimum value could be the upper limit and viceversa because the object could be
253  // behind the camera. However we know that, as the camera is outside the wall, the maximum possible
254  // angular sector of the view filed occupied by the wall is 180°. This means that also the angular
255  // distance of one vertex with the center of the wall must be less than 180°. So, if we compute this
256  // distance and get a value greater than 180°, we have to take (360° - computed_angular_distance)
257  // and invert min with max.
258  const wVector centerDir = m_centerOnPlane - mtr.w_pos;
259  const double centerAngle = getAngleWithXAxis(mtr, centerDir);
260  int minAngleID = 0;
261  int maxAngleID = 0;
262 
263  // These two are the angular distances of the current min and max angles from the center. Their initial
264  // value is the lowest possible
265  double minDelta = 0.0;
266  double maxDelta = 0.0;
267 
268  for (int i = 0; i < 4; i++) {
269  const double curDelta = fabs(angles[i] - centerAngle);
270 
271  // Checking if the vertex and the center are behind the camera
272  if (curDelta > PI_GRECO) {
273  const double actualDelta = (2.0 * PI_GRECO) - curDelta;
274  if (angles[i] > centerAngle) {
275  // This is a candidate minimum angle
276  if (actualDelta > minDelta) {
277  minAngleID = i;
278  minDelta = actualDelta;
279  }
280  } else {
281  // This is a candidate maximum angle
282  if (actualDelta > maxDelta) {
283  maxAngleID = i;
284  maxDelta = actualDelta;
285  }
286  }
287  } else {
288  if (angles[i] < centerAngle) {
289  // This is a candidate minimum angle
290  if (curDelta > minDelta) {
291  minAngleID = i;
292  minDelta = curDelta;
293  }
294  } else {
295  // This is a candidate maximum angle
296  if (curDelta > maxDelta) {
297  maxAngleID = i;
298  maxDelta = curDelta;
299  }
300  }
301  }
302  }
303 
304  // Filling the minAngle and maxAngle parameters
305  minAngle = angles[minAngleID];
306  maxAngle = angles[maxAngleID];
307 
308 #ifdef __GNUC__
309  #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)
310 #endif
311  // Now computing distance. This way of calculating the distance is plainly wrong (and I can't
312  // see why it is written this way), but it is the method used by Evorobot, so for the moment
313  // using it (moreover using distance for occlusion is not correct)
314  distance = ((mtr.w_pos - m_vertexes[minAngleID]).norm() + (mtr.w_pos - m_vertexes[maxAngleID]).norm()) / 2.0;
315 }
316 
319  m_cylinder(cylinder),
320  m_type(((type != SmallCylinder) && (type != BigCylinder) && (type != CircularTargetArea)) ? Cylinder : type)
321 {
322  if (m_type == CircularTargetArea) {
323  m_cylinder->setStatic(true);
324  }
325 }
326 
328 {
329  // Nothing to do here
330 }
331 
333 {
334  return m_cylinder;
335 }
336 
338 {
339  return m_cylinder;
340 }
341 
343 {
344  // CircularTargetArea cannot be made non-static
345  if (m_type == CircularTargetArea) {
346  return;
347  }
348 
349  phyObject()->setStatic(s);
350 }
351 
353 {
354  return m_type;
355 }
356 
357 void Cylinder2DWrapper::setPosition(real x, real y)
358 {
359  wVector pos = phyObject()->matrix().w_pos;
360 
361  pos.x = x;
362  pos.y = y;
363 
364  phyObject()->setPosition(pos);
365 }
366 
367 void Cylinder2DWrapper::computeLinearViewFieldOccupiedRange(const wMatrix& cameraMtr, double& minAngle, double& maxAngle, double& distance) const
368 {
369  // If this is a CircularTargetArea, we simply return a negative distance (it is not visible with
370  // a linear camera)
371  if (m_type == CircularTargetArea) {
372  distance = -1.0;
373  return;
374  }
375 
376  // The center of the lower base of the cylinder. The local x axis points towards the ground (cylinders
377  // are created this way by the arena...)
378  const wVector baseCenter = m_cylinder->matrix().w_pos + m_cylinder->matrix().x_ax.scale(m_cylinder->height() / 2.0);
379 
380  // We have to translate the camera to lie on the same plane of the cylinder base. We translate it along
381  // its local upvector (Z axis) until it reaches the plane containing the base of the cylinder. Of course
382  // this only works if the camera Z axis is not paraller to the plane with the base of the cylinder. In
383  // that case all computations would be invalid, so we don't do anything
384  wMatrix mtr = cameraMtr;
385  if (fabs(mtr.z_ax % m_cylinder->matrix().x_ax) < 0.0001) {
386  distance = -1.0;
387  return;
388  }
389  mtr.w_pos = mtr.w_pos + mtr.z_ax.scale((baseCenter.z - mtr.w_pos.z) / mtr.z_ax.z);
390 
391  // First of all we have to calculate the angle between the vector from the center of the
392  // camera to the center of the cylinder and the vector from the center of the camera and
393  // tangent to the circumference. We know that the tangent is always perpendicular to the
394  // radius to the contact point, so we can simply take the asin(radius/distance to center
395  // of cylinder)
396  const wVector centerDir = baseCenter - mtr.w_pos;
397  distance = centerDir.norm();
398  const double deltaAngle = asin(m_cylinder->radius() / distance);
399 
400  // Now computing the angle of the cylinder center
401  const double centerAngle = getAngleWithXAxis(mtr, centerDir);
402 
403  // Finally we can compute the min and max angle
404  minAngle = centerAngle - deltaAngle;
405  if (minAngle < -PI_GRECO) {
406  minAngle += 2.0 * PI_GRECO;
407  }
408  maxAngle = centerAngle + deltaAngle;
409  if (maxAngle > PI_GRECO) {
410  maxAngle -= 2.0 * PI_GRECO;
411  }
412 }
413 
414 } // end namespace farsa