#include "math/box2d.h" #include "math/math_utils.h" #include "math/polygon2d.h" #include #include using namespace decision::math; namespace { double outer_prod(double x0, double y0, double x1, double y1, double x2, double y2) { return (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0); } double pt_seg_distance(double query_x, double query_y, double start_x, double start_y, double end_x, double end_y, double length) { const double x0 = query_x - start_x; const double y0 = query_y - start_y; const double dx = end_x - start_x; const double dy = end_y - start_y; const double proj = x0 * dx + y0 * dy; if (proj <= 0.0) { return hypot(x0, y0); } if (proj >= length * length) { return hypot(x0 - dx, y0 - dy); } return std::abs(x0 * dy - y0 * dx) / length; } } // namespace Box2d::Box2d(const Vec2d& center, const double heading, const double length, const double width) : _center(center) , _length(length) , _width(width) , _half_length(length / 2.0) , _half_width(width / 2.0) , _heading(heading) , _cos_heading(cos(heading)) , _sin_heading(sin(heading)) { CHECK_GT(_length, -kMathEpsilon); CHECK_GT(_width, -kMathEpsilon); } Box2d::Box2d(const Segment2d& axis, const double width) : _center(axis.center()) , _length(axis.length()) , _width(width) , _half_length(axis.length() / 2.0) , _half_width(width / 2.0) , _heading(axis.heading()) , _cos_heading(axis.cos_heading()) , _sin_heading(axis.sin_heading()) { CHECK_GT(_length, -kMathEpsilon); CHECK_GT(_width, -kMathEpsilon); } Box2d::Box2d(const AABox2d& aabox) : _center(aabox.center()) , _length(aabox.length()) , _width(aabox.width()) , _half_length(aabox.half_length()) , _half_width(aabox.half_width()) , _heading(0.0) , _cos_heading(1.0) , _sin_heading(0.0) { CHECK_GT(_length, -kMathEpsilon); CHECK_GT(_width, -kMathEpsilon); } Box2d Box2d::create_aa_box(const Vec2d& one_corner, const Vec2d& opponent_corner) { const double x1 = std::min(one_corner.x(), opponent_corner.x()); const double x2 = std::max(one_corner.x(), opponent_corner.x()); const double y1 = std::min(one_corner.y(), opponent_corner.y()); const double y2 = std::max(one_corner.y(), opponent_corner.y()); return Box2d({ (x1 + x2) / 2.0, (y1 + y2) / 2.0 }, 0.0, x2 - x1, y2 - y1); } void Box2d::get_all_corners(std::vector* const corners) const { if (corners == nullptr) { return; } const double dx1 = _cos_heading * _half_length; const double dy1 = _sin_heading * _half_length; const double dx2 = _sin_heading * _half_width; const double dy2 = -_cos_heading * _half_width; corners->clear(); corners->reserve(4); corners->emplace_back(_center.x() + dx1 + dx2, _center.y() + dy1 + dy2); corners->emplace_back(_center.x() + dx1 - dx2, _center.y() + dy1 - dy2); corners->emplace_back(_center.x() - dx1 - dx2, _center.y() - dy1 - dy2); corners->emplace_back(_center.x() - dx1 + dx2, _center.y() - dy1 + dy2); } bool Box2d::is_point_in(const Vec2d& point) const { const double x0 = point.x() - _center.x(); const double y0 = point.y() - _center.y(); const double dx = std::abs(x0 * _cos_heading + y0 * _sin_heading); const double dy = std::abs(x0 * _sin_heading - y0 * _cos_heading); return dx <= _half_length + kMathEpsilon && dy <= _half_width + kMathEpsilon; } bool Box2d::is_point_on_boundary(const Vec2d& point) const { const double x0 = point.x() - _center.x(); const double y0 = point.y() - _center.y(); const double dx = std::abs(x0 * _cos_heading + y0 * _sin_heading); const double dy = std::abs(x0 * _sin_heading - y0 * _cos_heading); return (std::abs(dx - _half_length) <= kMathEpsilon && dy <= _half_width + kMathEpsilon) || (std::abs(dy - _half_width) <= kMathEpsilon && dx <= _half_length + kMathEpsilon); } double Box2d::distance_to(const Vec2d& point) const { const double x0 = point.x() - _center.x(); const double y0 = point.y() - _center.y(); const double dx = std::abs(x0 * _cos_heading + y0 * _sin_heading) - _half_length; const double dy = std::abs(x0 * _sin_heading - y0 * _cos_heading) - _half_width; if (dx <= 0.0) { return std::max(0.0, dy); } if (dy <= 0.0) { return dx; } return hypot(dx, dy); } bool Box2d::has_overlap(const Segment2d& segment) const { if (segment.length() <= kMathEpsilon) { return is_point_in(segment.start()); } return distance_to(segment) <= kMathEpsilon; } double Box2d::distance_to(const Segment2d& segment) const { if (segment.length() <= kMathEpsilon) { return distance_to(segment.start()); } const double ref_x1 = segment.start().x() - _center.x(); const double ref_y1 = segment.start().y() - _center.y(); double x1 = ref_x1 * _cos_heading + ref_y1 * _sin_heading; double y1 = ref_x1 * _sin_heading - ref_y1 * _cos_heading; double box_x = _half_length; double box_y = _half_width; int gx1 = (x1 >= box_x ? 1 : (x1 <= -box_x ? -1 : 0)); int gy1 = (y1 >= box_y ? 1 : (y1 <= -box_y ? -1 : 0)); if (gx1 == 0 && gy1 == 0) { return 0.0; } const double ref_x2 = segment.end().x() - _center.x(); const double ref_y2 = segment.end().y() - _center.y(); double x2 = ref_x2 * _cos_heading + ref_y2 * _sin_heading; double y2 = ref_x2 * _sin_heading - ref_y2 * _cos_heading; int gx2 = (x2 >= box_x ? 1 : (x2 <= -box_x ? -1 : 0)); int gy2 = (y2 >= box_y ? 1 : (y2 <= -box_y ? -1 : 0)); if (gx2 == 0 && gy2 == 0) { return 0.0; } if (gx1 < 0 || (gx1 == 0 && gx2 < 0)) { x1 = -x1; gx1 = -gx1; x2 = -x2; gx2 = -gx2; } if (gy1 < 0 || (gy1 == 0 && gy2 < 0)) { y1 = -y1; gy1 = -gy1; y2 = -y2; gy2 = -gy2; } if (gx1 < gy1 || (gx1 == gy1 && gx2 < gy2)) { std::swap(x1, y1); std::swap(gx1, gy1); std::swap(x2, y2); std::swap(gx2, gy2); std::swap(box_x, box_y); } if (gx1 == 1 && gy1 == 1) { switch (gx2 * 3 + gy2) { case 4: return pt_seg_distance(box_x, box_y, x1, y1, x2, y2, segment.length()); case 3: return (x1 > x2) ? (x2 - box_x) : pt_seg_distance(box_x, box_y, x1, y1, x2, y2, segment.length()); case 2: return (x1 > x2) ? pt_seg_distance(box_x, -box_y, x1, y1, x2, y2, segment.length()) : pt_seg_distance(box_x, box_y, x1, y1, x2, y2, segment.length()); case -1: return outer_prod(x1, y1, x2, y2, box_x, -box_y) >= 0.0 ? 0.0 : pt_seg_distance(box_x, -box_y, x1, y1, x2, y2, segment.length()); case -4: return outer_prod(x1, y1, x2, y2, box_x, -box_y) <= 0.0 ? pt_seg_distance(box_x, -box_y, x1, y1, x2, y2, segment.length()) : (outer_prod(x1, y1, x2, y2, -box_x, box_y) <= 0.0 ? 0.0 : pt_seg_distance(-box_x, box_y, x1, y1, x2, y2, segment.length())); } } else { switch (gx2 * 3 + gy2) { case 4: return (x1 < x2) ? (x1 - box_x) : pt_seg_distance(box_x, box_y, x1, y1, x2, y2, segment.length()); case 3: return std::min(x1, x2) - box_x; case 1: case -2: return outer_prod(x1, y1, x2, y2, box_x, box_y) <= 0.0 ? 0.0 : pt_seg_distance(box_x, box_y, x1, y1, x2, y2, segment.length()); case -3: return 0.0; } } // CHECK(0) << "unimplemented state: " << gx1 << " " << gy1 << " " << gx2 << " " << gy2; return 0.0; } double Box2d::distance_to(const Box2d& box) const { return Polygon2d(box).distance_to(*this); } bool Box2d::has_overlap(const Box2d& box) const { const double shift_x = box.center_x() - _center.x(); const double shift_y = box.center_y() - _center.y(); const double dx1 = _cos_heading * _half_length; const double dy1 = _sin_heading * _half_length; const double dx2 = _sin_heading * _half_width; const double dy2 = -_cos_heading * _half_width; const double dx3 = box.cos_heading() * box.half_length(); const double dy3 = box.sin_heading() * box.half_length(); const double dx4 = box.sin_heading() * box.half_width(); const double dy4 = -box.cos_heading() * box.half_width(); return std::abs(shift_x * _cos_heading + shift_y * _sin_heading) <= std::abs(dx3 * _cos_heading + dy3 * _sin_heading) + std::abs(dx4 * _cos_heading + dy4 * _sin_heading) + _half_length && std::abs(shift_x * _sin_heading - shift_y * _cos_heading) <= std::abs(dx3 * _sin_heading - dy3 * _cos_heading) + std::abs(dx4 * _sin_heading - dy4 * _cos_heading) + _half_width && std::abs(shift_x * box.cos_heading() + shift_y * box.sin_heading()) <= std::abs(dx1 * box.cos_heading() + dy1 * box.sin_heading()) + std::abs(dx2 * box.cos_heading() + dy2 * box.sin_heading()) + box.half_length() && std::abs(shift_x * box.sin_heading() - shift_y * box.cos_heading()) <= std::abs(dx1 * box.sin_heading() - dy1 * box.cos_heading()) + std::abs(dx2 * box.sin_heading() - dy2 * box.cos_heading()) + box.half_width(); } AABox2d Box2d::get_aa_box() const { const double dx1 = std::abs(_cos_heading * _half_length); const double dy1 = std::abs(_sin_heading * _half_length); const double dx2 = std::abs(_sin_heading * _half_width); const double dy2 = std::abs(_cos_heading * _half_width); return AABox2d(_center, (dx1 + dx2) * 2.0, (dy1 + dy2) * 2.0); } void Box2d::rotate_from_center(double rotate_angle) { _heading = normalize_angle(_heading + rotate_angle); _cos_heading = cos(_heading); _sin_heading = sin(_heading); } void Box2d::shift(const Vec2d& shift_vec) { _center += shift_vec; } std::string Box2d::debug_string() const { std::ostringstream sout; sout << "box2d ( center = " << _center.debug_string() << " heading = " << _heading << " length = " << _length << " width = " << _width << " )"; sout.flush(); return sout.str(); }