Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
166
libs/geometry/calipers_box.cpp
Normal file
166
libs/geometry/calipers_box.cpp
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
#include "geometry/calipers_box.hpp"
|
||||
|
||||
#include "geometry/bounding_box.hpp"
|
||||
#include "geometry/convex_hull.hpp"
|
||||
#include "geometry/line2d.hpp"
|
||||
#include "geometry/polygon.hpp"
|
||||
#include "geometry/segment2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
// Checks whether (p1 - p) x (p2 - p) >= 0.
|
||||
bool IsCCWNeg(PointD const & p1, PointD const & p2, PointD const & p, double eps)
|
||||
{
|
||||
return robust::OrientedS(p1, p2, p) > -eps;
|
||||
}
|
||||
|
||||
PointD Ort(PointD const & p)
|
||||
{
|
||||
return PointD(-p.y, p.x);
|
||||
}
|
||||
|
||||
// For each facet of the |hull| calls |fn| with the smallest rectangle
|
||||
// containing the hull and with one side collinear to the facet.
|
||||
template <typename Fn>
|
||||
void ForEachRect(ConvexHull const & hull, Fn && fn)
|
||||
{
|
||||
ASSERT_GREATER(hull.Size(), 2, ());
|
||||
|
||||
size_t j = 0, k = 0, l = 0;
|
||||
for (size_t i = 0; i < hull.Size(); ++i)
|
||||
{
|
||||
auto const ab = hull.SegmentAt(i).Dir();
|
||||
|
||||
j = std::max(j, i + 1);
|
||||
while (DotProduct(ab, hull.SegmentAt(j).Dir()) > CalipersBox::kEps)
|
||||
++j;
|
||||
|
||||
k = std::max(k, j);
|
||||
while (CrossProduct(ab, hull.SegmentAt(k).Dir()) > CalipersBox::kEps)
|
||||
++k;
|
||||
|
||||
l = std::max(l, k);
|
||||
while (DotProduct(ab, hull.SegmentAt(l).Dir()) < -CalipersBox::kEps)
|
||||
++l;
|
||||
|
||||
auto const oab = Ort(ab);
|
||||
std::array<Line2D, 4> const lines = {Line2D(hull.PointAt(i), ab), Line2D(hull.PointAt(j), oab),
|
||||
Line2D(hull.PointAt(k), ab), Line2D(hull.PointAt(l), oab)};
|
||||
std::vector<PointD> corners;
|
||||
for (size_t i = 0; i < lines.size(); ++i)
|
||||
{
|
||||
auto const j = (i + 1) % lines.size();
|
||||
auto result = Intersect(lines[i], lines[j], CalipersBox::kEps);
|
||||
if (result.m_type == IntersectionResult::Type::One)
|
||||
corners.push_back(result.m_point);
|
||||
}
|
||||
|
||||
if (corners.size() != 4)
|
||||
continue;
|
||||
|
||||
auto const it = std::min_element(corners.begin(), corners.end());
|
||||
std::rotate(corners.begin(), it, corners.end());
|
||||
|
||||
fn(std::move(corners));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CalipersBox::CalipersBox(std::vector<PointD> const & points) : m_points({})
|
||||
{
|
||||
ConvexHull hull(points, kEps);
|
||||
|
||||
if (hull.Size() < 3)
|
||||
{
|
||||
m_points = hull.Points();
|
||||
return;
|
||||
}
|
||||
|
||||
double bestArea = std::numeric_limits<double>::max();
|
||||
std::vector<PointD> bestPoints;
|
||||
ForEachRect(hull, [&](std::vector<PointD> && points)
|
||||
{
|
||||
ASSERT_EQUAL(points.size(), 4, ());
|
||||
double const area = GetPolygonArea(points.begin(), points.end());
|
||||
if (area < bestArea)
|
||||
{
|
||||
bestArea = area;
|
||||
bestPoints = std::move(points);
|
||||
}
|
||||
});
|
||||
|
||||
if (!bestPoints.empty())
|
||||
{
|
||||
ASSERT_EQUAL(bestPoints.size(), 4, ());
|
||||
m_points = std::move(bestPoints);
|
||||
}
|
||||
else
|
||||
m_points = BoundingBox(points).Points();
|
||||
}
|
||||
|
||||
void CalipersBox::Deserialize(std::vector<PointD> && points)
|
||||
{
|
||||
ASSERT_EQUAL(points.size(), 4, ());
|
||||
|
||||
// 1. Stable after ser-des.
|
||||
m_points = std::move(points);
|
||||
ASSERT(TestValid(), ());
|
||||
|
||||
// 2. Stable with input.
|
||||
// #ifdef DEBUG
|
||||
// CalipersBox test(m_points);
|
||||
// ASSERT(test.TestValid(), ());
|
||||
// *this = std::move(test);
|
||||
// #endif
|
||||
}
|
||||
|
||||
void CalipersBox::Normalize()
|
||||
{
|
||||
m_points.erase(std::unique(m_points.begin(), m_points.end()), m_points.end());
|
||||
if (m_points.size() == 3)
|
||||
{
|
||||
if (m_points.front() == m_points.back())
|
||||
m_points.pop_back();
|
||||
else
|
||||
m_points.push_back(m_points.back());
|
||||
}
|
||||
}
|
||||
|
||||
bool CalipersBox::TestValid() const
|
||||
{
|
||||
size_t const n = m_points.size();
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
if (!IsCCWNeg(m_points[i], m_points[(i + 1) % n], m_points[(i + 2) % n], kEps))
|
||||
return false;
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
bool CalipersBox::HasPoint(PointD const & p, double eps) const
|
||||
{
|
||||
auto const n = m_points.size();
|
||||
switch (n)
|
||||
{
|
||||
case 0: return false;
|
||||
case 1: return AlmostEqualAbs(m_points[0], p, eps);
|
||||
case 2: return IsPointOnSegmentEps(p, m_points[0], m_points[1], eps);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
auto const & a = m_points[i];
|
||||
auto const & b = m_points[(i + 1) % n];
|
||||
if (!IsCCWNeg(b, p, a, eps))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace m2
|
||||
Loading…
Add table
Add a link
Reference in a new issue