collision_checker.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018-9, Ubiquity Robotics
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  * POSSIBILITY OF SUCH DAMAGE.
25  *
26  * The views and conclusions contained in the software and documentation are
27  * those of the authors and should not be interpreted as representing official
28  * policies, either expressed or implied, of the FreeBSD Project.
29  *
30  */
31 
32 /*
33 
34  The `CollisionChecker` class processes range messages to determine the
35  distance to obstacles. As range messages are received, they are used
36  to either create or update a `RangeSensor` object. `RangeSensor` objects
37  are created by looking up the transform from `base_link` to their frame
38  and computing a pair of vectors corresponding to the sides of their
39  cone.
40 
41  `LaserScan` messages from lidar sensors are also processed. If such data is
42  received, the transform from `base_link` to the laser's frame is looked up
43  to determine the scanner's position.
44 
45  The distance to the closest object is calculated based upon the positions
46  of the end points of the sensors' cones. For this purpose, the robot
47  footprint is paramatized as having width `robot_width` either side of
48  base_link, and length `robot_front_length` forward of base_link and length
49  `robot_back_length` behind it. This could be extended to be an abitrary
50  polygon. When the robot is travelling forward or backwards, the distance
51  to the closest point that has an `x` value between `-robot_width` and
52  `robot_width` is used as the obstacle distance.
53 
54  In the case of rotation in place, the angle that the robot will rotate
55  before hitting an obstacle is determined. This is done by converting
56  each of the `(x,y)` points into `(r, theta)` and determining how much
57  `theta` would change by for the point to intersect with one of the four
58  line segments representing the robot footprint.
59 
60  Coordinate systems are as specified in http://www.ros.org/reps/rep-0103.html
61  x forward, y left
62 
63  Jim Vaughan <jimv@mrjim.com> January 2018
64 
65 */
66 
68 #include <tf2_ros/buffer.h>
70 #include <sensor_msgs/Range.h>
71 #include <visualization_msgs/Marker.h>
73 
74 
76  ObstaclePoints& op) : tf_buffer(tf_buffer),
77  ob_points(op)
78 {
79  nh.param<std::string>("base_frame", baseFrame, "base_link");
80 
82  nh.advertise<visualization_msgs::Marker>("/obstacle_viz", 10));
83 
84  max_age = nh.param<float>("max_age", 1.0);
85  no_obstacle_dist = nh.param<float>("no_obstacle_dist", 10.0);
86 
87  // Footprint
88  robot_width = nh.param<float>("robot_width", 0.08);
89  robot_front_length = nh.param<float>("robot_front_length", 0.09);
90  robot_back_length = nh.param<float>("robot_back_length", 0.19);
91 
95 
96  // To test if obstacles will intersect when rotating
97  front_diag = robot_width*robot_width + robot_front_length*robot_front_length;
98  back_diag = robot_width*robot_width + robot_back_length*robot_back_length;
99 
100 }
101 
102 void CollisionChecker::draw_line(const tf2::Vector3 &p1, const tf2::Vector3 &p2,
103  float r, float g, float b, int id)
104 {
105  visualization_msgs::Marker line;
106  line.type = visualization_msgs::Marker::LINE_LIST;
107  line.action = visualization_msgs::Marker::MODIFY;
108  line.header.frame_id = baseFrame;
109  line.color.r = r;
110  line.color.g = g;
111  line.color.b = b;
112  line.color.a = 1.0f;
113  line.id = id;
114  line.scale.x = line.scale.y = line.scale.z = 0.01;
115  line.pose.position.x = 0;
116  line.pose.position.y = 0;
117  line.pose.orientation.w = 1;
118  geometry_msgs::Point gp1, gp2;
119  gp1.x = p1.x();
120  gp1.y = p1.y();
121  gp1.z = p1.z();
122  gp2.x = p2.x();
123  gp2.y = p2.y();
124  gp2.z = p2.z();
125  line.points.push_back(gp1);
126  line.points.push_back(gp2);
127  line_pub.publish(line);
128 }
129 
131 {
132  visualization_msgs::Marker line;
133  line.type = visualization_msgs::Marker::LINE_LIST;
134  line.action = visualization_msgs::Marker::DELETE;
135  line.id = id;
136  line_pub.publish(line);
137 }
138 
139 inline void CollisionChecker::check_dist(float x, bool forward, float& min_dist) const
140 {
141  if (forward && x > robot_front_length) {
142  if (x < min_dist) {
143  min_dist = x;
144  }
145  }
146  if (!forward && -x > robot_back_length ) {
147  if (-x < min_dist) {
148  min_dist = -x;
149  }
150  }
151 }
152 
154  float &min_dist_left,
155  float &min_dist_right,
156  tf2::Vector3 &fl,
157  tf2::Vector3 &fr)
158 {
159  float min_dist = no_obstacle_dist;
160  min_dist_left = no_obstacle_dist;
161  min_dist_right = no_obstacle_dist;
162 
163  auto lines = ob_points.get_lines(ros::Duration(max_age));
164  for (const auto& points : lines) {
165  float x0 = points.first.x();
166  float y0 = points.first.y();
167  float x1 = points.second.x();
168  float y1 = points.second.y();
169  // Forward and rear limits
170  if (y0 < -robot_width && robot_width < y1) {
171  // linear interpolate to get closest point inside width
172  float ylen = y1 - y0;
173  float a0 = (y0 - robot_width) / ylen;
174  float a1 = (y1 - robot_width - y0) / ylen;
175  check_dist(a0 * x0 + (1.0 - a0) * x1, forward, min_dist);
176  check_dist(a1 * x1 + (1.0 - a1) * x0, forward, min_dist);
177  }
178  else if (y1 < -robot_width && robot_width < y0) {
179  // linear interpolate to get closest point inside width
180  float ylen = y0 - y1;
181  float a0 = (y0 - robot_width - y1) / ylen;
182  float a1 = (y1 - robot_width) / ylen;
183  check_dist(a0 * x0 + (1.0 - a0) * x1, forward, min_dist);
184  check_dist(a1 * x1 + (1.0 - a1) * x0, forward, min_dist);
185  }
186  else {
187  if (-robot_width < y0 && y0 < robot_width) {
188  check_dist(x0, forward, min_dist);
189  }
190  if (-robot_width < y1 && y1 < robot_width) {
191  check_dist(x1, forward, min_dist);
192  }
193  }
194  // Sides
195  if (x0 < -robot_back_length && x1 > robot_front_length) {
196  // linear interpolate to get closest point in side
197  float xlen = x1 - x0;
198  float ab = (-x0 - robot_back_length) / xlen;
199  float af = (x1 - robot_front_length - x0) / xlen;
200  float yb = ab * y0 + (1.0 - ab) * y1;
201  float yf = af * y1 + (1.0 - af) * y0;
202  if (yb > 0 && yb < min_dist_left) {
203  min_dist_left = yb;
204  }
205  if (yb < 0 && -yb < min_dist_right) {
206  min_dist_right = -yb;
207  }
208  if (yf> 0 && yf < min_dist_left) {
209  min_dist_left = yf;
210  }
211  if (yf < 0 && -yf < min_dist_right) {
212  min_dist_right = -yf;
213  }
214  }
215  else if (x1 < -robot_back_length && x0 > robot_front_length) {
216  // linear interpolate to get closest point in side
217  float xlen = x0 - x1;
218  float ab = (-x1 - robot_back_length) / xlen;
219  float af = (x0 - robot_front_length - x1) / xlen;
220  float yb = ab * y1 + (1.0 - ab) * y0;
221  float yf = af * y0 + (1.0 - af) * y1;
222  if (yb > 0 && yb < min_dist_left) {
223  min_dist_left = yb;
224  }
225  if (yb < 0 && -yb < min_dist_right) {
226  min_dist_right = -yb;
227  }
228  if (yf> 0 && yf < min_dist_left) {
229  min_dist_left = yf;
230  }
231  if (yf < 0 && -yf < min_dist_right) {
232  min_dist_right = -yf;
233  }
234  }
235  else {
236  if (x0 > -robot_back_length && x0 < robot_front_length) {
237  if (y0 > 0 && y0 < min_dist_left) {
238  min_dist_left = y0;
239  }
240  if (y0 < 0 && -y0 < min_dist_right) {
241  min_dist_right = -y0;
242  }
243  }
244  if (x1 > -robot_back_length && x1 < robot_front_length) {
245  if (y1> 0 && y1 < min_dist_left) {
246  min_dist_left = y1;
247  }
248  if (y1 < 0 && -y1 < min_dist_right) {
249  min_dist_right = -y1;
250  }
251  }
252  }
253  }
254 
255  // Forward side points
256  fl.setX(robot_front_length);
257  fl.setY(min_dist_left);
258  fr.setX(robot_front_length);
259  fr.setY(min_dist_right);
260 
262  for (const auto& p : pts) {
263  float y = p.y();
264  float x = p.x();
265  // Forward and rear
266  if (-robot_width < y && y < robot_width) {
267  check_dist(x, forward, min_dist);
268  }
269  // Sides
270  if (x > -robot_back_length && x < robot_front_length) {
271  if (y > 0 && y < min_dist_left) {
272  min_dist_left = y;
273  }
274  else if (y < 0 && -y < min_dist_right) {
275  min_dist_right = -y;
276  }
277  }
278  }
279 
280  // Green lines at sides
281  draw_line(tf2::Vector3(robot_front_length, min_dist_left, 0),
282  tf2::Vector3(-robot_back_length, min_dist_left, 0), 0, 1, 0, 20000);
283  draw_line(tf2::Vector3(robot_front_length, min_dist_left, 0),
284  tf2::Vector3(robot_front_length + 2, min_dist_left, 0), 0, 0.5, 0, 20001);
285 
286  draw_line(tf2::Vector3(robot_front_length, -min_dist_right, 0),
287  tf2::Vector3(-robot_back_length, -min_dist_right, 0), 0, 1, 0, 20002);
288 
289  draw_line(tf2::Vector3(robot_front_length, -min_dist_right, 0),
290  tf2::Vector3(robot_front_length + 2, -min_dist_right, 0), 0, 0.5, 0, 20003);
291 
292  // Blue
293  draw_line(tf2::Vector3(robot_front_length, min_dist_left, 0),
294  tf2::Vector3(fl.x(), fl.y(), 0), 0, 0, 1, 30000);
295 
296  draw_line(tf2::Vector3(robot_front_length, -min_dist_right, 0),
297  tf2::Vector3(fr.x(), -fr.y(), 0), 0, 0, 1, 30001);
298 
299  // Min side dist
301  tf2::Vector3(robot_front_length + 2, -robot_width -min_side_dist, 0), 0.5, 0.5, 0, 40001);
302 
304  tf2::Vector3(robot_front_length + 2, robot_width+min_side_dist, 0), 0.5, 0.5, 0, 40002);
305 
306  // Red line at front or back
307  if (forward) {
308  draw_line(tf2::Vector3(min_dist, -robot_width, 0),
309  tf2::Vector3(min_dist, robot_width, 0), 1, 0, 0, 10000);
310 
311  draw_line(tf2::Vector3(min_dist, -robot_width - 2, 0),
312  tf2::Vector3(min_dist, -robot_width, 0), 0.5, 0, 0, 10020);
313 
314  draw_line(tf2::Vector3(min_dist, robot_width + 2, 0),
315  tf2::Vector3(min_dist, robot_width, 0), 0.5, 0, 0, 10030);
316  min_dist -= robot_front_length;
317  }
318  else {
319  draw_line(tf2::Vector3(-min_dist, -robot_width, 0),
320  tf2::Vector3(-min_dist, robot_width, 0), 1, 0, 0, 10000);
321  min_dist -= robot_back_length;
322  }
323 
324  min_dist_left -= robot_width;
325  min_dist_right -= robot_width;
326  return min_dist;
327 }
328 
329 float CollisionChecker::degrees(float radians) const
330 {
331  return radians * 180.0 / M_PI;
332 }
333 
334 /*
335  Determine the rotation required to for to move point from its
336  initial rotation of theta to (x, y), and store the smallest
337  value
338 */
339 inline void CollisionChecker::check_angle(float theta, float x, float y,
340  bool left, float& min_dist) const
341 {
342  float theta_int = theta - std::atan2(y, x);
343  if (theta_int < -M_PI) {
344  theta_int += 2.0 * M_PI;
345  }
346  if (theta_int > M_PI) {
347  theta_int -= 2.0 * M_PI;
348  }
349  if (left && theta_int > 0 && theta_int < min_dist) {
350  min_dist = theta_int;
351  }
352  if (!left && theta_int < 0 && -theta_int < min_dist) {
353  min_dist = -theta_int;
354  }
355 }
356 
358 {
359  float min_angle = M_PI;
360 
361  auto points = ob_points.get_points(ros::Duration(max_age));
362  // draw footprint
363  draw_line(tf2::Vector3(robot_front_length, robot_width, 0),
364  tf2::Vector3(-robot_back_length, robot_width, 0),
365  0.28, 0.5, 1, 10003);
366  draw_line(tf2::Vector3(robot_front_length, -robot_width, 0),
367  tf2::Vector3(-robot_back_length, -robot_width, 0),
368  0.28, 0.5, 1, 10004);
369  draw_line(tf2::Vector3(robot_front_length, robot_width, 0),
370  tf2::Vector3(robot_front_length, -robot_width, 0),
371  0.28, 0.5, 1, 10005);
372  draw_line(tf2::Vector3(-robot_back_length, robot_width, 0),
373  tf2::Vector3(-robot_back_length, -robot_width, 0),
374  0.28, 0.5, 1, 10006);
375 
376  for (const auto& p : points) {
377  float x = p.x();
378  float y = p.y();
379  // initial orientation wrt base_link
380  float theta = std::atan2(y, x);
381  float r_squared = x*x + y*y;
382  if (r_squared <= back_diag) {
383  // left line segment:
384  // y = robot_width, -robot_back_length <= x <= robot_front_length
385  // right line segment:
386  // y = -robot_width, -robot_back_length <= x <= robot_front_length
387  if (robot_width_sq <= r_squared) {
388  float xi = std::sqrt(r_squared - robot_width_sq);
389  if (-robot_back_length <= xi && xi <= robot_front_length) {
390  check_angle(theta, xi, robot_width, left, min_angle);
391  check_angle(theta, xi, -robot_width, left, min_angle);
392  }
393  if (-robot_back_length <= -xi && -xi <= robot_front_length) {
394  check_angle(theta, -xi, robot_width, left, min_angle);
395  check_angle(theta, -xi, -robot_width, left, min_angle);
396  }
397  }
398 
399  // back line segment:
400  // x = -robot_back_length, -robot_width <= y <= robot_width
401  if (x < 0 && robot_back_length_sq <= r_squared) {
402  float yi = std::sqrt(r_squared - robot_back_length_sq);
403  if (-robot_width <= yi && yi <= robot_width) {
404  check_angle(theta, -robot_back_length, yi, left, min_angle);
405  }
406  if (-robot_width <= -yi && -yi <= robot_width) {
407  check_angle(theta, -robot_back_length, -yi, left, min_angle);
408  }
409  }
410 
411  // front line segment:
412  // x = robot_front_length, -robot_width <= y <= robot_width
413  if (x > 0 && r_squared <= front_diag && robot_front_length_sq <= r_squared) {
414  float yi = std::sqrt(r_squared - robot_front_length_sq);
415  if (-robot_width <= yi && yi <= robot_width) {
416  check_angle(theta, robot_front_length, yi, left, min_angle);
417  }
418  if (-robot_width <= -yi && -yi <= robot_width) {
419  check_angle(theta, robot_front_length, -yi, left, min_angle);
420  }
421  }
422  }
423  }
424 
425  // Draw rotated footprint to show limit of rotation
426  float rotation;
427  if (left) {
428  rotation = min_angle;
429  }
430  else {
431  rotation = -min_angle;
432  }
433  float sin_theta = std::sin(rotation);
434  float cos_theta = std::cos(rotation);
435 
436 
437  if (std::abs(min_angle) < M_PI) {
438  float x_fl = robot_front_length * cos_theta - robot_width * sin_theta;
439  float y_fl = robot_front_length * sin_theta + robot_width * cos_theta;
440  float x_fr = robot_front_length * cos_theta + robot_width * sin_theta;
441  float y_fr = robot_front_length * sin_theta - robot_width * cos_theta;
442  float x_bl = -robot_back_length * cos_theta - robot_width * sin_theta;
443  float y_bl = -robot_back_length * sin_theta + robot_width * cos_theta;
444  float x_br = -robot_back_length * cos_theta + robot_width * sin_theta;
445  float y_br = -robot_back_length * sin_theta - robot_width * cos_theta;
446  draw_line(tf2::Vector3(x_fl, y_fl, 0), tf2::Vector3(x_bl, y_bl, 0),
447  1, 0, 0, 10010);
448  draw_line(tf2::Vector3(x_bl, y_bl, 0), tf2::Vector3(x_br, y_br, 0),
449  1, 0, 0, 10011);
450  draw_line(tf2::Vector3(x_br, y_br, 0), tf2::Vector3(x_fr, y_fr, 0),
451  1, 0, 0, 10012);
452  draw_line(tf2::Vector3(x_fr, y_fr, 0), tf2::Vector3(x_fl, y_fl, 0),
453  1, 0, 0, 10013);
454  }
455  else {
456  clear_line(10010);
457  clear_line(10011);
458  clear_line(10012);
459  clear_line(10013);
460  }
461 
462  ROS_DEBUG("min angle %f\n", degrees(min_angle));
463  return min_angle;
464 }
465 
466 
467 float CollisionChecker::obstacle_arc_angle(double linear, double angular) {
468  const float radius = (float) std::abs(linear/angular);
469  const bool forward = linear >= 0;
470  const bool left = angular >= 0;
471 
472  // Point of rotation relative to base_link
473  const auto point_of_rotation = tf2::Vector3(0, (left) ? radius : -radius, 0);
474 
475  // Critical robot corners relative to point of rotation
476  const auto outer_point = tf2::Vector3(-robot_back_length,
477  (left) ? -robot_width: robot_width, 0) - point_of_rotation;
478  const auto inner_point = tf2::Vector3(robot_front_length,
479  (left) ? robot_width: -robot_width, 0) - point_of_rotation;
480 
481  // Critical robot points in polar (r^2, theta) form relative to center
482  // of rotation
483  const float outer_radius_sq = outer_point.length2();
484  const float outer_theta = std::atan2(outer_point.y(), outer_point.x());
485  const float inner_radius_sq = inner_point.length2();
486  //Not used const float inner_theta = std::atan2(inner_point.x(), -inner_point.y());
487 
488  // Utility function to make sure that the angle relevant and not behind the robot
489  const auto angle_relevant = [&outer_theta, &left, &forward](float angle) -> bool {
490  if (forward) {
491  if (left) {
492  return angle > outer_theta;
493  } else {
494  return angle < outer_theta;
495  }
496  } else {
497  if (angle < 0) {
498  angle += 2.0 * M_PI;
499  }
500 
501  if (left) {
502  return angle < outer_theta;
503  } else {
504  return angle > outer_theta;
505  }
506  }
507  };
508 
509  float closest_angle = M_PI;
510  const auto points = ob_points.get_points(ros::Duration(max_age));
511  for (const auto& p : points) {
512  // Trasform the obstacle point into the coordiate system with the
513  // point of rotation at the origin, with the same orientation as base_link
514  const tf2::Vector3 p_in_rot = p - point_of_rotation;
515  // Radius for polar coordinates around center of rotation
516  const float p_radius_sq = p_in_rot.length2();
517 
518  if(p_radius_sq < outer_radius_sq && p_radius_sq > inner_radius_sq) {
519  // Angle for polar coordinates around center of rotation
520  const float p_theta = std::atan2(p_in_rot.y(), p_in_rot.x());
521  if (angle_relevant(p_theta) && p_theta < M_PI) {
522  // TODO: This assumes that any collision with the point will be
523  // on the leading part of the robot, when in reality we can turn more
524  // than this amount if the point only causes a collision with the rear
525  // part of the robot as it swings around for a turn
526  closest_angle = std::min(closest_angle, p_theta);
527  }
528  }
529  }
530 
531  // TODO: Check obstacle lines for intersection with robot arc
532 
533  return closest_angle;
534 }
std::string baseFrame
void check_dist(float x, bool forward, float &min_dist) const
void publish(const boost::shared_ptr< M > &message) const
void check_angle(float theta, float x, float y, bool left, float &min_dist) const
float obstacle_arc_angle(double linear, double angular)
TFSIMD_FORCE_INLINE const tfScalar & y() const
TFSIMD_FORCE_INLINE tfScalar angle(const Quaternion &q1, const Quaternion &q2)
ObstaclePoints & ob_points
float degrees(float radians) const
float obstacle_angle(bool left)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
float obstacle_dist(bool forward, float &left_dist, float &right_dist, tf2::Vector3 &fl, tf2::Vector3 &fr)
TFSIMD_FORCE_INLINE const tfScalar & x() const
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
void draw_line(const tf2::Vector3 &p1, const tf2::Vector3 &p2, float r, float g, float b, int id)
std::vector< Line > get_lines(ros::Duration max_age)
std::vector< tf2::Vector3 > get_points(ros::Duration max_age)
ros::Publisher line_pub
CollisionChecker(ros::NodeHandle &nh, tf2_ros::Buffer &tf_buffer, ObstaclePoints &op)
#define ROS_DEBUG(...)


move_basic
Author(s): Jim Vaughan
autogenerated on Fri Mar 26 2021 02:46:58