2727 * POSSIBILITY OF SUCH DAMAGE.
2828 */
2929
30- #include < costmap_2d/costmap_math.h>
30+ #include < unordered_set>
31+
32+ #include < costmap_2d/costmap_math.h>
3133#include < boost/tokenizer.hpp>
3234#include < boost/foreach.hpp>
3335#include < boost/algorithm/string.hpp>
3436#include < costmap_2d/footprint.h>
3537#include < costmap_2d/array_parser.h>
36- #include < geometry_msgs/Point32.h>
38+ #include < geometry_msgs/Point32.h>
3739
3840namespace costmap_2d
3941{
@@ -66,107 +68,130 @@ void calculateMinAndMaxDistances(const std::vector<geometry_msgs::Point>& footpr
6668 max_dist = std::max (max_dist, std::max (vertex_dist, edge_dist));
6769}
6870
69- /* p[] is in standard form, ie, counterclockwise order,
70- distinct vertices, no collinear vertices.
71- ANGLE(m, n) is a procedure that returns the clockwise angle
72- swept out by a ray as it rotates from a position parallel
73- to the directed segment Pm,Pm+1 to a position parallel to Pn, Pn+1
74- We assume all indices are reduced to mod N (so that N+1 = 1).
75- */
76- std::vector<std::pair<int , int >> getAllAntipodalPairs (const std::vector<geometry_msgs::Point>& footprint)
71+ BoundingRect minBoundingRect (const std::vector<geometry_msgs::Point>& points)
7772{
78- std::vector<std::pair<int , int >> antipodal_pairs;
79-
80- // Find first antipodal pair by locating vertex opposite P1
81- int i = 0 ;
82- int j = 1 ;
83- while (positiveAngle (footprint[i].x , footprint[i].y , footprint[j].x , footprint[j].y ) < M_PI)
84- ++j;
85- antipodal_pairs.push_back ({ i, j });
86-
87- // Loop on j until all of P has been scanned
88- while (j < footprint.size ())
73+ Eigen::MatrixX2d hull_points_2d (points.size (), 2 ); // empty 2 column array
74+ for (size_t i = 0 ; i < points.size (); ++i)
75+ hull_points_2d.row (i) << points[i].x , points[i].y ;
76+ ROS_INFO_STREAM (" Input convex hull points:\n " << hull_points_2d);
77+
78+ // Compute edges (x2-x1,y2-y1)
79+ Eigen::MatrixX2d edges (hull_points_2d.rows () - 1 , 2 ); // empty 2 column array
80+ edges.setZero ();
81+ for (size_t i = 0 ; i < edges.rows (); ++i)
8982 {
90- bool last_pt = j == (footprint.size () - 1 );
91- double a = 2 * M_PI - positiveAngle (footprint[i].x , footprint[i].y , footprint[j].x , footprint[j].y );
92- if (a == M_PI) // Pi Pi+1 and Pj Pj+1 are parallel
93- {
94- antipodal_pairs.push_back ({ i + 1 , j });
95- antipodal_pairs.push_back ({ i, last_pt ? 0 : j + 1 });
96- antipodal_pairs.push_back ({ i + 1 , last_pt ? 0 : j + 1 });
97-
98- // Notice that (i, j) has been added to the result before being the pivots, so no need to yield i,j
99- ++i;
100- ++j;
101- }
102- else if (a < M_PI) // Will touch Pi Pi+1 first
103- {
104- antipodal_pairs.push_back ({ i + 1 , j });
105- ++i;
106- }
107- else
108- {
109- antipodal_pairs.push_back ({ i, last_pt ? 0 : j + 1 }); // Will touch Pj Pj+1 first
110- ++j;
111- }
83+ double edge_x = hull_points_2d (i + 1 , 0 ) - hull_points_2d (i, 0 );
84+ double edge_y = hull_points_2d (i + 1 , 1 ) - hull_points_2d (i, 1 );
85+ edges.row (i) << edge_x, edge_y;
11286 }
113-
114- return antipodal_pairs;
115- }
116-
117- double minSweepingAreaOrientation (const std::vector<geometry_msgs::Point>& footprint)
118- {
119- double min_dist = std::numeric_limits<double >::max ();
120- std::array<geometry_msgs::Point, 2 > closest_edge;
121-
122- if (footprint.size () <= 2 )
87+ ROS_INFO_STREAM (" Edges:\n " << edges);
88+
89+ // Calculate edge angles with atan2(y/x)
90+ std::vector<double > edge_angles (edges.rows ()); // empty 1 column array
91+ for (size_t i = 0 ; i < edge_angles.size (); ++i)
92+ edge_angles[i] = std::atan2 (edges.row (i)[1 ], edges.row (i)[0 ]);
93+ ROS_INFO_STREAM (" Edge angles:\n " << toString (edge_angles));
94+
95+ // Check for angles in 1st quadrant
96+ for (size_t i = 0 ; i < edge_angles.size (); ++i)
97+ edge_angles[i] = std::fmod (edge_angles[i] + M_PI, M_PI_2); // want strictly positive answers
98+ ROS_INFO_STREAM (" Edge angles in 1st Quadrant:\n " << toString (edge_angles));
99+
100+ // Remove duplicate angles
101+ std::unordered_set<float > s;
102+ auto end = std::remove_if (edge_angles.begin (), edge_angles.end (), [&s](double v) { return !s.insert (v).second ; });
103+ edge_angles.erase (end, edge_angles.end ());
104+ // std::sort(edge_angles.begin(), edge_angles.end());
105+ // auto ip = std::unique(edge_angles.begin(), edge_angles.end());
106+ // edge_angles.resize(std::distance(edge_angles.begin(), ip));
107+ ROS_INFO_STREAM (" Unique edge angles:\n " << toString (edge_angles));
108+
109+ // Test each angle to find bounding box with the smallest area
110+ // rot_angle, area, width, height, min_x, max_x, min_y, max_y
111+ std::array<double , 8 > min_bbox{ 0.0 , DBL_MAX, 0.0 , 0.0 , 0.0 , 0.0 , 0.0 , 0 };
112+ ROS_INFO_STREAM (" Testing " << edge_angles.size () << " possible rotations for bounding box..." );
113+ for (size_t i = 0 ; i < edge_angles.size (); ++i)
123114 {
124- return NAN;
125- }
126-
127- // check the distance from the robot center point to each footprint edged and keep the closest one
128- for (unsigned int i = 0 ; i < footprint.size () - 1 ; ++i)
129- {
130- double edge_dist = distanceToLine (0 , 0 , footprint[i].x , footprint[i].y , footprint[i + 1 ].x , footprint[i + 1 ].y );
131- if (edge_dist < min_dist)
115+ // Create rotation matrix to shift points to baseline
116+ // R = [ cos(theta) , cos(theta-PI/2)
117+ // cos(theta+PI/2) , cos(theta) ]
118+ // clang-format off
119+ Eigen::Matrix<double , 2 , 2 > R;
120+ R << std::cos (edge_angles[i]), std::cos (edge_angles[i] - M_PI_2),
121+ std::cos (edge_angles[i] + M_PI_2), std::cos (edge_angles[i]);
122+ // clang-format on
123+ ROS_INFO_STREAM (" Rotation matrix for " << edge_angles[i] << " is\n " << R);
124+
125+ // Apply this rotation to convex hull points
126+ Eigen::MatrixX2d rot_points = (R * hull_points_2d.transpose ()).transpose (); // 2x2 * 2xn
127+ ROS_INFO_STREAM (" Rotated hull points are\n " << rot_points);
128+
129+ // Find min/max x,y points
130+ const double min_x = rot_points.col (0 ).minCoeff ();
131+ const double max_x = rot_points.col (0 ).maxCoeff ();
132+ const double min_y = rot_points.col (1 ).minCoeff ();
133+ const double max_y = rot_points.col (1 ).maxCoeff ();
134+ ROS_INFO_STREAM (" Min x: " << min_x << " Max x: " << max_x << " Min y: " << min_y << " Max y: " << max_y);
135+
136+ // Calculate height/width/area of this bounding rectangle
137+ const double width = max_x - min_x;
138+ const double height = max_y - min_y;
139+ const double area = width * height;
140+ ROS_INFO_STREAM (" Bounding box " << i << " : width: " << width << " height: " << height << " area: " << area);
141+
142+ // Store the smallest rect found first (a simple convex hull might have 2 answers with same area)
143+ // Note that we require a non-neglectable difference to favor smaller rotations
144+ if (min_bbox[1 ] - area > 1e-3 )
132145 {
133- min_dist = edge_dist ;
134- closest_edge = { footprint [i], footprint[i + 1 ] };
146+ ROS_INFO_STREAM ( " Area " << min_bbox[ 1 ] << " -> " << area) ;
147+ min_bbox = { edge_angles [i], area, width, height, min_x, max_x, min_y, max_y };
135148 }
136149 }
150+ // Re-create rotation matrix for smallest rect
151+ // clang-format off
152+ const double angle = min_bbox[0 ];
153+ Eigen::Matrix<double , 2 , 2 > R;
154+ R << std::cos (angle), std::cos (angle - M_PI_2),
155+ std::cos (angle + M_PI_2), std::cos (angle);
156+ // clang-format on
157+ ROS_INFO_STREAM (" Projection matrix:\n " << R);
158+
159+ // Project convex hull points onto rotated frame
160+ Eigen::MatrixX2d proj_points = (R * hull_points_2d.transpose ()).transpose (); // 2x2 * 2xn
161+ ROS_INFO_STREAM (" Project hull points are\n " << proj_points);
162+
163+ // min/max x,y points are against baseline
164+ const double min_x = min_bbox[4 ];
165+ const double max_x = min_bbox[5 ];
166+ const double min_y = min_bbox[6 ];
167+ const double max_y = min_bbox[7 ];
168+ ROS_INFO_STREAM (" Min x: " << min_x << " Max x: " << max_x << " Min y: " << min_y << " Max y: " << max_y);
169+
170+ // Calculate center point and project onto rotated frame
171+ Eigen::Vector2d center{ (min_x + max_x) / 2.0 , (min_y + max_y) / 2.0 };
172+ Eigen::Vector2d center_point = center.transpose () * R;
173+ ROS_INFO_STREAM (" Bounding box center point:\n " << center_point);
174+
175+ // Calculate corner points and project onto rotated frame
176+ Eigen::Matrix<double , 4 , 2 > corner_points; // // = zeros((4, 2)) // empty 2 column array
177+ corner_points.row (0 ) = (Eigen::Vector2d{ max_x, min_y }.transpose () * R).transpose ();
178+ corner_points.row (1 ) = (Eigen::Vector2d{ min_x, min_y }.transpose () * R).transpose ();
179+ corner_points.row (2 ) = (Eigen::Vector2d{ min_x, max_y }.transpose () * R).transpose ();
180+ corner_points.row (3 ) = (Eigen::Vector2d{ max_x, max_y }.transpose () * R).transpose ();
181+ ROS_INFO_STREAM (" Bounding box corner points:\n " << corner_points);
182+
183+ ROS_INFO_STREAM (" Angle of rotation: " << angle << " rad " << angle * (180 / M_PI) << " deg" );
184+
185+ BoundingRect result;
186+ result.rot_angle = angle;
187+ result.area = min_bbox[1 ];
188+ result.width = min_bbox[2 ];
189+ result.height = min_bbox[3 ];
190+ result.center .x = center_point.x ();
191+ result.center .y = center_point.y ();
192+ for (int i = 0 ; i < corner_points.rows (); ++i)
193+ result.corners [i] = toPoint (corner_points.row (i));
137194
138- // we also need to do the last vertex and the first vertex
139- if (distanceToLine (0 , 0 , footprint.back ().x , footprint.back ().y , footprint.front ().x , footprint.front ().y ) < min_dist)
140- {
141- closest_edge = { footprint.back (), footprint.front () };
142- }
143-
144- // return the orientation of the closest edge, directed from back to front (+x axis direction)
145- std::sort (closest_edge.begin (), closest_edge.end (),
146- [](const geometry_msgs::Point& p1, const geometry_msgs::Point& p2) { return p1.x < p2.x ; });
147- // return orientation(closest_edge.front().x, closest_edge.front().y, closest_edge.back().x, closest_edge.back().y);
148- double result1 = orientation (closest_edge.front ().x , closest_edge.front ().y , closest_edge.back ().x , closest_edge.back ().y );
149-
150- std::vector<std::pair<int , int >> antipodal_pairs = getAllAntipodalPairs (footprint);
151- double footprint_width = INFINITY;
152- size_t closest_ap_pair = INFINITY;
153- for (int i = 0 ; i < antipodal_pairs.size (); ++i)
154- {
155- const auto & ap_pair = antipodal_pairs[i];
156- const geometry_msgs::Point& p1 = footprint[ap_pair.first ];
157- const geometry_msgs::Point& p2 = footprint[ap_pair.second ];
158- const double dist = distance (p1.x , p1.y , p2.x , p2.y );
159- if (dist < footprint_width)
160- {
161- footprint_width = dist;
162- closest_ap_pair = i;
163- }
164- }
165- const geometry_msgs::Point& p1 = footprint[antipodal_pairs[closest_ap_pair].first ];
166- const geometry_msgs::Point& p2 = footprint[antipodal_pairs[closest_ap_pair].second ];
167- double result = orientation (p1.x , p1.y , p2.x , p2.y );
168-
169- ROS_WARN_STREAM (result1 << " " <<result);
170195 return result;
171196}
172197
@@ -188,6 +213,14 @@ geometry_msgs::Point toPoint(geometry_msgs::Point32 pt)
188213 return point;
189214}
190215
216+ geometry_msgs::Point toPoint (Eigen::Vector2d pt)
217+ {
218+ geometry_msgs::Point point;
219+ point.x = pt.x ();
220+ point.y = pt.y ();
221+ return point;
222+ }
223+
191224geometry_msgs::Polygon toPolygon (std::vector<geometry_msgs::Point> pts)
192225{
193226 geometry_msgs::Polygon polygon;
@@ -207,6 +240,13 @@ std::vector<geometry_msgs::Point> toPointVector(geometry_msgs::Polygon polygon)
207240 return pts;
208241}
209242
243+ std::string toString (const std::vector<double >& numbers)
244+ {
245+ std::stringstream ss;
246+ std::for_each (numbers.begin (), numbers.end (), [&](double nb) { ss << nb << " " ; });
247+ return ss.str ();
248+ }
249+
210250void transformFootprint (double x, double y, double theta, const std::vector<geometry_msgs::Point>& footprint_spec,
211251 std::vector<geometry_msgs::Point>& oriented_footprint)
212252{
0 commit comments