Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
82
libs/geometry/CMakeLists.txt
Normal file
82
libs/geometry/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
project(geometry)
|
||||
|
||||
set(SRC
|
||||
algorithm.cpp
|
||||
algorithm.hpp
|
||||
angles.cpp
|
||||
angles.hpp
|
||||
any_rect2d.hpp
|
||||
area_on_earth.cpp
|
||||
area_on_earth.hpp
|
||||
avg_vector.hpp
|
||||
bounding_box.cpp
|
||||
bounding_box.hpp
|
||||
calipers_box.cpp
|
||||
calipers_box.hpp
|
||||
cellid.hpp
|
||||
circle_on_earth.cpp
|
||||
circle_on_earth.hpp
|
||||
clipping.cpp
|
||||
clipping.hpp
|
||||
convex_hull.cpp
|
||||
convex_hull.hpp
|
||||
covering.hpp
|
||||
covering_utils.hpp
|
||||
diamond_box.cpp
|
||||
diamond_box.hpp
|
||||
distance_on_sphere.cpp
|
||||
distance_on_sphere.hpp
|
||||
latlon.cpp
|
||||
latlon.hpp
|
||||
line2d.cpp
|
||||
line2d.hpp
|
||||
mercator.cpp
|
||||
mercator.hpp
|
||||
meter.hpp
|
||||
nearby_points_sweeper.cpp
|
||||
nearby_points_sweeper.hpp
|
||||
oblate_spheroid.cpp
|
||||
oblate_spheroid.hpp
|
||||
packer.cpp
|
||||
packer.hpp
|
||||
parametrized_segment.hpp
|
||||
point2d.hpp
|
||||
point3d.hpp
|
||||
point_with_altitude.cpp
|
||||
point_with_altitude.hpp
|
||||
polygon.hpp
|
||||
intersection_score.hpp
|
||||
polyline2d.hpp
|
||||
rect2d.hpp
|
||||
rect_intersect.hpp
|
||||
region2d.hpp
|
||||
robust_orientation.cpp
|
||||
robust_orientation.hpp
|
||||
screenbase.cpp
|
||||
screenbase.hpp
|
||||
segment2d.cpp
|
||||
segment2d.hpp
|
||||
simplification.hpp
|
||||
smoothing.cpp
|
||||
smoothing.hpp
|
||||
spline.cpp
|
||||
spline.hpp
|
||||
transformations.hpp
|
||||
tree4d.hpp
|
||||
triangle2d.cpp
|
||||
triangle2d.hpp
|
||||
)
|
||||
|
||||
if (NOT PLATFORM_IPHONE AND NOT PLATFORM_ANDROID)
|
||||
append(SRC
|
||||
region2d/binary_operators.cpp
|
||||
region2d/binary_operators.hpp
|
||||
region2d/boost_concept.hpp
|
||||
)
|
||||
endif()
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} base)
|
||||
|
||||
omim_add_test_subdirectory(geometry_tests)
|
||||
77
libs/geometry/algorithm.cpp
Normal file
77
libs/geometry/algorithm.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#include "geometry/algorithm.hpp"
|
||||
#include "geometry/triangle2d.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
namespace m2
|
||||
{
|
||||
// CalculatePolyLineCenter -------------------------------------------------------------------------
|
||||
void CalculatePolyLineCenter::operator()(m2::PointD const & pt)
|
||||
{
|
||||
m_length += (m_poly.empty() ? 0.0 : m_poly.back().m_p.Length(pt));
|
||||
m_poly.emplace_back(pt, m_length);
|
||||
}
|
||||
|
||||
PointD CalculatePolyLineCenter::GetResult() const
|
||||
{
|
||||
using TIter = std::vector<Value>::const_iterator;
|
||||
|
||||
double const l = m_length / 2.0;
|
||||
|
||||
TIter e = lower_bound(m_poly.begin(), m_poly.end(), Value(m2::PointD(0, 0), l));
|
||||
if (e == m_poly.begin())
|
||||
{
|
||||
/// @todo It's very strange, but we have linear objects with zero length.
|
||||
LOG(LWARNING, ("Zero length linear object"));
|
||||
return e->m_p;
|
||||
}
|
||||
|
||||
TIter b = e - 1;
|
||||
|
||||
double const f = (l - b->m_len) / (e->m_len - b->m_len);
|
||||
|
||||
// For safety reasons (floating point calculations) do comparison instead of ASSERT.
|
||||
if (0.0 <= f && f <= 1.0)
|
||||
return (b->m_p * (1 - f) + e->m_p * f);
|
||||
else
|
||||
return ((b->m_p + e->m_p) / 2.0);
|
||||
}
|
||||
|
||||
// CalculatePointOnSurface -------------------------------------------------------------------------
|
||||
CalculatePointOnSurface::CalculatePointOnSurface(m2::RectD const & rect)
|
||||
: m_rectCenter(rect.Center())
|
||||
, m_center(m_rectCenter)
|
||||
, m_squareDistanceToApproximate(std::numeric_limits<double>::max())
|
||||
{}
|
||||
|
||||
void CalculatePointOnSurface::operator()(PointD const & p1, PointD const & p2, PointD const & p3)
|
||||
{
|
||||
if (m_squareDistanceToApproximate == 0.0)
|
||||
return;
|
||||
if (m2::IsPointInsideTriangle(m_rectCenter, p1, p2, p3))
|
||||
{
|
||||
m_center = m_rectCenter;
|
||||
m_squareDistanceToApproximate = 0.0;
|
||||
return;
|
||||
}
|
||||
PointD triangleCenter(p1);
|
||||
triangleCenter += p2;
|
||||
triangleCenter += p3;
|
||||
triangleCenter = triangleCenter / 3.0;
|
||||
|
||||
double triangleDistance = m_rectCenter.SquaredLength(triangleCenter);
|
||||
if (triangleDistance <= m_squareDistanceToApproximate)
|
||||
{
|
||||
m_center = triangleCenter;
|
||||
m_squareDistanceToApproximate = triangleDistance;
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateBoundingBox ----------------------------------------------------------------------------
|
||||
void CalculateBoundingBox::operator()(PointD const & p)
|
||||
{
|
||||
// Works just fine. If you don't belive me, see geometry/rect2d.hpp.
|
||||
// Pay attention to MakeEmpty and Add functions.
|
||||
m_boundingBox.Add(p);
|
||||
}
|
||||
} // namespace m2
|
||||
119
libs/geometry/algorithm.hpp
Normal file
119
libs/geometry/algorithm.hpp
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
// This class is used to calculate a point on a polyline
|
||||
// such as that the paths (not the distance) from that point
|
||||
// to both ends of a polyline have the same length.
|
||||
class CalculatePolyLineCenter
|
||||
{
|
||||
public:
|
||||
CalculatePolyLineCenter() : m_length(0.0) {}
|
||||
void operator()(PointD const & pt);
|
||||
|
||||
PointD GetResult() const;
|
||||
|
||||
private:
|
||||
struct Value
|
||||
{
|
||||
Value(PointD const & p, double l) : m_p(p), m_len(l) {}
|
||||
bool operator<(Value const & r) const { return (m_len < r.m_len); }
|
||||
PointD m_p;
|
||||
double m_len;
|
||||
};
|
||||
|
||||
std::vector<Value> m_poly;
|
||||
double m_length;
|
||||
};
|
||||
|
||||
// This class is used to calculate a point such as that
|
||||
// it lays on the figure triangle that is the closest to
|
||||
// figure bounding box center.
|
||||
class CalculatePointOnSurface
|
||||
{
|
||||
public:
|
||||
CalculatePointOnSurface(RectD const & rect);
|
||||
|
||||
void operator()(PointD const & p1, PointD const & p2, PointD const & p3);
|
||||
PointD GetResult() const { return m_center; }
|
||||
|
||||
private:
|
||||
PointD m_rectCenter;
|
||||
PointD m_center;
|
||||
double m_squareDistanceToApproximate;
|
||||
};
|
||||
|
||||
// Calculates the smallest rect that includes given geometry.
|
||||
class CalculateBoundingBox
|
||||
{
|
||||
public:
|
||||
void operator()(PointD const & p);
|
||||
RectD GetResult() const { return m_boundingBox; }
|
||||
|
||||
private:
|
||||
RectD m_boundingBox;
|
||||
};
|
||||
|
||||
namespace impl
|
||||
{
|
||||
template <typename TCalculator, typename TIterator>
|
||||
m2::PointD ApplyPointOnSurfaceCalculator(TIterator begin, TIterator end, TCalculator && calc)
|
||||
{
|
||||
std::array<m2::PointD, 3> triangle;
|
||||
while (begin != end)
|
||||
{
|
||||
for (auto i = 0; i < 3; ++i)
|
||||
{
|
||||
// Cannot use ASSERT_NOT_EQUAL, due to absence of an approbriate DebugPrint.
|
||||
ASSERT(begin != end, ("Not enough points to calculate point on surface"));
|
||||
triangle[i] = *begin++;
|
||||
}
|
||||
calc(triangle[0], triangle[1], triangle[2]);
|
||||
}
|
||||
return calc.GetResult();
|
||||
}
|
||||
|
||||
template <typename TCalculator, typename TIterator>
|
||||
auto ApplyCalculator(TIterator begin, TIterator end, TCalculator && calc) -> decltype(calc.GetResult())
|
||||
{
|
||||
for (; begin != end; ++begin)
|
||||
calc(*begin);
|
||||
return calc.GetResult();
|
||||
}
|
||||
|
||||
template <typename TCalculator, typename TIterator>
|
||||
auto SelectImplementation(TIterator begin, TIterator end, TCalculator && calc, std::true_type const &)
|
||||
-> decltype(calc.GetResult())
|
||||
{
|
||||
return impl::ApplyPointOnSurfaceCalculator(begin, end, std::forward<TCalculator>(calc));
|
||||
}
|
||||
|
||||
template <typename TCalculator, typename TIterator>
|
||||
auto SelectImplementation(TIterator begin, TIterator end, TCalculator && calc, std::false_type const &)
|
||||
-> decltype(calc.GetResult())
|
||||
{
|
||||
return impl::ApplyCalculator(begin, end, std::forward<TCalculator>(calc));
|
||||
}
|
||||
} // namespace impl
|
||||
|
||||
template <typename TCalculator, typename TIterator>
|
||||
auto ApplyCalculator(TIterator begin, TIterator end, TCalculator && calc) -> decltype(calc.GetResult())
|
||||
{
|
||||
return impl::SelectImplementation(begin, end, std::forward<TCalculator>(calc),
|
||||
std::is_same<CalculatePointOnSurface, std::remove_reference_t<TCalculator>>());
|
||||
}
|
||||
|
||||
template <typename TCalculator, typename TCollection>
|
||||
auto ApplyCalculator(TCollection && collection, TCalculator && calc) -> decltype(calc.GetResult())
|
||||
{
|
||||
return ApplyCalculator(std::begin(collection), std::end(collection), std::forward<TCalculator>(calc));
|
||||
}
|
||||
} // namespace m2
|
||||
48
libs/geometry/angles.cpp
Normal file
48
libs/geometry/angles.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#include "geometry/angles.hpp"
|
||||
|
||||
namespace ang
|
||||
{
|
||||
double AngleIn2PI(double ang)
|
||||
{
|
||||
double constexpr period = 2.0 * math::pi;
|
||||
ang = fmod(ang, period);
|
||||
if (ang < 0.0)
|
||||
ang += period;
|
||||
|
||||
if (AlmostEqualULPs(period, ang))
|
||||
return 0.0;
|
||||
|
||||
return ang;
|
||||
}
|
||||
|
||||
double GetShortestDistance(double rad1, double rad2)
|
||||
{
|
||||
double constexpr period = 2.0 * math::pi;
|
||||
rad1 = fmod(rad1, period);
|
||||
rad2 = fmod(rad2, period);
|
||||
|
||||
double res = rad2 - rad1;
|
||||
if (fabs(res) > math::pi)
|
||||
{
|
||||
if (res < 0.0)
|
||||
res = period + res;
|
||||
else
|
||||
res = -period + res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
double GetMiddleAngle(double a1, double a2)
|
||||
{
|
||||
double ang = (a1 + a2) / 2.0;
|
||||
|
||||
if (fabs(a1 - a2) > math::pi)
|
||||
{
|
||||
if (ang > 0.0)
|
||||
ang -= math::pi;
|
||||
else
|
||||
ang += math::pi;
|
||||
}
|
||||
return ang;
|
||||
}
|
||||
} // namespace ang
|
||||
117
libs/geometry/angles.hpp
Normal file
117
libs/geometry/angles.hpp
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/matrix.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
namespace ang
|
||||
{
|
||||
template <typename T>
|
||||
class Angle
|
||||
{
|
||||
public:
|
||||
Angle() = default;
|
||||
explicit Angle(T const & val) : m_val(val), m_sin(std::sin(val)), m_cos(std::cos(val)) {}
|
||||
Angle(T const & sin, T const & cos) : m_val(std::atan2(sin, cos)), m_sin(sin), m_cos(cos) {}
|
||||
|
||||
T const & val() const { return m_val; }
|
||||
|
||||
T const & sin() const { return m_sin; }
|
||||
|
||||
T const & cos() const { return m_cos; }
|
||||
|
||||
Angle<T> const & operator*=(math::Matrix<T, 3, 3> const & m)
|
||||
{
|
||||
m2::Point<T> pt0 = m2::Point<T>::Zero();
|
||||
m2::Point<T> pt1(m_cos, m_sin);
|
||||
|
||||
pt1 *= m;
|
||||
pt0 *= m;
|
||||
|
||||
m_val = atan2(pt1.y - pt0.y, pt1.x - pt0.x);
|
||||
m_sin = std::sin(m_val);
|
||||
m_cos = std::cos(m_val);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend std::string DebugPrint(Angle<T> const & ang) { return DebugPrint(ang.m_val); }
|
||||
|
||||
private:
|
||||
T m_val = 0;
|
||||
T m_sin = 0;
|
||||
T m_cos = 1;
|
||||
};
|
||||
|
||||
using AngleD = Angle<double>;
|
||||
using AngleF = Angle<float>;
|
||||
|
||||
template <typename T>
|
||||
Angle<T> const operator*(Angle<T> const & a, math::Matrix<T, 3, 3> const & m)
|
||||
{
|
||||
Angle<T> ret(a);
|
||||
ret *= m;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Returns an angle of vector [p1, p2] from x-axis directed to y-axis.
|
||||
/// Angle is in range [-pi, pi].
|
||||
template <typename T>
|
||||
T AngleTo(m2::Point<T> const & p1, m2::Point<T> const & p2)
|
||||
{
|
||||
return atan2(p2.y - p1.y, p2.x - p1.x);
|
||||
}
|
||||
|
||||
/// Returns an angle from vector [p, p1] to vector [p, p2]. A counterclockwise rotation.
|
||||
/// Angle is in range [0, 2 * pi]
|
||||
template <typename T>
|
||||
T TwoVectorsAngle(m2::Point<T> const & p, m2::Point<T> const & p1, m2::Point<T> const & p2)
|
||||
{
|
||||
T a = ang::AngleTo(p, p2) - ang::AngleTo(p, p1);
|
||||
while (a < 0)
|
||||
a += 2.0 * math::pi;
|
||||
return a;
|
||||
}
|
||||
|
||||
double AngleIn2PI(double ang);
|
||||
|
||||
/// @return Oriented angle (<= PI) from rad1 to rad2.
|
||||
/// >0 - clockwise, <0 - counterclockwise
|
||||
double GetShortestDistance(double rad1, double rad2);
|
||||
|
||||
double GetMiddleAngle(double a1, double a2);
|
||||
|
||||
/// @return If north is zero - azimuth between geographic north and [p1, p2] vector is returned.
|
||||
/// If north is not zero - it is treated as azimuth of some custom direction(eg magnetic north or
|
||||
/// some other direction), and azimuth between that direction and [p1, p2] is returned. Azimuth is
|
||||
/// in range [0, 2 * pi]
|
||||
template <typename T>
|
||||
T Azimuth(m2::Point<T> const & p1, m2::Point<T> const & p2, T north = 0)
|
||||
{
|
||||
T azimuth = math::pi2 - (AngleTo(p1, p2) + north);
|
||||
if (azimuth < 0)
|
||||
azimuth += 2.0 * math::pi;
|
||||
return azimuth;
|
||||
}
|
||||
|
||||
/// Average angle calcker. Can't find any suitable solution, so decided to do like this:
|
||||
/// Avg(i) = Avg(Avg(i-1), Ai);
|
||||
class AverageCalc
|
||||
{
|
||||
public:
|
||||
void Add(double a)
|
||||
{
|
||||
m_ang = (m_isEmpty ? a : GetMiddleAngle(m_ang, a));
|
||||
m_isEmpty = false;
|
||||
}
|
||||
|
||||
double GetAverage() const { return m_ang; }
|
||||
|
||||
private:
|
||||
double m_ang = 0.0;
|
||||
bool m_isEmpty = true;
|
||||
};
|
||||
} // namespace ang
|
||||
234
libs/geometry/any_rect2d.hpp
Normal file
234
libs/geometry/any_rect2d.hpp
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/angles.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
#include "geometry/rect_intersect.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
/// axis aligned rect
|
||||
template <typename T>
|
||||
class AnyRect
|
||||
{
|
||||
public:
|
||||
using Corners = std::array<Point<T>, 4>;
|
||||
|
||||
AnyRect() = default;
|
||||
|
||||
/// creating from regular rect
|
||||
explicit AnyRect(Rect<T> const & r)
|
||||
{
|
||||
if (r.IsValid())
|
||||
{
|
||||
m_zero = Point<T>(r.minX(), r.minY());
|
||||
m_rect = Rect<T>(0, 0, r.SizeX(), r.SizeY());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_zero = Point<T>::Zero();
|
||||
m_rect = r;
|
||||
}
|
||||
}
|
||||
|
||||
AnyRect(Point<T> const & zero, ang::Angle<T> const & angle, Rect<T> const & r) : m_angle(angle), m_rect(r)
|
||||
{
|
||||
m_zero = Convert(zero, Point<T>(1, 0), Point<T>(0, 1), i(), j());
|
||||
}
|
||||
|
||||
Point<T> const & LocalZero() const { return m_zero; }
|
||||
|
||||
Point<T> GlobalZero() const { return Convert(m_zero, i(), j(), Point<T>(1, 0), Point<T>(0, 1)); }
|
||||
|
||||
Point<T> i() const { return Point<T>(m_angle.cos(), m_angle.sin()); }
|
||||
|
||||
Point<T> j() const { return Point<T>(-m_angle.sin(), m_angle.cos()); }
|
||||
|
||||
void SetAngle(ang::Angle<T> const & a)
|
||||
{
|
||||
Point<T> glbZero = GlobalZero();
|
||||
|
||||
m_angle = a;
|
||||
m_zero = Convert(glbZero, Point<T>(1, 0), Point<T>(0, 1), i(), j());
|
||||
}
|
||||
|
||||
ang::Angle<T> const & Angle() const { return m_angle; }
|
||||
|
||||
Point<T> GlobalCenter() const { return ConvertFrom(m_rect.Center()); }
|
||||
|
||||
Point<T> LocalCenter() const { return m_rect.Center(); }
|
||||
|
||||
T GetMaxSize() const { return max(m_rect.SizeX(), m_rect.SizeY()); }
|
||||
|
||||
bool EqualDxDy(AnyRect<T> const & r, T eps) const
|
||||
{
|
||||
Corners arr1;
|
||||
GetGlobalPoints(arr1);
|
||||
std::sort(arr1.begin(), arr1.end());
|
||||
|
||||
Corners arr2;
|
||||
r.GetGlobalPoints(arr2);
|
||||
std::sort(arr2.begin(), arr2.end());
|
||||
|
||||
for (size_t i = 0; i < arr1.size(); ++i)
|
||||
if (!arr1[i].EqualDxDy(arr2[i], eps))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsPointInside(Point<T> const & pt) const { return m_rect.IsPointInside(ConvertTo(pt)); }
|
||||
|
||||
bool IsRectInside(AnyRect<T> const & r) const
|
||||
{
|
||||
Corners pts;
|
||||
r.GetGlobalPoints(pts);
|
||||
ConvertTo(pts);
|
||||
return m_rect.IsPointInside(pts[0]) && m_rect.IsPointInside(pts[1]) && m_rect.IsPointInside(pts[2]) &&
|
||||
m_rect.IsPointInside(pts[3]);
|
||||
}
|
||||
|
||||
bool IsIntersect(AnyRect<T> const & r) const
|
||||
{
|
||||
if (r.GetLocalRect() == Rect<T>())
|
||||
return false;
|
||||
Corners pts;
|
||||
r.GetGlobalPoints(pts);
|
||||
ConvertTo(pts);
|
||||
|
||||
{
|
||||
Rect<T> r1;
|
||||
for (auto const & p : pts)
|
||||
r1.Add(p);
|
||||
|
||||
if (!GetLocalRect().IsIntersect(r1))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (r.IsRectInside(*this))
|
||||
return true;
|
||||
|
||||
if (IsRectInside(r))
|
||||
return true;
|
||||
|
||||
return Intersect(GetLocalRect(), pts[0], pts[1]) || Intersect(GetLocalRect(), pts[1], pts[2]) ||
|
||||
Intersect(GetLocalRect(), pts[2], pts[3]) || Intersect(GetLocalRect(), pts[3], pts[0]);
|
||||
}
|
||||
|
||||
/// Convert into coordinate system of this AnyRect
|
||||
Point<T> ConvertTo(Point<T> const & p) const
|
||||
{
|
||||
Point<T> i1(1, 0);
|
||||
Point<T> j1(0, 1);
|
||||
return Convert(p - Convert(m_zero, i(), j(), i1, j1), i1, j1, i(), j());
|
||||
}
|
||||
|
||||
void ConvertTo(Corners & pts) const
|
||||
{
|
||||
for (auto & p : pts)
|
||||
p = ConvertTo(p);
|
||||
}
|
||||
|
||||
/// Convert into global coordinates from the local coordinates of this AnyRect
|
||||
Point<T> ConvertFrom(Point<T> const & p) const
|
||||
{
|
||||
return Convert(p + m_zero, i(), j(), Point<T>(1, 0), Point<T>(0, 1));
|
||||
}
|
||||
|
||||
Rect<T> const & GetLocalRect() const { return m_rect; }
|
||||
|
||||
Rect<T> GetGlobalRect() const
|
||||
{
|
||||
Corners pts;
|
||||
GetGlobalPoints(pts);
|
||||
|
||||
Rect<T> res;
|
||||
for (auto const & p : pts)
|
||||
res.Add(p);
|
||||
return res;
|
||||
}
|
||||
|
||||
void GetGlobalPoints(Corners & pts) const
|
||||
{
|
||||
pts[0] = ConvertFrom(Point<T>(m_rect.minX(), m_rect.minY()));
|
||||
pts[1] = ConvertFrom(Point<T>(m_rect.minX(), m_rect.maxY()));
|
||||
pts[2] = ConvertFrom(Point<T>(m_rect.maxX(), m_rect.maxY()));
|
||||
pts[3] = ConvertFrom(Point<T>(m_rect.maxX(), m_rect.minY()));
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
void Inflate(U const & dx, U const & dy)
|
||||
{
|
||||
m_rect.Inflate(dx, dy);
|
||||
}
|
||||
|
||||
void Add(AnyRect<T> const & r)
|
||||
{
|
||||
Corners pts;
|
||||
r.GetGlobalPoints(pts);
|
||||
ConvertTo(pts);
|
||||
for (auto const & p : pts)
|
||||
m_rect.Add(p);
|
||||
}
|
||||
|
||||
void Offset(Point<T> const & p) { m_zero = ConvertTo(ConvertFrom(m_zero) + p); }
|
||||
|
||||
Point<T> const Center() const { return ConvertFrom(m_rect.Center()); }
|
||||
|
||||
void SetSizesToIncludePoint(Point<T> const & p) { m_rect.SetSizesToIncludePoint(ConvertTo(p)); }
|
||||
|
||||
friend std::string DebugPrint(AnyRect<T> const & r)
|
||||
{
|
||||
return "{ Zero = " + DebugPrint(r.m_zero) + ", Rect = " + DebugPrint(r.m_rect) +
|
||||
", Ang = " + DebugPrint(r.m_angle) + " }";
|
||||
}
|
||||
|
||||
private:
|
||||
static Point<T> Convert(Point<T> const & p, Point<T> const & fromI, Point<T> const & fromJ, Point<T> const & toI,
|
||||
Point<T> const & toJ)
|
||||
{
|
||||
Point<T> res;
|
||||
|
||||
res.x = p.x * DotProduct(fromI, toI) + p.y * DotProduct(fromJ, toI);
|
||||
res.y = p.x * DotProduct(fromI, toJ) + p.y * DotProduct(fromJ, toJ);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ang::Angle<T> m_angle;
|
||||
|
||||
Point<T> m_zero{};
|
||||
Rect<T> m_rect{};
|
||||
};
|
||||
|
||||
using AnyRectD = AnyRect<double>;
|
||||
using AnyRectF = AnyRect<float>;
|
||||
|
||||
template <typename T>
|
||||
AnyRect<T> Offset(AnyRect<T> const & r, Point<T> const & pt)
|
||||
{
|
||||
AnyRect<T> res(r);
|
||||
res.Offset(pt);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
AnyRect<T> Inflate(AnyRect<T> const & r, U const & dx, U const & dy)
|
||||
{
|
||||
AnyRect<T> res = r;
|
||||
res.Inflate(dx, dy);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
AnyRect<T> Inflate(AnyRect<T> const & r, Point<U> const & pt)
|
||||
{
|
||||
return Inflate(r, pt.x, pt.y);
|
||||
}
|
||||
} // namespace m2
|
||||
78
libs/geometry/area_on_earth.cpp
Normal file
78
libs/geometry/area_on_earth.cpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#include "geometry/area_on_earth.hpp"
|
||||
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
#include "geometry/point3d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace ms
|
||||
{
|
||||
namespace
|
||||
{
|
||||
double constexpr kEarthRadiusMetersSquared = kEarthRadiusMeters * kEarthRadiusMeters;
|
||||
|
||||
m3::PointD GetPointOnSphere(LatLon const & ll, double sphereRadius)
|
||||
{
|
||||
ASSERT(LatLon::kMinLat <= ll.m_lat && ll.m_lat <= LatLon::kMaxLat, (ll));
|
||||
ASSERT(LatLon::kMinLon <= ll.m_lon && ll.m_lon <= LatLon::kMaxLon, (ll));
|
||||
|
||||
double const latRad = math::DegToRad(ll.m_lat);
|
||||
double const lonRad = math::DegToRad(ll.m_lon);
|
||||
|
||||
double const x = sphereRadius * cos(latRad) * cos(lonRad);
|
||||
double const y = sphereRadius * cos(latRad) * sin(lonRad);
|
||||
double const z = sphereRadius * sin(latRad);
|
||||
|
||||
return {x, y, z};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Look to https://en.wikipedia.org/wiki/Solid_angle for more details.
|
||||
// Shortly:
|
||||
// It's possible to calculate area of triangle on sphere with it's solid angle.
|
||||
// Ω = A / R^2
|
||||
// Where Ω is solid angle of triangle, R - sphere radius and A - area of triangle on sphere.
|
||||
// So A = Ω * R^2
|
||||
double AreaOnEarth(LatLon const & ll1, LatLon const & ll2, LatLon const & ll3)
|
||||
{
|
||||
m3::PointD const a = GetPointOnSphere(ll1, 1.0 /* sphereRadius */);
|
||||
m3::PointD const b = GetPointOnSphere(ll2, 1.0 /* sphereRadius */);
|
||||
m3::PointD const c = GetPointOnSphere(ll3, 1.0 /* sphereRadius */);
|
||||
|
||||
double const triple = m3::DotProduct(a, m3::CrossProduct(b, c));
|
||||
|
||||
ASSERT(::AlmostEqualAbs(a.Length(), 1.0, 1e-5), ());
|
||||
ASSERT(::AlmostEqualAbs(b.Length(), 1.0, 1e-5), ());
|
||||
ASSERT(::AlmostEqualAbs(c.Length(), 1.0, 1e-5), ());
|
||||
|
||||
double constexpr lengthMultiplication = 1.0; // a.Length() * b.Length() * c.Length()
|
||||
double const abc = m3::DotProduct(a, b); // * c.Length() == 1
|
||||
double const acb = m3::DotProduct(a, c); // * b.Length() == 1
|
||||
double const bca = m3::DotProduct(b, c); // * a.Length() == 1
|
||||
|
||||
double const tanFromHalfSolidAngle = triple / (lengthMultiplication + abc + acb + bca);
|
||||
double const halfSolidAngle = atan(tanFromHalfSolidAngle);
|
||||
double const solidAngle = halfSolidAngle * 2.0;
|
||||
double const area = solidAngle * kEarthRadiusMetersSquared;
|
||||
return fabs(area);
|
||||
}
|
||||
|
||||
// Look to https://en.wikipedia.org/wiki/Solid_angle for details.
|
||||
// Shortly:
|
||||
// Ω = A / R^2
|
||||
// A is the spherical surface area which confined by solid angle.
|
||||
// R - sphere radius.
|
||||
// For circle: Ω = 2π(1 - cos(θ / 2)), where θ - is the cone apex angle.
|
||||
double CircleAreaOnEarth(double distanceOnSphereRadius)
|
||||
{
|
||||
double const theta = 2.0 * distanceOnSphereRadius / kEarthRadiusMeters;
|
||||
double constexpr kConst = 2 * math::pi * kEarthRadiusMetersSquared;
|
||||
double const sinValue = sin(theta / 4);
|
||||
// 1 - cos(θ / 2) = 2sin^2(θ / 4)
|
||||
return kConst * 2 * sinValue * sinValue;
|
||||
}
|
||||
} // namespace ms
|
||||
17
libs/geometry/area_on_earth.hpp
Normal file
17
libs/geometry/area_on_earth.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
namespace ms
|
||||
{
|
||||
// Returns area of triangle on earth.
|
||||
double AreaOnEarth(LatLon const & ll1, LatLon const & ll2, LatLon const & ll3);
|
||||
|
||||
// Area of the spherical cap that contains all points
|
||||
// within the distance |radius| from an arbitrary fixed point, measured
|
||||
// along the Earth surface.
|
||||
// In particular, the smallest cap spanning the whole Earth results
|
||||
// from radius = pi*EarthRadius.
|
||||
// For small enough radiuses, returns the value close to pi*|radius|^2.
|
||||
double CircleAreaOnEarth(double radius);
|
||||
} // namespace ms
|
||||
115
libs/geometry/avg_vector.hpp
Normal file
115
libs/geometry/avg_vector.hpp
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
namespace math
|
||||
{
|
||||
template <class T, size_t Dim>
|
||||
class AvgVector
|
||||
{
|
||||
public:
|
||||
explicit AvgVector(size_t count = 1) : m_count(count) { static_assert(std::is_floating_point<T>::value, ""); }
|
||||
|
||||
void SetCount(size_t count) { m_count = count; }
|
||||
|
||||
/// @param[in] Next measurement.
|
||||
/// @param[out] Average value.
|
||||
void Next(T * arr)
|
||||
{
|
||||
if (m_vectors.size() == m_count)
|
||||
m_vectors.pop_front();
|
||||
|
||||
m_vectors.push_back({});
|
||||
std::memcpy(m_vectors.back().data(), arr, Dim * sizeof(T));
|
||||
|
||||
if (m_vectors.size() > 1)
|
||||
CalcAverage(arr);
|
||||
}
|
||||
|
||||
private:
|
||||
using Cont = std::deque<std::array<T, Dim>>;
|
||||
using Value = typename Cont::value_type;
|
||||
|
||||
Cont m_vectors;
|
||||
size_t m_count;
|
||||
|
||||
static T Distance(Value const & a1, Value const & a2)
|
||||
{
|
||||
T res = 0;
|
||||
for (size_t i = 0; i < Dim; ++i)
|
||||
res += math::Pow2(a1[i] - a2[i]);
|
||||
|
||||
return std::sqrt(res);
|
||||
}
|
||||
|
||||
static void Average(Value const & a1, Value const & a2, T * res)
|
||||
{
|
||||
for (size_t i = 0; i < Dim; ++i)
|
||||
res[i] = (a1[i] + a2[i]) / 2.0;
|
||||
}
|
||||
|
||||
void CalcAverage(T * res) const
|
||||
{
|
||||
T minD = std::numeric_limits<T>::max();
|
||||
size_t I = 0, J = 1;
|
||||
|
||||
size_t const count = m_vectors.size();
|
||||
ASSERT_GREATER(count, 1, ());
|
||||
for (size_t i = 0; i < count - 1; ++i)
|
||||
{
|
||||
for (size_t j = i + 1; j < count; ++j)
|
||||
{
|
||||
T const d = Distance(m_vectors[i], m_vectors[j]);
|
||||
if (d < minD)
|
||||
{
|
||||
I = i;
|
||||
J = j;
|
||||
minD = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Average(m_vectors[I], m_vectors[J], res);
|
||||
}
|
||||
};
|
||||
|
||||
// Compass smoothing parameters
|
||||
// We're using technique described in
|
||||
// http://windowsteamblog.com/windows_phone/b/wpdev/archive/2010/09/08/using-the-accelerometer-on-windows-phone-7.aspx
|
||||
// In short it's a combination of low-pass filter to smooth the
|
||||
// small orientation changes and a threshold filter to get big changes fast.
|
||||
// k in the following formula: O(n) = O(n-1) + k * (I - O(n - 1));
|
||||
// smoothed heading angle. doesn't always correspond to the m_headingAngle
|
||||
// as we change the heading angle only if the delta between
|
||||
// smoothedHeadingRad and new heading value is bigger than smoothingThreshold.
|
||||
template <class T, size_t Dim>
|
||||
class LowPassVector
|
||||
{
|
||||
public:
|
||||
void SetFactor(T t) { m_factor = t; }
|
||||
|
||||
/// @param[in] Next measurement.
|
||||
/// @param[out] Average value.
|
||||
void Next(T * arr)
|
||||
{
|
||||
for (size_t i = 0; i < Dim; ++i)
|
||||
{
|
||||
m_val[i] = m_val[i] + m_factor * (arr[i] - m_val[i]);
|
||||
arr[i] = m_val[i];
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<T, Dim> m_val{};
|
||||
T m_factor = 0.15;
|
||||
};
|
||||
} // namespace math
|
||||
30
libs/geometry/bounding_box.cpp
Normal file
30
libs/geometry/bounding_box.cpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#include "geometry/bounding_box.hpp"
|
||||
|
||||
#include <algorithm> // std::min, std::max
|
||||
|
||||
namespace m2
|
||||
{
|
||||
BoundingBox::BoundingBox(std::vector<PointD> const & points)
|
||||
{
|
||||
for (auto const & p : points)
|
||||
Add(p);
|
||||
}
|
||||
|
||||
void BoundingBox::Add(double x, double y)
|
||||
{
|
||||
m_min.x = std::min(m_min.x, x);
|
||||
m_min.y = std::min(m_min.y, y);
|
||||
m_max.x = std::max(m_max.x, x);
|
||||
m_max.y = std::max(m_max.y, y);
|
||||
}
|
||||
|
||||
bool BoundingBox::HasPoint(double x, double y) const
|
||||
{
|
||||
return x >= m_min.x && x <= m_max.x && y >= m_min.y && y <= m_max.y;
|
||||
}
|
||||
|
||||
bool BoundingBox::HasPoint(double x, double y, double eps) const
|
||||
{
|
||||
return x >= m_min.x - eps && x <= m_max.x + eps && y >= m_min.y - eps && y <= m_max.y + eps;
|
||||
}
|
||||
} // namespace m2
|
||||
56
libs/geometry/bounding_box.hpp
Normal file
56
libs/geometry/bounding_box.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
class BoundingBox
|
||||
{
|
||||
public:
|
||||
BoundingBox() = default;
|
||||
BoundingBox(std::vector<PointD> const & points);
|
||||
|
||||
void Add(PointD const & p) { return Add(p.x, p.y); }
|
||||
void Add(double x, double y);
|
||||
|
||||
bool HasPoint(PointD const & p) const { return HasPoint(p.x, p.y); }
|
||||
bool HasPoint(double x, double y) const;
|
||||
|
||||
bool HasPoint(PointD const & p, double eps) const { return HasPoint(p.x, p.y, eps); }
|
||||
bool HasPoint(double x, double y, double eps) const;
|
||||
|
||||
PointD Min() const { return m_min; }
|
||||
PointD Max() const { return m_max; }
|
||||
|
||||
m2::RectD ToRect() const { return {Min(), Max()}; }
|
||||
|
||||
std::vector<m2::PointD> Points() const
|
||||
{
|
||||
std::vector<m2::PointD> points(4);
|
||||
points[0] = Min();
|
||||
points[2] = Max();
|
||||
points[1] = PointD(points[2].x, points[0].y);
|
||||
points[3] = PointD(points[0].x, points[2].y);
|
||||
return points;
|
||||
}
|
||||
|
||||
bool operator==(BoundingBox const & rhs) const { return m_min == rhs.m_min && m_max == rhs.m_max; }
|
||||
|
||||
DECLARE_VISITOR(visitor(m_min, "min"), visitor(m_max, "max"))
|
||||
DECLARE_DEBUG_PRINT(BoundingBox)
|
||||
|
||||
private:
|
||||
// Infinity can not be used with -ffast-math
|
||||
static double constexpr kLargestDouble = std::numeric_limits<double>::max();
|
||||
static double constexpr kLowestDouble = std::numeric_limits<double>::lowest();
|
||||
|
||||
m2::PointD m_min = m2::PointD(kLargestDouble, kLargestDouble);
|
||||
m2::PointD m_max = m2::PointD(kLowestDouble, kLowestDouble);
|
||||
};
|
||||
} // namespace m2
|
||||
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
|
||||
51
libs/geometry/calipers_box.hpp
Normal file
51
libs/geometry/calipers_box.hpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
// When the size of the convex hull over a set of |points| is less
|
||||
// than 3, stores convex hull explicitly, otherwise stores smallest
|
||||
// rectangle containing the hull. Note that at least one side of the
|
||||
// rectangle should contain a facet of the hull, and in general sides
|
||||
// of the rectangle may not be parallel to the axes. In any case,
|
||||
// CalipersBox stores points in counter-clockwise order.
|
||||
class CalipersBox
|
||||
{
|
||||
public:
|
||||
// 1e-12 is used here because of we are going to use the box on
|
||||
// Mercator plane, where the precision of all coords is 1e-5, so we
|
||||
// are off by two orders of magnitude from the precision of data.
|
||||
static double constexpr kEps = 1e-12;
|
||||
|
||||
CalipersBox() = default;
|
||||
explicit CalipersBox(std::vector<PointD> const & points);
|
||||
|
||||
// Used in CitiesBoundariesDecoder. Faster than ctor.
|
||||
void Deserialize(std::vector<PointD> && points);
|
||||
// Remove useless points in case of degenerate box. Used in unit tests.
|
||||
void Normalize();
|
||||
|
||||
bool TestValid() const;
|
||||
|
||||
std::vector<PointD> const & Points() const { return m_points; }
|
||||
|
||||
bool HasPoint(PointD const & p) const { return HasPoint(p, kEps); }
|
||||
bool HasPoint(double x, double y) const { return HasPoint(m2::PointD(x, y)); }
|
||||
|
||||
bool HasPoint(PointD const & p, double eps) const;
|
||||
bool HasPoint(double x, double y, double eps) const { return HasPoint(PointD(x, y), eps); }
|
||||
|
||||
bool operator==(CalipersBox const & rhs) const { return m_points == rhs.m_points; }
|
||||
|
||||
DECLARE_VISITOR(visitor(m_points, "points"))
|
||||
DECLARE_DEBUG_PRINT(CalipersBox)
|
||||
|
||||
private:
|
||||
std::vector<PointD> m_points;
|
||||
};
|
||||
} // namespace m2
|
||||
318
libs/geometry/cellid.hpp
Normal file
318
libs/geometry/cellid.hpp
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/base.hpp"
|
||||
#include "base/bits.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
template <int kDepthLevels = 31>
|
||||
class CellId
|
||||
{
|
||||
public:
|
||||
// Use enum to avoid linker errors.
|
||||
// Can't realize why static const or constexpr is invalid here ...
|
||||
enum
|
||||
{
|
||||
DEPTH_LEVELS = kDepthLevels
|
||||
};
|
||||
static uint8_t const MAX_CHILDREN = 4;
|
||||
static uint32_t const MAX_COORD = 1U << DEPTH_LEVELS;
|
||||
|
||||
CellId() : m_bits(0), m_level(0) { ASSERT(IsValid(), ()); }
|
||||
explicit CellId(std::string const & s) { *this = FromString(s); }
|
||||
|
||||
static CellId Root() { return CellId(0, 0); }
|
||||
static CellId FromBitsAndLevel(uint64_t bits, int level) { return CellId(bits, level); }
|
||||
static size_t TotalCellsOnLevel(size_t level) { return 1 << (2 * level); }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Simple getters
|
||||
int Level() const
|
||||
{
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
return m_level;
|
||||
}
|
||||
|
||||
CellId Parent() const
|
||||
{
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
ASSERT_GREATER(m_level, 0, ());
|
||||
return CellId(m_bits >> 2, m_level - 1);
|
||||
}
|
||||
|
||||
CellId AncestorAtLevel(int level) const
|
||||
{
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
ASSERT_GREATER_OR_EQUAL(m_level, level, ());
|
||||
return CellId(m_bits >> (2 * (m_level - level)), level);
|
||||
}
|
||||
|
||||
CellId Child(int8_t c) const
|
||||
{
|
||||
ASSERT(c >= 0 && c < 4, (c, m_bits, m_level));
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
ASSERT_LESS(m_level, DEPTH_LEVELS - 1, ());
|
||||
return CellId((m_bits << 2) | c, m_level + 1);
|
||||
}
|
||||
|
||||
char WhichChildOfParent() const
|
||||
{
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
ASSERT_GREATER(m_level, 0, ());
|
||||
return m_bits & 3;
|
||||
}
|
||||
|
||||
uint64_t SubTreeSize(int depth) const
|
||||
{
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
ASSERT(m_level < depth && depth <= DEPTH_LEVELS, (m_bits, m_level, depth));
|
||||
return TreeSizeForDepth(depth - m_level);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Operators
|
||||
bool operator==(CellId const & cellId) const
|
||||
{
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
ASSERT(cellId.IsValid(), (cellId.m_bits, cellId.m_level));
|
||||
return m_bits == cellId.m_bits && m_level == cellId.m_level;
|
||||
}
|
||||
|
||||
bool operator!=(CellId const & cellId) const { return !(*this == cellId); }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Conversion to/from string
|
||||
std::string ToString() const
|
||||
{
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
std::string result(m_level, '0');
|
||||
uint64_t bits = m_bits;
|
||||
for (int i = 0; i < m_level; ++i, bits >>= 2)
|
||||
result[m_level - 1 - i] += (bits & 3);
|
||||
ASSERT_EQUAL(*this, FromString(result), (m_bits, m_level, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Tests whether the string |s| is a valid CellId representation.
|
||||
// Note that an empty string is a valid CellId.
|
||||
static bool IsCellId(std::string const & s)
|
||||
{
|
||||
size_t const length = s.size();
|
||||
if (length >= DEPTH_LEVELS)
|
||||
return false;
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
if (s[i] < '0' || s[i] > '3')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static CellId FromString(std::string const & s)
|
||||
{
|
||||
ASSERT(IsCellId(s), (s));
|
||||
uint64_t bits = 0;
|
||||
size_t const level = s.size();
|
||||
ASSERT_LESS(level, static_cast<size_t>(DEPTH_LEVELS), (s));
|
||||
for (size_t i = 0; i < level; ++i)
|
||||
{
|
||||
ASSERT((s[i] >= '0') && (s[i] <= '3'), (s, i));
|
||||
bits = (bits << 2) | static_cast<uint64_t>(s[i] - '0');
|
||||
}
|
||||
return CellId(bits, static_cast<int>(level));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Conversion to/from point
|
||||
|
||||
// Cell area width and height.
|
||||
// Should be 1 for the bottom level cell.
|
||||
uint32_t Radius() const
|
||||
{
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
return 1 << (DEPTH_LEVELS - 1 - m_level);
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> XY() const
|
||||
{
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
std::pair<uint32_t, uint32_t> xy;
|
||||
bits::BitwiseSplit(m_bits, xy.first, xy.second);
|
||||
xy.first = 2 * xy.first + 1;
|
||||
xy.second = 2 * xy.second + 1;
|
||||
xy.first <<= DEPTH_LEVELS - 1 - m_level;
|
||||
xy.second <<= DEPTH_LEVELS - 1 - m_level;
|
||||
ASSERT_EQUAL(*this, FromXY(xy.first, xy.second, m_level), ());
|
||||
return xy;
|
||||
}
|
||||
|
||||
static CellId FromXY(uint32_t x, uint32_t y, int level)
|
||||
{
|
||||
ASSERT_LESS(level, static_cast<int>(DEPTH_LEVELS), (x, y, level));
|
||||
// Since MAX_COORD == 1 << DEPTH_LEVELS, if x|y == MAX_COORD, they should be decremented.
|
||||
if (x >= MAX_COORD)
|
||||
{
|
||||
ASSERT_EQUAL(x, static_cast<uint32_t>(MAX_COORD), (x, y, level));
|
||||
x = MAX_COORD - 1;
|
||||
}
|
||||
if (y >= MAX_COORD)
|
||||
{
|
||||
ASSERT_EQUAL(y, static_cast<uint32_t>(MAX_COORD), (x, y, level));
|
||||
y = MAX_COORD - 1;
|
||||
}
|
||||
x >>= DEPTH_LEVELS - level;
|
||||
y >>= DEPTH_LEVELS - level;
|
||||
uint64_t const bits = bits::BitwiseMerge(x, y);
|
||||
return CellId(bits, level);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Ordering
|
||||
struct LessLevelOrder
|
||||
{
|
||||
bool operator()(CellId<DEPTH_LEVELS> const & id1, CellId<DEPTH_LEVELS> const & id2) const
|
||||
{
|
||||
if (id1.m_level != id2.m_level)
|
||||
return id1.m_level < id2.m_level;
|
||||
if (id1.m_bits != id2.m_bits)
|
||||
return id1.m_bits < id2.m_bits;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct GreaterLevelOrder
|
||||
{
|
||||
bool operator()(CellId<DEPTH_LEVELS> const & id1, CellId<DEPTH_LEVELS> const & id2) const
|
||||
{
|
||||
if (id1.m_level != id2.m_level)
|
||||
return id1.m_level > id2.m_level;
|
||||
if (id1.m_bits != id2.m_bits)
|
||||
return id1.m_bits > id2.m_bits;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct LessPreOrder
|
||||
{
|
||||
bool operator()(CellId<DEPTH_LEVELS> const & id1, CellId<DEPTH_LEVELS> const & id2) const
|
||||
{
|
||||
int64_t const n1 = id1.ToInt64ZOrder(DEPTH_LEVELS);
|
||||
int64_t const n2 = id2.ToInt64ZOrder(DEPTH_LEVELS);
|
||||
ASSERT_EQUAL(n1 < n2, id1.ToString() < id2.ToString(), (id1, id2));
|
||||
return n1 < n2;
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Numbering
|
||||
|
||||
// Default ToInt64().
|
||||
int64_t ToInt64(int depth) const { return ToInt64ZOrder(depth); }
|
||||
|
||||
// Default FromInt64().
|
||||
static CellId FromInt64(int64_t v, int depth) { return FromInt64ZOrder(v, depth); }
|
||||
|
||||
// Returns the 1-based number this cell would get in the Z-curve ordering (pre-order
|
||||
// traversal of the quadtree) of all the cells in the tree of depth |depth|.
|
||||
// The tree of one vertex is assumed to have depth 1.
|
||||
int64_t ToInt64ZOrder(int depth) const
|
||||
{
|
||||
ASSERT(0 < depth && depth <= DEPTH_LEVELS, (m_bits, m_level, depth));
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
|
||||
if (m_level >= depth)
|
||||
return AncestorAtLevel(depth - 1).ToInt64ZOrder(depth);
|
||||
|
||||
uint64_t bits = m_bits;
|
||||
uint64_t res = 0;
|
||||
// When counting, group the nodes by their level.
|
||||
// All nodes to the left of the current one at its level
|
||||
// and, for every ancestor, all nodes to the left of the ancestor
|
||||
// at the ancestor's level will show up earlier during the traversal.
|
||||
// All ancestors are visited before the current node, so add +1 for them.
|
||||
// The result is 1-based, so add +1 for the node itself too.
|
||||
for (int i = 0; i <= m_level; ++i)
|
||||
{
|
||||
res += bits + 1;
|
||||
bits >>= 2;
|
||||
}
|
||||
|
||||
// By the same reasoning, if the children of every node are ordered
|
||||
// left to right, then all nodes at deeper levels that
|
||||
// are strictly to the left will be visited before the current one.
|
||||
bits = m_bits;
|
||||
for (int i = m_level + 1; i < depth; ++i)
|
||||
{
|
||||
bits <<= 2;
|
||||
res += bits;
|
||||
}
|
||||
|
||||
ASSERT_GREATER(res, 0, (m_bits, m_level));
|
||||
ASSERT_LESS_OR_EQUAL(res, TreeSizeForDepth(depth), (m_bits, m_level));
|
||||
return static_cast<int64_t>(res);
|
||||
}
|
||||
|
||||
// Returns the CellId with the 1-based number |v| in the Z-curve ordering (pre-order
|
||||
// traversal of the quadtree) of all the cells in the tree of depth |depth|.
|
||||
// The tree of one vertex is assumed to have depth 1.
|
||||
static CellId FromInt64ZOrder(int64_t v, int depth)
|
||||
{
|
||||
ASSERT_GREATER(v, 0, ());
|
||||
ASSERT(0 < depth && depth <= DEPTH_LEVELS, (v, depth));
|
||||
ASSERT_LESS_OR_EQUAL(static_cast<uint64_t>(v), TreeSizeForDepth(depth), ());
|
||||
uint64_t bits = 0;
|
||||
int level = 0;
|
||||
while (v > 1)
|
||||
{
|
||||
bits <<= 2;
|
||||
++level;
|
||||
uint64_t const subtreeSize = TreeSizeForDepth(depth - level);
|
||||
for (--v; static_cast<uint64_t>(v) > subtreeSize; v -= subtreeSize)
|
||||
++bits;
|
||||
}
|
||||
return CellId(bits, level);
|
||||
}
|
||||
|
||||
private:
|
||||
// Returns the total number of nodes in the tree of depth |depth|.
|
||||
// The tree of one vertex is assumed to have depth 1.
|
||||
// The first few values are 1, 5, 21, 85.
|
||||
static uint64_t TreeSizeForDepth(int depth)
|
||||
{
|
||||
ASSERT(0 < depth && depth <= DEPTH_LEVELS, (depth));
|
||||
return ((1ULL << 2 * depth) - 1) / 3ULL;
|
||||
}
|
||||
|
||||
CellId(uint64_t bits, int level) : m_bits(bits), m_level(level)
|
||||
{
|
||||
ASSERT_LESS(level, DEPTH_LEVELS, (bits, level));
|
||||
ASSERT_LESS(bits, 1ULL << m_level * 2, (bits, m_level));
|
||||
ASSERT(IsValid(), (m_bits, m_level));
|
||||
}
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
if (m_level < 0 || m_level >= DEPTH_LEVELS)
|
||||
return false;
|
||||
if (m_bits >= (1ULL << m_level * 2))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t m_bits;
|
||||
int m_level;
|
||||
};
|
||||
|
||||
template <int DEPTH_LEVELS>
|
||||
std::string DebugPrint(CellId<DEPTH_LEVELS> const & id)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out << "CellId<" << DEPTH_LEVELS << ">(\"" << id.ToString().c_str() << "\")";
|
||||
return out.str();
|
||||
}
|
||||
} // namespace m2
|
||||
68
libs/geometry/circle_on_earth.cpp
Normal file
68
libs/geometry/circle_on_earth.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include "geometry/circle_on_earth.hpp"
|
||||
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point3d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::vector<m3::PointD> CreateCircleOnNorth(double radiusMeters, double angleStepDegree)
|
||||
{
|
||||
double const angle = radiusMeters / ms::kEarthRadiusMeters;
|
||||
double const circleRadiusMeters = ms::kEarthRadiusMeters * sin(angle);
|
||||
|
||||
double const z = ms::kEarthRadiusMeters * cos(angle);
|
||||
|
||||
std::vector<m3::PointD> result;
|
||||
double const stepRad = math::DegToRad(angleStepDegree);
|
||||
for (double angleRad = 0; angleRad < 2 * math::pi; angleRad += stepRad)
|
||||
result.emplace_back(circleRadiusMeters * cos(angleRad), circleRadiusMeters * sin(angleRad), z);
|
||||
return result;
|
||||
}
|
||||
|
||||
ms::LatLon FromEarth3dToSpherical(m3::PointD const & vec)
|
||||
{
|
||||
ASSERT(AlmostEqualAbs(vec.Length(), ms::kEarthRadiusMeters, 1e-5), (vec.Length(), ms::kEarthRadiusMeters));
|
||||
|
||||
double sinLatRad = vec.z / ms::kEarthRadiusMeters;
|
||||
sinLatRad = math::Clamp(sinLatRad, -1.0, 1.0);
|
||||
double const cosLatRad = std::sqrt(1 - sinLatRad * sinLatRad);
|
||||
CHECK(-1.0 <= cosLatRad && cosLatRad <= 1.0, (cosLatRad));
|
||||
|
||||
double const latRad = asin(sinLatRad);
|
||||
double sinLonRad = vec.y / ms::kEarthRadiusMeters / cosLatRad;
|
||||
sinLonRad = math::Clamp(sinLonRad, -1.0, 1.0);
|
||||
double lonRad = asin(sinLonRad);
|
||||
if (vec.y > 0 && vec.x < 0)
|
||||
lonRad = math::pi - lonRad;
|
||||
else if (vec.y < 0 && vec.x < 0)
|
||||
lonRad = -(math::pi - fabs(lonRad));
|
||||
|
||||
auto const lat = math::RadToDeg(latRad);
|
||||
auto const lon = math::RadToDeg(lonRad);
|
||||
|
||||
return {lat, lon};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ms
|
||||
{
|
||||
std::vector<m2::PointD> CreateCircleGeometryOnEarth(ms::LatLon const & center, double radiusMeters,
|
||||
double angleStepDegree)
|
||||
{
|
||||
auto const circleOnNorth = CreateCircleOnNorth(radiusMeters, angleStepDegree);
|
||||
std::vector<m2::PointD> result;
|
||||
for (auto const & point3d : circleOnNorth)
|
||||
{
|
||||
auto const rotateByLat = point3d.RotateAroundY(90.0 - center.m_lat);
|
||||
auto const rotateByLon = rotateByLat.RotateAroundZ(center.m_lon);
|
||||
|
||||
auto const latlonRotated = FromEarth3dToSpherical(rotateByLon);
|
||||
result.emplace_back(mercator::FromLatLon(latlonRotated));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace ms
|
||||
12
libs/geometry/circle_on_earth.hpp
Normal file
12
libs/geometry/circle_on_earth.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace ms
|
||||
{
|
||||
std::vector<m2::PointD> CreateCircleGeometryOnEarth(ms::LatLon const & center, double radiusMeters,
|
||||
double angleStepDegree);
|
||||
} // namespace ms
|
||||
311
libs/geometry/clipping.cpp
Normal file
311
libs/geometry/clipping.cpp
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
#include "geometry/clipping.hpp"
|
||||
|
||||
#include "geometry/rect_intersect.hpp"
|
||||
#include "geometry/triangle2d.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
namespace
|
||||
{
|
||||
int GetRectSideIndex(int code)
|
||||
{
|
||||
if (code == m2::detail::LEFT)
|
||||
return 0;
|
||||
if (code == m2::detail::TOP)
|
||||
return 1;
|
||||
if (code == m2::detail::RIGHT)
|
||||
return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
using CornersT = std::array<m2::PointD, 4>;
|
||||
|
||||
template <class AddPointFnT>
|
||||
void InsertCorners(CornersT const & corners, m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3,
|
||||
AddPointFnT const & addPolygonPoint, int code1, int code2)
|
||||
{
|
||||
int cornerInd = GetRectSideIndex(code1);
|
||||
int endCornerInd = GetRectSideIndex(code2);
|
||||
|
||||
if (!IsPointInsideTriangle(corners[cornerInd], p1, p2, p3))
|
||||
{
|
||||
if (!IsPointInsideTriangle(corners[endCornerInd], p1, p2, p3))
|
||||
return;
|
||||
std::swap(cornerInd, endCornerInd);
|
||||
}
|
||||
|
||||
while (cornerInd != endCornerInd)
|
||||
{
|
||||
addPolygonPoint(corners[cornerInd]);
|
||||
cornerInd = (cornerInd + 1) % 4;
|
||||
}
|
||||
}
|
||||
|
||||
template <class AddPointFnT>
|
||||
bool IntersectEdge(m2::RectD const & rect, CornersT const & corners, m2::PointD const & pp1, m2::PointD const & pp2,
|
||||
m2::PointD const & pp3, AddPointFnT const & addPolygonPoint, int prevClipCode, int nextClipCode,
|
||||
int & firstClipCode, int & lastClipCode)
|
||||
{
|
||||
m2::PointD p1 = pp1;
|
||||
m2::PointD p2 = pp2;
|
||||
|
||||
if (m2::Intersect(rect, p1, p2, firstClipCode, lastClipCode))
|
||||
{
|
||||
if (firstClipCode != 0 && prevClipCode != 0 && ((firstClipCode & prevClipCode) == 0))
|
||||
InsertCorners(corners, pp1, pp2, pp3, addPolygonPoint, prevClipCode, firstClipCode);
|
||||
|
||||
addPolygonPoint(p1);
|
||||
addPolygonPoint(p2);
|
||||
|
||||
if (lastClipCode != 0 && nextClipCode != 0 && ((lastClipCode & nextClipCode) == 0) &&
|
||||
firstClipCode != lastClipCode && prevClipCode != nextClipCode)
|
||||
InsertCorners(corners, pp1, pp2, pp3, addPolygonPoint, lastClipCode, nextClipCode);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (prevClipCode != 0 && nextClipCode != 0)
|
||||
{
|
||||
InsertCorners(corners, pp1, pp2, pp3, addPolygonPoint, prevClipCode, nextClipCode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ClipTriangleByRect(m2::RectD const & rect, m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3,
|
||||
ClipTriangleByRectResultIt const & resultIterator)
|
||||
{
|
||||
if (rect.IsPointInside(p1) && rect.IsPointInside(p2) && rect.IsPointInside(p3))
|
||||
{
|
||||
resultIterator(p1, p2, p3);
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr double kEps = 1e-8;
|
||||
std::vector<m2::PointD> polygon;
|
||||
auto const addPolygonPoint = [&polygon](m2::PointD const & pt)
|
||||
{
|
||||
if (polygon.empty() || !polygon.back().EqualDxDy(pt, kEps))
|
||||
polygon.push_back(pt);
|
||||
};
|
||||
|
||||
CornersT const corners = {rect.LeftTop(), rect.RightTop(), rect.RightBottom(), rect.LeftBottom()};
|
||||
|
||||
int firstClipCode[3];
|
||||
int lastClipCode[3];
|
||||
bool intersected[3];
|
||||
|
||||
intersected[0] = IntersectEdge(rect, corners, p1, p2, p3, addPolygonPoint, 0, 0, firstClipCode[0], lastClipCode[0]);
|
||||
|
||||
intersected[1] =
|
||||
IntersectEdge(rect, corners, p2, p3, p1, addPolygonPoint, lastClipCode[0], 0, firstClipCode[1], lastClipCode[1]);
|
||||
|
||||
intersected[2] = IntersectEdge(
|
||||
rect, corners, p3, p1, p2, addPolygonPoint, lastClipCode[1] != 0 ? lastClipCode[1] : lastClipCode[0],
|
||||
firstClipCode[0] != 0 ? firstClipCode[0] : firstClipCode[1], firstClipCode[2], lastClipCode[2]);
|
||||
|
||||
int const intersectCount = intersected[0] + intersected[1] + intersected[2];
|
||||
if (intersectCount == 0)
|
||||
{
|
||||
if (IsPointInsideTriangle(rect.Center(), p1, p2, p3))
|
||||
{
|
||||
resultIterator(rect.LeftTop(), rect.RightTop(), rect.RightBottom());
|
||||
resultIterator(rect.RightBottom(), rect.LeftBottom(), rect.LeftTop());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (intersectCount == 1 && intersected[2])
|
||||
InsertCorners(corners, p1, p2, p3, addPolygonPoint, lastClipCode[2], firstClipCode[2]);
|
||||
|
||||
if (!polygon.empty() && polygon.back().EqualDxDy(polygon[0], kEps))
|
||||
polygon.pop_back();
|
||||
|
||||
if (polygon.size() < 3)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < polygon.size() - 2; ++i)
|
||||
resultIterator(polygon[0], polygon[i + 1], polygon[i + 2]);
|
||||
}
|
||||
|
||||
template <class FnT>
|
||||
void ClipPathByRectImpl(m2::RectD const & rect, std::vector<m2::PointD> const & path, FnT && fn)
|
||||
{
|
||||
size_t const sz = path.size();
|
||||
if (sz < 2)
|
||||
return;
|
||||
|
||||
// Divide spline into parts.
|
||||
m2::PointD p1, p2;
|
||||
int code1 = 0;
|
||||
int code2 = 0;
|
||||
m2::SharedSpline s;
|
||||
|
||||
for (size_t i = 0; i < sz - 1; i++)
|
||||
{
|
||||
p1 = path[i];
|
||||
p2 = path[i + 1];
|
||||
if (m2::Intersect(rect, p1, p2, code1, code2))
|
||||
{
|
||||
if (s.IsNull())
|
||||
s.Reset(new m2::Spline(sz - i));
|
||||
|
||||
s->AddPoint(p1);
|
||||
s->AddPoint(p2);
|
||||
|
||||
if (code2 != 0 || i + 2 == sz)
|
||||
{
|
||||
if (s->GetSize() > 1)
|
||||
fn(std::move(s));
|
||||
s.Reset(nullptr);
|
||||
}
|
||||
}
|
||||
else if (!s.IsNull() && !s->IsEmpty())
|
||||
{
|
||||
if (s->GetSize() > 1)
|
||||
fn(std::move(s));
|
||||
s.Reset(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class RectCase
|
||||
{
|
||||
Inside,
|
||||
Outside,
|
||||
Intersect
|
||||
};
|
||||
|
||||
RectCase GetRectCase(m2::RectD const & rect, std::vector<m2::PointD> const & path)
|
||||
{
|
||||
m2::RectD pathRect;
|
||||
for (auto const & p : path)
|
||||
pathRect.Add(p);
|
||||
|
||||
if (rect.IsRectInside(pathRect))
|
||||
return RectCase::Inside;
|
||||
|
||||
if (rect.IsIntersect(pathRect))
|
||||
return RectCase::Intersect;
|
||||
|
||||
return RectCase::Outside;
|
||||
}
|
||||
|
||||
std::vector<m2::SharedSpline> ClipSplineByRect(m2::RectD const & rect, m2::SharedSpline const & spline)
|
||||
{
|
||||
switch (GetRectCase(rect, spline->GetPath()))
|
||||
{
|
||||
case RectCase::Inside: return {spline};
|
||||
case RectCase::Outside: return {};
|
||||
case RectCase::Intersect:
|
||||
{
|
||||
std::vector<m2::SharedSpline> res;
|
||||
res.reserve(2); // keep previous behavior, but not sure that its actually needed
|
||||
ClipPathByRectImpl(rect, spline->GetPath(), base::MakeBackInsertFunctor(res));
|
||||
return res;
|
||||
}
|
||||
}
|
||||
CHECK(false, ("Unreachable"));
|
||||
return {};
|
||||
}
|
||||
|
||||
void ClipPathByRect(m2::RectD const & rect, std::vector<m2::PointD> && path,
|
||||
std::function<void(m2::SharedSpline &&)> const & fn)
|
||||
{
|
||||
switch (GetRectCase(rect, path))
|
||||
{
|
||||
case RectCase::Inside: fn(m2::SharedSpline(std::move(path))); break;
|
||||
case RectCase::Outside: break;
|
||||
case RectCase::Intersect: ClipPathByRectImpl(rect, path, fn); break;
|
||||
}
|
||||
}
|
||||
|
||||
void ClipPathByRectBeforeSmooth(m2::RectD const & rect, std::vector<m2::PointD> const & path,
|
||||
GuidePointsForSmooth & guidePoints, std::vector<std::vector<m2::PointD>> & clippedPaths)
|
||||
{
|
||||
if (path.size() < 2)
|
||||
return;
|
||||
|
||||
auto const rectCase = GetRectCase(rect, path);
|
||||
if (rectCase == RectCase::Outside)
|
||||
return;
|
||||
|
||||
m2::PointD guideFront;
|
||||
m2::PointD guideBack;
|
||||
double constexpr kEps = 1e-5;
|
||||
if (path.front().EqualDxDy(path.back(), kEps))
|
||||
{
|
||||
guideFront = path[path.size() - 2];
|
||||
guideBack = path[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
guideFront = path[0] + (path[0] - path[1]) * 2.0;
|
||||
guideBack = path.back() + (path.back() - path[path.size() - 2]) * 2.0;
|
||||
}
|
||||
|
||||
if (rectCase == RectCase::Inside)
|
||||
{
|
||||
clippedPaths.push_back(path);
|
||||
guidePoints.push_back({guideFront, guideBack});
|
||||
return;
|
||||
}
|
||||
|
||||
// Divide spline into parts.
|
||||
clippedPaths.reserve(2);
|
||||
std::vector<m2::PointD> currentPath;
|
||||
m2::PointD currentGuideFront;
|
||||
m2::PointD currentGuideBack;
|
||||
|
||||
auto const startCurrentPath = [&](size_t pos)
|
||||
{
|
||||
if (pos > 0)
|
||||
currentPath.push_back(path[pos - 1]);
|
||||
currentGuideFront = pos > 1 ? path[pos - 2] : guideFront;
|
||||
};
|
||||
|
||||
auto const finishCurrentPath = [&](size_t pos)
|
||||
{
|
||||
currentPath.push_back(path[pos]);
|
||||
currentGuideBack = pos < path.size() - 1 ? path[pos + 1] : guideBack;
|
||||
|
||||
clippedPaths.emplace_back(std::move(currentPath));
|
||||
guidePoints.push_back({currentGuideFront, currentGuideBack});
|
||||
|
||||
currentPath = {};
|
||||
};
|
||||
|
||||
for (size_t pos = 0; pos < path.size(); ++pos)
|
||||
{
|
||||
if (rect.IsPointInside(path[pos]))
|
||||
{
|
||||
if (currentPath.empty())
|
||||
startCurrentPath(pos);
|
||||
currentPath.push_back(path[pos]);
|
||||
}
|
||||
else if (!currentPath.empty())
|
||||
{
|
||||
finishCurrentPath(pos);
|
||||
}
|
||||
else if (pos > 0)
|
||||
{
|
||||
auto p1 = path[pos - 1];
|
||||
auto p2 = path[pos];
|
||||
if (m2::Intersect(rect, p1, p2))
|
||||
{
|
||||
startCurrentPath(pos);
|
||||
finishCurrentPath(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentPath.empty())
|
||||
{
|
||||
clippedPaths.emplace_back(std::move(currentPath));
|
||||
guidePoints.push_back({currentGuideFront, guideBack});
|
||||
}
|
||||
}
|
||||
} // namespace m2
|
||||
25
libs/geometry/clipping.hpp
Normal file
25
libs/geometry/clipping.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
#include "geometry/spline.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
using ClipTriangleByRectResultIt = std::function<void(m2::PointD const &, m2::PointD const &, m2::PointD const &)>;
|
||||
|
||||
void ClipTriangleByRect(m2::RectD const & rect, m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3,
|
||||
ClipTriangleByRectResultIt const & resultIterator);
|
||||
|
||||
void ClipPathByRect(m2::RectD const & rect, std::vector<m2::PointD> && path,
|
||||
std::function<void(m2::SharedSpline &&)> const & fn);
|
||||
std::vector<m2::SharedSpline> ClipSplineByRect(m2::RectD const & rect, m2::SharedSpline const & spline);
|
||||
|
||||
using GuidePointsForSmooth = std::vector<std::pair<m2::PointD, m2::PointD>>;
|
||||
void ClipPathByRectBeforeSmooth(m2::RectD const & rect, std::vector<m2::PointD> const & path,
|
||||
GuidePointsForSmooth & guidePoints,
|
||||
std::vector<std::vector<m2::PointD>> & clippedPaths);
|
||||
} // namespace m2
|
||||
66
libs/geometry/convex_hull.cpp
Normal file
66
libs/geometry/convex_hull.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#include "geometry/convex_hull.hpp"
|
||||
|
||||
#include "geometry/robust_orientation.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// Checks whether (p1 - p) x (p2 - p) > 0.
|
||||
bool IsCCW(PointD const & p1, PointD const & p2, PointD const & p, double eps)
|
||||
{
|
||||
return robust::OrientedS(p1, p2, p) > eps;
|
||||
}
|
||||
|
||||
bool IsContinuedBy(std::vector<PointD> const & hull, PointD const & p, double eps)
|
||||
{
|
||||
auto const n = hull.size();
|
||||
if (n < 2)
|
||||
return true;
|
||||
|
||||
auto const & p1 = hull[n - 2];
|
||||
auto const & p2 = hull[n - 1];
|
||||
|
||||
// Checks whether (p2 - p1) x (p - p2) > 0.
|
||||
return IsCCW(p, p1, p2, eps);
|
||||
}
|
||||
|
||||
std::vector<PointD> BuildConvexHull(std::vector<PointD> points, double eps)
|
||||
{
|
||||
base::SortUnique(points);
|
||||
|
||||
ASSERT(points.empty() || points.begin() == min_element(points.begin(), points.end()), ());
|
||||
|
||||
if (points.size() < 3)
|
||||
return points;
|
||||
|
||||
auto const pivot = points[0];
|
||||
|
||||
std::sort(points.begin() + 1, points.end(), [&pivot, &eps](PointD const & lhs, PointD const & rhs)
|
||||
{
|
||||
if (IsCCW(lhs, rhs, pivot, eps))
|
||||
return true;
|
||||
if (IsCCW(rhs, lhs, pivot, eps))
|
||||
return false;
|
||||
return lhs.SquaredLength(pivot) < rhs.SquaredLength(pivot);
|
||||
});
|
||||
|
||||
std::vector<PointD> hull;
|
||||
|
||||
for (auto const & p : points)
|
||||
{
|
||||
while (!IsContinuedBy(hull, p, eps))
|
||||
hull.pop_back();
|
||||
hull.push_back(p);
|
||||
}
|
||||
|
||||
return hull;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ConvexHull::ConvexHull(std::vector<PointD> const & points, double eps) : m_hull(BuildConvexHull(points, eps)) {}
|
||||
} // namespace m2
|
||||
42
libs/geometry/convex_hull.hpp
Normal file
42
libs/geometry/convex_hull.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/segment2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
class ConvexHull
|
||||
{
|
||||
public:
|
||||
// Builds a convex hull around |points|. The hull polygon points are
|
||||
// listed in the order of a counterclockwise traversal with no three
|
||||
// points lying on the same straight line.
|
||||
//
|
||||
// Complexity: O(n * log(n)), where n is the number of points.
|
||||
ConvexHull(std::vector<PointD> const & points, double eps);
|
||||
|
||||
size_t Size() const { return m_hull.size(); }
|
||||
bool Empty() const { return m_hull.empty(); }
|
||||
|
||||
PointD const & PointAt(size_t i) const
|
||||
{
|
||||
ASSERT(!Empty(), ());
|
||||
return m_hull[i % Size()];
|
||||
}
|
||||
|
||||
Segment2D SegmentAt(size_t i) const
|
||||
{
|
||||
ASSERT_GREATER_OR_EQUAL(Size(), 2, ());
|
||||
return Segment2D(PointAt(i), PointAt(i + 1));
|
||||
}
|
||||
|
||||
std::vector<PointD> const & Points() const { return m_hull; }
|
||||
|
||||
private:
|
||||
std::vector<PointD> m_hull;
|
||||
};
|
||||
} // namespace m2
|
||||
276
libs/geometry/covering.hpp
Normal file
276
libs/geometry/covering.hpp
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/covering_utils.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/base.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/set_operations.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace covering
|
||||
{
|
||||
template <class CellIdT>
|
||||
class Covering
|
||||
{
|
||||
public:
|
||||
typedef CellIdT CellId;
|
||||
typedef typename CellId::LessLevelOrder LessLevelOrder;
|
||||
|
||||
Covering() : m_Size(0) {}
|
||||
|
||||
explicit Covering(CellId cell) : m_Size(1) { m_Covering[cell.Level()].push_back(cell); }
|
||||
|
||||
explicit Covering(std::vector<CellId> const & v)
|
||||
{
|
||||
for (size_t i = 0; i < v.size(); ++i)
|
||||
m_Covering[v[i].Level()].push_back(v[i]);
|
||||
Sort();
|
||||
Unique();
|
||||
RemoveDuplicateChildren();
|
||||
RemoveFullSquares();
|
||||
m_Size = CalculateSize();
|
||||
}
|
||||
|
||||
// Cover triangle.
|
||||
Covering(m2::PointD const & a, m2::PointD const & b, m2::PointD const & c, int level = CellId::DEPTH_LEVELS - 1)
|
||||
{
|
||||
// TODO: ASSERT(a != b), ASSERT(b != c), ASSERT(a != c) ?
|
||||
ASSERT(0 <= level && level <= CellId::DEPTH_LEVELS, (level, CellId::Root()));
|
||||
CoverTriangleInfo info;
|
||||
info.m_pCovering = this;
|
||||
info.m_A = a;
|
||||
info.m_B = b;
|
||||
info.m_C = c;
|
||||
info.m_Level = level;
|
||||
CoverTriangleImpl(info, CellId::Root());
|
||||
Sort();
|
||||
RemoveFullSquares();
|
||||
m_Size = CalculateSize();
|
||||
}
|
||||
|
||||
size_t Size() const
|
||||
{
|
||||
ASSERT_EQUAL(m_Size, CalculateSize(), ());
|
||||
return m_Size;
|
||||
}
|
||||
|
||||
void Append(Covering<CellId> const & c)
|
||||
{
|
||||
AppendWithoutNormalize(c);
|
||||
RemoveDuplicateChildren();
|
||||
RemoveFullSquares();
|
||||
m_Size = CalculateSize();
|
||||
}
|
||||
|
||||
void OutputToVector(std::vector<CellId> & result) const
|
||||
{
|
||||
for (int level = 0; level < CellId::DEPTH_LEVELS; ++level)
|
||||
result.insert(result.end(), m_Covering[level].begin(), m_Covering[level].end());
|
||||
}
|
||||
|
||||
void OutputToVector(std::vector<int64_t> & result, int cellDepth) const
|
||||
{
|
||||
for (int level = 0; level < CellId::DEPTH_LEVELS; ++level)
|
||||
for (size_t i = 0; i < m_Covering[level].size(); ++i)
|
||||
result.push_back(m_Covering[level][i].ToInt64(cellDepth));
|
||||
}
|
||||
|
||||
void Simplify()
|
||||
{
|
||||
size_t cellsSimplified = 0;
|
||||
auto const initialSize = m_Size;
|
||||
for (int level = CellId::DEPTH_LEVELS - 1; level > 1; --level)
|
||||
{
|
||||
if (m_Covering[level].size() >= 2)
|
||||
{
|
||||
auto const initialLevelSize = m_Covering[level].size();
|
||||
SimplifyLevel(level);
|
||||
ASSERT_GREATER_OR_EQUAL(initialLevelSize, m_Covering[level].size(), ());
|
||||
cellsSimplified += initialLevelSize - m_Covering[level].size();
|
||||
if (cellsSimplified > initialSize / 2)
|
||||
break;
|
||||
}
|
||||
}
|
||||
RemoveDuplicateChildren();
|
||||
RemoveFullSquares();
|
||||
m_Size = CalculateSize();
|
||||
}
|
||||
|
||||
private:
|
||||
void SimplifyLevel(int level)
|
||||
{
|
||||
std::map<CellId, uint32_t, LessLevelOrder> parentCellCounts;
|
||||
using ConstIterator = typename std::vector<CellId>::const_iterator;
|
||||
for (ConstIterator it = m_Covering[level].begin(); it != m_Covering[level].end(); ++it)
|
||||
++parentCellCounts[it->Parent()];
|
||||
|
||||
std::vector<CellId> parentCells;
|
||||
std::vector<CellId> childCells;
|
||||
for (ConstIterator it = m_Covering[level].begin(); it != m_Covering[level].end(); ++it)
|
||||
if (parentCellCounts[it->Parent()] > 1)
|
||||
parentCells.push_back(it->Parent());
|
||||
else
|
||||
childCells.push_back(*it);
|
||||
ASSERT(std::is_sorted(parentCells.begin(), parentCells.end(), LessLevelOrder()), (parentCells));
|
||||
ASSERT(std::is_sorted(childCells.begin(), childCells.end(), LessLevelOrder()), (childCells));
|
||||
m_Covering[level].swap(childCells);
|
||||
parentCells.erase(std::unique(parentCells.begin(), parentCells.end()), parentCells.end());
|
||||
AppendToVector(m_Covering[level - 1], parentCells);
|
||||
}
|
||||
|
||||
static void AppendToVector(std::vector<CellId> & a, std::vector<CellId> const & b)
|
||||
{
|
||||
ASSERT(base::IsSortedAndUnique(a.begin(), a.end(), LessLevelOrder()), (a));
|
||||
ASSERT(base::IsSortedAndUnique(b.begin(), b.end(), LessLevelOrder()), (b));
|
||||
std::vector<CellId> merged;
|
||||
std::set_union(a.begin(), a.end(), b.begin(), b.end(), std::back_inserter(merged), LessLevelOrder());
|
||||
a.swap(merged);
|
||||
}
|
||||
|
||||
template <typename CompareT>
|
||||
struct CompareCellsAtLevel
|
||||
{
|
||||
explicit CompareCellsAtLevel(int level) : m_Level(level) {}
|
||||
|
||||
bool operator()(CellId id1, CellId id2) const
|
||||
{
|
||||
return m_Comp(id1.AncestorAtLevel(m_Level), id2.AncestorAtLevel(m_Level));
|
||||
}
|
||||
|
||||
CompareT m_Comp;
|
||||
int m_Level;
|
||||
};
|
||||
|
||||
void AppendWithoutNormalize(Covering const & c)
|
||||
{
|
||||
for (int level = CellId::DEPTH_LEVELS - 1; level >= 0; --level)
|
||||
AppendToVector(m_Covering[level], c.m_Covering[level]);
|
||||
}
|
||||
|
||||
void Sort()
|
||||
{
|
||||
for (int level = 0; level < CellId::DEPTH_LEVELS; ++level)
|
||||
std::sort(m_Covering[level].begin(), m_Covering[level].end(), LessLevelOrder());
|
||||
}
|
||||
|
||||
void Unique()
|
||||
{
|
||||
for (int level = 0; level < CellId::DEPTH_LEVELS; ++level)
|
||||
{
|
||||
std::vector<CellId> & covering = m_Covering[level];
|
||||
ASSERT(std::is_sorted(covering.begin(), covering.end(), LessLevelOrder()), (covering));
|
||||
covering.erase(std::unique(covering.begin(), covering.end()), covering.end());
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveDuplicateChildren()
|
||||
{
|
||||
RemoveDuplicateChildrenImpl();
|
||||
#ifdef DEBUG
|
||||
// Assert that all duplicate children were removed.
|
||||
std::vector<CellId> v1, v2;
|
||||
OutputToVector(v1);
|
||||
RemoveDuplicateChildrenImpl();
|
||||
OutputToVector(v2);
|
||||
ASSERT_EQUAL(v1, v2, ());
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemoveDuplicateChildrenImpl()
|
||||
{
|
||||
for (int parentLevel = 0; parentLevel < static_cast<int>(m_Covering.size()) - 1; ++parentLevel)
|
||||
{
|
||||
if (m_Covering[parentLevel].empty())
|
||||
continue;
|
||||
for (int childLevel = parentLevel + 1; childLevel < static_cast<int>(m_Covering.size()); ++childLevel)
|
||||
{
|
||||
std::vector<CellId> subtracted;
|
||||
CompareCellsAtLevel<LessLevelOrder> comparator(parentLevel);
|
||||
ASSERT(std::is_sorted(m_Covering[childLevel].begin(), m_Covering[childLevel].end(), comparator),
|
||||
(m_Covering[childLevel]));
|
||||
ASSERT(std::is_sorted(m_Covering[parentLevel].begin(), m_Covering[parentLevel].end(), comparator),
|
||||
(m_Covering[parentLevel]));
|
||||
SetDifferenceUnlimited(m_Covering[childLevel].begin(), m_Covering[childLevel].end(),
|
||||
m_Covering[parentLevel].begin(), m_Covering[parentLevel].end(),
|
||||
std::back_inserter(subtracted), comparator);
|
||||
m_Covering[childLevel].swap(subtracted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveFullSquares()
|
||||
{
|
||||
std::vector<CellId> cellsToAppend;
|
||||
for (int level = static_cast<int>(m_Covering.size()) - 1; level >= 0; --level)
|
||||
{
|
||||
// a -> b + parents
|
||||
std::vector<CellId> const & a = m_Covering[level];
|
||||
std::vector<CellId> b;
|
||||
std::vector<CellId> parents;
|
||||
b.reserve(a.size());
|
||||
for (size_t i = 0; i < a.size(); ++i)
|
||||
{
|
||||
if (i + 3 < a.size())
|
||||
{
|
||||
CellId const parent = a[i].Parent();
|
||||
if (parent == a[i + 1].Parent() && parent == a[i + 2].Parent() && parent == a[i + 3].Parent())
|
||||
{
|
||||
parents.push_back(parent);
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
b.push_back(a[i]);
|
||||
}
|
||||
m_Covering[level].swap(b);
|
||||
if (level > 0)
|
||||
AppendToVector(m_Covering[level - 1], parents);
|
||||
}
|
||||
}
|
||||
|
||||
size_t CalculateSize() const
|
||||
{
|
||||
size_t size = 0;
|
||||
for (int level = 0; level < CellId::DEPTH_LEVELS; ++level)
|
||||
size += m_Covering[level].size();
|
||||
return size;
|
||||
}
|
||||
|
||||
struct CoverTriangleInfo
|
||||
{
|
||||
m2::PointD m_A, m_B, m_C;
|
||||
int m_Level;
|
||||
Covering<CellId> * m_pCovering;
|
||||
};
|
||||
|
||||
static void CoverTriangleImpl(CoverTriangleInfo const & info, CellId const cell)
|
||||
{
|
||||
ASSERT_LESS_OR_EQUAL(cell.Level(), info.m_Level, (info.m_A, info.m_B, info.m_C));
|
||||
CellObjectIntersection intersection = IntersectCellWithTriangle(cell, info.m_A, info.m_B, info.m_C);
|
||||
|
||||
if (intersection == CELL_OBJECT_NO_INTERSECTION)
|
||||
return;
|
||||
|
||||
if (cell.Level() == info.m_Level || intersection == CELL_INSIDE_OBJECT)
|
||||
{
|
||||
info.m_pCovering->m_Covering[cell.Level()].push_back(cell);
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t child = 0; child < 4; ++child)
|
||||
CoverTriangleImpl(info, cell.Child(child));
|
||||
}
|
||||
|
||||
std::array<std::vector<CellId>, CellId::DEPTH_LEVELS> m_Covering; // Covering by level.
|
||||
size_t m_Size;
|
||||
};
|
||||
} // namespace covering
|
||||
111
libs/geometry/covering_utils.hpp
Normal file
111
libs/geometry/covering_utils.hpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/segment2d.hpp"
|
||||
#include "geometry/triangle2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/base.hpp"
|
||||
#include "base/buffer_vector.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
namespace covering
|
||||
{
|
||||
// Result of an intersection between object and cell.
|
||||
enum CellObjectIntersection
|
||||
{
|
||||
// No intersection. It is important, that its value is 0, so one can do if (intersection) ... .
|
||||
CELL_OBJECT_NO_INTERSECTION = 0,
|
||||
CELL_OBJECT_INTERSECT = 1,
|
||||
CELL_INSIDE_OBJECT = 2,
|
||||
OBJECT_INSIDE_CELL = 3
|
||||
};
|
||||
|
||||
template <class CellId>
|
||||
CellObjectIntersection IntersectCellWithLine(CellId const cell, m2::PointD const & a, m2::PointD const & b)
|
||||
{
|
||||
std::pair<uint32_t, uint32_t> const xy = cell.XY();
|
||||
uint32_t const r = cell.Radius();
|
||||
m2::PointD const cellCorners[4] = {m2::PointD(xy.first - r, xy.second - r), m2::PointD(xy.first - r, xy.second + r),
|
||||
m2::PointD(xy.first + r, xy.second + r), m2::PointD(xy.first + r, xy.second - r)};
|
||||
for (int i = 0; i < 4; ++i)
|
||||
if (m2::SegmentsIntersect(a, b, cellCorners[i], cellCorners[i == 0 ? 3 : i - 1]))
|
||||
return CELL_OBJECT_INTERSECT;
|
||||
if (xy.first - r <= a.x && a.x <= xy.first + r && xy.second - r <= a.y && a.y <= xy.second + r)
|
||||
return OBJECT_INSIDE_CELL;
|
||||
return CELL_OBJECT_NO_INTERSECTION;
|
||||
}
|
||||
|
||||
template <class CellId>
|
||||
CellObjectIntersection IntersectCellWithTriangle(CellId const cell, m2::PointD const & a, m2::PointD const & b,
|
||||
m2::PointD const & c)
|
||||
{
|
||||
CellObjectIntersection const i1 = IntersectCellWithLine(cell, a, b);
|
||||
if (i1 == CELL_OBJECT_INTERSECT)
|
||||
return CELL_OBJECT_INTERSECT;
|
||||
CellObjectIntersection const i2 = IntersectCellWithLine(cell, b, c);
|
||||
if (i2 == CELL_OBJECT_INTERSECT)
|
||||
return CELL_OBJECT_INTERSECT;
|
||||
CellObjectIntersection const i3 = IntersectCellWithLine(cell, c, a);
|
||||
if (i3 == CELL_OBJECT_INTERSECT)
|
||||
return CELL_OBJECT_INTERSECT;
|
||||
// At this point either:
|
||||
// 1. Triangle is inside cell.
|
||||
// 2. Cell is inside triangle.
|
||||
// 3. Cell and triangle do not intersect.
|
||||
ASSERT_EQUAL(i1, i2, (cell, a, b, c));
|
||||
ASSERT_EQUAL(i2, i3, (cell, a, b, c));
|
||||
ASSERT_EQUAL(i3, i1, (cell, a, b, c));
|
||||
if (i1 == OBJECT_INSIDE_CELL || i2 == OBJECT_INSIDE_CELL || i3 == OBJECT_INSIDE_CELL)
|
||||
return OBJECT_INSIDE_CELL;
|
||||
std::pair<uint32_t, uint32_t> const xy = cell.XY();
|
||||
if (m2::IsPointStrictlyInsideTriangle(m2::PointD(xy.first, xy.second), a, b, c))
|
||||
return CELL_INSIDE_OBJECT;
|
||||
return CELL_OBJECT_NO_INTERSECTION;
|
||||
}
|
||||
|
||||
template <class CellId, class CellIdContainerT, typename IntersectF>
|
||||
void CoverObject(IntersectF const & intersect, uint64_t cellPenaltyArea, CellIdContainerT & out, int cellDepth,
|
||||
CellId cell)
|
||||
{
|
||||
if (cell.Level() == cellDepth - 1)
|
||||
{
|
||||
out.push_back(cell);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t const cellArea = math::Pow2(uint64_t(1 << (cellDepth - 1 - cell.Level())));
|
||||
CellObjectIntersection const intersection = intersect(cell);
|
||||
|
||||
if (intersection == CELL_OBJECT_NO_INTERSECTION)
|
||||
return;
|
||||
if (intersection == CELL_INSIDE_OBJECT || cellPenaltyArea >= cellArea)
|
||||
{
|
||||
out.push_back(cell);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer_vector<CellId, 32> subdiv;
|
||||
for (uint8_t i = 0; i < 4; ++i)
|
||||
CoverObject(intersect, cellPenaltyArea, subdiv, cellDepth, cell.Child(i));
|
||||
|
||||
uint64_t subdivArea = 0;
|
||||
for (size_t i = 0; i < subdiv.size(); ++i)
|
||||
subdivArea += math::Pow2(uint64_t(1 << (cellDepth - 1 - subdiv[i].Level())));
|
||||
|
||||
ASSERT(!subdiv.empty(), (cellPenaltyArea, out, cell));
|
||||
|
||||
// This criteria is more clear for me. Let's divide if we can save more than cellPenaltyArea.
|
||||
if (subdiv.size() > 1 && cellPenaltyArea >= cellArea - subdivArea)
|
||||
out.push_back(cell);
|
||||
else
|
||||
for (size_t i = 0; i < subdiv.size(); ++i)
|
||||
out.push_back(subdiv[i]);
|
||||
}
|
||||
} // namespace covering
|
||||
10
libs/geometry/diamond_box.cpp
Normal file
10
libs/geometry/diamond_box.cpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#include "geometry/diamond_box.hpp"
|
||||
|
||||
namespace m2
|
||||
{
|
||||
DiamondBox::DiamondBox(std::vector<PointD> const & points)
|
||||
{
|
||||
for (auto const & p : points)
|
||||
Add(p);
|
||||
}
|
||||
} // namespace m2
|
||||
48
libs/geometry/diamond_box.hpp
Normal file
48
libs/geometry/diamond_box.hpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/bounding_box.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
// Bounding box for a set of points on the plane, rotated by 45
|
||||
// degrees.
|
||||
class DiamondBox
|
||||
{
|
||||
public:
|
||||
DiamondBox() = default;
|
||||
explicit DiamondBox(std::vector<PointD> const & points);
|
||||
|
||||
void Add(PointD const & p) { return Add(p.x, p.y); }
|
||||
void Add(double x, double y) { return m_box.Add(x + y, x - y); }
|
||||
|
||||
bool HasPoint(PointD const & p) const { return HasPoint(p.x, p.y); }
|
||||
bool HasPoint(double x, double y) const { return m_box.HasPoint(x + y, x - y); }
|
||||
|
||||
bool HasPoint(PointD const & p, double eps) const { return HasPoint(p.x, p.y, eps); }
|
||||
|
||||
bool HasPoint(double x, double y, double eps) const { return m_box.HasPoint(x + y, x - y, 2 * eps); }
|
||||
|
||||
std::vector<m2::PointD> Points() const
|
||||
{
|
||||
auto points = m_box.Points();
|
||||
for (auto & p : points)
|
||||
p = ToOrig(p);
|
||||
return points;
|
||||
}
|
||||
|
||||
bool operator==(DiamondBox const & rhs) const { return m_box == rhs.m_box; }
|
||||
|
||||
DECLARE_VISITOR(visitor(m_box, "box"))
|
||||
DECLARE_DEBUG_PRINT(DiamondBox)
|
||||
|
||||
private:
|
||||
static m2::PointD ToOrig(m2::PointD const & p) { return m2::PointD(0.5 * (p.x + p.y), 0.5 * (p.x - p.y)); }
|
||||
|
||||
BoundingBox m_box;
|
||||
};
|
||||
} // namespace m2
|
||||
38
libs/geometry/distance_on_sphere.cpp
Normal file
38
libs/geometry/distance_on_sphere.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#include "geometry/distance_on_sphere.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace ms
|
||||
{
|
||||
double DistanceOnSphere(double lat1Deg, double lon1Deg, double lat2Deg, double lon2Deg)
|
||||
{
|
||||
double const lat1 = math::DegToRad(lat1Deg);
|
||||
double const lat2 = math::DegToRad(lat2Deg);
|
||||
double const dlat = sin((lat2 - lat1) * 0.5);
|
||||
double const dlon = sin((math::DegToRad(lon2Deg) - math::DegToRad(lon1Deg)) * 0.5);
|
||||
double const y = dlat * dlat + dlon * dlon * cos(lat1) * cos(lat2);
|
||||
return 2.0 * atan2(sqrt(y), sqrt(std::max(0.0, 1.0 - y)));
|
||||
}
|
||||
|
||||
double DistanceOnEarth(double lat1Deg, double lon1Deg, double lat2Deg, double lon2Deg)
|
||||
{
|
||||
return kEarthRadiusMeters * DistanceOnSphere(lat1Deg, lon1Deg, lat2Deg, lon2Deg);
|
||||
}
|
||||
|
||||
double DistanceOnEarth(LatLon const & ll1, LatLon const & ll2)
|
||||
{
|
||||
return DistanceOnEarth(ll1.m_lat, ll1.m_lon, ll2.m_lat, ll2.m_lon);
|
||||
}
|
||||
|
||||
m3::Point<double> ToVector(LatLon const & ll)
|
||||
{
|
||||
double const lat = math::DegToRad(ll.m_lat);
|
||||
double const lon = math::DegToRad(ll.m_lon);
|
||||
return {kEarthRadiusMeters * cos(lat) * cos(lon), kEarthRadiusMeters * cos(lat) * sin(lon),
|
||||
kEarthRadiusMeters * sin(lat)};
|
||||
}
|
||||
|
||||
} // namespace ms
|
||||
19
libs/geometry/distance_on_sphere.hpp
Normal file
19
libs/geometry/distance_on_sphere.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/point3d.hpp"
|
||||
|
||||
// namespace ms - "math on sphere", similar to namespace m2.
|
||||
namespace ms
|
||||
{
|
||||
double constexpr kEarthRadiusMeters = 6378000.0;
|
||||
// Distance on unit sphere between (lat1, lon1) and (lat2, lon2).
|
||||
// lat1, lat2, lon1, lon2 - in degrees.
|
||||
double DistanceOnSphere(double lat1Deg, double lon1Deg, double lat2Deg, double lon2Deg);
|
||||
|
||||
// Distance in meteres on Earth between (lat1, lon1) and (lat2, lon2).
|
||||
// lat1, lat2, lon1, lon2 - in degrees.
|
||||
double DistanceOnEarth(double lat1Deg, double lon1Deg, double lat2Deg, double lon2Deg);
|
||||
double DistanceOnEarth(LatLon const & ll1, LatLon const & ll2);
|
||||
m3::Point<double> ToVector(LatLon const & ll);
|
||||
} // namespace ms
|
||||
49
libs/geometry/geometry_tests/CMakeLists.txt
Normal file
49
libs/geometry/geometry_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
project(geometry_tests)
|
||||
|
||||
set(SRC
|
||||
algorithm_test.cpp
|
||||
angle_test.cpp
|
||||
anyrect_test.cpp
|
||||
area_on_earth_tests.cpp
|
||||
bounding_box_tests.cpp
|
||||
calipers_box_tests.cpp
|
||||
cellid_test.cpp
|
||||
circle_on_earth_tests.cpp
|
||||
clipping_test.cpp
|
||||
common_test.cpp
|
||||
convex_hull_tests.cpp
|
||||
covering_test.cpp
|
||||
diamond_box_tests.cpp
|
||||
distance_on_sphere_test.cpp
|
||||
equality.hpp
|
||||
intersect_test.cpp
|
||||
intersection_score_tests.cpp
|
||||
large_polygon.hpp
|
||||
latlon_test.cpp
|
||||
line2d_tests.cpp
|
||||
mercator_test.cpp
|
||||
nearby_points_sweeper_test.cpp
|
||||
oblate_spheroid_tests.cpp
|
||||
packer_test.cpp
|
||||
parametrized_segment_tests.cpp
|
||||
point3d_tests.cpp
|
||||
point_test.cpp
|
||||
polygon_test.cpp
|
||||
polyline_tests.cpp
|
||||
rect_test.cpp
|
||||
region2d_binary_op_test.cpp
|
||||
region_tests.cpp
|
||||
robust_test.cpp
|
||||
screen_test.cpp
|
||||
segment2d_tests.cpp
|
||||
simplification_test.cpp
|
||||
spline_test.cpp
|
||||
test_regions.hpp
|
||||
transformations_test.cpp
|
||||
tree_test.cpp
|
||||
vector_test.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC} NO_PLATFORM_INIT)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} geometry)
|
||||
118
libs/geometry/geometry_tests/algorithm_test.cpp
Normal file
118
libs/geometry/geometry_tests/algorithm_test.cpp
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/algorithm.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace algorithm_test
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
using m2::CalculateBoundingBox;
|
||||
using m2::CalculatePointOnSurface;
|
||||
using m2::CalculatePolyLineCenter;
|
||||
using m2::PointD;
|
||||
using m2::RectD;
|
||||
|
||||
PointD GetPolyLineCenter(vector<PointD> const & points)
|
||||
{
|
||||
return m2::ApplyCalculator(points, m2::CalculatePolyLineCenter());
|
||||
}
|
||||
|
||||
RectD GetBoundingBox(vector<PointD> const & points)
|
||||
{
|
||||
return m2::ApplyCalculator(points, m2::CalculateBoundingBox());
|
||||
}
|
||||
|
||||
PointD GetPointOnSurface(vector<PointD> const & points)
|
||||
{
|
||||
ASSERT(!points.empty() && points.size() % 3 == 0, ("points.size() =", points.size()));
|
||||
auto const boundingBox = GetBoundingBox(points);
|
||||
return m2::ApplyCalculator(points, m2::CalculatePointOnSurface(boundingBox));
|
||||
}
|
||||
|
||||
bool PointsAlmostEqual(PointD const & p1, PointD const & p2)
|
||||
{
|
||||
return p1.EqualDxDy(p2, mercator::kPointEqualityEps);
|
||||
}
|
||||
|
||||
UNIT_TEST(CalculatePolyLineCenter)
|
||||
{
|
||||
{
|
||||
vector<PointD> const points{{0, 0}, {1, 1}, {2, 2}};
|
||||
TEST_EQUAL(GetPolyLineCenter(points), PointD(1, 1), ());
|
||||
}
|
||||
{
|
||||
vector<PointD> const points{{0, 2}, {1, 1}, {2, 2}};
|
||||
TEST_EQUAL(GetPolyLineCenter(points), PointD(1, 1), ());
|
||||
}
|
||||
{
|
||||
vector<PointD> const points{
|
||||
{1, 1},
|
||||
{2, 2},
|
||||
{4, 4},
|
||||
};
|
||||
TEST_EQUAL(GetPolyLineCenter(points), PointD(2.5, 2.5), ());
|
||||
}
|
||||
{
|
||||
vector<PointD> const points{{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}};
|
||||
// Also logs a warning message.
|
||||
TEST_EQUAL(GetPolyLineCenter(points), PointD(0, 0), ());
|
||||
}
|
||||
{
|
||||
vector<PointD> const points{{0, 0}, {0, 0}, {0, 0}, {0, 0.000001}, {0, 0.000001}, {0, 0.000001}, {0, 0.000001}};
|
||||
// Also logs a warning message.
|
||||
TEST(GetPolyLineCenter(points).EqualDxDy(PointD(0, .0000005), 1e-7), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(CalculateCenter)
|
||||
{
|
||||
{
|
||||
vector<PointD> const points{{2, 2}};
|
||||
TEST_EQUAL(GetBoundingBox(points), RectD({2, 2}, {2, 2}), ());
|
||||
}
|
||||
{
|
||||
vector<PointD> const points{{0, 0}, {1, 1}, {2, 2}};
|
||||
TEST_EQUAL(GetBoundingBox(points), RectD({0, 0}, {2, 2}), ());
|
||||
}
|
||||
{
|
||||
vector<PointD> const points{{0, 2}, {1, 1}, {2, 2}, {1, 2}, {2, 2}, {2, 0}};
|
||||
TEST_EQUAL(GetBoundingBox(points), RectD({0, 0}, {2, 2}), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(CalculatePointOnSurface)
|
||||
{
|
||||
{
|
||||
vector<PointD> const points{{0, 0}, {1, 1}, {2, 2}};
|
||||
TEST_EQUAL(GetPointOnSurface(points), PointD(1, 1), ());
|
||||
}
|
||||
{
|
||||
vector<PointD> const points{{0, 2}, {1, 1}, {2, 2}, {1, 2}, {2, 2}, {2, 0}};
|
||||
TEST_EQUAL(GetPointOnSurface(points), PointD(1, 1), ());
|
||||
}
|
||||
{
|
||||
vector<PointD> const points{
|
||||
{0, 0}, {1, 1}, {2, 0}, {1, 1}, {2, 0}, {3, 1}, // Center of this triangle is used as a result.
|
||||
{2, 0}, {3, 1}, {4, 0},
|
||||
|
||||
{4, 0}, {3, 1}, {4, 2}, {3, 1}, {4, 2}, {3, 3}, // Or this.
|
||||
{4, 2}, {3, 3}, {4, 4},
|
||||
|
||||
{3, 3}, {4, 4}, {2, 4}, {3, 3}, {2, 4}, {1, 3}, // Or this.
|
||||
{1, 3}, {2, 4}, {0, 4},
|
||||
|
||||
{0, 4}, {1, 3}, {0, 2}, {1, 3}, {0, 2}, {1, 1}, // Or this
|
||||
{0, 2}, {1, 1}, {0, 0},
|
||||
};
|
||||
auto const result = GetPointOnSurface(points);
|
||||
TEST(PointsAlmostEqual(result, {10.0 / 3.0, 2}) || PointsAlmostEqual(result, {2, 2.0 / 3.0}) ||
|
||||
PointsAlmostEqual(result, {2, 10.0 / 3.0}) || PointsAlmostEqual(result, {2.0 / 3.0, 2}),
|
||||
("result = ", result));
|
||||
}
|
||||
}
|
||||
} // namespace algorithm_test
|
||||
109
libs/geometry/geometry_tests/angle_test.cpp
Normal file
109
libs/geometry/geometry_tests/angle_test.cpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#include "geometry/geometry_tests/equality.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/angles.hpp"
|
||||
|
||||
using namespace test;
|
||||
using math::pi;
|
||||
|
||||
UNIT_TEST(Atan)
|
||||
{
|
||||
double const h4 = math::pi / 4.0;
|
||||
|
||||
TEST(is_equal_atan(1, 1, h4), ());
|
||||
TEST(is_equal_atan(-1, 1, math::pi - h4), ());
|
||||
TEST(is_equal_atan(-1, -1, h4 - math::pi), ());
|
||||
TEST(is_equal_atan(1, -1, -h4), ());
|
||||
|
||||
double const hh = atan(1.0 / 2.0);
|
||||
|
||||
TEST(is_equal_atan(2, 1, hh), ());
|
||||
TEST(is_equal_atan(-2, 1, math::pi - hh), ());
|
||||
TEST(is_equal_atan(-2, -1, hh - math::pi), ());
|
||||
TEST(is_equal_atan(2, -1, -hh), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Atan2)
|
||||
{
|
||||
TEST_ALMOST_EQUAL_ULPS(atan2(1, 0), pi / 2.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(atan2(-1, 0), -pi / 2.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(atan2(0, 1), 0.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(atan2(0, -1), pi, ());
|
||||
|
||||
TEST_ALMOST_EQUAL_ULPS(atan2(1, 1), pi / 4.0, ());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void check_avg(double arr[], size_t n, double v)
|
||||
{
|
||||
ang::AverageCalc calc;
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
calc.Add(arr[i]);
|
||||
|
||||
double const avg = calc.GetAverage();
|
||||
TEST(is_equal_angle(avg, v), (avg, v));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(Average)
|
||||
{
|
||||
double constexpr eps = 1.0E-3;
|
||||
|
||||
double arr1[] = {math::pi - eps, -math::pi + eps};
|
||||
TEST(is_equal_angle(ang::GetMiddleAngle(arr1[0], arr1[1]), math::pi), ());
|
||||
check_avg(arr1, ARRAY_SIZE(arr1), math::pi);
|
||||
|
||||
double arr2[] = {eps, -eps};
|
||||
TEST(is_equal_angle(ang::GetMiddleAngle(arr2[0], arr2[1]), 0.0), ());
|
||||
check_avg(arr2, ARRAY_SIZE(arr2), 0.0);
|
||||
}
|
||||
|
||||
UNIT_TEST(ShortestDistance)
|
||||
{
|
||||
TEST_ALMOST_EQUAL_ULPS(ang::GetShortestDistance(0, math::pi), math::pi, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(ang::GetShortestDistance(0, math::pi + 1), -math::pi + 1, ());
|
||||
|
||||
TEST_ALMOST_EQUAL_ULPS(ang::GetShortestDistance(math::pi - 1, 0), -math::pi + 1, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(ang::GetShortestDistance(math::pi + 1, 0), math::pi - 1, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TwoVectorsAngle)
|
||||
{
|
||||
double constexpr eps = 1e-10;
|
||||
TEST(AlmostEqualAbs(ang::TwoVectorsAngle(m2::Point<double>(0, 0) /* p */, m2::Point<double>(0, 1) /* p1 */,
|
||||
m2::Point<double>(1, 0)) /* p2 */,
|
||||
3 * math::pi2, eps),
|
||||
());
|
||||
TEST(AlmostEqualAbs(ang::TwoVectorsAngle(m2::Point<double>(1, 1) /* p */, m2::Point<double>(2, 2) /* p1 */,
|
||||
m2::Point<double>(1, 2)) /* p2 */,
|
||||
math::pi4, eps),
|
||||
());
|
||||
TEST(AlmostEqualAbs(ang::TwoVectorsAngle(m2::Point<double>(0, 0) /* p */, m2::Point<double>(1, 0) /* p1 */,
|
||||
m2::Point<double>(0, -1)) /* p2 */,
|
||||
3 * math::pi2, eps),
|
||||
());
|
||||
TEST(AlmostEqualAbs(ang::TwoVectorsAngle(m2::Point<double>(0, 0) /* p */, m2::Point<double>(1, 0) /* p1 */,
|
||||
m2::Point<double>(-1, 0)) /* p2 */,
|
||||
math::pi, eps),
|
||||
());
|
||||
}
|
||||
|
||||
UNIT_TEST(Azimuth)
|
||||
{
|
||||
TEST(is_equal_angle(ang::Azimuth(m2::Point<double>(-1, 0), m2::Point<double>(0, 1), math::pi2), -math::pi4), ());
|
||||
TEST(is_equal_angle(ang::Azimuth(m2::Point<double>(-1, 0), m2::Point<double>(0, 1), 0.0), math::pi4), ());
|
||||
TEST(is_equal_angle(ang::Azimuth(m2::Point<double>(-1, 1), m2::Point<double>(1, -1), 0.0), 3 * math::pi4), ());
|
||||
TEST(is_equal_angle(ang::Azimuth(m2::Point<double>(1, 1), m2::Point<double>(0, 1), -math::pi2), 0.0), ());
|
||||
TEST(is_equal_angle(ang::Azimuth(m2::Point<double>(1, -1), m2::Point<double>(-1, -1), math::pi), math::pi2), ());
|
||||
TEST(is_equal_angle(ang::Azimuth(m2::Point<double>(0, 0), m2::Point<double>(-1, -1), math::pi4), math::pi), ());
|
||||
TEST(is_equal_angle(ang::Azimuth(m2::Point<double>(0.5, -0.5), m2::Point<double>(-0.5, 0.5), math::pi4), -math::pi2),
|
||||
());
|
||||
TEST(is_equal_angle(ang::Azimuth(m2::Point<double>(0.1, 0.1), m2::Point<double>(0.2, 0.2), math::pi4), 0.0), ());
|
||||
TEST(is_equal_angle(ang::Azimuth(m2::Point<double>(0.7, 0.7), m2::Point<double>(-0.2, -0.2), math::pi2),
|
||||
3 * math::pi4),
|
||||
());
|
||||
}
|
||||
93
libs/geometry/geometry_tests/anyrect_test.cpp
Normal file
93
libs/geometry/geometry_tests/anyrect_test.cpp
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/any_rect2d.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
UNIT_TEST(AnyRect_TestConvertTo)
|
||||
{
|
||||
AnyRectD const r(PointD(0, 0), ang::Angle<double>(math::pi / 4), RectD(0, 0, 10, 10));
|
||||
|
||||
PointD const pt1(100, 0);
|
||||
|
||||
double const sqrt2 = sqrt(2.0);
|
||||
TEST(r.ConvertTo(pt1).EqualDxDy(PointD(100 / sqrt2, -100 / sqrt2), 10e-5), ());
|
||||
TEST(r.ConvertTo(PointD(100, 100)).EqualDxDy(PointD(100 * sqrt2, 0), 10e-5), ());
|
||||
|
||||
AnyRectD r1(PointD(100, 100), ang::Angle<double>(math::pi / 4), RectD(0, 0, 10, 10));
|
||||
|
||||
PointD pt(100, 100 + 50 * sqrt2);
|
||||
|
||||
TEST(r1.ConvertTo(pt).EqualDxDy(PointD(50, 50), 10e-5), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(AnyRect_TestConvertFrom)
|
||||
{
|
||||
AnyRectD const r(PointD(100, 100), ang::Angle<double>(math::pi / 6), RectD(0, 0, 10, 10));
|
||||
|
||||
double const sqrt3 = sqrt(3.0);
|
||||
TEST(r.ConvertFrom(PointD(50, 0)).EqualDxDy(PointD(100 + 50 * sqrt3 / 2, 100 + 50 * 1 / 2.0), 10e-5), ());
|
||||
TEST(r.ConvertTo(PointD(100 + 50 * sqrt3 / 2, 100 + 50 * 1.0 / 2)).EqualDxDy(PointD(50, 0), 10e-5), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(AnyRect_ZeroRect)
|
||||
{
|
||||
AnyRectD const r0(RectD(0, 0, 0, 0));
|
||||
PointD const centerPt(300.0, 300.0);
|
||||
AnyRectD const r1(Offset(r0, centerPt));
|
||||
TEST_EQUAL(r1.GlobalCenter(), centerPt, ());
|
||||
AnyRectD const r2(Inflate(r0, 2.0, 2.0));
|
||||
TEST_EQUAL(r2.GetLocalRect(), RectD(-2, -2, 2, 2), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(AnyRect_TestIntersection)
|
||||
{
|
||||
AnyRectD const r0(PointD(93.196, 104.21), ang::Angle<double>(+1.03), RectD(2, 0, 4, 15));
|
||||
AnyRectD const r1(PointD(99.713, 116.02), ang::Angle<double>(-1.03), RectD(0, 0, 14, 14));
|
||||
|
||||
std::array<PointD, 4> pts;
|
||||
r0.GetGlobalPoints(pts);
|
||||
r1.ConvertTo(pts);
|
||||
RectD r2(pts[0].x, pts[0].y, pts[0].x, pts[0].y);
|
||||
r2.Add(pts[1]);
|
||||
r2.Add(pts[2]);
|
||||
r2.Add(pts[3]);
|
||||
|
||||
TEST(r1.GetLocalRect().IsIntersect(r2) == false, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(AnyRect_TestIsIntersect)
|
||||
{
|
||||
auto const pi6 = ang::Angle<double>(math::pi / 6);
|
||||
auto const pi8 = ang::Angle<double>(math::pi / 8);
|
||||
|
||||
AnyRectD const r0(PointD(100, 100), pi6, RectD(0, 0, 50, 20));
|
||||
AnyRectD const r1(PointD(100, 100), pi6, RectD(0, -10, 50, 10));
|
||||
AnyRectD const r2(PointD(100, 100), pi6, RectD(0, -21, 50, -1));
|
||||
|
||||
TEST(r0.IsIntersect(r1), ());
|
||||
TEST(r1.IsIntersect(r2), ());
|
||||
TEST(!r0.IsIntersect(r2), ());
|
||||
TEST(r1.IsIntersect(r2), ());
|
||||
|
||||
AnyRectD const r3(PointD(50, 50), pi8, RectD(0, 0, 80, 30));
|
||||
TEST(r0.IsIntersect(r3), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(AnyRect_SetSizesToIncludePoint)
|
||||
{
|
||||
AnyRectD rect(PointD(100, 100), ang::Angle<double>(math::pi / 6), RectD(0, 0, 50, 50));
|
||||
|
||||
TEST(!rect.IsPointInside(PointD(0, 0)), ());
|
||||
TEST(!rect.IsPointInside(PointD(200, 200)), ());
|
||||
|
||||
rect.SetSizesToIncludePoint(PointD(0, 0));
|
||||
TEST(rect.IsPointInside(PointD(0, 0)), ());
|
||||
|
||||
rect.SetSizesToIncludePoint(PointD(200, 200));
|
||||
TEST(rect.IsPointInside(PointD(200, 200)), ());
|
||||
}
|
||||
} // namespace m2
|
||||
28
libs/geometry/geometry_tests/area_on_earth_tests.cpp
Normal file
28
libs/geometry/geometry_tests/area_on_earth_tests.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/area_on_earth.hpp"
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
UNIT_TEST(AreaOnEarth_Circle)
|
||||
{
|
||||
double const kEarthSurfaceArea = 4.0 * math::pi * ms::kEarthRadiusMeters * ms::kEarthRadiusMeters;
|
||||
TEST_ALMOST_EQUAL_ABS(ms::CircleAreaOnEarth(math::pi * ms::kEarthRadiusMeters), kEarthSurfaceArea, 1e-1, ());
|
||||
|
||||
TEST_ALMOST_EQUAL_ABS(ms::CircleAreaOnEarth(2.0 /* radiusMeters */), math::pi * 2.0 * 2.0, 1e-2, ());
|
||||
|
||||
TEST_ALMOST_EQUAL_ABS(ms::CircleAreaOnEarth(2000.0 /* radiusMeters */), math::pi * 2000.0 * 2000.0, 1.0, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(AreaOnEarth_ThreePoints)
|
||||
{
|
||||
double const kEarthSurfaceArea = 4.0 * math::pi * ms::kEarthRadiusMeters * ms::kEarthRadiusMeters;
|
||||
|
||||
TEST_ALMOST_EQUAL_ABS(ms::AreaOnEarth({90.0, 0.0}, {0.0, 0.0}, {0.0, 90.0}), kEarthSurfaceArea / 8.0, 1e-1, ());
|
||||
|
||||
TEST_ALMOST_EQUAL_ABS(ms::AreaOnEarth({90.0, 0.0}, {0.0, 90.0}, {0.0, -90.0}), kEarthSurfaceArea / 4.0, 1e-1, ());
|
||||
}
|
||||
33
libs/geometry/geometry_tests/bounding_box_tests.cpp
Normal file
33
libs/geometry/geometry_tests/bounding_box_tests.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/bounding_box.hpp"
|
||||
|
||||
namespace bounding_box_tests
|
||||
{
|
||||
UNIT_TEST(BoundingBox_Smoke)
|
||||
{
|
||||
{
|
||||
m2::BoundingBox bbox;
|
||||
|
||||
TEST(!bbox.HasPoint(0, 0), ());
|
||||
TEST(!bbox.HasPoint(-1, 1), ());
|
||||
}
|
||||
|
||||
{
|
||||
m2::BoundingBox bbox;
|
||||
|
||||
bbox.Add(0, 0);
|
||||
TEST(bbox.HasPoint(0, 0), ());
|
||||
TEST(!bbox.HasPoint(1, 0), ());
|
||||
TEST(!bbox.HasPoint(0, 1), ());
|
||||
TEST(!bbox.HasPoint(1, 1), ());
|
||||
TEST(!bbox.HasPoint(0.5, 0.5), ());
|
||||
|
||||
bbox.Add(1, 1);
|
||||
TEST(bbox.HasPoint(1, 0), ());
|
||||
TEST(bbox.HasPoint(0, 1), ());
|
||||
TEST(bbox.HasPoint(1, 1), ());
|
||||
TEST(bbox.HasPoint(0.5, 0.5), ());
|
||||
}
|
||||
}
|
||||
} // namespace bounding_box_tests
|
||||
73
libs/geometry/geometry_tests/calipers_box_tests.cpp
Normal file
73
libs/geometry/geometry_tests/calipers_box_tests.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/calipers_box.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace calipers_box_tests
|
||||
{
|
||||
using namespace m2;
|
||||
using namespace std;
|
||||
|
||||
UNIT_TEST(CalipersBox_Smoke)
|
||||
{
|
||||
{
|
||||
CalipersBox const cbox(vector<PointD>{});
|
||||
TEST(cbox.Points().empty(), ());
|
||||
TEST(!cbox.HasPoint(0, 0), ());
|
||||
TEST(!cbox.HasPoint(0, 1), ());
|
||||
TEST(!cbox.HasPoint(1, 0), ());
|
||||
}
|
||||
|
||||
{
|
||||
vector<PointD> const points = {{PointD(2, 3)}};
|
||||
CalipersBox const cbox(points);
|
||||
TEST_EQUAL(cbox.Points(), points, ());
|
||||
TEST(cbox.HasPoint(2, 3), ());
|
||||
TEST(!cbox.HasPoint(4, 6), ());
|
||||
TEST(!cbox.HasPoint(0, 0), ());
|
||||
}
|
||||
|
||||
{
|
||||
vector<PointD> const points = {{PointD(2, 3), PointD(2, 3), PointD(2, 3)}};
|
||||
CalipersBox const cbox(points);
|
||||
TEST_EQUAL(cbox.Points(), vector<PointD>{{PointD(2, 3)}}, ());
|
||||
}
|
||||
|
||||
{
|
||||
vector<PointD> const points = {{PointD(1, 1), PointD(1, 2)}};
|
||||
CalipersBox const cbox(points);
|
||||
TEST_EQUAL(cbox.Points(), points, ());
|
||||
TEST(cbox.HasPoint(1, 1.5), ());
|
||||
TEST(!cbox.HasPoint(1, 3), ());
|
||||
TEST(!cbox.HasPoint(0, 0), ());
|
||||
}
|
||||
|
||||
{
|
||||
vector<PointD> const points = {
|
||||
{PointD(0, 0), PointD(-2, 3), PointD(1, 5), PointD(3, 2), PointD(1, 2), PointD(0, 3)}};
|
||||
CalipersBox const cbox(points);
|
||||
TEST_EQUAL(cbox.Points(), (vector<PointD>{{PointD(-2, 3), PointD(0, 0), PointD(3, 2), PointD(1, 5)}}), ());
|
||||
for (auto const & p : points)
|
||||
TEST(cbox.HasPoint(p), (p));
|
||||
TEST(!cbox.HasPoint(1, 0), ());
|
||||
}
|
||||
|
||||
{
|
||||
vector<PointD> const points = {{PointD(0, 0), PointD(1, 0), PointD(0, 5), PointD(1, 5), PointD(-2, 2),
|
||||
PointD(-2, 3), PointD(3, 2), PointD(3, 3)}};
|
||||
vector<PointD> const expected = {{PointD(-2.5, 2.5), PointD(0.5, -0.5), PointD(3.5, 2.5), PointD(0.5, 5.5)}};
|
||||
CalipersBox const cbox(points);
|
||||
TEST_EQUAL(cbox.Points(), expected, ());
|
||||
|
||||
for (auto const & p : points)
|
||||
TEST(cbox.HasPoint(p), (p));
|
||||
|
||||
TEST(cbox.HasPoint(0, 2), ());
|
||||
|
||||
TEST(!cbox.HasPoint(2, 0), ());
|
||||
TEST(!cbox.HasPoint(4, 2), ());
|
||||
}
|
||||
}
|
||||
} // namespace calipers_box_tests
|
||||
240
libs/geometry/geometry_tests/cellid_test.cpp
Normal file
240
libs/geometry/geometry_tests/cellid_test.cpp
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/cellid.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cellid_test
|
||||
{
|
||||
using std::make_pair, std::string, std::vector;
|
||||
|
||||
UNIT_TEST(CellId_Parent)
|
||||
{
|
||||
TEST_EQUAL(m2::CellId<3>("1").Parent(), m2::CellId<3>(""), ());
|
||||
TEST_EQUAL(m2::CellId<4>("032").Parent(), m2::CellId<4>("03"), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_AncestorAtLevel)
|
||||
{
|
||||
TEST_EQUAL(m2::CellId<3>("1").AncestorAtLevel(0), m2::CellId<3>(""), ());
|
||||
TEST_EQUAL(m2::CellId<4>("032").AncestorAtLevel(2), m2::CellId<4>("03"), ());
|
||||
TEST_EQUAL(m2::CellId<4>("032").AncestorAtLevel(1), m2::CellId<4>("0"), ());
|
||||
TEST_EQUAL(m2::CellId<4>("032").AncestorAtLevel(0), m2::CellId<4>(""), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_FromString)
|
||||
{
|
||||
TEST_EQUAL(m2::CellId<3>(""), m2::CellId<3>::FromBitsAndLevel(0, 0), ());
|
||||
TEST_EQUAL(m2::CellId<4>("032"), m2::CellId<4>::FromBitsAndLevel(14, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("03"), m2::CellId<3>::FromBitsAndLevel(3, 2), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_ToString)
|
||||
{
|
||||
TEST_EQUAL(m2::CellId<3>("").ToString(), "", ());
|
||||
TEST_EQUAL(m2::CellId<4>("032").ToString(), "032", ());
|
||||
TEST_EQUAL(m2::CellId<3>("03").ToString(), "03", ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_ToInt64)
|
||||
{
|
||||
TEST_EQUAL(m2::CellId<3>("").ToInt64(3), 1, ());
|
||||
TEST_EQUAL(m2::CellId<3>("0").ToInt64(3), 2, ());
|
||||
TEST_EQUAL(m2::CellId<3>("1").ToInt64(3), 7, ());
|
||||
TEST_EQUAL(m2::CellId<3>("2").ToInt64(3), 12, ());
|
||||
TEST_EQUAL(m2::CellId<3>("3").ToInt64(3), 17, ());
|
||||
TEST_EQUAL(m2::CellId<3>("00").ToInt64(3), 3, ());
|
||||
TEST_EQUAL(m2::CellId<3>("01").ToInt64(3), 4, ());
|
||||
TEST_EQUAL(m2::CellId<3>("03").ToInt64(3), 6, ());
|
||||
TEST_EQUAL(m2::CellId<3>("10").ToInt64(3), 8, ());
|
||||
TEST_EQUAL(m2::CellId<3>("20").ToInt64(3), 13, ());
|
||||
TEST_EQUAL(m2::CellId<3>("23").ToInt64(3), 16, ());
|
||||
TEST_EQUAL(m2::CellId<3>("30").ToInt64(3), 18, ());
|
||||
TEST_EQUAL(m2::CellId<3>("31").ToInt64(3), 19, ());
|
||||
TEST_EQUAL(m2::CellId<3>("33").ToInt64(3), 21, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_ToInt64_LevelLessThanDepth)
|
||||
{
|
||||
TEST_EQUAL(m2::CellId<3>("").ToInt64(2), 1, ());
|
||||
TEST_EQUAL(m2::CellId<3>("0").ToInt64(2), 2, ());
|
||||
TEST_EQUAL(m2::CellId<3>("1").ToInt64(2), 3, ());
|
||||
TEST_EQUAL(m2::CellId<3>("2").ToInt64(2), 4, ());
|
||||
TEST_EQUAL(m2::CellId<3>("3").ToInt64(2), 5, ());
|
||||
TEST_EQUAL(m2::CellId<3>("00").ToInt64(2), 2, ());
|
||||
TEST_EQUAL(m2::CellId<3>("01").ToInt64(2), 2, ());
|
||||
TEST_EQUAL(m2::CellId<3>("03").ToInt64(2), 2, ());
|
||||
TEST_EQUAL(m2::CellId<3>("10").ToInt64(2), 3, ());
|
||||
TEST_EQUAL(m2::CellId<3>("20").ToInt64(2), 4, ());
|
||||
TEST_EQUAL(m2::CellId<3>("23").ToInt64(2), 4, ());
|
||||
TEST_EQUAL(m2::CellId<3>("30").ToInt64(2), 5, ());
|
||||
TEST_EQUAL(m2::CellId<3>("31").ToInt64(2), 5, ());
|
||||
TEST_EQUAL(m2::CellId<3>("33").ToInt64(2), 5, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_FromInt64)
|
||||
{
|
||||
TEST_EQUAL(m2::CellId<3>(""), m2::CellId<3>::FromInt64(1, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("0"), m2::CellId<3>::FromInt64(2, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("1"), m2::CellId<3>::FromInt64(7, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("2"), m2::CellId<3>::FromInt64(12, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("3"), m2::CellId<3>::FromInt64(17, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("00"), m2::CellId<3>::FromInt64(3, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("01"), m2::CellId<3>::FromInt64(4, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("03"), m2::CellId<3>::FromInt64(6, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("10"), m2::CellId<3>::FromInt64(8, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("20"), m2::CellId<3>::FromInt64(13, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("23"), m2::CellId<3>::FromInt64(16, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("30"), m2::CellId<3>::FromInt64(18, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("31"), m2::CellId<3>::FromInt64(19, 3), ());
|
||||
TEST_EQUAL(m2::CellId<3>("33"), m2::CellId<3>::FromInt64(21, 3), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_XY)
|
||||
{
|
||||
TEST_EQUAL(m2::CellId<3>("").XY(), make_pair(4U, 4U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("0").XY(), make_pair(2U, 2U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("1").XY(), make_pair(6U, 2U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("2").XY(), make_pair(2U, 6U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("3").XY(), make_pair(6U, 6U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("00").XY(), make_pair(1U, 1U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("01").XY(), make_pair(3U, 1U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("03").XY(), make_pair(3U, 3U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("10").XY(), make_pair(5U, 1U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("20").XY(), make_pair(1U, 5U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("23").XY(), make_pair(3U, 7U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("30").XY(), make_pair(5U, 5U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("31").XY(), make_pair(7U, 5U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("33").XY(), make_pair(7U, 7U), ());
|
||||
TEST_EQUAL(m2::CellId<3>("33").XY(), make_pair(7U, 7U), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_Radius)
|
||||
{
|
||||
TEST_EQUAL(m2::CellId<3>("").Radius(), 4, ());
|
||||
TEST_EQUAL(m2::CellId<3>("1").Radius(), 2, ());
|
||||
TEST_EQUAL(m2::CellId<3>("00").Radius(), 1, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_FromXY)
|
||||
{
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(0, 0, 2)), (m2::CellId<3>("00")), ());
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(0, 0, 1)), (m2::CellId<3>("0")), ());
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(0, 0, 0)), (m2::CellId<3>("")), ());
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(5, 4, 0)), (m2::CellId<3>("")), ());
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(5, 0, 2)), (m2::CellId<3>("10")), ());
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(5, 0, 1)), (m2::CellId<3>("1")), ());
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(5, 0, 1)), (m2::CellId<3>("1")), ());
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(7, 7, 2)), (m2::CellId<3>("33")), ());
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(7, 7, 1)), (m2::CellId<3>("3")), ());
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(7, 7, 0)), (m2::CellId<3>("")), ());
|
||||
TEST_EQUAL((m2::CellId<3>::FromXY(8, 8, 2)), (m2::CellId<3>("33")), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_FromXY_XY_Match)
|
||||
{
|
||||
TEST_EQUAL((m2::CellId<9>::FromXY(48, 80, 8).XY()), make_pair(49U, 81U), ());
|
||||
TEST_EQUAL((m2::CellId<9>::FromXY(192, 320, 8).XY()), make_pair(193U, 321U), ());
|
||||
TEST_EQUAL((m2::CellId<11>::FromXY(768, 1280, 10).XY()), make_pair(769U, 1281U), ());
|
||||
TEST_EQUAL((m2::CellId<21>::FromXY(786432, 1310720, 20).XY()), make_pair(786433U, 1310721U), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_SubTreeSize)
|
||||
{
|
||||
TEST_EQUAL(m2::CellId<3>("00").SubTreeSize(3), 1, ());
|
||||
TEST_EQUAL(m2::CellId<3>("22").SubTreeSize(3), 1, ());
|
||||
TEST_EQUAL(m2::CellId<3>("33").SubTreeSize(3), 1, ());
|
||||
TEST_EQUAL(m2::CellId<3>("0").SubTreeSize(3), 5, ());
|
||||
TEST_EQUAL(m2::CellId<3>("1").SubTreeSize(3), 5, ());
|
||||
TEST_EQUAL(m2::CellId<3>("3").SubTreeSize(3), 5, ());
|
||||
TEST_EQUAL(m2::CellId<3>("").SubTreeSize(3), 21, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_LessQueueOrder)
|
||||
{
|
||||
vector<string> v;
|
||||
v.emplace_back("0");
|
||||
v.emplace_back("1");
|
||||
v.emplace_back("00");
|
||||
v.emplace_back("00");
|
||||
v.emplace_back("02");
|
||||
v.emplace_back("002");
|
||||
v.emplace_back("101");
|
||||
vector<string> e = v;
|
||||
do
|
||||
{
|
||||
vector<m2::CellId<4>> tst, exp;
|
||||
for (size_t i = 0; i < v.size(); ++i)
|
||||
{
|
||||
tst.emplace_back(e[i]);
|
||||
exp.emplace_back(v[i]);
|
||||
}
|
||||
sort(tst.begin(), tst.end(), m2::CellId<4>::LessLevelOrder());
|
||||
TEST_EQUAL(tst, exp, ());
|
||||
}
|
||||
while (next_permutation(e.begin(), e.end()));
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_LessStackOrder)
|
||||
{
|
||||
vector<string> v;
|
||||
v.emplace_back("0");
|
||||
v.emplace_back("00");
|
||||
v.emplace_back("00");
|
||||
v.emplace_back("002");
|
||||
v.emplace_back("02");
|
||||
v.emplace_back("1");
|
||||
v.emplace_back("101");
|
||||
vector<string> e = v;
|
||||
do
|
||||
{
|
||||
vector<m2::CellId<4>> tst, exp;
|
||||
for (size_t i = 0; i < v.size(); ++i)
|
||||
{
|
||||
tst.emplace_back(e[i]);
|
||||
exp.emplace_back(v[i]);
|
||||
}
|
||||
sort(tst.begin(), tst.end(), m2::CellId<4>::LessPreOrder());
|
||||
TEST_EQUAL(tst, exp, ());
|
||||
}
|
||||
while (next_permutation(e.begin(), e.end()));
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_IsStringValid)
|
||||
{
|
||||
using Id = m2::CellId<9>;
|
||||
TEST(Id::IsCellId("0123132"), ());
|
||||
TEST(Id::IsCellId(""), ());
|
||||
TEST(!Id::IsCellId("-1332"), ());
|
||||
TEST(!Id::IsCellId("023."), ());
|
||||
TEST(!Id::IsCellId("121832"), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CellId_ToAndFromInt64ZOrder)
|
||||
{
|
||||
int const kMaxDepth = 4;
|
||||
using Id = m2::CellId<kMaxDepth>;
|
||||
for (int depth = 1; depth <= kMaxDepth; ++depth)
|
||||
{
|
||||
int64_t const treeSize = ((int64_t{1} << (2 * depth)) - 1) / 3;
|
||||
LOG(LINFO, ("Depth =", depth, " TreeSize =", treeSize));
|
||||
for (int64_t id = 1; id <= treeSize; ++id)
|
||||
{
|
||||
auto const cell = Id::FromInt64ZOrder(id, depth);
|
||||
TEST_EQUAL(id, cell.ToInt64ZOrder(depth), ());
|
||||
}
|
||||
}
|
||||
|
||||
vector<string> const atDepth3 = {
|
||||
"", "0", "00", "01", "02", "03", "1", "10", "11", "12", "13",
|
||||
"2", "20", "21", "22", "23", "3", "30", "31", "32", "33",
|
||||
};
|
||||
|
||||
for (uint64_t id = 1; id <= atDepth3.size(); ++id)
|
||||
TEST_EQUAL(Id::FromInt64(id, 3), Id(atDepth3[id - 1]), ());
|
||||
}
|
||||
} // namespace cellid_test
|
||||
73
libs/geometry/geometry_tests/circle_on_earth_tests.cpp
Normal file
73
libs/geometry/geometry_tests/circle_on_earth_tests.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/circle_on_earth.hpp"
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
void TestGeometryAlmostEqualAbs(std::vector<m2::PointD> const & geometry, std::vector<m2::PointD> const & answer,
|
||||
double eps)
|
||||
{
|
||||
TEST_EQUAL(geometry.size(), answer.size(), ());
|
||||
|
||||
for (size_t i = 0; i < geometry.size(); ++i)
|
||||
TEST_ALMOST_EQUAL_ABS(geometry[i], answer[i], eps, ());
|
||||
}
|
||||
|
||||
void TestGeometryAlmostEqualMeters(std::vector<m2::PointD> const & geometry, std::vector<m2::PointD> const & answer,
|
||||
double epsMeters)
|
||||
{
|
||||
TEST_EQUAL(geometry.size(), answer.size(), ());
|
||||
|
||||
for (size_t i = 0; i < geometry.size(); ++i)
|
||||
TEST_LESS(mercator::DistanceOnEarth(geometry[i], answer[i]), epsMeters, ());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(CircleOnEarth)
|
||||
{
|
||||
ms::LatLon const center(90.0 /* lat */, 0.0 /* lon */);
|
||||
double const radiusMeters = 2.0 * math::pi * ms::kEarthRadiusMeters / 4.0;
|
||||
auto const geometry = ms::CreateCircleGeometryOnEarth(center, radiusMeters, 90.0 /* angleStepDegree */);
|
||||
|
||||
std::vector<m2::PointD> const result = {mercator::FromLatLon(0.0, 0.0), mercator::FromLatLon(0.0, 90.0),
|
||||
mercator::FromLatLon(0.0, 180.0), mercator::FromLatLon(0.0, -90.0)};
|
||||
|
||||
TestGeometryAlmostEqualAbs(geometry, result, 1e-9 /* eps */);
|
||||
}
|
||||
|
||||
UNIT_TEST(CircleOnEarthEquator)
|
||||
{
|
||||
ms::LatLon const center(0.0 /* lat */, 0.0 /* lon */);
|
||||
auto const point = mercator::FromLatLon(center);
|
||||
|
||||
auto constexpr kRadiusMeters = 10000.0;
|
||||
double const kRadiusMercator = mercator::MetersToMercator(kRadiusMeters);
|
||||
auto constexpr kAngleStepDegree = 30.0;
|
||||
auto constexpr kN = static_cast<size_t>(360.0 / kAngleStepDegree);
|
||||
|
||||
std::vector<m2::PointD> result;
|
||||
result.reserve(kN);
|
||||
auto constexpr kStepRad = math::DegToRad(kAngleStepDegree);
|
||||
double angleSumRad = 0.0;
|
||||
double angleRad = -math::pi2;
|
||||
while (angleSumRad < 2 * math::pi)
|
||||
{
|
||||
angleSumRad += kStepRad;
|
||||
result.emplace_back(point.x + kRadiusMercator * cos(angleRad), point.y + kRadiusMercator * sin(angleRad));
|
||||
angleRad += kStepRad;
|
||||
if (angleRad > 2 * math::pi)
|
||||
angleRad -= 2 * math::pi;
|
||||
}
|
||||
|
||||
auto const geometry = ms::CreateCircleGeometryOnEarth(center, kRadiusMeters, kAngleStepDegree);
|
||||
|
||||
TestGeometryAlmostEqualMeters(geometry, result, 20.0 /* epsMeters */);
|
||||
}
|
||||
278
libs/geometry/geometry_tests/clipping_test.cpp
Normal file
278
libs/geometry/geometry_tests/clipping_test.cpp
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/clipping.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace clipping_test
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
bool CompareTriangleLists(vector<m2::PointD> const & list1, vector<m2::PointD> const & list2)
|
||||
{
|
||||
if (list1.size() != list2.size())
|
||||
return false;
|
||||
|
||||
double const kEps = 1e-5;
|
||||
for (size_t i = 0; i < list1.size(); i++)
|
||||
if (!list1[i].EqualDxDy(list2[i], kEps))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompareSplineLists(vector<m2::SharedSpline> const & list1, vector<m2::SharedSpline> const & list2)
|
||||
{
|
||||
if (list1.size() != list2.size())
|
||||
return false;
|
||||
|
||||
double const kEps = 1e-5;
|
||||
for (size_t i = 0; i < list1.size(); i++)
|
||||
{
|
||||
auto & path1 = list1[i]->GetPath();
|
||||
auto & path2 = list2[i]->GetPath();
|
||||
if (path1.size() != path2.size())
|
||||
return false;
|
||||
|
||||
for (size_t j = 0; j < path1.size(); j++)
|
||||
if (!path1[j].EqualDxDy(path2[j], kEps))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
vector<m2::SharedSpline> ConstructSplineList(vector<vector<m2::PointD>> const & segments)
|
||||
{
|
||||
vector<m2::SharedSpline> result;
|
||||
result.reserve(segments.size());
|
||||
for (size_t i = 0; i < segments.size(); i++)
|
||||
{
|
||||
m2::SharedSpline s;
|
||||
s.Reset(new m2::Spline(segments[i].size()));
|
||||
for (size_t j = 0; j < segments[i].size(); j++)
|
||||
s->AddPoint(segments[i][j]);
|
||||
result.push_back(std::move(s));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
UNIT_TEST(Clipping_ClipTriangleByRect)
|
||||
{
|
||||
m2::RectD r(-1.0, -1.0, 1.0, 1.0);
|
||||
|
||||
// Completely inside.
|
||||
vector<m2::PointD> result1;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(0.5, 0.5), m2::PointD(0.5, -0.5), m2::PointD(0.0, 0.0),
|
||||
[&result1](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result1.push_back(p1);
|
||||
result1.push_back(p2);
|
||||
result1.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult1 = {m2::PointD(0.5, 0.5), m2::PointD(0.5, -0.5), m2::PointD(0.0, 0.0)};
|
||||
TEST(CompareTriangleLists(result1, expectedResult1), (result1, expectedResult1));
|
||||
|
||||
// 1 point inside.
|
||||
vector<m2::PointD> result2;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(0.0, 0.0), m2::PointD(2.0, 2.0), m2::PointD(2.0, -2.0),
|
||||
[&result2](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result2.push_back(p1);
|
||||
result2.push_back(p2);
|
||||
result2.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult2 = {m2::PointD(0.0, 0.0), m2::PointD(1.0, 1.0), m2::PointD(1.0, -1.0)};
|
||||
TEST(CompareTriangleLists(result2, expectedResult2), (result2, expectedResult2));
|
||||
|
||||
// 2 points inside.
|
||||
vector<m2::PointD> result3;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(0.0, 0.5), m2::PointD(2.0, 0.0), m2::PointD(0.0, -0.5),
|
||||
[&result3](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result3.push_back(p1);
|
||||
result3.push_back(p2);
|
||||
result3.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult3 = {m2::PointD(0.0, 0.5), m2::PointD(1.0, 0.25), m2::PointD(1.0, -0.25),
|
||||
m2::PointD(0.0, 0.5), m2::PointD(1.0, -0.25), m2::PointD(0.0, -0.5)};
|
||||
TEST(CompareTriangleLists(result3, expectedResult3), (result3, expectedResult3));
|
||||
|
||||
// 2 edges clipping.
|
||||
vector<m2::PointD> result4;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(0.0, 0.0), m2::PointD(0.0, 1.5), m2::PointD(1.5, 0.0),
|
||||
[&result4](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result4.push_back(p1);
|
||||
result4.push_back(p2);
|
||||
result4.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult4 = {m2::PointD(0.0, 0.0), m2::PointD(0.0, 1.0), m2::PointD(0.5, 1.0),
|
||||
m2::PointD(0.0, 0.0), m2::PointD(0.5, 1.0), m2::PointD(1.0, 0.5),
|
||||
m2::PointD(0.0, 0.0), m2::PointD(1.0, 0.5), m2::PointD(1.0, 0.0)};
|
||||
TEST(CompareTriangleLists(result4, expectedResult4), (result4, expectedResult4));
|
||||
|
||||
// 3 edges clipping.
|
||||
vector<m2::PointD> result5;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(-1.5, 0.0), m2::PointD(0.0, 1.5), m2::PointD(1.5, 0.0),
|
||||
[&result5](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result5.push_back(p1);
|
||||
result5.push_back(p2);
|
||||
result5.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult5 = {m2::PointD(-1.0, 0.5), m2::PointD(-0.5, 1.0), m2::PointD(0.5, 1.0),
|
||||
m2::PointD(-1.0, 0.5), m2::PointD(0.5, 1.0), m2::PointD(1.0, 0.5),
|
||||
m2::PointD(-1.0, 0.5), m2::PointD(1.0, 0.5), m2::PointD(1.0, 0.0),
|
||||
m2::PointD(-1.0, 0.5), m2::PointD(1.0, 0.0), m2::PointD(-1.0, 0.0)};
|
||||
TEST(CompareTriangleLists(result5, expectedResult5), (result5, expectedResult5));
|
||||
|
||||
// Completely outside.
|
||||
vector<m2::PointD> result6;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(1.5, 1.5), m2::PointD(1.5, -1.5), m2::PointD(2.0, 0.0),
|
||||
[&result6](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result6.push_back(p1);
|
||||
result6.push_back(p2);
|
||||
result6.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult6 = {};
|
||||
TEST(CompareTriangleLists(result6, expectedResult6), (result6, expectedResult6));
|
||||
|
||||
// Clip with an angle of rect.
|
||||
vector<m2::PointD> result7;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(0.5, 0.5), m2::PointD(0.5, 2.0), m2::PointD(2.0, 0.5),
|
||||
[&result7](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result7.push_back(p1);
|
||||
result7.push_back(p2);
|
||||
result7.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult7 = {m2::PointD(0.5, 0.5), m2::PointD(0.5, 1.0), m2::PointD(1.0, 1.0),
|
||||
m2::PointD(0.5, 0.5), m2::PointD(1.0, 1.0), m2::PointD(1.0, 0.5)};
|
||||
TEST(CompareTriangleLists(result7, expectedResult7), (result7, expectedResult7));
|
||||
|
||||
// Triangle covers rect.
|
||||
vector<m2::PointD> result8;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(0.0, 3.0), m2::PointD(5.0, -2.0), m2::PointD(-5.0, -2.0),
|
||||
[&result8](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result8.push_back(p1);
|
||||
result8.push_back(p2);
|
||||
result8.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult8 = {m2::PointD(-1.0, 1.0), m2::PointD(1.0, 1.0), m2::PointD(1.0, -1.0),
|
||||
m2::PointD(1.0, -1.0), m2::PointD(-1.0, -1.0), m2::PointD(-1.0, 1.0)};
|
||||
TEST(CompareTriangleLists(result8, expectedResult8), (result8, expectedResult8));
|
||||
|
||||
// Clip with an angle of rect.
|
||||
vector<m2::PointD> result9;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(1.5, 0.0), m2::PointD(1.5, -1.5), m2::PointD(0.0, -1.5),
|
||||
[&result9](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result9.push_back(p1);
|
||||
result9.push_back(p2);
|
||||
result9.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult9 = {m2::PointD(0.5, -1.0), m2::PointD(1.0, -0.5), m2::PointD(1.0, -1.0)};
|
||||
TEST(CompareTriangleLists(result9, expectedResult9), (result9, expectedResult9));
|
||||
|
||||
// Clip with an angle of rect.
|
||||
vector<m2::PointD> result10;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(-2.0, -0.5), m2::PointD(-0.5, -0.5), m2::PointD(-0.5, -2.0),
|
||||
[&result10](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result10.push_back(p1);
|
||||
result10.push_back(p2);
|
||||
result10.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult10 = {m2::PointD(-1.0, -0.5), m2::PointD(-0.5, -0.5), m2::PointD(-0.5, -1.0),
|
||||
m2::PointD(-1.0, -0.5), m2::PointD(-0.5, -1.0), m2::PointD(-1.0, -1.0)};
|
||||
TEST(CompareTriangleLists(result10, expectedResult10), (result10, expectedResult10));
|
||||
|
||||
// Clip with 3 angles of rect.
|
||||
vector<m2::PointD> result11;
|
||||
m2::ClipTriangleByRect(r, m2::PointD(2.0, -3.0), m2::PointD(-2.0, 1.0), m2::PointD(2.0, 2.0),
|
||||
[&result11](m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
result11.push_back(p1);
|
||||
result11.push_back(p2);
|
||||
result11.push_back(p3);
|
||||
});
|
||||
vector<m2::PointD> expectedResult11 = {m2::PointD(0.0, -1.0), m2::PointD(-1.0, 0.0), m2::PointD(-1.0, 1.0),
|
||||
m2::PointD(0.0, -1.0), m2::PointD(-1.0, 1.0), m2::PointD(1.0, 1.0),
|
||||
m2::PointD(0.0, -1.0), m2::PointD(1.0, 1.0), m2::PointD(1.0, -1.0)};
|
||||
TEST(CompareTriangleLists(result11, expectedResult11), (result11, expectedResult11));
|
||||
}
|
||||
|
||||
UNIT_TEST(Clipping_ClipSplineByRect)
|
||||
{
|
||||
m2::RectD r(-1.0, -1.0, 1.0, 1.0);
|
||||
|
||||
// Intersection.
|
||||
m2::SharedSpline spline1;
|
||||
spline1.Reset(new m2::Spline(2));
|
||||
spline1->AddPoint(m2::PointD(-2.0, 0.0));
|
||||
spline1->AddPoint(m2::PointD(2.0, 1.0));
|
||||
vector<m2::SharedSpline> result1 = m2::ClipSplineByRect(r, spline1);
|
||||
vector<m2::SharedSpline> expectedResult1 = ConstructSplineList({{m2::PointD(-1.0, 0.25), m2::PointD(1.0, 0.75)}});
|
||||
TEST(CompareSplineLists(result1, expectedResult1), ());
|
||||
|
||||
// Intersection. Several segments.
|
||||
m2::SharedSpline spline2;
|
||||
spline2.Reset(new m2::Spline(4));
|
||||
spline2->AddPoint(m2::PointD(-2.0, 0.0));
|
||||
spline2->AddPoint(m2::PointD(2.0, 1.0));
|
||||
spline2->AddPoint(m2::PointD(0.5, -2.0));
|
||||
spline2->AddPoint(m2::PointD(-0.5, -0.5));
|
||||
vector<m2::SharedSpline> result2 = m2::ClipSplineByRect(r, spline2);
|
||||
vector<m2::SharedSpline> expectedResult2 = ConstructSplineList(
|
||||
{{m2::PointD(-1.0, 0.25), m2::PointD(1.0, 0.75)}, {m2::PointD(-0.166666666, -1.0), m2::PointD(-0.5, -0.5)}});
|
||||
TEST(CompareSplineLists(result2, expectedResult2), ());
|
||||
|
||||
// Completely outside.
|
||||
m2::SharedSpline spline3;
|
||||
spline3.Reset(new m2::Spline(2));
|
||||
spline3->AddPoint(m2::PointD(-2.0, 2.0));
|
||||
spline3->AddPoint(m2::PointD(2.0, 3.0));
|
||||
vector<m2::SharedSpline> result3 = m2::ClipSplineByRect(r, spline3);
|
||||
vector<m2::SharedSpline> expectedResult3 = {};
|
||||
TEST(CompareSplineLists(result3, expectedResult3), ());
|
||||
|
||||
// Completely inside.
|
||||
m2::SharedSpline spline4;
|
||||
spline4.Reset(new m2::Spline(2));
|
||||
spline4->AddPoint(m2::PointD(-0.5, 0.0));
|
||||
spline4->AddPoint(m2::PointD(0.5, 0.5));
|
||||
vector<m2::SharedSpline> result4 = m2::ClipSplineByRect(r, spline4);
|
||||
vector<m2::SharedSpline> expectedResult4 = ConstructSplineList({{m2::PointD(-0.5, 0.0), m2::PointD(0.5, 0.5)}});
|
||||
TEST(CompareSplineLists(result4, expectedResult4), ());
|
||||
|
||||
// Intersection. Long spline.
|
||||
m2::SharedSpline spline5;
|
||||
spline5.Reset(new m2::Spline(4));
|
||||
spline5->AddPoint(m2::PointD(-2.0, 0.0));
|
||||
spline5->AddPoint(m2::PointD(0.0, 0.0));
|
||||
spline5->AddPoint(m2::PointD(0.5, 0.5));
|
||||
spline5->AddPoint(m2::PointD(2.0, 1.0));
|
||||
vector<m2::SharedSpline> result5 = m2::ClipSplineByRect(r, spline5);
|
||||
vector<m2::SharedSpline> expectedResult5 = ConstructSplineList(
|
||||
{{m2::PointD(-1.0, 0.0), m2::PointD(0.0, 0.0), m2::PointD(0.5, 0.5), m2::PointD(1.0, 0.66666666)}});
|
||||
TEST(CompareSplineLists(result5, expectedResult5), ());
|
||||
|
||||
// Intersection. Several segments.
|
||||
m2::SharedSpline spline6;
|
||||
spline6.Reset(new m2::Spline(5));
|
||||
spline6->AddPoint(m2::PointD(-1.0, 0.0));
|
||||
spline6->AddPoint(m2::PointD(-0.5, 1.0));
|
||||
spline6->AddPoint(m2::PointD(-0.5, 1.000000001));
|
||||
spline6->AddPoint(m2::PointD(0.0, 1.5));
|
||||
spline6->AddPoint(m2::PointD(0.0, 0.0));
|
||||
vector<m2::SharedSpline> result6 = m2::ClipSplineByRect(r, spline6);
|
||||
vector<m2::SharedSpline> expectedResult6 = ConstructSplineList(
|
||||
{{m2::PointD(-1.0, 0.0), m2::PointD(-0.5, 1.0)}, {m2::PointD(0.0, 1.0), m2::PointD(0.0, 0.0)}});
|
||||
TEST(CompareSplineLists(result6, expectedResult6), ());
|
||||
}
|
||||
} // namespace clipping_test
|
||||
39
libs/geometry/geometry_tests/common_test.cpp
Normal file
39
libs/geometry/geometry_tests/common_test.cpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#include "base/macros.hpp"
|
||||
|
||||
#include "geometry/geometry_tests/equality.hpp"
|
||||
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
using namespace test;
|
||||
|
||||
UNIT_TEST(Rect)
|
||||
{
|
||||
m2::RectD rect(0, 0, 500, 300);
|
||||
|
||||
double factor[] = {0.2, 0.3, 0.5, 0.7, 1.0, 1.3, 1.5, 2.0};
|
||||
for (size_t i = 0; i < ARRAY_SIZE(factor); ++i)
|
||||
{
|
||||
m2::RectD r(rect);
|
||||
r.Scale(factor[i]);
|
||||
TEST(is_equal_center(rect, r), ());
|
||||
}
|
||||
|
||||
m2::RectD external(0, 0, 100, 100);
|
||||
TEST(external.IsIntersect(m2::RectD(10, 10, 90, 90)), ());
|
||||
TEST(external.IsPointInside(m2::PointD(5, 33)), ());
|
||||
TEST(external.IsIntersect(m2::RectD(99, 99, 1000, 1000)), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point)
|
||||
{
|
||||
double const l = sqrt(2.0);
|
||||
double const a = math::pi / 4.0;
|
||||
m2::PointD const start(0.0, 0.0);
|
||||
|
||||
TEST(is_equal(start.Move(l, a), m2::PointD(1, 1)), ());
|
||||
TEST(is_equal(start.Move(l, math::pi - a), m2::PointD(-1, 1)), ());
|
||||
TEST(is_equal(start.Move(l, -math::pi + a), m2::PointD(-1, -1)), ());
|
||||
TEST(is_equal(start.Move(l, -a), m2::PointD(1, -1)), ());
|
||||
}
|
||||
46
libs/geometry/geometry_tests/convex_hull_tests.cpp
Normal file
46
libs/geometry/geometry_tests/convex_hull_tests.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/convex_hull.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace convex_hull_tests
|
||||
{
|
||||
using namespace m2;
|
||||
using namespace std;
|
||||
|
||||
double constexpr kEps = 1e-12;
|
||||
|
||||
vector<PointD> BuildConvexHull(vector<PointD> const & points)
|
||||
{
|
||||
return ConvexHull(points, kEps).Points();
|
||||
}
|
||||
|
||||
UNIT_TEST(ConvexHull_Smoke)
|
||||
{
|
||||
TEST_EQUAL(BuildConvexHull({}), vector<PointD>{}, ());
|
||||
TEST_EQUAL(BuildConvexHull({PointD(0, 0)}), vector<PointD>{PointD(0, 0)}, ());
|
||||
TEST_EQUAL(BuildConvexHull({PointD(0, 0), PointD(0, 0)}), vector<PointD>{PointD(0, 0)}, ());
|
||||
|
||||
TEST_EQUAL(BuildConvexHull({PointD(0, 0), PointD(1, 1), PointD(0, 0)}), vector<PointD>({PointD(0, 0), PointD(1, 1)}),
|
||||
());
|
||||
|
||||
TEST_EQUAL(BuildConvexHull({PointD(0, 0), PointD(1, 1), PointD(2, 2)}), vector<PointD>({PointD(0, 0), PointD(2, 2)}),
|
||||
());
|
||||
|
||||
{
|
||||
int const kXMax = 100;
|
||||
int const kYMax = 200;
|
||||
vector<PointD> points;
|
||||
for (int x = 0; x <= kXMax; ++x)
|
||||
for (int y = 0; y <= kYMax; ++y)
|
||||
points.emplace_back(x, y);
|
||||
TEST_EQUAL(BuildConvexHull(points),
|
||||
vector<PointD>({PointD(0, 0), PointD(kXMax, 0), PointD(kXMax, kYMax), PointD(0, kYMax)}), ());
|
||||
}
|
||||
|
||||
TEST_EQUAL(BuildConvexHull({PointD(0, 0), PointD(0, 5), PointD(10, 5), PointD(3, 3), PointD(10, 0)}),
|
||||
vector<PointD>({PointD(0, 0), PointD(10, 0), PointD(10, 5), PointD(0, 5)}), ());
|
||||
}
|
||||
} // namespace convex_hull_tests
|
||||
136
libs/geometry/geometry_tests/covering_test.cpp
Normal file
136
libs/geometry/geometry_tests/covering_test.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/cellid.hpp"
|
||||
#include "geometry/covering.hpp"
|
||||
#include "geometry/covering_utils.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
// TODO: Add covering unit tests here.
|
||||
|
||||
namespace covering_test
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
using CellId = m2::CellId<5>;
|
||||
|
||||
UNIT_TEST(CoverTriangle_Simple)
|
||||
{
|
||||
vector<CellId> v;
|
||||
covering::Covering<CellId> c(m2::PointD(3 * 2, 3 * 2), m2::PointD(4 * 2, 12 * 2), m2::PointD(14 * 2, 3 * 2));
|
||||
c.OutputToVector(v);
|
||||
vector<CellId> e;
|
||||
e.push_back(CellId("03"));
|
||||
e.push_back(CellId("003"));
|
||||
e.push_back(CellId("012"));
|
||||
e.push_back(CellId("013"));
|
||||
e.push_back(CellId("102"));
|
||||
e.push_back(CellId("103"));
|
||||
e.push_back(CellId("112"));
|
||||
e.push_back(CellId("120"));
|
||||
e.push_back(CellId("121"));
|
||||
e.push_back(CellId("122"));
|
||||
e.push_back(CellId("210"));
|
||||
e.push_back(CellId("211"));
|
||||
e.push_back(CellId("212"));
|
||||
e.push_back(CellId("0211"));
|
||||
e.push_back(CellId("0213"));
|
||||
e.push_back(CellId("0231"));
|
||||
e.push_back(CellId("0233"));
|
||||
e.push_back(CellId("1130"));
|
||||
e.push_back(CellId("1132"));
|
||||
e.push_back(CellId("1230"));
|
||||
e.push_back(CellId("1300"));
|
||||
e.push_back(CellId("2011"));
|
||||
e.push_back(CellId("2013"));
|
||||
e.push_back(CellId("2031"));
|
||||
e.push_back(CellId("2033"));
|
||||
e.push_back(CellId("2130"));
|
||||
e.push_back(CellId("2211"));
|
||||
e.push_back(CellId("2300"));
|
||||
e.push_back(CellId("3000"));
|
||||
TEST_EQUAL(v, e, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CoverTriangle_TriangleInsideCell)
|
||||
{
|
||||
vector<CellId> v;
|
||||
covering::Covering<CellId> c(m2::PointD(0.1, 0.1), m2::PointD(0.2, 0.2), m2::PointD(0.2, 0.4));
|
||||
c.OutputToVector(v);
|
||||
vector<CellId> e;
|
||||
e.push_back(CellId("0000"));
|
||||
TEST_EQUAL(v, e, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Covering_Append_Simple)
|
||||
{
|
||||
vector<CellId> v1, v2, v3;
|
||||
|
||||
v1.push_back(CellId("012"));
|
||||
v2.push_back(CellId("0123"));
|
||||
v3.push_back(CellId("012"));
|
||||
|
||||
v1.push_back(CellId("023"));
|
||||
v2.push_back(CellId("023"));
|
||||
v3.push_back(CellId("023"));
|
||||
|
||||
v1.push_back(CellId("130"));
|
||||
v1.push_back(CellId("131"));
|
||||
v2.push_back(CellId("133"));
|
||||
v1.push_back(CellId("1320"));
|
||||
v1.push_back(CellId("1321"));
|
||||
v2.push_back(CellId("1322"));
|
||||
v2.push_back(CellId("1323"));
|
||||
v3.push_back(CellId("13"));
|
||||
|
||||
covering::Covering<CellId> c1(v1);
|
||||
c1.Append(covering::Covering<CellId>(v2));
|
||||
vector<CellId> v4;
|
||||
c1.OutputToVector(v4);
|
||||
sort(v4.begin(), v4.end(), CellId::LessPreOrder());
|
||||
TEST_EQUAL(v3, v4, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(IntersectCellWithTriangle_EmptyTriangle)
|
||||
{
|
||||
m2::PointD pt(27.0, 31.0);
|
||||
TEST_EQUAL(covering::CELL_OBJECT_NO_INTERSECTION, covering::IntersectCellWithTriangle(CellId("0"), pt, pt, pt), ());
|
||||
TEST_EQUAL(covering::CELL_OBJECT_NO_INTERSECTION, covering::IntersectCellWithTriangle(CellId("1"), pt, pt, pt), ());
|
||||
TEST_EQUAL(covering::CELL_OBJECT_NO_INTERSECTION, covering::IntersectCellWithTriangle(CellId("2"), pt, pt, pt), ());
|
||||
TEST_EQUAL(covering::OBJECT_INSIDE_CELL, covering::IntersectCellWithTriangle(CellId("3"), pt, pt, pt), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Covering_EmptyTriangle)
|
||||
{
|
||||
m2::PointU pt(27, 31);
|
||||
m2::PointD ptd(pt);
|
||||
CellId const expectedCellId = CellId::FromXY(pt.x, pt.y, CellId::DEPTH_LEVELS - 1);
|
||||
TEST_GREATER(expectedCellId.ToInt64(CellId::DEPTH_LEVELS), 5, ());
|
||||
covering::Covering<CellId> covering(ptd, ptd, ptd);
|
||||
vector<CellId> ids;
|
||||
covering.OutputToVector(ids);
|
||||
TEST_EQUAL(ids, vector<CellId>(1, expectedCellId), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Covering_Simplify_Smoke)
|
||||
{
|
||||
vector<CellId> v;
|
||||
v.push_back(CellId("03"));
|
||||
v.push_back(CellId("020"));
|
||||
v.push_back(CellId("021"));
|
||||
v.push_back(CellId("022"));
|
||||
v.push_back(CellId("0012"));
|
||||
covering::Covering<CellId> covering(v);
|
||||
v.clear();
|
||||
covering.Simplify();
|
||||
covering.OutputToVector(v);
|
||||
vector<CellId> e;
|
||||
e.push_back(CellId("02"));
|
||||
e.push_back(CellId("03"));
|
||||
e.push_back(CellId("0012"));
|
||||
TEST_EQUAL(v, e, ());
|
||||
}
|
||||
} // namespace covering_test
|
||||
50
libs/geometry/geometry_tests/diamond_box_tests.cpp
Normal file
50
libs/geometry/geometry_tests/diamond_box_tests.cpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/diamond_box.hpp"
|
||||
|
||||
namespace diamond_box_tests
|
||||
{
|
||||
UNIT_TEST(DiamondBox_Smoke)
|
||||
{
|
||||
{
|
||||
m2::DiamondBox dbox;
|
||||
TEST(!dbox.HasPoint(0, 0), ());
|
||||
}
|
||||
|
||||
{
|
||||
m2::DiamondBox dbox;
|
||||
dbox.Add(0, 0);
|
||||
TEST(dbox.HasPoint(0, 0), ());
|
||||
TEST(!dbox.HasPoint(0, 1), ());
|
||||
TEST(!dbox.HasPoint(1, 0), ());
|
||||
TEST(!dbox.HasPoint(1, 1), ());
|
||||
TEST(!dbox.HasPoint(0.5, 0.5), ());
|
||||
|
||||
dbox.Add(1, 1);
|
||||
TEST(dbox.HasPoint(0, 0), ());
|
||||
TEST(dbox.HasPoint(1, 1), ());
|
||||
TEST(dbox.HasPoint(0.5, 0.5), ());
|
||||
TEST(!dbox.HasPoint(1, 0), ());
|
||||
TEST(!dbox.HasPoint(0, 1), ());
|
||||
}
|
||||
|
||||
{
|
||||
m2::DiamondBox dbox;
|
||||
|
||||
dbox.Add(0, 1);
|
||||
dbox.Add(0, -1);
|
||||
dbox.Add(-1, 0);
|
||||
dbox.Add(1, 0);
|
||||
TEST(dbox.HasPoint(0, 0), ());
|
||||
TEST(dbox.HasPoint(0.5, 0.5), ());
|
||||
TEST(dbox.HasPoint(0.5, -0.5), ());
|
||||
TEST(dbox.HasPoint(-0.5, 0.5), ());
|
||||
TEST(dbox.HasPoint(-0.5, -0.5), ());
|
||||
|
||||
TEST(!dbox.HasPoint(0.51, 0.51), ());
|
||||
TEST(!dbox.HasPoint(0.51, -0.51), ());
|
||||
TEST(!dbox.HasPoint(-0.51, 0.51), ());
|
||||
TEST(!dbox.HasPoint(-0.51, -0.51), ());
|
||||
}
|
||||
}
|
||||
} // namespace diamond_box_tests
|
||||
24
libs/geometry/geometry_tests/distance_on_sphere_test.cpp
Normal file
24
libs/geometry/geometry_tests/distance_on_sphere_test.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
UNIT_TEST(DistanceOnSphere)
|
||||
{
|
||||
TEST_LESS(fabs(ms::DistanceOnSphere(0, -180, 0, 180)), 1.0e-6, ());
|
||||
TEST_LESS(fabs(ms::DistanceOnSphere(30, 0, 30, 360)), 1.0e-6, ());
|
||||
TEST_LESS(fabs(ms::DistanceOnSphere(-30, 23, -30, 23)), 1.0e-6, ());
|
||||
TEST_LESS(fabs(ms::DistanceOnSphere(90, 0, 90, 120)), 1.0e-6, ());
|
||||
TEST_LESS(fabs(ms::DistanceOnSphere(0, 0, 0, 180) - math::pi), 1.0e-6, ());
|
||||
TEST_LESS(fabs(ms::DistanceOnSphere(90, 0, -90, 120) - math::pi), 1.0e-6, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(DistanceOnEarth)
|
||||
{
|
||||
TEST_LESS(fabs(ms::DistanceOnEarth(30, 0, 30, 180) * 0.001 - 13358), 1, ());
|
||||
TEST_LESS(fabs(ms::DistanceOnEarth(30, 0, 30, 45) * 0.001 - 4309), 1, ());
|
||||
TEST_LESS(fabs(ms::DistanceOnEarth(-30, 0, -30, 45) * 0.001 - 4309), 1, ());
|
||||
TEST_LESS(fabs(ms::DistanceOnEarth(47.37, 8.56, 53.91, 27.56) * 0.001 - 1519), 1, ());
|
||||
TEST_LESS(fabs(ms::DistanceOnEarth(43, 132, 38, -122.5) * 0.001 - 8302), 1, ());
|
||||
}
|
||||
49
libs/geometry/geometry_tests/equality.hpp
Normal file
49
libs/geometry/geometry_tests/equality.hpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include "geometry/angles.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
namespace test
|
||||
{
|
||||
inline bool is_equal(double a1, double a2)
|
||||
{
|
||||
return (fabs(a1 - a2) < 1.0E-10);
|
||||
}
|
||||
|
||||
inline bool is_equal_atan(double x, double y, double v)
|
||||
{
|
||||
return is_equal(ang::AngleTo(m2::PointD(0, 0), m2::PointD(x, y)), v);
|
||||
}
|
||||
|
||||
inline bool is_equal_angle(double a1, double a2)
|
||||
{
|
||||
double const two_pi = 2.0 * math::pi;
|
||||
if (a1 < 0.0)
|
||||
a1 += two_pi;
|
||||
if (a2 < 0.0)
|
||||
a2 += two_pi;
|
||||
|
||||
return is_equal(a1, a2);
|
||||
}
|
||||
|
||||
inline bool is_equal(m2::PointD const & p1, m2::PointD const & p2)
|
||||
{
|
||||
return p1.EqualDxDy(p2, 1.0E-8);
|
||||
}
|
||||
|
||||
inline bool is_equal_center(m2::RectD const & r1, m2::RectD const & r2)
|
||||
{
|
||||
return is_equal(r1.Center(), r2.Center());
|
||||
}
|
||||
|
||||
struct strict_equal
|
||||
{
|
||||
bool operator()(m2::PointD const & p1, m2::PointD const & p2) const { return p1 == p2; }
|
||||
};
|
||||
|
||||
struct epsilon_equal
|
||||
{
|
||||
bool operator()(m2::PointD const & p1, m2::PointD const & p2) const { return is_equal(p1, p2); }
|
||||
};
|
||||
} // namespace test
|
||||
177
libs/geometry/geometry_tests/intersect_test.cpp
Normal file
177
libs/geometry/geometry_tests/intersect_test.cpp
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
#include "geometry/geometry_tests/equality.hpp"
|
||||
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/angles.hpp"
|
||||
#include "geometry/rect_intersect.hpp"
|
||||
|
||||
using namespace test;
|
||||
|
||||
namespace
|
||||
{
|
||||
typedef m2::PointD P;
|
||||
}
|
||||
|
||||
m2::PointD get_point(m2::RectD const & r, int ind)
|
||||
{
|
||||
switch (ind % 4)
|
||||
{
|
||||
case 0: return r.LeftBottom();
|
||||
case 1: return r.LeftTop();
|
||||
case 2: return r.RightTop();
|
||||
case 3: return r.RightBottom();
|
||||
default: ASSERT(false, ()); return m2::PointD();
|
||||
}
|
||||
}
|
||||
|
||||
void make_section_longer(m2::PointD & p1, m2::PointD & p2, double sm)
|
||||
{
|
||||
if (p1.x == p2.x)
|
||||
{
|
||||
if (p1.y > p2.y)
|
||||
sm = -sm;
|
||||
|
||||
p1.y -= sm;
|
||||
p2.y += sm;
|
||||
}
|
||||
else if (p1.y == p2.y)
|
||||
{
|
||||
if (p1.x > p2.x)
|
||||
sm = -sm;
|
||||
|
||||
p1.x -= sm;
|
||||
p2.x += sm;
|
||||
}
|
||||
else
|
||||
{
|
||||
double const az = ang::AngleTo(p1, p2);
|
||||
p1.Move(-sm, az);
|
||||
p2.Move(sm, az);
|
||||
}
|
||||
}
|
||||
|
||||
template <class TComp>
|
||||
void check_full_equal(m2::RectD const & r, m2::PointD const & p1, m2::PointD const & p2, TComp comp)
|
||||
{
|
||||
m2::PointD pp1 = p1;
|
||||
m2::PointD pp2 = p2;
|
||||
make_section_longer(pp1, pp2, 1000.0);
|
||||
|
||||
TEST(m2::Intersect(r, pp1, pp2), ());
|
||||
TEST(comp(pp1, p1) && comp(pp2, p2), ());
|
||||
}
|
||||
|
||||
void check_inside(m2::RectD const & r, m2::PointD const & p1, m2::PointD const & p2)
|
||||
{
|
||||
m2::PointD pp1 = p1;
|
||||
m2::PointD pp2 = p2;
|
||||
TEST(m2::Intersect(r, pp1, pp2), ());
|
||||
TEST((pp1 == p1) && (pp2 == p2), ());
|
||||
}
|
||||
|
||||
void check_intersect_boundaries(m2::RectD const & r)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
check_full_equal(r, get_point(r, i), get_point(r, i + 1), strict_equal());
|
||||
check_inside(r, get_point(r, i), get_point(r, i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
void check_intersect_diagonal(m2::RectD const & r)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
check_full_equal(r, get_point(r, i), get_point(r, i + 2), epsilon_equal());
|
||||
check_inside(r, get_point(r, i), get_point(r, i + 2));
|
||||
}
|
||||
}
|
||||
|
||||
void check_sides(m2::RectD const & r)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
m2::PointD p1 = (get_point(r, i) + get_point(r, i + 1)) / 2.0;
|
||||
m2::PointD p2 = (get_point(r, i + 2) + get_point(r, i + 3)) / 2.0;
|
||||
check_full_equal(r, p1, p2, strict_equal());
|
||||
check_inside(r, p1, p2);
|
||||
}
|
||||
}
|
||||
|
||||
void check_eps_boundaries(m2::RectD const & r, double eps = 1.0E-6)
|
||||
{
|
||||
m2::RectD rr = r;
|
||||
rr.Inflate(eps, eps);
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
m2::PointD p1 = get_point(rr, i);
|
||||
m2::PointD p2 = get_point(rr, i + 1);
|
||||
|
||||
TEST(!m2::Intersect(r, p1, p2), ());
|
||||
}
|
||||
|
||||
rr = r;
|
||||
rr.Inflate(-eps, -eps);
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
check_inside(r, get_point(rr, i), get_point(rr, i + 1));
|
||||
}
|
||||
|
||||
UNIT_TEST(IntersectRect_Section)
|
||||
{
|
||||
m2::RectD r(-1, -1, 2, 2);
|
||||
check_intersect_boundaries(r);
|
||||
check_intersect_diagonal(r);
|
||||
check_sides(r);
|
||||
check_eps_boundaries(r);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void check_point_in_rect(m2::RectD const & r, m2::PointD const & p)
|
||||
{
|
||||
m2::PointD p1 = p;
|
||||
m2::PointD p2 = p;
|
||||
|
||||
TEST(m2::Intersect(r, p1, p2), ());
|
||||
TEST(p == p1 && p == p2, ());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(IntersectRect_Point)
|
||||
{
|
||||
{
|
||||
m2::RectD r(-100, -100, 200, 200);
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
check_point_in_rect(r, get_point(r, i));
|
||||
check_point_in_rect(r, (get_point(r, i) + get_point(r, i + 1)) / 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
m2::RectD r(-1000, -1000, 1000, 1000);
|
||||
double const eps = 1.0E-6;
|
||||
P sm[] = {P(-eps, -eps), P(-eps, eps), P(eps, eps), P(eps, -eps)};
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
P p1 = get_point(r, i);
|
||||
P p2 = p1 - sm[i];
|
||||
check_inside(r, p1, p2);
|
||||
|
||||
p1 = p1 + sm[i];
|
||||
p2 = p1 + sm[i];
|
||||
TEST(!m2::Intersect(r, p1, p2), ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(IntersectRect_NAN)
|
||||
{
|
||||
m2::RectD r(-47.622792787168442885, -8.5438097219402173721, 134.06976090684074165, 9.0000000000000337508);
|
||||
m2::PointD p1(134.06976090684077008, 9.0000000000001847411);
|
||||
m2::PointD p2(134.06976090684074165, -8.5438097219401640814);
|
||||
|
||||
m2::Intersect(r, p1, p2);
|
||||
}
|
||||
93
libs/geometry/geometry_tests/intersection_score_tests.cpp
Normal file
93
libs/geometry/geometry_tests/intersection_score_tests.cpp
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/any_rect2d.hpp"
|
||||
#include "geometry/intersection_score.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
UNIT_TEST(IntersectionScore_PointsToPolygon)
|
||||
{
|
||||
{
|
||||
m2::RectD rectD = {0, 0, 10, 10};
|
||||
m2::AnyRectD const anyRect1(rectD);
|
||||
rectD = {0, 0, 10, 9};
|
||||
m2::AnyRectD const anyRect2(rectD);
|
||||
|
||||
m2::AnyRectD::Corners corners1;
|
||||
anyRect1.GetGlobalPoints(corners1);
|
||||
|
||||
m2::AnyRectD::Corners corners2;
|
||||
anyRect2.GetGlobalPoints(corners2);
|
||||
|
||||
auto const score = geometry::GetIntersectionScoreForPoints(corners1, corners2);
|
||||
|
||||
TEST(AlmostEqualAbs(score, 0.9, 1e-10), ());
|
||||
}
|
||||
{
|
||||
m2::RectD rectD = {0, 0, 10, 10};
|
||||
m2::AnyRectD const anyRect1(rectD);
|
||||
rectD = {10, 10, 20, 20};
|
||||
m2::AnyRectD const anyRect2(rectD);
|
||||
|
||||
m2::AnyRectD::Corners corners1;
|
||||
anyRect1.GetGlobalPoints(corners1);
|
||||
|
||||
m2::AnyRectD::Corners corners2;
|
||||
anyRect2.GetGlobalPoints(corners2);
|
||||
|
||||
auto const score = geometry::GetIntersectionScoreForPoints(corners1, corners2);
|
||||
|
||||
TEST(AlmostEqualAbs(score, 0.0, 1e-10), ());
|
||||
}
|
||||
{
|
||||
m2::RectD rectD = {0, 0, 10, 10};
|
||||
m2::AnyRectD const anyRect1(rectD);
|
||||
m2::AnyRectD::Corners corners1;
|
||||
anyRect1.GetGlobalPoints(corners1);
|
||||
|
||||
// Backward
|
||||
m2::AnyRectD::Corners corners2 = {m2::PointD{10.0, 10.0}, {10.0, 0.0}, {0.0, 0.0}, {0.0, 10.0}};
|
||||
auto const score = geometry::GetIntersectionScoreForPoints(corners1, corners2);
|
||||
|
||||
TEST(AlmostEqualAbs(score, 1.0, 1e-10), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(IntersectionScore_TrianglesToPolygon)
|
||||
{
|
||||
{
|
||||
std::vector<m2::PointD> triangiulated1 = {{0.0, 0.0}, {0.0, 10.0}, {10.0, 0.0},
|
||||
{10.0, 0.0}, {0.0, 10.0}, {10.0, 10.0}};
|
||||
std::vector<m2::PointD> triangiulated2 = {{0.0, 0.0}, {0.0, 9.0}, {10.0, 0.0},
|
||||
{10.0, 0.0}, {0.0, 9.0}, {10.0, 9.0}};
|
||||
|
||||
auto const score = geometry::GetIntersectionScoreForTriangulated(triangiulated1, triangiulated2);
|
||||
|
||||
TEST(AlmostEqualAbs(score, 0.9, 1e-10), ());
|
||||
}
|
||||
{
|
||||
m2::RectD rectD = {0, 0, 10, 10};
|
||||
m2::AnyRectD const anyRect1(rectD);
|
||||
rectD = {10, 10, 20, 20};
|
||||
m2::AnyRectD const anyRect2(rectD);
|
||||
|
||||
m2::AnyRectD::Corners corners1;
|
||||
anyRect1.GetGlobalPoints(corners1);
|
||||
|
||||
m2::AnyRectD::Corners corners2;
|
||||
anyRect2.GetGlobalPoints(corners2);
|
||||
|
||||
std::vector<m2::PointD> triangiulated1 = {{0.0, 0.0}, {0.0, 10.0}, {10.0, 0.0},
|
||||
{10.0, 0.0}, {0.0, 10.0}, {10.0, 10.0}};
|
||||
std::vector<m2::PointD> triangiulated2 = {{10.0, 10.0}, {10.0, 20.0}, {20.0, 10.0},
|
||||
{20.0, 10.0}, {10.0, 20.0}, {20.0, 20.0}};
|
||||
|
||||
auto const score = geometry::GetIntersectionScoreForTriangulated(triangiulated1, triangiulated2);
|
||||
|
||||
TEST(AlmostEqualAbs(score, 0.0, 1e-10), ());
|
||||
}
|
||||
}
|
||||
5549
libs/geometry/geometry_tests/large_polygon.hpp
Normal file
5549
libs/geometry/geometry_tests/large_polygon.hpp
Normal file
File diff suppressed because it is too large
Load diff
13
libs/geometry/geometry_tests/latlon_test.cpp
Normal file
13
libs/geometry/geometry_tests/latlon_test.cpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
UNIT_TEST(LatLonPointConstructorTest)
|
||||
{
|
||||
m2::PointD basePoint(39.123, 42.456);
|
||||
ms::LatLon wgsPoint = mercator::ToLatLon(basePoint);
|
||||
m2::PointD resultPoint = mercator::FromLatLon(wgsPoint);
|
||||
TEST_ALMOST_EQUAL_ULPS(basePoint.x, resultPoint.x, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(basePoint.y, resultPoint.y, ());
|
||||
}
|
||||
48
libs/geometry/geometry_tests/line2d_tests.cpp
Normal file
48
libs/geometry/geometry_tests/line2d_tests.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/line2d.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/segment2d.hpp"
|
||||
|
||||
namespace line2d_tests
|
||||
{
|
||||
using namespace m2;
|
||||
|
||||
double const kEps = 1e-12;
|
||||
|
||||
using Result = IntersectionResult;
|
||||
using Type = Result::Type;
|
||||
|
||||
Result Intersect(Line2D const & lhs, Line2D const & rhs)
|
||||
{
|
||||
return Intersect(lhs, rhs, kEps);
|
||||
}
|
||||
|
||||
UNIT_TEST(LineIntersection_Smoke)
|
||||
{
|
||||
{
|
||||
Line2D const line(Segment2D(PointD(0, 0), PointD(1, 0)));
|
||||
TEST_EQUAL(Intersect(line, line).m_type, Type::Infinity, ());
|
||||
}
|
||||
|
||||
{
|
||||
Line2D const lhs(Segment2D(PointD(0, 0), PointD(1, 1)));
|
||||
Line2D const rhs(Segment2D(PointD(-10, -10), PointD(-100, -100)));
|
||||
TEST_EQUAL(Intersect(lhs, rhs).m_type, Type::Infinity, ());
|
||||
}
|
||||
|
||||
{
|
||||
Line2D const lhs(Segment2D(PointD(0, 0), PointD(10, 10)));
|
||||
Line2D const rhs(Segment2D(PointD(10, 11), PointD(0, 1)));
|
||||
TEST_EQUAL(Intersect(lhs, rhs).m_type, Type::Zero, ());
|
||||
}
|
||||
|
||||
{
|
||||
Line2D const lhs(Segment2D(PointD(10, 0), PointD(9, 10)));
|
||||
Line2D const rhs(Segment2D(PointD(-10, 0), PointD(-9, 10)));
|
||||
auto const result = Intersect(lhs, rhs);
|
||||
TEST_EQUAL(result.m_type, Type::One, ());
|
||||
TEST(AlmostEqualAbs(result.m_point, PointD(0, 100), kEps), (result.m_point));
|
||||
}
|
||||
}
|
||||
} // namespace line2d_tests
|
||||
81
libs/geometry/geometry_tests/mercator_test.cpp
Normal file
81
libs/geometry/geometry_tests/mercator_test.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/macros.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
UNIT_TEST(Mercator_Grid)
|
||||
{
|
||||
for (int lat = -85; lat <= 85; ++lat)
|
||||
{
|
||||
for (int lon = -180; lon <= 180; ++lon)
|
||||
{
|
||||
double const x = mercator::LonToX(lon);
|
||||
double const y = mercator::LatToY(lat);
|
||||
double const lat1 = mercator::YToLat(y);
|
||||
double const lon1 = mercator::XToLon(x);
|
||||
|
||||
// Normal assumption for any projection.
|
||||
TEST_ALMOST_EQUAL_ULPS(static_cast<double>(lat), lat1, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(static_cast<double>(lon), lon1, ());
|
||||
|
||||
// x is actually lon unmodified.
|
||||
TEST_ALMOST_EQUAL_ULPS(x, static_cast<double>(lon), ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Mercator_DirectInferseF)
|
||||
{
|
||||
double const eps = 0.0000001;
|
||||
double lon = 63.45421;
|
||||
double x = mercator::LonToX(lon);
|
||||
double lon1 = mercator::XToLon(x);
|
||||
TEST_LESS(fabs(lon - lon1), eps, ("Too big round error"));
|
||||
double lat = 34.28754;
|
||||
double y = mercator::LatToY(lat);
|
||||
double lat1 = mercator::YToLat(y);
|
||||
TEST_LESS(fabs(lat - lat1), eps, ("Too big round error"));
|
||||
TEST_LESS(fabs(mercator::Bounds::kMaxX - mercator::Bounds::kMaxY), eps, ("Non-square maxX and maxY"));
|
||||
TEST_LESS(fabs(mercator::Bounds::kMinX - mercator::Bounds::kMinY), eps, ("Non-square minX and minY"));
|
||||
}
|
||||
|
||||
UNIT_TEST(Mercator_ErrorToRadius)
|
||||
{
|
||||
double const points[] = {-85.0, -45.0, -10.0, -1.0, -0.003, 0.0, 0.003, 1.0, 10.0, 45.0, 85.0};
|
||||
|
||||
double const error1 = 1.0; // 1 metre
|
||||
double const error10 = 10.0; // 10 metres
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(points); ++i)
|
||||
{
|
||||
for (size_t j = 0; j < ARRAY_SIZE(points); ++j)
|
||||
{
|
||||
double const lon = points[i];
|
||||
double const lat = points[j];
|
||||
m2::PointD const mercPoint(mercator::LonToX(lon), mercator::LatToY(lat));
|
||||
|
||||
m2::RectD const radius1 = mercator::MetersToXY(lon, lat, error1);
|
||||
TEST(radius1.IsPointInside(mercPoint), (lat, lon));
|
||||
TEST(radius1.Center().EqualDxDy(mercPoint, 1.0E-8), ());
|
||||
|
||||
m2::RectD const radius10 = mercator::MetersToXY(lon, lat, error10);
|
||||
TEST(radius10.IsPointInside(mercPoint), (lat, lon));
|
||||
TEST(radius10.Center().EqualDxDy(mercPoint, 1.0E-8), ());
|
||||
|
||||
TEST_EQUAL(m2::Add(radius10, radius1), radius10, (lat, lon));
|
||||
|
||||
TEST(radius10.IsPointInside(radius1.LeftTop()), (lat, lon));
|
||||
TEST(radius10.IsPointInside(radius1.LeftBottom()), (lat, lon));
|
||||
TEST(radius10.IsPointInside(radius1.RightTop()), (lat, lon));
|
||||
TEST(radius10.IsPointInside(radius1.RightBottom()), (lat, lon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Mercator_Sample1)
|
||||
{
|
||||
LOG(LINFO, (mercator::XToLon(27.531491200000001385), mercator::YToLat(64.392864299248202542)));
|
||||
}
|
||||
146
libs/geometry/geometry_tests/nearby_points_sweeper_test.cpp
Normal file
146
libs/geometry/geometry_tests/nearby_points_sweeper_test.cpp
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/nearby_points_sweeper.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace nearby_points_sweeper_test
|
||||
{
|
||||
using namespace m2;
|
||||
using namespace std;
|
||||
|
||||
// Multiset is used here to catch situations when some index is reported more than once.
|
||||
using TIndexSet = multiset<size_t>;
|
||||
|
||||
UNIT_TEST(NearbyPointsSweeper_Smoke)
|
||||
{
|
||||
{
|
||||
uint8_t const priority = 0;
|
||||
NearbyPointsSweeper sweeper(0.0);
|
||||
for (size_t i = 0; i < 10; ++i)
|
||||
sweeper.Add(10.0, 10.0, i, priority);
|
||||
|
||||
TIndexSet expected = {0};
|
||||
|
||||
TIndexSet actual;
|
||||
sweeper.Sweep(base::MakeInsertFunctor(actual));
|
||||
|
||||
TEST_EQUAL(expected, actual, ());
|
||||
}
|
||||
|
||||
{
|
||||
uint8_t const priority = 0;
|
||||
vector<double> const coords = {0.0, 0.5, 1.0, 1.5, 1.4, 1.6};
|
||||
|
||||
{
|
||||
NearbyPointsSweeper sweeper(0.5);
|
||||
|
||||
for (size_t i = 0; i < coords.size(); ++i)
|
||||
sweeper.Add(coords[i], 0.0, i, priority);
|
||||
|
||||
TIndexSet expected = {0, 2, 5};
|
||||
|
||||
TIndexSet actual;
|
||||
sweeper.Sweep(base::MakeInsertFunctor(actual));
|
||||
|
||||
TEST_EQUAL(expected, actual, ());
|
||||
}
|
||||
|
||||
{
|
||||
NearbyPointsSweeper sweeper(0.5);
|
||||
|
||||
for (size_t i = 0; i < coords.size(); ++i)
|
||||
sweeper.Add(0.0, coords[i], i, priority);
|
||||
|
||||
TIndexSet expected = {0, 2, 5};
|
||||
|
||||
TIndexSet actual;
|
||||
sweeper.Sweep(base::MakeInsertFunctor(actual));
|
||||
|
||||
TEST_EQUAL(expected, actual, ());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
uint8_t const priority = 0;
|
||||
vector<PointD> const points = {PointD(0.0, 0.0), PointD(1.0, 1.0), PointD(1.5, 0.0), PointD(1.5 + 1.01, 1.5 + 1.0)};
|
||||
NearbyPointsSweeper sweeper(1.0);
|
||||
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
sweeper.Add(points[i].x, points[i].y, i, priority);
|
||||
|
||||
TIndexSet expected = {0, 2, 3};
|
||||
|
||||
TIndexSet actual;
|
||||
sweeper.Sweep(base::MakeInsertFunctor(actual));
|
||||
|
||||
TEST_EQUAL(expected, actual, ());
|
||||
}
|
||||
|
||||
{
|
||||
uint8_t const priority = 0;
|
||||
vector<PointD> const points = {PointD(0, 0), PointD(0, 0), PointD(1, 0), PointD(0, 1),
|
||||
PointD(1, 1), PointD(1, 0), PointD(0.5, 0.5), PointD(0, 1)};
|
||||
|
||||
NearbyPointsSweeper sweeper(10.0);
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
sweeper.Add(points[i].x, points[i].y, i, priority);
|
||||
|
||||
TIndexSet expected = {0};
|
||||
TIndexSet actual;
|
||||
sweeper.Sweep(base::MakeInsertFunctor(actual));
|
||||
|
||||
TEST_EQUAL(expected, actual, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(NearbyPointsSweeper_Priority)
|
||||
{
|
||||
{
|
||||
NearbyPointsSweeper sweeper(0.0);
|
||||
for (size_t i = 0; i < 10; ++i)
|
||||
sweeper.Add(10.0, 10.0, i /* index */, i /* priority */);
|
||||
|
||||
TIndexSet expected = {9};
|
||||
|
||||
TIndexSet actual;
|
||||
sweeper.Sweep(base::MakeInsertFunctor(actual));
|
||||
|
||||
TEST_EQUAL(expected, actual, ());
|
||||
}
|
||||
{
|
||||
vector<pair<double, uint8_t>> const objects = {{0.0, 0}, {0.5, 1}, {1.0, 1}, {1.5, 1}, {1.4, 0}, {1.6, 0}};
|
||||
NearbyPointsSweeper sweeper(0.5);
|
||||
|
||||
for (size_t i = 0; i < objects.size(); ++i)
|
||||
sweeper.Add(objects[i].first, 0.0, i /* index */, objects[i].second);
|
||||
|
||||
TIndexSet expected = {1, 3};
|
||||
|
||||
TIndexSet actual;
|
||||
sweeper.Sweep(base::MakeInsertFunctor(actual));
|
||||
|
||||
TEST_EQUAL(expected, actual, ());
|
||||
}
|
||||
{
|
||||
vector<pair<PointD, uint8_t>> const objects = {
|
||||
{PointD(0.0, 0.0), 0}, {PointD(1.0, 1.0), 1}, {PointD(1.5, 0.0), 0}, {PointD(1.5 + 1.01, 1.5 + 1.0), 0}};
|
||||
NearbyPointsSweeper sweeper(1.0);
|
||||
|
||||
for (size_t i = 0; i < objects.size(); ++i)
|
||||
sweeper.Add(objects[i].first.x, objects[i].first.y, i /* index */, objects[i].second);
|
||||
|
||||
TIndexSet expected = {1, 3};
|
||||
|
||||
TIndexSet actual;
|
||||
sweeper.Sweep(base::MakeInsertFunctor(actual));
|
||||
|
||||
TEST_EQUAL(expected, actual, ());
|
||||
}
|
||||
}
|
||||
} // namespace nearby_points_sweeper_test
|
||||
71
libs/geometry/geometry_tests/oblate_spheroid_tests.cpp
Normal file
71
libs/geometry/geometry_tests/oblate_spheroid_tests.cpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/oblate_spheroid.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
double constexpr kAccuracyEps = 1e-3;
|
||||
|
||||
void testDistance(ms::LatLon const & a, ms::LatLon const & b, double planDistance)
|
||||
{
|
||||
double const factDistance = oblate_spheroid::GetDistance(a, b);
|
||||
TEST_ALMOST_EQUAL_ABS(factDistance, planDistance, kAccuracyEps, ());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(Distance_EdgeCaseEquatorialLine)
|
||||
{
|
||||
ms::LatLon const a(0.0, 0.0);
|
||||
ms::LatLon const b(0.0, 45.0);
|
||||
testDistance(a, b, 5009377.085);
|
||||
}
|
||||
|
||||
UNIT_TEST(Distance_EdgeCaseSameLatitude)
|
||||
{
|
||||
ms::LatLon const a(30.0, 30.0);
|
||||
ms::LatLon const b(30.0, 70.0);
|
||||
testDistance(a, b, 3839145.440);
|
||||
}
|
||||
|
||||
UNIT_TEST(Distance_EdgeCaseSameLongtitude)
|
||||
{
|
||||
ms::LatLon const a(10.0, 40.0);
|
||||
ms::LatLon const b(21.0, 40.0);
|
||||
testDistance(a, b, 1217222.035);
|
||||
}
|
||||
|
||||
UNIT_TEST(Distance_Long)
|
||||
{
|
||||
ms::LatLon const a(-24.02861, 123.53353);
|
||||
ms::LatLon const b(58.25020, -6.54459);
|
||||
testDistance(a, b, 14556482.656);
|
||||
}
|
||||
|
||||
UNIT_TEST(Distance_NearlyAntipodal)
|
||||
{
|
||||
ms::LatLon const a(52.02247, 168.18196);
|
||||
ms::LatLon const b(31.22321, -171.07584);
|
||||
testDistance(a, b, 2863337.631);
|
||||
}
|
||||
|
||||
UNIT_TEST(Distance_Small)
|
||||
{
|
||||
ms::LatLon const a(54.15820, 36.95131);
|
||||
ms::LatLon const b(54.15814, 36.95143);
|
||||
testDistance(a, b, 10.29832);
|
||||
}
|
||||
|
||||
UNIT_TEST(Distance_ReallyTiny)
|
||||
{
|
||||
ms::LatLon const a(-34.39292, -71.16413);
|
||||
ms::LatLon const b(-34.39294, -71.16410);
|
||||
testDistance(a, b, 3.54013);
|
||||
}
|
||||
|
||||
UNIT_TEST(Distance_Fallback)
|
||||
{
|
||||
ms::LatLon const a(0.0, 0.0);
|
||||
ms::LatLon const b(0.5, 179.5);
|
||||
testDistance(a, b, 19958365.368);
|
||||
}
|
||||
77
libs/geometry/geometry_tests/packer_test.cpp
Normal file
77
libs/geometry/geometry_tests/packer_test.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/packer.hpp"
|
||||
|
||||
static int i = 1;
|
||||
|
||||
void rectOverflowFn1()
|
||||
{
|
||||
i = i + 5;
|
||||
}
|
||||
|
||||
void rectOverflowFn2()
|
||||
{
|
||||
i *= 2;
|
||||
}
|
||||
|
||||
UNIT_TEST(PackerTest_SimplePack)
|
||||
{
|
||||
m2::Packer p(20, 20);
|
||||
|
||||
m2::Packer::handle_t h0 = p.pack(10, 10);
|
||||
|
||||
p.addOverflowFn(rectOverflowFn2, 10);
|
||||
p.addOverflowFn(rectOverflowFn1, 0);
|
||||
|
||||
TEST_EQUAL(p.isPacked(h0), true, ());
|
||||
m2::RectU r0 = p.find(h0).second;
|
||||
|
||||
TEST_EQUAL(r0, m2::RectU(0, 0, 10, 10), ());
|
||||
|
||||
m2::Packer::handle_t h1 = p.pack(20, 10);
|
||||
|
||||
TEST_EQUAL(p.isPacked(h1), true, ());
|
||||
m2::RectU r1 = p.find(h1).second;
|
||||
TEST_EQUAL(r1, m2::RectU(0, 10, 20, 20), ());
|
||||
|
||||
m2::Packer::handle_t h2 = p.pack(5, 5);
|
||||
|
||||
// Possibly we should restore this checks
|
||||
|
||||
// TEST_EQUAL(p.isPacked(h0), false, ());
|
||||
// TEST_EQUAL(p.isPacked(h1), false, ());
|
||||
|
||||
TEST_EQUAL(p.isPacked(h2), true, ());
|
||||
|
||||
TEST_EQUAL(i, 7, ("Handlers priorities doesn't work"));
|
||||
TEST_NOT_EQUAL(i, 12, ("Handlers priorities doesn't work"));
|
||||
|
||||
m2::RectU r2 = p.find(h2).second;
|
||||
|
||||
TEST_EQUAL(r2, m2::RectU(0, 0, 5, 5), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PackerTest_HasRoom_Sequence)
|
||||
{
|
||||
m2::Packer p(20, 20);
|
||||
|
||||
m2::PointU pts[] = {m2::PointU(10, 10), m2::PointU(11, 3), m2::PointU(5, 5), m2::PointU(5, 5)};
|
||||
|
||||
TEST(p.hasRoom(pts, sizeof(pts) / sizeof(m2::PointU)), ());
|
||||
|
||||
m2::PointU pts1[] = {m2::PointU(10, 10), m2::PointU(11, 3), m2::PointU(5, 5), m2::PointU(5, 5), m2::PointU(16, 5)};
|
||||
|
||||
TEST(!p.hasRoom(pts1, sizeof(pts1) / sizeof(m2::PointU)), ());
|
||||
|
||||
m2::PointU pts2[] = {m2::PointU(10, 10), m2::PointU(11, 3), m2::PointU(5, 5), m2::PointU(5, 5), m2::PointU(10, 6)};
|
||||
|
||||
TEST(!p.hasRoom(pts2, sizeof(pts2) / sizeof(m2::PointU)), ());
|
||||
|
||||
m2::PointU pts3[] = {m2::PointU(10, 10), m2::PointU(11, 3), m2::PointU(5, 5), m2::PointU(5, 5), m2::PointU(15, 5)};
|
||||
|
||||
TEST(p.hasRoom(pts3, sizeof(pts3) / sizeof(m2::PointU)), ());
|
||||
|
||||
m2::PointU pts4[] = {m2::PointU(10, 10), m2::PointU(11, 3), m2::PointU(5, 5), m2::PointU(5, 5), m2::PointU(16, 5)};
|
||||
|
||||
TEST(!p.hasRoom(pts4, sizeof(pts4) / sizeof(m2::PointU)), ());
|
||||
}
|
||||
69
libs/geometry/geometry_tests/parametrized_segment_tests.cpp
Normal file
69
libs/geometry/geometry_tests/parametrized_segment_tests.cpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/parametrized_segment.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
template <typename Point>
|
||||
void FloatingPointsTest()
|
||||
{
|
||||
m2::ParametrizedSegment<Point> d(Point(-1, 3), Point(2, 1));
|
||||
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(Point(-1, 3)), 0.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(Point(2, 1)), 0.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(Point(-0.5, 0.5)), 3.25, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(Point(3.5, 0.0)), 3.25, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(Point(4.0, 4.0)), 13.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(Point(0.5, 2.0)), 0.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(Point(0.0, 1.25)), 0.5 * 0.5 + 0.75 * 0.75, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ParametrizedSegment2D_Floating)
|
||||
{
|
||||
FloatingPointsTest<m2::PointD>();
|
||||
FloatingPointsTest<m2::PointF>();
|
||||
}
|
||||
|
||||
UNIT_TEST(ParametrizedSegment2D_Integer)
|
||||
{
|
||||
m2::ParametrizedSegment<m2::PointI> d(m2::PointI(-1, 3), m2::PointI(2, 1));
|
||||
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(m2::PointI(-1, 3)), 0.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(m2::PointI(2, 1)), 0.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(m2::PointI(4, 4)), 13.0, ());
|
||||
|
||||
double const sqSin = 4.0 / m2::PointI(-1, 3).SquaredLength(m2::PointI(2, 1));
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(m2::PointI(0, 1)), 4.0 * sqSin, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(m2::PointI(-1, 1)), 9.0 * sqSin, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ParametrizedSegment2D_DegenerateSection)
|
||||
{
|
||||
using P = m2::PointD;
|
||||
m2::ParametrizedSegment<P> d(P(5, 5), P(5, 5));
|
||||
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(P(5, 5)), 0.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(P(6, 6)), 2.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(P(0, 0)), 50.0, ());
|
||||
TEST_ALMOST_EQUAL_ULPS(d.SquaredDistanceToPoint(P(-1, -2)), 36.0 + 49.0, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ParametrizedSegment2D_ClosestPoint)
|
||||
{
|
||||
using P = m2::PointD;
|
||||
|
||||
P arr[][4] = {{P(3, 4), P(0, 0), P(10, 0), P(3, 0)}, {P(3, 4), P(0, 0), P(0, 10), P(0, 4)},
|
||||
|
||||
{P(3, 5), P(2, 2), P(5, 5), P(4, 4)}, {P(5, 3), P(2, 2), P(5, 5), P(4, 4)},
|
||||
{P(2, 4), P(2, 2), P(5, 5), P(3, 3)}, {P(4, 2), P(2, 2), P(5, 5), P(3, 3)},
|
||||
|
||||
{P(5, 6), P(2, 2), P(5, 5), P(5, 5)}, {P(1, 0), P(2, 2), P(5, 5), P(2, 2)}};
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(arr); ++i)
|
||||
{
|
||||
m2::ParametrizedSegment<P> segment(arr[i][1], arr[i][2]);
|
||||
TEST(AlmostEqualULPs(segment.ClosestPointTo(arr[i][0]), arr[i][3]), (i));
|
||||
}
|
||||
}
|
||||
100
libs/geometry/geometry_tests/point3d_tests.cpp
Normal file
100
libs/geometry/geometry_tests/point3d_tests.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/point3d.hpp"
|
||||
|
||||
namespace point3d_tests
|
||||
{
|
||||
UNIT_TEST(Point3d_DotProduct)
|
||||
{
|
||||
m3::Point<int> p1(1, 4, 3);
|
||||
m3::Point<int> p2(1, 2, 3);
|
||||
m3::Point<int> p3(1, 0, 3);
|
||||
|
||||
TEST_EQUAL(m3::DotProduct(p1, p2), 18, ());
|
||||
TEST_EQUAL(m3::DotProduct(p2, p3), 10, ());
|
||||
TEST_EQUAL(m3::DotProduct(p3, p2), 10, ());
|
||||
TEST_EQUAL(m3::DotProduct(p1, p3), 10, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point3d_CrossProduct_1)
|
||||
{
|
||||
m3::Point<int> p1(1, 0, 0);
|
||||
m3::Point<int> p2(0, 1, 0);
|
||||
m3::Point<int> p3(0, 0, 1);
|
||||
|
||||
TEST_EQUAL(m3::CrossProduct(p1, p2), p3, ());
|
||||
TEST_EQUAL(m3::CrossProduct(p2, p3), p1, ());
|
||||
TEST_EQUAL(m3::CrossProduct(p3, p1), p2, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point3d_CrossProduct_2)
|
||||
{
|
||||
m3::Point<int> p1(1, 2, 3);
|
||||
m3::Point<int> p2(4, 5, 6);
|
||||
|
||||
TEST_EQUAL(m3::CrossProduct(p1, p2), m3::Point<int>(-3, 6, -3), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point3d_CrossProduct_3)
|
||||
{
|
||||
m3::Point<int> p1(3, 7, 1);
|
||||
m3::Point<int> p2(6, 2, 9);
|
||||
|
||||
TEST_EQUAL(m3::CrossProduct(p1, p2), m3::Point<int>(61, -21, -36), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point3d_RotateX_1)
|
||||
{
|
||||
m3::PointD p(0.0, 1.0, 0.0);
|
||||
auto const rotated = p.RotateAroundX(90.0);
|
||||
TEST_ALMOST_EQUAL_ABS(rotated, m3::PointD(0.0, 0.0, 1.0), 1e-10, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point3d_RotateX_2)
|
||||
{
|
||||
m3::PointD p(1.0, 2.0, 3.0);
|
||||
auto const rotated = p.RotateAroundX(90.0);
|
||||
TEST_ALMOST_EQUAL_ABS(rotated, m3::PointD(1.0, -3.0, 2.0), 1e-10, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point3d_RotateY_1)
|
||||
{
|
||||
m3::PointD p(1.0, 0.0, 0.0);
|
||||
auto const rotated = p.RotateAroundY(90.0);
|
||||
TEST_ALMOST_EQUAL_ABS(rotated, m3::PointD(0.0, 0.0, -1.0), 1e-10, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point3d_RotateY_2)
|
||||
{
|
||||
m3::PointD p(1.0, 2.0, 3.0);
|
||||
auto const rotated = p.RotateAroundY(90.0);
|
||||
TEST_ALMOST_EQUAL_ABS(rotated, m3::PointD(3.0, 2.0, -1.0), 1e-10, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point3d_RotateZ_1)
|
||||
{
|
||||
m3::PointD p(1.0, 0.0, 0.0);
|
||||
auto const rotated = p.RotateAroundZ(90.0);
|
||||
TEST_ALMOST_EQUAL_ABS(rotated, m3::PointD(0.0, 1.0, 0.0), 1e-10, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point3d_RotateZ_2)
|
||||
{
|
||||
m3::PointD p(1.0, 2.0, 3.0);
|
||||
auto const rotated = p.RotateAroundZ(90.0);
|
||||
TEST_ALMOST_EQUAL_ABS(rotated, m3::PointD(-2.0, 1.0, 3.0), 1e-10, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Point3d_RotateXYZ)
|
||||
{
|
||||
m3::PointD p(1.0, 1.0, 1.0);
|
||||
auto const rotatedFirst = p.RotateAroundZ(-45.0);
|
||||
|
||||
TEST_ALMOST_EQUAL_ABS(rotatedFirst, m3::PointD(std::sqrt(2.0), 0.0, 1.0), 1e-10, ());
|
||||
|
||||
double const angleDegree = math::RadToDeg(acos(rotatedFirst.z / rotatedFirst.Length()));
|
||||
|
||||
auto const north = rotatedFirst.RotateAroundY(-angleDegree);
|
||||
TEST_ALMOST_EQUAL_ABS(north, m3::PointD(0.0, 0.0, std::sqrt(3.0)), 1e-10, ());
|
||||
}
|
||||
} // namespace point3d_tests
|
||||
88
libs/geometry/geometry_tests/point_test.cpp
Normal file
88
libs/geometry/geometry_tests/point_test.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/geometry_tests/equality.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/triangle2d.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
UNIT_TEST(Point_Rotate)
|
||||
{
|
||||
m2::PointD p(1.0, 0.0);
|
||||
p.Rotate(math::pi / 6.0);
|
||||
TEST(test::is_equal(p.x, sqrt(3.0) / 2.0), ());
|
||||
TEST(test::is_equal(p.y, 1.0 / 2.0), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PointInTriangle)
|
||||
{
|
||||
m2::PointD const a(1, 0);
|
||||
m2::PointD const b(2, 0);
|
||||
m2::PointD const c(-1, 3);
|
||||
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(a, a, a, a), ());
|
||||
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(a, a, a, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(b, a, a, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(a, a, b, a), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(b, a, b, a), ());
|
||||
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(a, a, b, c), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(a, a, c, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(b, a, b, c), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(b, a, c, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(c, a, b, c), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(c, a, c, b), ());
|
||||
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 1), a, b, c), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 1), a, c, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 1.5), a, b, c), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 1.5), a, c, b), ());
|
||||
TEST(m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 1.77), a, b, c), ());
|
||||
TEST(m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 1.77), a, c, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 2), a, b, c), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 2), a, c, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(1, 1), a, b, c), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(1, 1), a, c, b), ());
|
||||
TEST(m2::IsPointStrictlyInsideTriangle(m2::PointD(1, 0.5), a, b, c), ());
|
||||
TEST(m2::IsPointStrictlyInsideTriangle(m2::PointD(1, 0.5), a, c, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(100, 100), a, b, c), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(100, 100), a, c, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(5, 0), a, b, c), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(5, 0), a, c, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(-1, -1), a, c, b), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 0.5), a, b, c), ());
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 0.5), a, c, b), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PointInTriangle_EmptyTriangle)
|
||||
{
|
||||
m2::PointD pt(27, 31);
|
||||
TEST(!m2::IsPointStrictlyInsideTriangle(m2::PointD(0, 16), pt, pt, pt), ());
|
||||
}
|
||||
|
||||
/// @todo add more tests
|
||||
UNIT_TEST(GetArrowPoints)
|
||||
{
|
||||
std::array<m2::PointF, 3> arrPntsFlt;
|
||||
m2::GetArrowPoints(m2::PointF(0, 0), m2::PointF(1, 0), 1.f, 1.f, arrPntsFlt);
|
||||
TEST(AlmostEqualULPs(arrPntsFlt[0], m2::PointF(1.f, 1.f)), ());
|
||||
TEST(AlmostEqualULPs(arrPntsFlt[1], m2::PointF(2.f, 0.f)), ());
|
||||
TEST(AlmostEqualULPs(arrPntsFlt[2], m2::PointF(1.f, -1.f)), ());
|
||||
|
||||
std::array<m2::PointD, 3> arrPntsDbl;
|
||||
m2::GetArrowPoints(m2::PointD(-1., 2.), m2::PointD(-1., 100.), 2., 5., arrPntsDbl);
|
||||
TEST(AlmostEqualULPs(arrPntsDbl[0], m2::PointD(-3.f, 100.f)), ());
|
||||
TEST(AlmostEqualULPs(arrPntsDbl[1], m2::PointD(-1.f, 105.f)), ());
|
||||
TEST(AlmostEqualULPs(arrPntsDbl[2], m2::PointD(1.f, 100.f)), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PointAtSegment)
|
||||
{
|
||||
TEST(AlmostEqualULPs(m2::PointAtSegment(m2::PointF(0, 0), m2::PointF(1, 0), 0.5f), m2::PointF(0.5f, 0.f)), ());
|
||||
TEST(AlmostEqualULPs(m2::PointAtSegment(m2::PointF(0, 0), m2::PointF(0, 1), 0.3f), m2::PointF(0.f, 0.3f)), ());
|
||||
TEST(AlmostEqualULPs(m2::PointAtSegment(m2::PointD(0., 0.), m2::PointD(30., 40.), 5.), m2::PointD(3., 4.)), ());
|
||||
TEST(AlmostEqualULPs(m2::PointAtSegment(m2::PointF(-3, -4), m2::PointF(-30, -40), 5.f), m2::PointF(-6.f, -8.f)), ());
|
||||
TEST(AlmostEqualULPs(m2::PointAtSegment(m2::PointD(14., -48.), m2::PointD(70., -240.), 25.), m2::PointD(21., -72.)),
|
||||
());
|
||||
}
|
||||
200
libs/geometry/geometry_tests/polygon_test.cpp
Normal file
200
libs/geometry/geometry_tests/polygon_test.cpp
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/polygon.hpp"
|
||||
#include "geometry/triangle2d.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace polygon_test
|
||||
{
|
||||
using namespace std;
|
||||
using namespace m2::robust;
|
||||
|
||||
using P = m2::PointD;
|
||||
|
||||
template <typename Iter>
|
||||
void TestDiagonalVisible(Iter beg, Iter end, Iter i0, Iter i1, bool res)
|
||||
{
|
||||
TEST_EQUAL(IsDiagonalVisible(beg, end, i0, i1), res, ());
|
||||
TEST_EQUAL(IsDiagonalVisible(beg, end, i1, i0), res, ());
|
||||
}
|
||||
|
||||
void TestFindStrip(P const * beg, size_t n)
|
||||
{
|
||||
size_t const i = FindSingleStrip(n, IsDiagonalVisibleFunctor<P const *>(beg, beg + n));
|
||||
TEST_LESS(i, n, ());
|
||||
|
||||
vector<size_t> test;
|
||||
MakeSingleStripFromIndex(i, n, base::MakeBackInsertFunctor(test));
|
||||
|
||||
base::SortUnique(test);
|
||||
TEST_EQUAL(test.size(), n, ());
|
||||
}
|
||||
|
||||
void TestFindStripMulti(P const * beg, size_t n)
|
||||
{
|
||||
for (size_t i = 3; i <= n; ++i)
|
||||
TestFindStrip(beg, i);
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
void TestPolygonCCW(Iter beg, Iter end)
|
||||
{
|
||||
TEST_EQUAL(m2::robust::CheckPolygonSelfIntersections(beg, end), false, ());
|
||||
|
||||
TEST(IsPolygonCCW(beg, end), ());
|
||||
using ReverseIter = reverse_iterator<Iter>;
|
||||
TEST(!IsPolygonCCW(ReverseIter(end), ReverseIter(beg)), ());
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
void TestPolygonOrReverseCCW(Iter beg, Iter end)
|
||||
{
|
||||
TEST_EQUAL(m2::robust::CheckPolygonSelfIntersections(beg, end), false, ());
|
||||
|
||||
bool const bForwardCCW = IsPolygonCCW(beg, end);
|
||||
using ReverseIter = reverse_iterator<Iter>;
|
||||
bool const bReverseCCW = IsPolygonCCW(ReverseIter(end), ReverseIter(beg));
|
||||
TEST_NOT_EQUAL(bForwardCCW, bReverseCCW, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(IsSegmentInCone)
|
||||
{
|
||||
TEST(IsSegmentInCone(P(0, 0), P(0, 3), P(-1, -1), P(1, -1)), ());
|
||||
TEST(IsSegmentInCone(P(0, 0), P(2, 3), P(-1, -1), P(1, -1)), ());
|
||||
TEST(IsSegmentInCone(P(0, 0), P(-3, 3), P(-1, -1), P(1, -1)), ());
|
||||
TEST(IsSegmentInCone(P(0, 0), P(-3, 0), P(-1, -1), P(1, -1)), ());
|
||||
TEST(IsSegmentInCone(P(0, 0), P(3, 0), P(-1, -1), P(1, -1)), ());
|
||||
TEST(!IsSegmentInCone(P(0, 0), P(0, -1), P(-1, -1), P(1, -1)), ());
|
||||
TEST(!IsSegmentInCone(P(0, 0), P(1, -3), P(-1, -1), P(1, -1)), ());
|
||||
TEST(!IsSegmentInCone(P(0, 0), P(-1, -3), P(-1, -1), P(1, -1)), ());
|
||||
|
||||
TEST(IsSegmentInCone(P(0, 0), P(0, 3), P(-1, 1), P(1, 1)), ());
|
||||
TEST(IsSegmentInCone(P(0, 0), P(2, 3), P(-1, 1), P(1, 1)), ());
|
||||
TEST(!IsSegmentInCone(P(0, 0), P(-3, 3), P(-1, 1), P(1, 1)), ());
|
||||
TEST(!IsSegmentInCone(P(0, 0), P(-3, 0), P(-1, 1), P(1, 1)), ());
|
||||
TEST(!IsSegmentInCone(P(0, 0), P(3, 0), P(-1, 1), P(1, 1)), ());
|
||||
TEST(!IsSegmentInCone(P(0, 0), P(0, -1), P(-1, 1), P(1, 1)), ());
|
||||
TEST(!IsSegmentInCone(P(0, 0), P(1, -3), P(-1, 1), P(1, 1)), ());
|
||||
TEST(!IsSegmentInCone(P(0, 0), P(-1, -3), P(-1, 1), P(1, 1)), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(IsDiagonalVisible)
|
||||
{
|
||||
P const poly[] = {P(0, 0), P(3, 0), P(3, 2), P(2, 2), P(2, 1), P(0, 1)};
|
||||
P const * b = poly;
|
||||
P const * e = poly + ARRAY_SIZE(poly);
|
||||
|
||||
TestDiagonalVisible(b, e, b + 0, b + 1, true);
|
||||
TestDiagonalVisible(b, e, b + 0, b + 2, false);
|
||||
TestDiagonalVisible(b, e, b + 0, b + 3, false);
|
||||
TestDiagonalVisible(b, e, b + 0, b + 4, true);
|
||||
TestDiagonalVisible(b, e, b + 0, b + 5, true);
|
||||
TestDiagonalVisible(b, e, b + 5, b + 4, true);
|
||||
TestDiagonalVisible(b, e, b + 5, b + 3, false);
|
||||
TestDiagonalVisible(b, e, b + 5, b + 2, false);
|
||||
TestDiagonalVisible(b, e, b + 5, b + 1, true);
|
||||
}
|
||||
|
||||
UNIT_TEST(FindSingleStrip)
|
||||
{
|
||||
{
|
||||
P const poly[] = {P(0, 0), P(3, 0), P(3, 2), P(2, 2), P(2, 1), P(0, 1)};
|
||||
TestFindStripMulti(poly, ARRAY_SIZE(poly));
|
||||
}
|
||||
|
||||
{
|
||||
P const poly[] = {P(0, 0), P(2, 0), P(2, -1), P(3, -1), P(3, 2), P(2, 2), P(2, 1), P(0, 1)};
|
||||
size_t const n = ARRAY_SIZE(poly);
|
||||
TEST_EQUAL(FindSingleStrip(n, IsDiagonalVisibleFunctor<P const *>(poly, poly + n)), n, ());
|
||||
}
|
||||
|
||||
{
|
||||
// Minsk, Bobryiskaya str., 7
|
||||
P const poly[] = {P(53.8926922, 27.5460021), P(53.8926539, 27.5461821), P(53.8926164, 27.5461591),
|
||||
P(53.8925455, 27.5464921), P(53.8925817, 27.5465143), P(53.8925441, 27.5466909),
|
||||
P(53.8923762, 27.5465881), P(53.8925229, 27.5458984)};
|
||||
TestFindStrip(poly, ARRAY_SIZE(poly));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(IsPolygonCCW_Smoke)
|
||||
{
|
||||
P arr1[] = {P(1, 1), P(2, 0), P(3, 2)};
|
||||
TestPolygonCCW(arr1, arr1 + ARRAY_SIZE(arr1));
|
||||
|
||||
P arr2[] = {P(0, 0), P(1, 0), P(0, 1)};
|
||||
TestPolygonCCW(arr2, arr2 + ARRAY_SIZE(arr2));
|
||||
|
||||
P arr3[] = {P(0, 1), P(1, 1), P(1, 0), P(2, 0), P(2, 1), P(1, 1), P(1, 2), P(0, 2)};
|
||||
TestPolygonCCW(arr3, arr3 + ARRAY_SIZE(arr3));
|
||||
}
|
||||
|
||||
UNIT_TEST(IsPolygonCCW_DataSet)
|
||||
{
|
||||
P arr[] = {P(27.3018836975098, 61.7740631103516), P(27.2981071472168, 61.7816162109375),
|
||||
P(27.2962188720703, 61.7831611633301), P(27.293815612793, 61.7814445495605),
|
||||
P(27.2926139831543, 61.783332824707), P(27.2919273376465, 61.787109375),
|
||||
P(27.2948455810547, 61.7865943908691), P(27.2958755493164, 61.7883110046387),
|
||||
P(27.3001670837402, 61.779899597168), P(27.3036003112793, 61.7771530151367),
|
||||
P(27.3015403747559, 61.7747497558594)};
|
||||
|
||||
TestPolygonOrReverseCCW(arr, arr + ARRAY_SIZE(arr));
|
||||
}
|
||||
|
||||
UNIT_TEST(PolygonArea_Smoke)
|
||||
{
|
||||
{
|
||||
P arr[] = {P(-1, 0), P(0, 1), P(1, -1)};
|
||||
TEST_ALMOST_EQUAL_ULPS(m2::GetTriangleArea(arr[0], arr[1], arr[2]), GetPolygonArea(arr, arr + ARRAY_SIZE(arr)), ());
|
||||
}
|
||||
|
||||
{
|
||||
P arr[] = {P(-5, -7), P(-3.5, 10), P(7.2, 5), P(14, -6.4)};
|
||||
TEST_ALMOST_EQUAL_ULPS(m2::GetTriangleArea(arr[0], arr[1], arr[2]) + m2::GetTriangleArea(arr[2], arr[3], arr[0]),
|
||||
GetPolygonArea(arr, arr + ARRAY_SIZE(arr)), ());
|
||||
}
|
||||
}
|
||||
|
||||
// This polygon has self-intersections.
|
||||
/*
|
||||
UNIT_TEST(IsPolygonCCW_DataSet2)
|
||||
{
|
||||
P arr[] = { P(0.747119766424532, 61.4800033732131), P(0.747098308752385, 61.4800496413187),
|
||||
P(0.747129489432211, 61.4800647287444), P(0.74715195293274, 61.4800191311911),
|
||||
P(0.745465178736907, 61.4795420332621), P(0.746959839711849, 61.4802327020841),
|
||||
P(0.746994373152972, 61.4802085622029), P(0.747182463060312, 61.479815953858),
|
||||
P(0.747314226578283, 61.479873956628), P(0.747109037588444, 61.480298416205),
|
||||
P(0.747035947392732, 61.4803450195867), P(0.746934023450081, 61.4803403257209),
|
||||
P(0.745422933944894, 61.4796322225403), P(0.745465178736907, 61.4795420332621) };
|
||||
|
||||
TestPolygonOrReverseCCW(arr, arr + ARRAY_SIZE(arr));
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
UNIT_TEST(IsPolygonCCW_DataSet3)
|
||||
{
|
||||
P arr[] = { P(0.738019701780757, 61.1239696304537), P(0.738234278502148, 61.1240028227903),
|
||||
P(0.738675837161651, 61.1240276332237), P(0.738234278502148, 61.1240028227903),
|
||||
P(0.738019701780757, 61.1239696304537), P(0.737414528371232, 61.1238241206145) };
|
||||
|
||||
TestPolygonOrReverseCCW(arr, arr + ARRAY_SIZE(arr));
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
UNIT_TEST(IsPolygonCCW_DataSet4)
|
||||
{
|
||||
P arr[] = { P(37.368060441099174795, 67.293103344080122952),
|
||||
P(37.368017525754879671, 67.292797572252140981), P(37.367990703664702323, 67.292969568905391498),
|
||||
P(37.368060441099174795, 67.293103344080122952), P(37.368017525754879671, 67.292797572252140981),
|
||||
P(37.368097992025411713, 67.292830094036474975), P(37.368216009222180674, 67.292969568905391498) };
|
||||
TestPolygonOrReverseCCW(arr, arr + ARRAY_SIZE(arr));
|
||||
}
|
||||
*/
|
||||
} // namespace polygon_test
|
||||
58
libs/geometry/geometry_tests/polyline_tests.cpp
Normal file
58
libs/geometry/geometry_tests/polyline_tests.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/polyline2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace polyline_tests
|
||||
{
|
||||
double constexpr kEps = 1e-5;
|
||||
|
||||
void TestClosest(std::vector<m2::PointD> const & points, m2::PointD const & point, double expectedSquaredDist,
|
||||
uint32_t expectedIndex)
|
||||
{
|
||||
auto const closestByPoints = m2::CalcMinSquaredDistance(points.begin(), points.end(), point);
|
||||
TEST_ALMOST_EQUAL_ABS(closestByPoints.first, expectedSquaredDist, kEps, ());
|
||||
TEST_EQUAL(closestByPoints.second, expectedIndex, ());
|
||||
|
||||
m2::PolylineD const poly(points);
|
||||
auto const closestByPoly = poly.CalcMinSquaredDistance(m2::PointD(point));
|
||||
TEST_ALMOST_EQUAL_ABS(closestByPoly.first, expectedSquaredDist, kEps, ());
|
||||
TEST_EQUAL(closestByPoly.second, expectedIndex, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Rect_PolylineSmokeTest)
|
||||
{
|
||||
m2::PolylineD poly = {{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}};
|
||||
TEST_EQUAL(poly.GetSize(), 3, ());
|
||||
TEST_ALMOST_EQUAL_ABS(poly.GetLength(), 2.0, kEps, ());
|
||||
|
||||
auto const limitRect = poly.GetLimitRect();
|
||||
TEST_ALMOST_EQUAL_ABS(limitRect.LeftBottom(), m2::PointD(0.0, 0.0), kEps, ());
|
||||
TEST(AlmostEqualAbs(limitRect.RightTop(), m2::PointD(1.0, 1.0), kEps), ());
|
||||
|
||||
poly.PopBack();
|
||||
TEST_EQUAL(poly.GetSize(), 2, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Rect_PolylineMinDistanceTest)
|
||||
{
|
||||
// 1 | |
|
||||
// | |
|
||||
// 0----1----2----3
|
||||
std::vector<m2::PointD> const poly = {{0.0, 1.0}, {0.0, 0.0}, {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}, {3.0, 1.0}};
|
||||
|
||||
TestClosest(poly, m2::PointD(0.0, 1.0), 0.0 /* expectedSquaredDist */, 0 /* expectedIndex */);
|
||||
TestClosest(poly, m2::PointD(0.0, 0.0), 0.0 /* expectedSquaredDist */, 0 /* expectedIndex */);
|
||||
TestClosest(poly, m2::PointD(0.1, 0.0), 0.0 /* expectedSquaredDist */, 1 /* expectedIndex */);
|
||||
TestClosest(poly, m2::PointD(0.5, 0.2), 0.2 * 0.2 /* expectedSquaredDist */, 1 /* expectedIndex */);
|
||||
TestClosest(poly, m2::PointD(1.5, 1.0), 1.0 /* expectedSquaredDist */, 2 /* expectedIndex */);
|
||||
TestClosest(poly, m2::PointD(1.5, -5.0), 5.0 * 5.0 /* expectedSquaredDist */, 2 /* expectedIndex */);
|
||||
TestClosest(poly, m2::PointD(1.5, 5.0), 4.0 * 4.0 + 1.5 * 1.5 /* expectedSquaredDist */, 0 /* expectedIndex */);
|
||||
TestClosest(poly, m2::PointD(3.0, 1.0), 0.0 /* expectedSquaredDist */, 4 /* expectedIndex */);
|
||||
}
|
||||
} // namespace polyline_tests
|
||||
21
libs/geometry/geometry_tests/rect_test.cpp
Normal file
21
libs/geometry/geometry_tests/rect_test.cpp
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#include "geometry/rect2d.hpp"
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
UNIT_TEST(Rect_Intersect)
|
||||
{
|
||||
m2::RectD r(0, 0, 100, 100);
|
||||
m2::RectD r1(10, 10, 20, 20);
|
||||
|
||||
TEST(r1.IsIntersect(r), ());
|
||||
TEST(r.IsIntersect(r1), ());
|
||||
|
||||
m2::RectD r2(-100, -100, -50, -50);
|
||||
|
||||
TEST(!r2.IsIntersect(r), ());
|
||||
TEST(!r.IsIntersect(r2), ());
|
||||
|
||||
m2::RectD r3(-10, -10, 10, 10);
|
||||
|
||||
TEST(r3.IsIntersect(r), ());
|
||||
TEST(r.IsIntersect(r3), ());
|
||||
}
|
||||
240
libs/geometry/geometry_tests/region2d_binary_op_test.cpp
Normal file
240
libs/geometry/geometry_tests/region2d_binary_op_test.cpp
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/geometry_tests/test_regions.hpp"
|
||||
#include "geometry/region2d/binary_operators.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace region2d_binary_op_test
|
||||
{
|
||||
using namespace std;
|
||||
using P = m2::PointI;
|
||||
using R = m2::RegionI;
|
||||
|
||||
UNIT_TEST(RegionIntersect_Smoke)
|
||||
{
|
||||
{
|
||||
P arr1[] = {P(-2, 1), P(2, 1), P(2, -1), P(-2, -1)};
|
||||
P arr2[] = {P(-1, 2), P(1, 2), P(1, -2), P(-1, -2)};
|
||||
|
||||
R r1, r2;
|
||||
r1.Assign(arr1, arr1 + ARRAY_SIZE(arr1));
|
||||
r2.Assign(arr2, arr2 + ARRAY_SIZE(arr2));
|
||||
|
||||
vector<R> res;
|
||||
m2::IntersectRegions(r1, r2, res);
|
||||
|
||||
TEST_EQUAL(res.size(), 1, ());
|
||||
TEST_EQUAL(res[0].GetRect(), m2::RectI(-1, -1, 1, 1), ());
|
||||
}
|
||||
|
||||
{
|
||||
P arr1[] = {P(0, 0), P(1, 1), P(2, 0)};
|
||||
P arr2[] = {P(0, 0), P(1, -1), P(2, 0)};
|
||||
|
||||
R r1, r2;
|
||||
r1.Assign(arr1, arr1 + ARRAY_SIZE(arr1));
|
||||
r2.Assign(arr2, arr2 + ARRAY_SIZE(arr2));
|
||||
|
||||
vector<R> res;
|
||||
m2::IntersectRegions(r1, r2, res);
|
||||
|
||||
TEST_EQUAL(res.size(), 0, ());
|
||||
}
|
||||
|
||||
{
|
||||
P arr1[] = {P(-10, -10), P(10, -10), P(10, 10), P(-10, 10)};
|
||||
P arr2[] = {P(-5, -5), P(5, -5), P(5, 5), P(-5, 5)};
|
||||
|
||||
R r1, r2;
|
||||
r1.Assign(arr1, arr1 + ARRAY_SIZE(arr1));
|
||||
r2.Assign(arr2, arr2 + ARRAY_SIZE(arr2));
|
||||
|
||||
vector<R> res;
|
||||
res.push_back(r1); // do some smoke
|
||||
m2::IntersectRegions(r1, r2, res);
|
||||
|
||||
TEST_EQUAL(res.size(), 2, ());
|
||||
TEST_EQUAL(res[1].GetRect(), m2::RectI(-5, -5, 5, 5), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(RegionDifference_Smoke)
|
||||
{
|
||||
{
|
||||
P arr1[] = {P(-1, 1), P(1, 1), P(1, -1), P(-1, -1)};
|
||||
P arr2[] = {P(-2, 2), P(2, 2), P(2, -2), P(-2, -2)};
|
||||
|
||||
R r1, r2;
|
||||
r1.Assign(arr1, arr1 + ARRAY_SIZE(arr1));
|
||||
r2.Assign(arr2, arr2 + ARRAY_SIZE(arr2));
|
||||
|
||||
vector<R> res;
|
||||
m2::DiffRegions(r1, r2, res);
|
||||
TEST_EQUAL(res.size(), 0, ());
|
||||
|
||||
m2::DiffRegions(r2, r1, res);
|
||||
|
||||
TEST_EQUAL(res.size(), 1, ());
|
||||
TEST_EQUAL(res[0].GetRect(), r2.GetRect(), ());
|
||||
}
|
||||
|
||||
{
|
||||
P arr1[] = {P(0, 1), P(2, 1), P(2, 0), P(0, 0)};
|
||||
P arr2[] = {P(1, 2), P(2, 2), P(2, -1), P(1, -1)};
|
||||
|
||||
R r1, r2;
|
||||
r1.Assign(arr1, arr1 + ARRAY_SIZE(arr1));
|
||||
r2.Assign(arr2, arr2 + ARRAY_SIZE(arr2));
|
||||
|
||||
vector<R> res;
|
||||
m2::DiffRegions(r1, r2, res);
|
||||
|
||||
TEST_EQUAL(res.size(), 1, ());
|
||||
TEST_EQUAL(res[0].GetRect(), m2::RectI(0, 0, 1, 1), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(AddRegion_Smoke)
|
||||
{
|
||||
{
|
||||
P arr1[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}};
|
||||
P arr2[] = {{2, 2}, {2, 3}, {3, 3}, {3, 2}};
|
||||
|
||||
R r1, r2;
|
||||
r1.Assign(arr1, arr1 + ARRAY_SIZE(arr1));
|
||||
r2.Assign(arr2, arr2 + ARRAY_SIZE(arr2));
|
||||
|
||||
m2::MultiRegionI res;
|
||||
res.push_back(r2);
|
||||
m2::AddRegion(r1, res);
|
||||
|
||||
TEST_EQUAL(res.size(), 2, ());
|
||||
TEST_EQUAL(m2::Area(res), 2, ());
|
||||
|
||||
res = m2::IntersectRegions(r1, {r2});
|
||||
TEST_EQUAL(res.size(), 0, ());
|
||||
TEST_EQUAL(m2::Area(res), 0, ());
|
||||
}
|
||||
|
||||
{
|
||||
P arr1[] = {{0, 0}, {0, 3}, {3, 3}, {3, 0}};
|
||||
P arr2[] = {{1, 1}, {1, 2}, {2, 2}, {2, 1}};
|
||||
|
||||
R r1, r2;
|
||||
r1.Assign(arr1, arr1 + ARRAY_SIZE(arr1));
|
||||
r2.Assign(arr2, arr2 + ARRAY_SIZE(arr2));
|
||||
|
||||
m2::MultiRegionI res;
|
||||
res.push_back(r2);
|
||||
m2::AddRegion(r1, res);
|
||||
|
||||
TEST_EQUAL(res.size(), 1, ());
|
||||
TEST_EQUAL(m2::Area(res), 9, ());
|
||||
|
||||
res = m2::IntersectRegions(r1, {r2});
|
||||
TEST_EQUAL(res.size(), 1, ());
|
||||
TEST_EQUAL(m2::Area(res), 1, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(RegionIntersect_Floats)
|
||||
{
|
||||
P arr1[] = {{0, 1}, {2, 3}, {3, 2}, {1, 0}};
|
||||
P arr2[] = {{0, 2}, {1, 3}, {3, 1}, {2, 0}};
|
||||
|
||||
R r1, r2;
|
||||
r1.Assign(arr1, arr1 + ARRAY_SIZE(arr1));
|
||||
r2.Assign(arr2, arr2 + ARRAY_SIZE(arr2));
|
||||
|
||||
// Moved diamond as a result with sqrt(2) edge's length and nearest integer coordinates.
|
||||
m2::MultiRegionI res;
|
||||
m2::IntersectRegions(r1, r2, res);
|
||||
|
||||
TEST_EQUAL(m2::Area(res), 2, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(RegionArea_2Directions)
|
||||
{
|
||||
P arr[] = {
|
||||
{1, 1}, {1, 0}, {2, 0}, {2, 1}, {1, 1}, // CCW direction
|
||||
{1, 1}, {1, 0}, {0, 0}, {0, 1}, {1, 1} // CW direction
|
||||
};
|
||||
|
||||
R r;
|
||||
r.Assign(arr, arr + ARRAY_SIZE(arr));
|
||||
|
||||
// Natural area is 2, but calculated area is 0, because one region with 2 opposite directions.
|
||||
TEST_EQUAL(r.CalculateArea(), 0, ());
|
||||
}
|
||||
|
||||
/*
|
||||
UNIT_TEST(RegionDifference_Data1)
|
||||
{
|
||||
using namespace geom_test;
|
||||
|
||||
vector<R> vec;
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB1, arrB1 + ARRAY_SIZE(arrB1));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB2, arrB2 + ARRAY_SIZE(arrB2));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB3, arrB3 + ARRAY_SIZE(arrB3));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB4, arrB4 + ARRAY_SIZE(arrB4));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB5, arrB5 + ARRAY_SIZE(arrB5));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB6, arrB6 + ARRAY_SIZE(arrB6));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB7, arrB7 + ARRAY_SIZE(arrB7));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB8, arrB8 + ARRAY_SIZE(arrB8));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB9, arrB9 + ARRAY_SIZE(arrB9));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB10, arrB10 + ARRAY_SIZE(arrB10));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB11, arrB11 + ARRAY_SIZE(arrB11));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB12, arrB12 + ARRAY_SIZE(arrB12));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB13, arrB13 + ARRAY_SIZE(arrB13));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB14, arrB14 + ARRAY_SIZE(arrB14));
|
||||
|
||||
vec.push_back(R());
|
||||
vec.back().Assign(arrB15, arrB15 + ARRAY_SIZE(arrB15));
|
||||
|
||||
vector<R> res;
|
||||
res.push_back(R());
|
||||
res.back().Assign(arrMain, arrMain + ARRAY_SIZE(arrMain));
|
||||
|
||||
for (size_t i = 0; i < vec.size(); ++i)
|
||||
{
|
||||
vector<R> local;
|
||||
|
||||
for (size_t j = 0; j < res.size(); ++j)
|
||||
m2::DiffRegions(res[j], vec[i], local);
|
||||
|
||||
local.swap(res);
|
||||
}
|
||||
}
|
||||
*/
|
||||
} // namespace region2d_binary_op_test
|
||||
367
libs/geometry/geometry_tests/region_tests.cpp
Normal file
367
libs/geometry/geometry_tests/region_tests.cpp
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include "geometry/convex_hull.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/region2d.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace region_tests
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
template <class Region>
|
||||
struct ContainsChecker
|
||||
{
|
||||
ContainsChecker(Region const & region) : m_region(region) {}
|
||||
|
||||
void operator()(typename Region::Value const & pt)
|
||||
{
|
||||
TEST(m_region.Contains(pt), ("Region should contain all its points"));
|
||||
}
|
||||
|
||||
Region const & m_region;
|
||||
};
|
||||
|
||||
/// Region should have CCW orientation from left, down corner.
|
||||
template <class Point>
|
||||
void TestContainsRectangular(Point const * arr)
|
||||
{
|
||||
m2::Region<Point> region;
|
||||
|
||||
size_t const count = 4;
|
||||
region.Assign(arr, arr + count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
TEST(region.Contains(arr[i]), ());
|
||||
|
||||
if constexpr (std::is_floating_point<typename Point::value_type>::value)
|
||||
TEST(region.Contains((arr[i] + arr[(i + 1) % count]) / 2), ());
|
||||
}
|
||||
|
||||
Point dx(1, 0);
|
||||
Point dy(0, 1);
|
||||
|
||||
TEST(!region.Contains(arr[0] - dx), ());
|
||||
TEST(!region.Contains(arr[0] - dy), ());
|
||||
TEST(!region.Contains(arr[0] - dx - dy), ());
|
||||
TEST(region.Contains(arr[0] + dx + dy), ());
|
||||
|
||||
TEST(!region.Contains(arr[1] + dx), ());
|
||||
TEST(!region.Contains(arr[1] - dy), ());
|
||||
TEST(!region.Contains(arr[1] + dx - dy), ());
|
||||
TEST(region.Contains(arr[1] - dx + dy), ());
|
||||
|
||||
TEST(!region.Contains(arr[2] + dx), ());
|
||||
TEST(!region.Contains(arr[2] + dy), ());
|
||||
TEST(!region.Contains(arr[2] + dx + dy), ());
|
||||
TEST(region.Contains(arr[2] - dx - dy), ());
|
||||
|
||||
TEST(!region.Contains(arr[3] - dx), ());
|
||||
TEST(!region.Contains(arr[3] + dy), ());
|
||||
TEST(!region.Contains(arr[3] - dx + dy), ());
|
||||
TEST(region.Contains(arr[3] + dx - dy), ());
|
||||
}
|
||||
|
||||
template <class Region>
|
||||
void TestContains()
|
||||
{
|
||||
Region region;
|
||||
ContainsChecker<Region> checker(region);
|
||||
|
||||
// point type
|
||||
using P = typename Region::Value;
|
||||
|
||||
// rectangular polygon
|
||||
{
|
||||
P const data[] = {P(1, 1), P(10, 1), P(10, 10), P(1, 10)};
|
||||
TestContainsRectangular(data);
|
||||
}
|
||||
{
|
||||
P const data[] = {P(-100, -100), P(-50, -100), P(-50, -50), P(-100, -50)};
|
||||
TestContainsRectangular(data);
|
||||
}
|
||||
{
|
||||
P const data[] = {P(-2000000000, -2000000000), P(-1000000000, -2000000000), P(-1000000000, -1000000000),
|
||||
P(-2000000000, -1000000000)};
|
||||
TestContainsRectangular(data);
|
||||
}
|
||||
{
|
||||
P const data[] = {P(1000000000, 1000000000), P(2000000000, 1000000000), P(2000000000, 2000000000),
|
||||
P(1000000000, 2000000000)};
|
||||
TestContainsRectangular(data);
|
||||
}
|
||||
|
||||
// triangle
|
||||
{
|
||||
P const data[] = {P(0, 0), P(2, 0), P(2, 2)};
|
||||
region.Assign(data, data + ARRAY_SIZE(data));
|
||||
}
|
||||
TEST_EQUAL(region.GetRect(), m2::Rect<typename P::value_type>(0, 0, 2, 2), ());
|
||||
TEST(region.Contains(P(2, 0)), ());
|
||||
TEST(region.Contains(P(1, 1)), ("point on diagonal"));
|
||||
TEST(!region.Contains(P(33, 0)), ());
|
||||
region.ForEachPoint(checker);
|
||||
|
||||
// complex polygon
|
||||
{
|
||||
P const data[] = {P(0, 0), P(2, 0), P(2, 2), P(3, 1), P(4, 2), P(5, 2), P(3, 3), P(3, 2), P(2, 4), P(6, 3),
|
||||
P(7, 4), P(7, 2), P(8, 5), P(8, 7), P(7, 7), P(8, 8), P(5, 9), P(6, 6), P(5, 7), P(4, 6),
|
||||
P(4, 8), P(3, 7), P(2, 7), P(3, 6), P(4, 4), P(0, 7), P(2, 3), P(0, 2)};
|
||||
region.Assign(data, data + ARRAY_SIZE(data));
|
||||
}
|
||||
TEST_EQUAL(region.GetRect(), m2::Rect<typename P::value_type>(0, 0, 8, 9), ());
|
||||
TEST(region.Contains(P(0, 0)), ());
|
||||
TEST(region.Contains(P(3, 7)), ());
|
||||
TEST(region.Contains(P(1, 2)), ());
|
||||
TEST(region.Contains(P(1, 1)), ());
|
||||
TEST(!region.Contains(P(6, 2)), ());
|
||||
TEST(!region.Contains(P(3, 5)), ());
|
||||
TEST(!region.Contains(P(5, 8)), ());
|
||||
region.ForEachPoint(checker);
|
||||
}
|
||||
|
||||
template <class Point>
|
||||
class PointsSummator
|
||||
{
|
||||
public:
|
||||
PointsSummator(Point & res) : m_res(res) {}
|
||||
|
||||
void operator()(Point const & pt) { m_res += pt; }
|
||||
|
||||
private:
|
||||
Point & m_res;
|
||||
};
|
||||
|
||||
UNIT_TEST(Region)
|
||||
{
|
||||
typedef m2::PointD P;
|
||||
P p1[] = {P(0.1, 0.2)};
|
||||
|
||||
m2::Region<P> region(p1, p1 + ARRAY_SIZE(p1));
|
||||
TEST(!region.IsValid(), ());
|
||||
|
||||
{
|
||||
P p2[] = {P(1.0, 2.0), P(55.0, 33.0)};
|
||||
region.Assign(p2, p2 + ARRAY_SIZE(p2));
|
||||
}
|
||||
TEST(!region.IsValid(), ());
|
||||
|
||||
region.AddPoint(P(34.4, 33.2));
|
||||
TEST(region.IsValid(), ());
|
||||
|
||||
{
|
||||
// equality case
|
||||
{
|
||||
P const data[] = {P(1, 1), P(0, 4.995), P(1, 4.999996), P(1.000003, 5.000001), P(0.5, 10), P(10, 10), P(10, 1)};
|
||||
region.Assign(data, data + ARRAY_SIZE(data));
|
||||
}
|
||||
TEST(!region.Contains(P(0.9999987, 0.9999938)), ());
|
||||
TEST(!region.Contains(P(0.999998, 4.9999987)), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Region_Contains_int32)
|
||||
{
|
||||
TestContains<m2::RegionI>();
|
||||
|
||||
// negative triangle
|
||||
{
|
||||
using P = m2::PointI;
|
||||
m2::Region<P> region;
|
||||
P const data[] = {P(1, -1), P(-2, -2), P(-3, 1)};
|
||||
region.Assign(data, data + ARRAY_SIZE(data));
|
||||
|
||||
TEST_EQUAL(region.GetRect(), m2::Rect<P::value_type>(-3, -2, 1, 1), ());
|
||||
|
||||
TEST(region.Contains(P(-2, -2)), ());
|
||||
TEST(region.Contains(P(-2, 0)), ());
|
||||
TEST(!region.Contains(P(0, 0)), ());
|
||||
}
|
||||
|
||||
{
|
||||
using P = m2::PointI;
|
||||
m2::Region<P> region;
|
||||
P const data[] = {P(1, -1), P(3, 0), P(3, 3), P(0, 3), P(0, 2), P(0, 1), P(2, 2)};
|
||||
region.Assign(data, data + ARRAY_SIZE(data));
|
||||
|
||||
TEST_EQUAL(region.GetRect(), m2::Rect<P::value_type>(0, -1, 3, 3), ());
|
||||
|
||||
TEST(region.Contains(P(2, 2)), ());
|
||||
TEST(region.Contains(P(1, 3)), ());
|
||||
TEST(region.Contains(P(3, 1)), ());
|
||||
TEST(!region.Contains(P(1, 1)), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Region_Contains_uint32)
|
||||
{
|
||||
TestContains<m2::RegionU>();
|
||||
}
|
||||
|
||||
UNIT_TEST(Region_Contains_double)
|
||||
{
|
||||
using Region = m2::RegionD;
|
||||
using Point = Region::Value;
|
||||
|
||||
TestContains<Region>();
|
||||
|
||||
{
|
||||
Region region;
|
||||
Point const data[] = {{0, 7}, {4, 4}, {3, 6}, {8, 6}, {8, 5}, {6, 3}, {2, 2}};
|
||||
region.Assign(data, data + ARRAY_SIZE(data));
|
||||
|
||||
TEST_EQUAL(region.GetRect(), m2::Rect<Point::value_type>(0, 2, 8, 7), ());
|
||||
TEST(!region.Contains({3, 5}), ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Region_ForEachPoint)
|
||||
{
|
||||
using P = m2::PointF;
|
||||
P const points[] = {P(0.0, 1.0), P(1.0, 2.0), P(10.5, 11.5)};
|
||||
m2::Region<P> region(points, points + ARRAY_SIZE(points));
|
||||
|
||||
P res(0, 0);
|
||||
region.ForEachPoint(PointsSummator<P>(res));
|
||||
|
||||
TEST_EQUAL(res, P(11.5, 14.5), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Region_point_at_border_test)
|
||||
{
|
||||
using P = m2::PointF;
|
||||
P const points[] = {P(0.0, 1.0), P(0.0, 10.0), P(5.0, 7.0), P(10.0, 10.0), P(10.0, 1.0)};
|
||||
m2::Region<P> region(points, points + ARRAY_SIZE(points));
|
||||
|
||||
P p1(0, 0);
|
||||
P p2(5.0, 5.0);
|
||||
P p3(0.0, 1.0);
|
||||
P p4(5.0, 1.0);
|
||||
P p5(5.0, 1.01);
|
||||
P p6(5.0, 0.99);
|
||||
P p7(5.0, 7.0);
|
||||
P p8(5.0, 6.98);
|
||||
P p9(5.0, 6.995);
|
||||
P p10(5.0, 7.01);
|
||||
|
||||
TEST(!region.AtBorder(p1, 0.01), ("Point lies outside the border"));
|
||||
TEST(!region.AtBorder(p2, 0.01), ("Point lies strictly inside the border"));
|
||||
TEST(region.AtBorder(p3, 0.01), ("Point has same point with the border"));
|
||||
TEST(region.AtBorder(p4, 0.01), ("Point lies at the border"));
|
||||
TEST(region.AtBorder(p5, 0.01), ("Point lies at delta interval near the border inside polygon"));
|
||||
TEST(region.AtBorder(p6, 0.01), ("Point lies at delta interval near the border outside polygon"));
|
||||
TEST(region.AtBorder(p7, 0.01), ("Point has same point with the border"));
|
||||
TEST(!region.AtBorder(p8, 0.01), ("Point is too far from border"));
|
||||
TEST(region.AtBorder(p9, 0.01), ("Point lies at delta interval near the border outside polygon"));
|
||||
TEST(region.AtBorder(p10, 0.01), ("Point lies at delta interval near the border inside polygon"));
|
||||
}
|
||||
|
||||
UNIT_TEST(Region_border_intersecion_Test)
|
||||
{
|
||||
using P = m2::PointF;
|
||||
P const points[] = {P(0.0, 1.0), P(0.0, 10.0), P(10.0, 10.0), P(10.0, 1.0)};
|
||||
m2::Region<P> region(points, points + ARRAY_SIZE(points));
|
||||
|
||||
P intersection;
|
||||
|
||||
TEST(region.FindIntersection(P(5.0, 5.0), P(15.0, 5.0), intersection), ());
|
||||
TEST(intersection == P(10.0, 5.0), ());
|
||||
|
||||
TEST(region.FindIntersection(P(5.0, 5.0), P(15.0, 15.0), intersection), ());
|
||||
TEST(intersection == P(10.0, 10.0), ());
|
||||
|
||||
TEST(region.FindIntersection(P(7.0, 7.0), P(7.0, 10.0), intersection), ());
|
||||
TEST(intersection == P(7.0, 10.0), ());
|
||||
|
||||
TEST(!region.FindIntersection(P(5.0, 5.0), P(2.0, 2.0), intersection), ("This case has no intersection"));
|
||||
}
|
||||
|
||||
UNIT_TEST(Region_Area)
|
||||
{
|
||||
using P = m2::PointD;
|
||||
|
||||
{
|
||||
m2::Region<P> region;
|
||||
TEST_EQUAL(region.CalculateArea(), 0.0, ());
|
||||
}
|
||||
{
|
||||
// Counterclockwise.
|
||||
P const points[] = {P(0.0, 0.0), P(1.0, 0.0), P(1.0, 1.0), P(0.0, 1.0)};
|
||||
m2::Region<P> region(points, points + ARRAY_SIZE(points));
|
||||
TEST_EQUAL(region.CalculateArea(), 1.0, ());
|
||||
}
|
||||
{
|
||||
// Clockwise.
|
||||
P const points[] = {P(0.0, 0.0), P(0.0, 1.0), P(1.0, 1.0), P(1.0, 0.0)};
|
||||
m2::Region<P> region(points, points + ARRAY_SIZE(points));
|
||||
TEST_EQUAL(region.CalculateArea(), 1.0, ());
|
||||
}
|
||||
{
|
||||
// Non-convex.
|
||||
P const points[] = {P(0.0, 0.0), P(1.0, 0.0), P(1.0, 1.0), P(0.5, 0.5), P(0.0, 1.0)};
|
||||
m2::Region<P> region(points, points + ARRAY_SIZE(points));
|
||||
TEST_EQUAL(region.CalculateArea(), 0.75, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Region_GetRandomPoint)
|
||||
{
|
||||
using P = m2::PointD;
|
||||
|
||||
// Run several iterations of Monte-Carlo and check that areas are similar.
|
||||
size_t const kNumIterations = 1000;
|
||||
bool const kNeedPlot = true;
|
||||
|
||||
auto testConvexRegion = [&](m2::Region<P> const & region)
|
||||
{
|
||||
minstd_rand rng(0);
|
||||
vector<P> points;
|
||||
points.reserve(kNumIterations);
|
||||
for (size_t i = 0; i < kNumIterations; ++i)
|
||||
points.emplace_back(region.GetRandomPoint(rng));
|
||||
m2::ConvexHull const hull(points, 1e-9 /* eps */);
|
||||
auto const hullRegion = m2::Region<P>(hull.Points().begin(), hull.Points().end());
|
||||
LOG(LINFO, (hullRegion.CalculateArea()));
|
||||
TEST(AlmostEqualRel(region.CalculateArea(), hullRegion.CalculateArea(), 0.05), ());
|
||||
|
||||
if (kNeedPlot)
|
||||
{
|
||||
cout << "import matplotlib.pyplot as plt" << endl;
|
||||
cout << endl;
|
||||
|
||||
cout << "x = [";
|
||||
for (size_t i = 0; i < points.size(); i++)
|
||||
cout << points[i].x << ",";
|
||||
cout << "]" << endl;
|
||||
|
||||
cout << "y = [";
|
||||
for (size_t i = 0; i < points.size(); i++)
|
||||
cout << points[i].y << ",";
|
||||
cout << "]" << endl;
|
||||
|
||||
cout << endl;
|
||||
cout << "plt.scatter(x, y)" << endl;
|
||||
cout << "plt.show()" << endl;
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
P const points[] = {P(0.0, 0.0), P(1.0, 0.0), P(1.0, 1.0), P(0.0, 1.0)};
|
||||
m2::Region<P> region(points, points + ARRAY_SIZE(points));
|
||||
testConvexRegion(region);
|
||||
}
|
||||
|
||||
{
|
||||
P const points[] = {P(0.0, -1.0), P(1.0, 0.0), P(0.0, 1.0), P(-1.0, 0.0)};
|
||||
m2::Region<P> region(points, points + ARRAY_SIZE(points));
|
||||
testConvexRegion(region);
|
||||
}
|
||||
}
|
||||
} // namespace region_tests
|
||||
179
libs/geometry/geometry_tests/robust_test.cpp
Normal file
179
libs/geometry/geometry_tests/robust_test.cpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/robust_orientation.hpp"
|
||||
#include "geometry/segment2d.hpp"
|
||||
#include "geometry/triangle2d.hpp"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
namespace robust_test
|
||||
{
|
||||
using namespace m2::robust;
|
||||
using P = m2::PointD;
|
||||
|
||||
template <typename TIt>
|
||||
void CheckSelfIntersections(TIt beg, TIt end, bool res)
|
||||
{
|
||||
TEST_EQUAL(CheckPolygonSelfIntersections(beg, end), res, ());
|
||||
using TRevIt = std::reverse_iterator<TIt>;
|
||||
TEST_EQUAL(CheckPolygonSelfIntersections(TRevIt(end), TRevIt(beg)), res, ());
|
||||
}
|
||||
|
||||
bool OnSegment(P const & p, P const ps[])
|
||||
{
|
||||
return IsPointOnSegment(p, ps[0], ps[1]);
|
||||
}
|
||||
|
||||
bool InsideTriangle(P const & p, P const ps[])
|
||||
{
|
||||
return IsPointInsideTriangle(p, ps[0], ps[1], ps[2]);
|
||||
}
|
||||
|
||||
UNIT_TEST(OrientedS_Smoke)
|
||||
{
|
||||
P arr[] = {{-1, -1}, {0, 0}, {1, -1}};
|
||||
TEST(OrientedS(arr[0], arr[2], arr[1]) > 0, ());
|
||||
TEST(OrientedS(arr[2], arr[0], arr[1]) < 0, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Segment_Smoke)
|
||||
{
|
||||
double constexpr eps = 1.0E-10;
|
||||
{
|
||||
P ps[] = {{0, 0}, {1, 0}};
|
||||
TEST(OnSegment(ps[0], ps), ());
|
||||
TEST(OnSegment(ps[1], ps), ());
|
||||
TEST(OnSegment(P(0.5, 0), ps), ());
|
||||
|
||||
TEST(OnSegment(P(eps, 0), ps), ());
|
||||
TEST(OnSegment(P(1.0 - eps, 0), ps), ());
|
||||
|
||||
TEST(!OnSegment(P(-eps, 0), ps), ());
|
||||
TEST(!OnSegment(P(1.0 + eps, 0), ps), ());
|
||||
TEST(!OnSegment(P(eps, eps), ps), ());
|
||||
TEST(!OnSegment(P(eps, -eps), ps), ());
|
||||
}
|
||||
|
||||
{
|
||||
P ps[] = {{10, 10}, {10, 10}};
|
||||
TEST(OnSegment(ps[0], ps), ());
|
||||
TEST(OnSegment(ps[1], ps), ());
|
||||
TEST(!OnSegment(P(10 - eps, 10), ps), ());
|
||||
TEST(!OnSegment(P(10 + eps, 10), ps), ());
|
||||
TEST(!OnSegment(P(0, 0), ps), ());
|
||||
}
|
||||
}
|
||||
|
||||
// This paranoid test doesn' work with Release optimizations (LTO?).
|
||||
#ifndef NDEBUG
|
||||
UNIT_TEST(Segment_Paranoid)
|
||||
{
|
||||
{
|
||||
P ps[] = {{0, 0}, {1e100, 1e100}};
|
||||
TEST(OnSegment(ps[0], ps), ());
|
||||
TEST(OnSegment(ps[1], ps), ());
|
||||
#if defined(DEBUG) || __apple_build_version__ < 15000000 // True if __apple_build_version__ is not defined (e.g. Linux)
|
||||
// TODO(AB): Fails on Mac's clang with any optimization enabled and -fassociative-math
|
||||
TEST(OnSegment(P(1e50, 1e50), ps), ());
|
||||
#endif
|
||||
TEST(!OnSegment(P(1e50, 1.00000000001e50), ps), ());
|
||||
|
||||
#if defined(DEBUG) || __apple_build_version__ < 15000000
|
||||
// TODO(AB): Fails on Mac's clang with any optimization enabled and -fassociative-math
|
||||
TEST(OnSegment(P(1e-100, 1e-100), ps), ());
|
||||
#endif
|
||||
TEST(!OnSegment(P(1e-100, 1e-100 + 1e-115), ps), ());
|
||||
}
|
||||
|
||||
#if defined(DEBUG) || __apple_build_version__ < 15000000
|
||||
// TODO(AB): Fails on Mac's clang with any optimization enabled and -fassociative-math
|
||||
{
|
||||
P ps[] = {{0, 0}, {2e100, 1e100}};
|
||||
TEST(OnSegment(P(2.0 / 3.0, 1.0 / 3.0), ps), ());
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
P ps[] = {{0, 0}, {1e-15, 1e-15}};
|
||||
TEST(!OnSegment(P(1e-16, 2.0 * 1e-16), ps), ());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
UNIT_TEST(Triangle_Smoke)
|
||||
{
|
||||
P arr[] = {{0, 0}, {0, 3}, {3, 0}};
|
||||
|
||||
TEST(IsPointInsideTriangle(arr[0], arr[0], arr[1], arr[2]), ());
|
||||
TEST(IsPointInsideTriangle(arr[1], arr[0], arr[1], arr[2]), ());
|
||||
TEST(IsPointInsideTriangle(arr[2], arr[0], arr[1], arr[2]), ());
|
||||
TEST(IsPointInsideTriangle({1, 1}, arr[0], arr[1], arr[2]), ());
|
||||
TEST(IsPointInsideTriangle({1, 2}, arr[0], arr[1], arr[2]), ());
|
||||
TEST(IsPointInsideTriangle({2, 1}, arr[0], arr[1], arr[2]), ());
|
||||
|
||||
double constexpr eps = 1.0E-10;
|
||||
TEST(!IsPointInsideTriangle({-eps, -eps}, arr[0], arr[1], arr[2]), ());
|
||||
TEST(!IsPointInsideTriangle({1 + eps, 2}, arr[0], arr[1], arr[2]), ());
|
||||
TEST(!IsPointInsideTriangle({2, 1 + eps}, arr[0], arr[1], arr[2]), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Triangle_PointInsideSegment)
|
||||
{
|
||||
double constexpr eps = 1.0E-10;
|
||||
|
||||
P ps[] = {{0, 0}, {0, 1}, {0, 1}};
|
||||
TEST(InsideTriangle(ps[0], ps), ());
|
||||
TEST(InsideTriangle(ps[1], ps), ());
|
||||
TEST(InsideTriangle(ps[2], ps), ());
|
||||
TEST(InsideTriangle(P(0, eps), ps), ());
|
||||
TEST(InsideTriangle(P(0, 1.0 - eps), ps), ());
|
||||
|
||||
TEST(!InsideTriangle(P(0, -eps), ps), ());
|
||||
TEST(!InsideTriangle(P(0, 1.0 + eps), ps), ());
|
||||
TEST(!InsideTriangle(P(-eps, eps), ps), ());
|
||||
TEST(!InsideTriangle(P(eps, eps), ps), ());
|
||||
}
|
||||
|
||||
// This paranoid test doesn' work with Release optimizations (LTO?).
|
||||
#ifndef NDEBUG
|
||||
UNIT_TEST(Triangle_PointInsidePoint)
|
||||
{
|
||||
double constexpr eps = 1.0E-10;
|
||||
|
||||
P ps[] = {{0, 0}, {0, 0}, {0, 0}};
|
||||
TEST(InsideTriangle(ps[0], ps), ());
|
||||
TEST(InsideTriangle(ps[1], ps), ());
|
||||
TEST(InsideTriangle(ps[2], ps), ());
|
||||
|
||||
TEST(!InsideTriangle(P(0, eps), ps), ());
|
||||
TEST(!InsideTriangle(P(0, -eps), ps), ());
|
||||
|
||||
#if defined(DEBUG) || __apple_build_version__ < 15000000
|
||||
// TODO(AB): Fail on Mac's clang with any optimization enabled and -fassociative-math
|
||||
TEST(!InsideTriangle(P(-eps, eps), ps), ());
|
||||
TEST(!InsideTriangle(P(eps, eps), ps), ());
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
UNIT_TEST(PolygonSelfIntersections_IntersectSmoke)
|
||||
{
|
||||
{
|
||||
P arr[] = {P(0, 1), P(2, -1), P(2, 1), P(0, -1)};
|
||||
CheckSelfIntersections(&arr[0], arr + ARRAY_SIZE(arr), true);
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(PolygonSelfIntersections_TangentSmoke)
|
||||
{
|
||||
{
|
||||
P arr[] = {P(0, 1), P(1, 0), P(2, 1), P(2, -1), P(1, 0), P(0, -1)};
|
||||
CheckSelfIntersections(&arr[0], arr + ARRAY_SIZE(arr), false);
|
||||
}
|
||||
|
||||
{
|
||||
P arr[] = {P(0, 0), P(2, 0), P(2, 1), P(1, 0), P(0, 1)};
|
||||
CheckSelfIntersections(&arr[0], arr + ARRAY_SIZE(arr), false);
|
||||
}
|
||||
}
|
||||
} // namespace robust_test
|
||||
215
libs/geometry/geometry_tests/screen_test.cpp
Normal file
215
libs/geometry/geometry_tests/screen_test.cpp
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
#include "geometry/geometry_tests/equality.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
#include "geometry/screenbase.hpp"
|
||||
#include "geometry/transformations.hpp"
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
namespace screen_test
|
||||
{
|
||||
using test::is_equal;
|
||||
|
||||
static void check_set_from_rect(ScreenBase & screen, int width, int height)
|
||||
{
|
||||
screen.OnSize(0, 0, width, height);
|
||||
|
||||
m2::PointD b1(0.0, 0.0);
|
||||
m2::PointD b2(300.0, 300.0);
|
||||
screen.SetFromRect(m2::AnyRectD(m2::RectD(b1, b2)));
|
||||
|
||||
b1 = screen.GtoP(b1);
|
||||
b2 = screen.GtoP(b2);
|
||||
|
||||
// check that we are in boundaries.
|
||||
TEST(math::Between(0, width, math::iround(b1.x)), ());
|
||||
TEST(math::Between(0, width, math::iround(b2.x)), ());
|
||||
TEST(math::Between(0, height, math::iround(b1.y)), ());
|
||||
TEST(math::Between(0, height, math::iround(b2.y)), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ScreenBase_P2G2P)
|
||||
{
|
||||
ScreenBase screen;
|
||||
|
||||
check_set_from_rect(screen, 1000, 500);
|
||||
check_set_from_rect(screen, 500, 1000);
|
||||
|
||||
screen.OnSize(0, 0, 640, 480);
|
||||
screen.SetFromRect(m2::AnyRectD(m2::RectD(-100, -200, 500, 680)));
|
||||
|
||||
// checking that PtoG(GtoP(p)) == p
|
||||
|
||||
m2::PointD pp(10.0, 20.0);
|
||||
m2::PointD pg = screen.PtoG(pp);
|
||||
TEST(is_equal(pp, screen.GtoP(pg)), ());
|
||||
|
||||
pg = m2::PointD(550, 440);
|
||||
pp = screen.GtoP(pg);
|
||||
TEST(is_equal(pg, screen.PtoG(pp)), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ScreenBase_3dTransform)
|
||||
{
|
||||
ScreenBase screen;
|
||||
|
||||
double const rotationAngle = math::pi4;
|
||||
|
||||
screen.SetFromRects(m2::AnyRectD(m2::RectD(50, 25, 55, 30)), m2::RectD(0, 0, 200, 400));
|
||||
screen.ApplyPerspective(rotationAngle, rotationAngle, math::pi / 3.0);
|
||||
|
||||
TEST(screen.PixelRectIn3d().SizeX() < screen.PixelRect().SizeX(), ());
|
||||
TEST(screen.PixelRectIn3d().SizeY() < screen.PixelRect().SizeY(), ());
|
||||
|
||||
double const kEps = 1.0e-3;
|
||||
|
||||
m2::PointD pp(screen.PixelRect().SizeX() / 2.0, screen.PixelRect().SizeY());
|
||||
m2::PointD p3d = screen.PtoP3d(pp);
|
||||
TEST(p3d.EqualDxDy(m2::PointD(screen.PixelRectIn3d().SizeX() / 2.0, screen.PixelRectIn3d().SizeY()), kEps), ());
|
||||
|
||||
p3d = m2::PointD(screen.PixelRectIn3d().SizeX() / 2.0, screen.PixelRectIn3d().SizeY() / 2.0);
|
||||
pp = screen.P3dtoP(p3d);
|
||||
TEST(
|
||||
pp.EqualDxDy(m2::PointD(screen.PixelRect().SizeX() / 2.0,
|
||||
screen.PixelRect().SizeY() - screen.PixelRectIn3d().SizeY() / (2.0 * cos(rotationAngle))),
|
||||
kEps),
|
||||
());
|
||||
|
||||
p3d = m2::PointD(0, 0);
|
||||
pp = screen.P3dtoP(p3d);
|
||||
TEST(pp.x < kEps, ());
|
||||
|
||||
p3d = m2::PointD(screen.PixelRectIn3d().SizeX(), 0);
|
||||
pp = screen.P3dtoP(p3d);
|
||||
TEST(fabs(pp.x - screen.PixelRect().maxX()) < kEps, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ScreenBase_P2P3d2P)
|
||||
{
|
||||
ScreenBase screen;
|
||||
|
||||
screen.SetFromRects(m2::AnyRectD(m2::RectD(50, 25, 55, 30)), m2::RectD(0, 0, 600, 400));
|
||||
screen.ApplyPerspective(math::pi4, math::pi4, math::pi / 3.0);
|
||||
|
||||
double const kEps = 1.0e-3;
|
||||
|
||||
// checking that P3dtoP(PtoP3d(p)) == p
|
||||
m2::PointD pp(500, 300);
|
||||
m2::PointD p3d = screen.PtoP3d(pp);
|
||||
TEST(pp.EqualDxDy(screen.P3dtoP(p3d), kEps), ());
|
||||
|
||||
// checking that PtoP3(P3dtoP(p)) == p
|
||||
p3d = m2::PointD(400, 300);
|
||||
pp = screen.P3dtoP(p3d);
|
||||
TEST(p3d.EqualDxDy(screen.PtoP3d(pp), kEps), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ScreenBase_AxisOrientation)
|
||||
{
|
||||
ScreenBase screen;
|
||||
|
||||
screen.OnSize(0, 0, 300, 200);
|
||||
screen.SetFromRect(m2::AnyRectD(m2::RectD(0, 0, 300, 200)));
|
||||
|
||||
TEST(is_equal(m2::PointD(150, 100), screen.GtoP(m2::PointD(150, 100))), ());
|
||||
TEST(is_equal(m2::PointD(0, 0), screen.GtoP(m2::PointD(0, 200))), ());
|
||||
TEST(is_equal(m2::PointD(300, 0), screen.GtoP(m2::PointD(300, 200))), ());
|
||||
TEST(is_equal(m2::PointD(300, 200), screen.GtoP(m2::PointD(300, 0))), ());
|
||||
TEST(is_equal(m2::PointD(0, 200), screen.GtoP(m2::PointD(0, 0))), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ScreenBase_X0Y0)
|
||||
{
|
||||
ScreenBase screen;
|
||||
screen.OnSize(10, 10, 300, 200);
|
||||
screen.SetFromRect(m2::AnyRectD(m2::RectD(0, 0, 300, 200)));
|
||||
|
||||
TEST(is_equal(m2::PointD(10, 210), screen.GtoP(m2::PointD(0, 0))), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ScreenBase_ChoosingMaxScale)
|
||||
{
|
||||
ScreenBase screen;
|
||||
screen.OnSize(10, 10, 300, 200);
|
||||
screen.SetFromRect(m2::AnyRectD(m2::RectD(0, 0, 200, 400)));
|
||||
|
||||
TEST(is_equal(screen.GtoP(m2::PointD(100, 200)), m2::PointD(160, 110)), ());
|
||||
TEST(is_equal(screen.GtoP(m2::PointD(0, 0)), m2::PointD(110, 210)), ());
|
||||
TEST(is_equal(screen.GtoP(m2::PointD(200, 0)), m2::PointD(210, 210)), ());
|
||||
TEST(is_equal(screen.GtoP(m2::PointD(0, 400)), m2::PointD(110, 10)), ());
|
||||
TEST(is_equal(screen.GtoP(m2::PointD(200, 400)), m2::PointD(210, 10)), ());
|
||||
|
||||
TEST(is_equal(screen.GtoP(m2::PointD(-200, 0)), m2::PointD(10, 210)), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ScreenBase_CalcTransform)
|
||||
{
|
||||
double s = 2;
|
||||
double a = sqrt(3.) / 2.0;
|
||||
double dx = 1;
|
||||
double dy = 2;
|
||||
double s1, a1, dx1, dy1;
|
||||
math::Matrix<double, 3, 3> m =
|
||||
ScreenBase::CalcTransform(m2::PointD(0, 1), m2::PointD(1, 1), m2::PointD(s * sin(a) + dx, s * cos(a) + dy),
|
||||
m2::PointD(s * cos(a) + s * sin(a) + dx, -s * sin(a) + s * cos(a) + dy),
|
||||
true /* allow rotate */, true /* allow scale*/);
|
||||
|
||||
ScreenBase::ExtractGtoPParams(m, a1, s1, dx1, dy1);
|
||||
|
||||
TEST(fabs(s - s1) < 0.00001, (s, s1));
|
||||
TEST(fabs(a - a1) < 0.00001, (a, a1));
|
||||
TEST(fabs(dx - dx1) < 0.00001, (dx, dx1));
|
||||
TEST(fabs(dy - dy1) < 0.00001, (dy, dy1));
|
||||
}
|
||||
|
||||
UNIT_TEST(ScreenBase_Rotate)
|
||||
{
|
||||
ScreenBase s;
|
||||
s.OnSize(0, 0, 100, 200);
|
||||
s.SetFromRect(m2::AnyRectD(m2::RectD(0, 0, 100, 200)));
|
||||
s.Rotate(math::pi / 4);
|
||||
|
||||
TEST_EQUAL(s.PixelRect(), m2::RectD(0, 0, 100, 200), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ScreenBase_CombineTransforms)
|
||||
{
|
||||
ScreenBase s;
|
||||
s.OnSize(0, 0, 640, 480);
|
||||
s.SetFromRect(m2::AnyRectD(m2::RectD(50, 25, 55, 30)));
|
||||
s.SetAngle(1.0);
|
||||
|
||||
m2::PointD g1(40, 50);
|
||||
m2::PointD g2(60, 70);
|
||||
|
||||
m2::PointD p1 = s.GtoP(g1);
|
||||
m2::PointD p2 = s.GtoP(g2);
|
||||
|
||||
m2::PointD const org = s.GtoP(m2::PointD(0, 0));
|
||||
double const angle = s.GetAngle();
|
||||
double const scale = s.GetScale();
|
||||
double const fixedScale = 666.666;
|
||||
|
||||
ScreenBase sCopy(s, m2::PointD(0, 0), fixedScale, 0.0);
|
||||
|
||||
{
|
||||
// GtoP matrix for scale only.
|
||||
math::Matrix<double, 3, 3> m = math::Shift(
|
||||
math::Scale(math::Identity<double, 3>(), 1.0 / fixedScale, -1.0 / fixedScale), sCopy.PixelRect().Center());
|
||||
|
||||
TEST(is_equal(sCopy.GtoP(g1), g1 * m), ());
|
||||
TEST(is_equal(sCopy.GtoP(g2), g2 * m), ());
|
||||
}
|
||||
|
||||
// GtoP matrix to make full final transformation.
|
||||
math::Matrix<double, 3, 3> m = math::Shift(
|
||||
math::Scale(math::Rotate(math::Shift(math::Identity<double, 3>(), -sCopy.PixelRect().Center()), angle),
|
||||
fixedScale / scale, fixedScale / scale),
|
||||
org);
|
||||
|
||||
m2::PointD pp1 = sCopy.GtoP(g1) * m;
|
||||
m2::PointD pp2 = sCopy.GtoP(g2) * m;
|
||||
|
||||
TEST(is_equal(p1, pp1), (p1, pp1));
|
||||
TEST(is_equal(p2, pp2), (p2, pp2));
|
||||
}
|
||||
} // namespace screen_test
|
||||
99
libs/geometry/geometry_tests/segment2d_tests.cpp
Normal file
99
libs/geometry/geometry_tests/segment2d_tests.cpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/segment2d.hpp"
|
||||
|
||||
namespace segment2d_tests
|
||||
{
|
||||
using namespace m2;
|
||||
|
||||
double const kEps = 1e-10;
|
||||
|
||||
void TestIntersectionResult(IntersectionResult const & result, IntersectionResult::Type expectedType,
|
||||
PointD const & expectedPoint)
|
||||
{
|
||||
TEST_EQUAL(result.m_type, expectedType, ());
|
||||
TEST(AlmostEqualAbs(result.m_point, expectedPoint, kEps), (result.m_point, expectedPoint, kEps));
|
||||
}
|
||||
|
||||
bool TestSegmentsIntersect(PointD a, PointD b, PointD c, PointD d)
|
||||
{
|
||||
bool const res = SegmentsIntersect(a, b, c, d);
|
||||
TEST_EQUAL(res, SegmentsIntersect(a, b, d, c), (a, b, c, d));
|
||||
TEST_EQUAL(res, SegmentsIntersect(b, a, c, d), (a, b, c, d));
|
||||
TEST_EQUAL(res, SegmentsIntersect(b, a, d, c), (a, b, c, d));
|
||||
TEST_EQUAL(res, SegmentsIntersect(c, d, a, b), (a, b, c, d));
|
||||
TEST_EQUAL(res, SegmentsIntersect(c, d, b, a), (a, b, c, d));
|
||||
TEST_EQUAL(res, SegmentsIntersect(d, c, a, b), (a, b, c, d));
|
||||
TEST_EQUAL(res, SegmentsIntersect(d, c, b, a), (a, b, c, d));
|
||||
return res;
|
||||
}
|
||||
|
||||
UNIT_TEST(SegmentIntersection_Collinear)
|
||||
{
|
||||
TEST(!TestSegmentsIntersect({0.0, 0.0}, {1.0, 1.0}, {2.0, 3.0}, {3.0, 3.0}), ("Far away"));
|
||||
TEST(TestSegmentsIntersect({0.0, 0.0}, {1.0, 1.0}, {1.0, 1.0}, {3.0, 3.0}), ("Border intersection"));
|
||||
TEST(TestSegmentsIntersect({0.0, 0.0}, {2.0, 2.0}, {1.0, 1.0}, {3.0, 3.0}), ("Normal intersection"));
|
||||
TEST(TestSegmentsIntersect({0.0, 0.0}, {2.0, 2.0}, {0.0, 0.0}, {3.0, 3.0}), ("Border inclusion"));
|
||||
TEST(TestSegmentsIntersect({1.0, 1.0}, {2.0, 2.0}, {0.0, 0.0}, {3.0, 3.0}), ("Normal inclusion"));
|
||||
}
|
||||
|
||||
UNIT_TEST(SegmentIntersection_NoIntersection)
|
||||
{
|
||||
TEST(!TestSegmentsIntersect({0.0, 1.0}, {2.0, 5.0}, {7.0, 7.0}, {15.0, 7.0}), ("Far away"));
|
||||
TEST(!TestSegmentsIntersect({0.0, 0.0}, {2.0, 4.0}, {1.0, 1.0}, {4.0, 3.0}), ("Rect intersect, segments don't"));
|
||||
TEST(!TestSegmentsIntersect({0.0, 0.0}, {8.0, 8.0}, {1.0, 2.0}, {4.0, 8.0}), ("Only one cross product is 0"));
|
||||
}
|
||||
|
||||
UNIT_TEST(SegmentIntersection_Intersect)
|
||||
{
|
||||
TEST(TestSegmentsIntersect({0.0, 0.0}, {2.0, 4.0}, {0.0, 3.0}, {4.0, 0.0}), ("Normal"));
|
||||
TEST(TestSegmentsIntersect({1.0, 2.0}, {3.0, 4.0}, {1.0, 2.0}, {5.0, 1.0}), ("Fan"));
|
||||
TEST(TestSegmentsIntersect({1.0, 2.0}, {3.0, 4.0}, {1.0, 2.0}, {3.0, -2.0}), ("Fan"));
|
||||
TEST(TestSegmentsIntersect({0.0, 0.0}, {2.0, 2.0}, {0.0, 4.0}, {4.0, 0.0}), ("Border"));
|
||||
}
|
||||
|
||||
UNIT_TEST(SegmentIntersection_NoIntersectionPoint)
|
||||
{
|
||||
TEST_EQUAL(Intersect(Segment2D({0.0, 0.0}, {1.0, 0.0}), Segment2D({2.0, 0.0}, {4.0, 0.0}), kEps).m_type,
|
||||
IntersectionResult::Type::Zero, ());
|
||||
|
||||
TEST_EQUAL(Intersect(Segment2D({0.0, 0.0}, {1.0, 1.0}), Segment2D({2.0, 0.0}, {4.0, 0.0}), kEps).m_type,
|
||||
IntersectionResult::Type::Zero, ());
|
||||
|
||||
TEST_EQUAL(Intersect(Segment2D({0.0, 0.0}, {1.0, 1.0}), Segment2D({4.0, 4.0}, {2.0, 2.0}), kEps).m_type,
|
||||
IntersectionResult::Type::Zero, ());
|
||||
|
||||
TEST_EQUAL(Intersect(Segment2D({0.0, 0.0}, {4.0, 4.0}), Segment2D({10.0, 0.0}, {6.0, 4.0}), kEps).m_type,
|
||||
IntersectionResult::Type::Zero, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(SegmentIntersection_OneIntersectionPoint)
|
||||
{
|
||||
// Two intersected segments. The intersection point is in the middle of both of them.
|
||||
{
|
||||
auto const result = Intersect(Segment2D({-1.0, -1.0}, {1.0, 1.0}), Segment2D({-1.0, 0.0}, {1.0, 0.0}), kEps);
|
||||
TestIntersectionResult(result, IntersectionResult::Type::One, {0.0, 0.0});
|
||||
}
|
||||
|
||||
{
|
||||
auto const result = Intersect(Segment2D({0.0, 0.0}, {10.0, 10.0}), Segment2D({10.0, 0.0}, {0.0, 10.0}), kEps);
|
||||
TestIntersectionResult(result, IntersectionResult::Type::One, {5.0, 5.0});
|
||||
}
|
||||
|
||||
// Two intersected segments. The intersection point is in an end of both of them.
|
||||
{
|
||||
auto const result = Intersect(Segment2D({-1.0, -1.0}, {0.0, 0.0}), Segment2D({0.0, 0.0}, {11.0, 0.0}), kEps);
|
||||
TestIntersectionResult(result, IntersectionResult::Type::One, {0.0, 0.0});
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(SegmentIntersection_InfinityIntersectionPoints)
|
||||
{
|
||||
TEST_EQUAL(Intersect(Segment2D({0.0, 0.0}, {2.0, 0.0}), Segment2D({1.0, 0.0}, {4.0, 0.0}), kEps).m_type,
|
||||
IntersectionResult::Type::Infinity, ());
|
||||
|
||||
TEST_EQUAL(Intersect(Segment2D({0.0, 0.0}, {2.0, 4.0}), Segment2D({1.0, 2.0}, {2.0, 4.0}), kEps).m_type,
|
||||
IntersectionResult::Type::Infinity, ());
|
||||
}
|
||||
} // namespace segment2d_tests
|
||||
150
libs/geometry/geometry_tests/simplification_test.cpp
Normal file
150
libs/geometry/geometry_tests/simplification_test.cpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/geometry_tests/large_polygon.hpp"
|
||||
#include "geometry/parametrized_segment.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/simplification.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/macros.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace simplification_test
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
using P = m2::PointD;
|
||||
using DistanceFn = m2::SquaredDistanceFromSegmentToPoint;
|
||||
using PointOutput = base::BackInsertFunctor<vector<m2::PointD>>;
|
||||
using SimplifyFn = void (*)(m2::PointD const *, m2::PointD const *, double, DistanceFn, PointOutput);
|
||||
|
||||
struct LargePolylineTestData
|
||||
{
|
||||
static m2::PointD const * m_Data;
|
||||
static size_t m_Size;
|
||||
};
|
||||
|
||||
m2::PointD const * LargePolylineTestData::m_Data = LargePolygon::kLargePolygon;
|
||||
size_t LargePolylineTestData::m_Size = ARRAY_SIZE(LargePolygon::kLargePolygon);
|
||||
|
||||
void TestSimplificationSmoke(SimplifyFn simplifyFn)
|
||||
{
|
||||
m2::PointD const points[] = {P(0.0, 1.0), P(2.2, 3.6), P(3.2, 3.6)};
|
||||
double const epsilon = 0.1;
|
||||
vector<m2::PointD> result, expectedResult(points, points + 3);
|
||||
simplifyFn(points, points + 3, epsilon, DistanceFn(), base::MakeBackInsertFunctor(result));
|
||||
TEST_EQUAL(result, expectedResult, (epsilon));
|
||||
}
|
||||
|
||||
void TestSimplificationOfLine(SimplifyFn simplifyFn)
|
||||
{
|
||||
m2::PointD const points[] = {P(0.0, 1.0), P(2.2, 3.6)};
|
||||
for (double epsilon = numeric_limits<double>::denorm_min(); epsilon < 1000; epsilon *= 2)
|
||||
{
|
||||
vector<m2::PointD> result, expectedResult(points, points + 2);
|
||||
simplifyFn(points, points + 2, epsilon, DistanceFn(), base::MakeBackInsertFunctor(result));
|
||||
TEST_EQUAL(result, expectedResult, (epsilon));
|
||||
}
|
||||
}
|
||||
|
||||
void TestSimplificationOfPoly(m2::PointD const * points, size_t count, SimplifyFn simplifyFn)
|
||||
{
|
||||
for (double epsilon = 0.00001; epsilon < 0.11; epsilon *= 10)
|
||||
{
|
||||
vector<m2::PointD> result;
|
||||
simplifyFn(points, points + count, epsilon, DistanceFn(), base::MakeBackInsertFunctor(result));
|
||||
// LOG(LINFO, ("eps:", epsilon, "size:", result.size()));
|
||||
|
||||
TEST_GREATER(result.size(), 1, ());
|
||||
TEST_EQUAL(result.front(), points[0], (epsilon));
|
||||
TEST_EQUAL(result.back(), points[count - 1], (epsilon));
|
||||
TEST_LESS(result.size(), count, (epsilon));
|
||||
}
|
||||
}
|
||||
|
||||
void SimplifyNearOptimal10(m2::PointD const * f, m2::PointD const * l, double e, DistanceFn distFn, PointOutput out)
|
||||
{
|
||||
SimplifyNearOptimal(10, f, l, e, distFn, out);
|
||||
}
|
||||
|
||||
void SimplifyNearOptimal20(m2::PointD const * f, m2::PointD const * l, double e, DistanceFn distFn, PointOutput out)
|
||||
{
|
||||
SimplifyNearOptimal(20, f, l, e, distFn, out);
|
||||
}
|
||||
|
||||
void CheckDPStrict(P const * arr, size_t n, double eps, size_t expectedCount)
|
||||
{
|
||||
vector<P> vec;
|
||||
DistanceFn distFn;
|
||||
SimplifyDP(arr, arr + n, eps, distFn, AccumulateSkipSmallTrg<DistanceFn, P>(distFn, vec, eps));
|
||||
|
||||
TEST_GREATER(vec.size(), 1, ());
|
||||
TEST_EQUAL(arr[0], vec.front(), ());
|
||||
TEST_EQUAL(arr[n - 1], vec.back(), ());
|
||||
|
||||
if (expectedCount > 0)
|
||||
TEST_EQUAL(expectedCount, vec.size(), ());
|
||||
|
||||
for (size_t i = 2; i < vec.size(); ++i)
|
||||
{
|
||||
auto const d = DistanceFn();
|
||||
TEST_GREATER_OR_EQUAL(d(vec[i - 2], vec[i], vec[i - 1]), eps, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Simplification_TestDataIsCorrect)
|
||||
{
|
||||
TEST_GREATER_OR_EQUAL(LargePolylineTestData::m_Size, 3, ());
|
||||
// This test provides information about the data set size.
|
||||
TEST_EQUAL(LargePolylineTestData::m_Size, 5539, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Simplification_DP_Smoke)
|
||||
{
|
||||
TestSimplificationSmoke(&SimplifyDP<DistanceFn>);
|
||||
}
|
||||
|
||||
UNIT_TEST(Simplification_DP_Line)
|
||||
{
|
||||
TestSimplificationOfLine(&SimplifyDP<DistanceFn>);
|
||||
}
|
||||
|
||||
UNIT_TEST(Simplification_DP_Polyline)
|
||||
{
|
||||
TestSimplificationOfPoly(LargePolylineTestData::m_Data, LargePolylineTestData::m_Size, &SimplifyDP<DistanceFn>);
|
||||
}
|
||||
|
||||
UNIT_TEST(Simplification_Opt_Smoke)
|
||||
{
|
||||
TestSimplificationSmoke(&SimplifyNearOptimal10);
|
||||
}
|
||||
|
||||
UNIT_TEST(Simplification_Opt_Line)
|
||||
{
|
||||
TestSimplificationOfLine(&SimplifyNearOptimal10);
|
||||
}
|
||||
|
||||
UNIT_TEST(Simplification_Opt10_Polyline)
|
||||
{
|
||||
TestSimplificationOfPoly(LargePolylineTestData::m_Data, LargePolylineTestData::m_Size, &SimplifyNearOptimal10);
|
||||
}
|
||||
|
||||
UNIT_TEST(Simplification_Opt20_Polyline)
|
||||
{
|
||||
TestSimplificationOfPoly(LargePolylineTestData::m_Data, LargePolylineTestData::m_Size, &SimplifyNearOptimal20);
|
||||
}
|
||||
|
||||
UNIT_TEST(Simpfication_DP_DegenerateTrg)
|
||||
{
|
||||
P arr1[] = {P(0, 0), P(100, 100), P(100, 500), P(0, 600)};
|
||||
CheckDPStrict(arr1, ARRAY_SIZE(arr1), 1.0, 4);
|
||||
|
||||
P arr2[] = {P(0, 0), P(100, 100), P(100.1, 150), P(100.2, 200), P(100.3, 250), P(100.4, 300),
|
||||
P(100.3, 350), P(100.2, 400), P(100.1, 450), P(100, 500), P(0, 600)};
|
||||
CheckDPStrict(arr2, ARRAY_SIZE(arr2), 1.0, 4);
|
||||
}
|
||||
} // namespace simplification_test
|
||||
248
libs/geometry/geometry_tests/spline_test.cpp
Normal file
248
libs/geometry/geometry_tests/spline_test.cpp
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/geometry_tests/equality.hpp"
|
||||
#include "geometry/spline.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace spline_test
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
using m2::PointD;
|
||||
using m2::Spline;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
double constexpr kAlmostZero = 1.0E-16;
|
||||
|
||||
void TestEqual(double x, double y)
|
||||
{
|
||||
if (fabs(x) < kAlmostZero || fabs(y) < kAlmostZero)
|
||||
TEST_ALMOST_EQUAL_ABS(x, y, kAlmostZero, ());
|
||||
else
|
||||
TEST_ALMOST_EQUAL_ULPS(x, y, ());
|
||||
}
|
||||
|
||||
void TestPointDDir(PointD const & dst, PointD const & src)
|
||||
{
|
||||
double const len1 = dst.Length();
|
||||
double const len2 = src.Length();
|
||||
if (len1 < kAlmostZero || len2 < kAlmostZero)
|
||||
{
|
||||
TestEqual(dst.x, src.x);
|
||||
TestEqual(dst.y, src.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
TestEqual(dst.x / len1, src.x / len2);
|
||||
TestEqual(dst.y / len1, src.y / len2);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(Spline_SmoothedDirections)
|
||||
{
|
||||
vector<PointD> path;
|
||||
path.push_back(PointD(0, 0));
|
||||
path.push_back(PointD(40, 40));
|
||||
path.push_back(PointD(80, 0));
|
||||
|
||||
Spline spl(path);
|
||||
double const sqrt2 = sqrt(2.0);
|
||||
Spline::iterator itr;
|
||||
PointD dir1(sqrt2 / 2.0, sqrt2 / 2.0);
|
||||
PointD dir2(sqrt2 / 2.0, -sqrt2 / 2.0);
|
||||
itr.Attach(spl);
|
||||
TestPointDDir(itr.m_avrDir, dir1);
|
||||
itr.Advance(sqrt2 * 30.0);
|
||||
TestPointDDir(itr.m_avrDir, dir1);
|
||||
itr.Advance(sqrt2 * 40.0);
|
||||
TestPointDDir(itr.m_avrDir, dir1 * 0.25 + dir2 * 0.75);
|
||||
itr.Advance(sqrt2 * 10.0);
|
||||
TestPointDDir(itr.m_avrDir, dir2);
|
||||
|
||||
path.clear();
|
||||
|
||||
path.push_back(PointD(0, 0));
|
||||
path.push_back(PointD(40, 40));
|
||||
path.push_back(PointD(80, 40));
|
||||
path.push_back(PointD(120, 0));
|
||||
|
||||
PointD dir12(1.0, 0.0);
|
||||
Spline spl2(path);
|
||||
itr.Attach(spl2);
|
||||
TestPointDDir(itr.m_avrDir, dir1);
|
||||
itr.Advance(sqrt2 * 80.0 + 40.0);
|
||||
#if defined(DEBUG) || __apple_build_version__ < 15000000
|
||||
// TODO(AB): Fails on Mac's clang with any optimization enabled and -ffp-contract=fast
|
||||
TestPointDDir(itr.m_avrDir, dir12);
|
||||
#endif
|
||||
itr.Attach(spl2);
|
||||
itr.Advance(sqrt2 * 40.0);
|
||||
TestPointDDir(itr.m_avrDir, dir1);
|
||||
itr.Advance(80.0);
|
||||
TestPointDDir(itr.m_avrDir, dir12 * 0.5 + dir2 * 0.5);
|
||||
}
|
||||
|
||||
UNIT_TEST(UsualDirections)
|
||||
{
|
||||
vector<PointD> path;
|
||||
path.push_back(PointD(0, 0));
|
||||
path.push_back(PointD(40, 40));
|
||||
path.push_back(PointD(80, 0));
|
||||
|
||||
Spline spl(path);
|
||||
double const sqrt2 = sqrtf(2.0);
|
||||
Spline::iterator itr;
|
||||
PointD dir1(sqrt2 / 2.0, sqrt2 / 2.0);
|
||||
PointD dir2(sqrt2 / 2.0, -sqrt2 / 2.0);
|
||||
itr.Attach(spl);
|
||||
TestPointDDir(itr.m_dir, dir1);
|
||||
itr.Advance(sqrt2 * 30.0);
|
||||
TestPointDDir(itr.m_dir, dir1);
|
||||
itr.Advance(sqrt2 * 40.0);
|
||||
TestPointDDir(itr.m_dir, dir2);
|
||||
|
||||
path.clear();
|
||||
|
||||
path.push_back(PointD(0, 0));
|
||||
path.push_back(PointD(40, 40));
|
||||
path.push_back(PointD(80, 40));
|
||||
path.push_back(PointD(120, 0));
|
||||
|
||||
PointD dir12(1.0, 0.0);
|
||||
Spline spl2(path);
|
||||
itr.Attach(spl2);
|
||||
TestPointDDir(itr.m_dir, dir1);
|
||||
itr.Advance(sqrt2 * 80.0 + 35.0);
|
||||
TestPointDDir(itr.m_dir, dir2);
|
||||
itr.Attach(spl2);
|
||||
itr.Advance(sqrt2 * 45.0);
|
||||
TestPointDDir(itr.m_dir, dir12);
|
||||
itr.Advance(80.0);
|
||||
TestPointDDir(itr.m_dir, dir2);
|
||||
}
|
||||
|
||||
UNIT_TEST(Positions)
|
||||
{
|
||||
vector<PointD> path;
|
||||
path.push_back(PointD(0, 0));
|
||||
path.push_back(PointD(40, 40));
|
||||
path.push_back(PointD(80, 0));
|
||||
|
||||
Spline spl0(path);
|
||||
Spline spl4;
|
||||
spl4 = spl0;
|
||||
double const sqrt2 = sqrt(2.0);
|
||||
Spline::iterator itr;
|
||||
itr.Attach(spl0);
|
||||
TestPointDDir(itr.m_pos, PointD(0, 0));
|
||||
itr.Advance(sqrt2 * 40.0);
|
||||
TestPointDDir(itr.m_pos, PointD(40, 40));
|
||||
itr.Advance(sqrt2 * 40.0);
|
||||
#if defined(DEBUG) || __apple_build_version__ < 15000000
|
||||
// TODO(AB): Fails on Mac's clang with any optimization enabled and -ffp-contract=fast
|
||||
TestPointDDir(itr.m_pos, PointD(80, 0));
|
||||
#endif
|
||||
itr.Attach(spl4);
|
||||
TestPointDDir(itr.m_pos, PointD(0, 0));
|
||||
itr.Advance(sqrt2 * 40.0);
|
||||
TestPointDDir(itr.m_pos, PointD(40, 40));
|
||||
itr.Advance(sqrt2 * 40.0);
|
||||
#if defined(DEBUG) || __apple_build_version__ < 15000000
|
||||
// TODO(AB): Fails on Mac's clang with any optimization enabled and -ffp-contract=fast
|
||||
TestPointDDir(itr.m_pos, PointD(80, 0));
|
||||
#endif
|
||||
|
||||
path.clear();
|
||||
|
||||
path.push_back(PointD(0, 0));
|
||||
path.push_back(PointD(40, 40));
|
||||
path.push_back(PointD(80, 40));
|
||||
path.push_back(PointD(120, 0));
|
||||
|
||||
Spline spl2(path);
|
||||
Spline spl3 = spl2;
|
||||
itr.Attach(spl3);
|
||||
TestPointDDir(itr.m_pos, PointD(0, 0));
|
||||
itr.Advance(sqrt2 * 80.0 + 40.0);
|
||||
#if defined(DEBUG) || __apple_build_version__ < 15000000
|
||||
// TODO(AB): Fails on Mac's clang with any optimization enabled and -ffp-contract=fast
|
||||
TestPointDDir(itr.m_pos, PointD(120, 0));
|
||||
#endif
|
||||
itr.Attach(spl2);
|
||||
itr.Advance(sqrt2 * 40.0);
|
||||
TestPointDDir(itr.m_pos, PointD(40, 40));
|
||||
itr.Advance(2.0);
|
||||
TestPointDDir(itr.m_pos, PointD(42, 40));
|
||||
itr.Advance(20.0);
|
||||
TestPointDDir(itr.m_pos, PointD(62, 40));
|
||||
itr.Advance(18.0);
|
||||
TestPointDDir(itr.m_pos, PointD(80, 40));
|
||||
}
|
||||
|
||||
UNIT_TEST(BeginAgain)
|
||||
{
|
||||
vector<PointD> path;
|
||||
path.push_back(PointD(0, 0));
|
||||
path.push_back(PointD(40, 40));
|
||||
path.push_back(PointD(80, 0));
|
||||
|
||||
Spline spl(path);
|
||||
double const sqrt2 = sqrtf(2.0);
|
||||
Spline::iterator itr;
|
||||
PointD dir1(sqrt2 / 2.0, sqrt2 / 2.0);
|
||||
PointD dir2(sqrt2 / 2.0, -sqrt2 / 2.0);
|
||||
itr.Attach(spl);
|
||||
TEST_EQUAL(itr.BeginAgain(), false, ());
|
||||
itr.Advance(90.0);
|
||||
TEST_EQUAL(itr.BeginAgain(), false, ());
|
||||
itr.Advance(90.0);
|
||||
TEST_EQUAL(itr.BeginAgain(), true, ());
|
||||
itr.Advance(190.0);
|
||||
TEST_EQUAL(itr.BeginAgain(), true, ());
|
||||
|
||||
path.clear();
|
||||
|
||||
path.push_back(PointD(0, 0));
|
||||
path.push_back(PointD(40, 40));
|
||||
path.push_back(PointD(80, 40));
|
||||
path.push_back(PointD(120, 0));
|
||||
|
||||
Spline spl2(path);
|
||||
itr.Attach(spl2);
|
||||
TEST_EQUAL(itr.BeginAgain(), false, ());
|
||||
itr.Advance(90.0);
|
||||
TEST_EQUAL(itr.BeginAgain(), false, ());
|
||||
itr.Advance(90.0);
|
||||
TEST_EQUAL(itr.BeginAgain(), true, ());
|
||||
itr.Advance(190.0);
|
||||
TEST_EQUAL(itr.BeginAgain(), true, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Length)
|
||||
{
|
||||
vector<PointD> path;
|
||||
PointD const p1(27.5536633, 64.2492523);
|
||||
PointD const p2(27.5547638, 64.2474289);
|
||||
PointD const p3(27.5549412, 64.2471237);
|
||||
PointD const p4(27.5559044, 64.2456436);
|
||||
PointD const p5(27.556284, 64.2451782);
|
||||
path.push_back(p1);
|
||||
path.push_back(p2);
|
||||
path.push_back(p3);
|
||||
path.push_back(p4);
|
||||
path.push_back(p5);
|
||||
Spline spl(path);
|
||||
double len1 = spl.GetLength();
|
||||
double l1 = p1.Length(p2);
|
||||
double l2 = p2.Length(p3);
|
||||
double l3 = p3.Length(p4);
|
||||
double l4 = p4.Length(p5);
|
||||
double len2 = l1 + l2 + l3 + l4;
|
||||
TEST_ALMOST_EQUAL_ULPS(len1, len2, ());
|
||||
}
|
||||
} // namespace spline_test
|
||||
2060
libs/geometry/geometry_tests/test_regions.hpp
Normal file
2060
libs/geometry/geometry_tests/test_regions.hpp
Normal file
File diff suppressed because it is too large
Load diff
42
libs/geometry/geometry_tests/transformations_test.cpp
Normal file
42
libs/geometry/geometry_tests/transformations_test.cpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#include "geometry/transformations.hpp"
|
||||
#include "base/matrix.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
UNIT_TEST(Transformations_Shift)
|
||||
{
|
||||
math::Matrix<double, 3, 3> m = math::Shift(math::Identity<double, 3>(), 200.0, 100.0);
|
||||
|
||||
m2::PointD pt = m2::PointD(30, 20) * m;
|
||||
|
||||
TEST(pt.EqualDxDy(m2::PointD(230, 120), 1.0E-10), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Transformations_ShiftScale)
|
||||
{
|
||||
math::Matrix<double, 3, 3> m = math::Scale(math::Shift(math::Identity<double, 3>(), 100, 100), 2, 3);
|
||||
m2::PointD pt = m2::PointD(20, 10) * m;
|
||||
|
||||
TEST(pt.EqualDxDy(m2::PointD(240, 330), 1.0E-10), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Transformations_Rotate)
|
||||
{
|
||||
math::Matrix<double, 3, 3> m = math::Rotate(math::Identity<double, 3>(), math::pi / 2);
|
||||
TEST(m2::PointD(0, 100).EqualDxDy(m2::PointD(100, 0) * m, 1.0E-10), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Transformations_ShiftScaleRotate)
|
||||
{
|
||||
math::Matrix<double, 3, 3> m =
|
||||
math::Rotate(math::Scale(math::Shift(math::Identity<double, 3>(), 100, 100), 2, 3), -math::pi / 2);
|
||||
m2::PointD pt = m2::PointD(20, 10) * m;
|
||||
|
||||
TEST(pt.EqualDxDy(m2::PointD(330, -240), 1.0E-10), ());
|
||||
|
||||
math::Matrix<double, 3, 3> invM = math::Inverse(m);
|
||||
|
||||
m2::PointD invPt = m2::PointD(330, -240) * invM;
|
||||
|
||||
TEST(invPt.EqualDxDy(m2::PointD(20, 10), 1.0E-10), ());
|
||||
}
|
||||
183
libs/geometry/geometry_tests/tree_test.cpp
Normal file
183
libs/geometry/geometry_tests/tree_test.cpp
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/tree4d.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace tree_test
|
||||
{
|
||||
using namespace std;
|
||||
using R = m2::RectD;
|
||||
|
||||
struct traits_t
|
||||
{
|
||||
m2::RectD LimitRect(m2::RectD const & r) const { return r; }
|
||||
};
|
||||
|
||||
using Tree = m4::Tree<R, traits_t>;
|
||||
|
||||
template <typename T>
|
||||
bool RTrue(T const &, T const &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool RFalse(T const &, T const &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UNIT_TEST(Tree4D_Smoke)
|
||||
{
|
||||
Tree theTree;
|
||||
|
||||
R arr[] = {R(0, 0, 1, 1), R(1, 1, 2, 2), R(2, 2, 3, 3)};
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(arr); ++i)
|
||||
theTree.ReplaceAllInRect(arr[i], &RTrue<R>);
|
||||
|
||||
vector<R> test;
|
||||
theTree.ForEach(base::MakeBackInsertFunctor(test));
|
||||
TEST_EQUAL(3, test.size(), ());
|
||||
|
||||
test.clear();
|
||||
R const searchR(1.5, 1.5, 1.5, 1.5);
|
||||
theTree.ForEachInRect(searchR, base::MakeBackInsertFunctor(test));
|
||||
TEST_EQUAL(1, test.size(), ());
|
||||
TEST_EQUAL(test[0], arr[1], ());
|
||||
|
||||
R const replaceR(0.5, 0.5, 2.5, 2.5);
|
||||
theTree.ReplaceAllInRect(replaceR, &RTrue<R>);
|
||||
|
||||
test.clear();
|
||||
theTree.ForEach(base::MakeBackInsertFunctor(test));
|
||||
TEST_EQUAL(1, test.size(), ());
|
||||
TEST_EQUAL(test[0], replaceR, ());
|
||||
|
||||
test.clear();
|
||||
theTree.ForEachInRect(searchR, base::MakeBackInsertFunctor(test));
|
||||
TEST_EQUAL(1, test.size(), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Tree4D_ForAnyInRect)
|
||||
{
|
||||
Tree theTree;
|
||||
|
||||
R arr[] = {R(0, 0, 1, 1), R(0, 0, 5, 5), R(1, 1, 2, 2), R(1, 1, 6.5, 6.5), R(2, 2, 3, 3), R(2, 2, 7, 7)};
|
||||
for (auto const & r : arr)
|
||||
theTree.Add(r);
|
||||
|
||||
TEST(theTree.ForAnyInRect(R(0, 0, 100, 100), [](R const & rect) { return rect.maxX() > 4; }), ());
|
||||
TEST(theTree.ForAnyInRect(R(0, 0, 100, 100), [](R const & rect) { return rect.maxX() > 5; }), ());
|
||||
TEST(theTree.ForAnyInRect(R(0, 0, 100, 100), [](R const & rect) { return rect.maxX() > 0; }), ());
|
||||
TEST(!theTree.ForAnyInRect(R(0, 0, 100, 100), [](R const & rect) { return rect.maxX() > 10; }), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Tree4D_ReplaceAllInRect)
|
||||
{
|
||||
Tree theTree;
|
||||
|
||||
R arr[] = {R(8, 13, 554, 32), R(555, 13, 700, 32), R(8, 33, 554, 52), R(555, 33, 700, 52),
|
||||
R(8, 54, 554, 73), R(555, 54, 700, 73), R(8, 76, 554, 95), R(555, 76, 700, 95)};
|
||||
|
||||
R arr1[] = {R(3, 23, 257, 42), R(600, 23, 800, 42), R(3, 43, 257, 62), R(600, 43, 800, 62),
|
||||
R(3, 65, 257, 84), R(600, 65, 800, 84), R(3, 87, 257, 106), R(600, 87, 800, 106)};
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(arr); ++i)
|
||||
{
|
||||
size_t const count = theTree.GetSize();
|
||||
|
||||
theTree.ReplaceAllInRect(arr[i], &RFalse<R>);
|
||||
TEST_EQUAL(theTree.GetSize(), count + 1, ());
|
||||
|
||||
theTree.ReplaceAllInRect(arr1[i], &RFalse<R>);
|
||||
TEST_EQUAL(theTree.GetSize(), count + 1, ());
|
||||
}
|
||||
|
||||
vector<R> test;
|
||||
theTree.ForEach(base::MakeBackInsertFunctor(test));
|
||||
|
||||
TEST_EQUAL(ARRAY_SIZE(arr), test.size(), ());
|
||||
for (size_t i = 0; i < test.size(); ++i)
|
||||
TEST_EQUAL(test[i], arr[i], ());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void CheckInRect(R const * arr, size_t count, R const & searchR, size_t expected)
|
||||
{
|
||||
Tree theTree;
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
theTree.Add(arr[i], arr[i]);
|
||||
|
||||
vector<R> test;
|
||||
theTree.ForEachInRect(searchR, base::MakeBackInsertFunctor(test));
|
||||
|
||||
TEST_EQUAL(test.size(), expected, ());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(Tree4D_ForEachInRect)
|
||||
{
|
||||
R arr[] = {R(0, 0, 1, 1), R(5, 5, 10, 10), R(-1, -1, 0, 0), R(-10, -10, -5, -5)};
|
||||
CheckInRect(arr, ARRAY_SIZE(arr), R(1, 1, 5, 5), 0);
|
||||
CheckInRect(arr, ARRAY_SIZE(arr), R(-5, -5, -1, -1), 0);
|
||||
CheckInRect(arr, ARRAY_SIZE(arr), R(3, 3, 3, 3), 0);
|
||||
CheckInRect(arr, ARRAY_SIZE(arr), R(-3, -3, -3, -3), 0);
|
||||
|
||||
CheckInRect(arr, ARRAY_SIZE(arr), R(0.5, 0.5, 0.5, 0.5), 1);
|
||||
CheckInRect(arr, ARRAY_SIZE(arr), R(8, 8, 8, 8), 1);
|
||||
CheckInRect(arr, ARRAY_SIZE(arr), R(-0.5, -0.5, -0.5, -0.5), 1);
|
||||
CheckInRect(arr, ARRAY_SIZE(arr), R(-8, -8, -8, -8), 1);
|
||||
|
||||
CheckInRect(arr, ARRAY_SIZE(arr), R(0.5, 0.5, 5.5, 5.5), 2);
|
||||
CheckInRect(arr, ARRAY_SIZE(arr), R(-5.5, -5.5, -0.5, -0.5), 2);
|
||||
}
|
||||
|
||||
struct TestObj : public m2::RectD
|
||||
{
|
||||
int m_id;
|
||||
|
||||
TestObj(double minX, double minY, double maxX, double maxY, int id) : m2::RectD(minX, minY, maxX, maxY), m_id(id) {}
|
||||
|
||||
bool operator==(TestObj const & r) const { return m_id == r.m_id; }
|
||||
};
|
||||
|
||||
UNIT_TEST(Tree4D_ReplaceEqual)
|
||||
{
|
||||
typedef TestObj T;
|
||||
m4::Tree<T, traits_t> theTree;
|
||||
|
||||
T arr[] = {T(0, 0, 1, 1, 1), T(1, 1, 2, 2, 2), T(2, 2, 3, 3, 3)};
|
||||
|
||||
// 1.
|
||||
for (size_t i = 0; i < ARRAY_SIZE(arr); ++i)
|
||||
theTree.ReplaceEqualInRect(arr[i], equal_to<T>(), &RTrue<T>);
|
||||
|
||||
vector<T> test;
|
||||
theTree.ForEach(base::MakeBackInsertFunctor(test));
|
||||
TEST_EQUAL(3, test.size(), ());
|
||||
|
||||
// 2.
|
||||
theTree.ReplaceEqualInRect(T(0, 0, 3, 3, 2), equal_to<T>(), &RFalse<T>);
|
||||
|
||||
test.clear();
|
||||
theTree.ForEach(base::MakeBackInsertFunctor(test));
|
||||
TEST_EQUAL(3, test.size(), ());
|
||||
|
||||
auto i = find(test.begin(), test.end(), T(1, 1, 2, 2, 2));
|
||||
TEST_EQUAL(R(*i), R(1, 1, 2, 2), ());
|
||||
|
||||
// 3.
|
||||
theTree.ReplaceEqualInRect(T(0, 0, 3, 3, 2), equal_to<T>(), &RTrue<T>);
|
||||
|
||||
test.clear();
|
||||
theTree.ForEach(base::MakeBackInsertFunctor(test));
|
||||
TEST_EQUAL(3, test.size(), ());
|
||||
|
||||
i = find(test.begin(), test.end(), T(1, 1, 2, 2, 2));
|
||||
TEST_EQUAL(R(*i), R(0, 0, 3, 3), ());
|
||||
}
|
||||
} // namespace tree_test
|
||||
41
libs/geometry/geometry_tests/vector_test.cpp
Normal file
41
libs/geometry/geometry_tests/vector_test.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "geometry/avg_vector.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
template <class T, size_t N>
|
||||
bool EqualArrays(T (&a1)[N], T (&a2)[N])
|
||||
{
|
||||
for (size_t i = 0; i < N; ++i)
|
||||
if (!AlmostEqualULPs(a1[i], a2[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(AvgVector_Smoke)
|
||||
{
|
||||
math::AvgVector<double, 3> holder(3);
|
||||
|
||||
double ethalon1[] = {5, 5, 5};
|
||||
double ethalon2[] = {5.5, 5.5, 5.5};
|
||||
double ethalon3[] = {6, 6, 6};
|
||||
|
||||
double arr1[] = {5, 5, 5};
|
||||
double arr2[] = {6, 6, 6};
|
||||
double arr3[] = {5, 5, 5};
|
||||
double arr4[] = {6, 6, 6};
|
||||
|
||||
holder.Next(arr1);
|
||||
TEST(EqualArrays(arr1, ethalon1), ());
|
||||
|
||||
holder.Next(arr2);
|
||||
TEST(EqualArrays(arr2, ethalon2), ());
|
||||
|
||||
holder.Next(arr3);
|
||||
TEST(EqualArrays(arr3, ethalon1), ());
|
||||
|
||||
holder.Next(arr4);
|
||||
TEST(EqualArrays(arr4, ethalon3), ());
|
||||
}
|
||||
148
libs/geometry/intersection_score.hpp
Normal file
148
libs/geometry/intersection_score.hpp
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/any_rect2d.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/exception.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>
|
||||
#include <boost/geometry/geometries/point_xy.hpp>
|
||||
#include <boost/geometry/geometries/polygon.hpp>
|
||||
#include "std/boost_geometry.hpp"
|
||||
|
||||
namespace geometry
|
||||
{
|
||||
double constexpr kPenaltyScore = -1.0;
|
||||
DECLARE_EXCEPTION(NotAPolygonException, RootException);
|
||||
|
||||
namespace impl
|
||||
{
|
||||
using PointXY = boost::geometry::model::d2::point_xy<double>;
|
||||
using Polygon = boost::geometry::model::polygon<PointXY>;
|
||||
using MultiPolygon = boost::geometry::model::multi_polygon<Polygon>;
|
||||
} // namespace impl
|
||||
|
||||
template <typename Container>
|
||||
impl::Polygon PointsToPolygon(Container const & points);
|
||||
template <typename Container>
|
||||
impl::MultiPolygon TrianglesToPolygon(Container const & points);
|
||||
|
||||
// The return value is a real number from [-1.0, 1.0].
|
||||
// * Returns positive value when the geometries intersect (returns intersection area divided by union area).
|
||||
// In particular, returns 1.0 when the geometries are equal.
|
||||
// * Returns zero when the geometries do not intersect.
|
||||
// * Returns kPenaltyScore as penalty. It is possible when any of the geometries is empty or invalid.
|
||||
/// |lhs| and |rhs| are any areal boost::geometry types.
|
||||
template <typename LGeometry, typename RGeometry>
|
||||
double GetIntersectionScore(LGeometry const & lhs, RGeometry const & rhs)
|
||||
{
|
||||
if (!boost::geometry::is_valid(lhs) || !boost::geometry::is_valid(rhs) || boost::geometry::is_empty(lhs) ||
|
||||
boost::geometry::is_empty(rhs))
|
||||
{
|
||||
return kPenaltyScore;
|
||||
}
|
||||
|
||||
auto const lhsArea = boost::geometry::area(lhs);
|
||||
auto const rhsArea = boost::geometry::area(rhs);
|
||||
impl::MultiPolygon result;
|
||||
boost::geometry::intersection(lhs, rhs, result);
|
||||
auto const intersectionArea = boost::geometry::area(result);
|
||||
auto const unionArea = lhsArea + rhsArea - intersectionArea;
|
||||
|
||||
auto const score = intersectionArea / unionArea;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/// Throws NotAPolygonException exception.
|
||||
/// For detailed info see comment for
|
||||
/// double GetIntersectionScore(LPolygon const & lhs, RPolygon const & rhs).
|
||||
/// |lhs| and |rhs| are any standard container of m2::Point with random access iterator.
|
||||
/// |toPolygonConverter| is a method which converts |lhs| and |rhs| to boost::geometry areal type.
|
||||
template <typename Container, typename Converter>
|
||||
double GetIntersectionScore(Container const & lhs, Container const & rhs, Converter const & toPolygonConverter)
|
||||
{
|
||||
auto const lhsPolygon = toPolygonConverter(lhs);
|
||||
if (boost::geometry::is_empty(lhsPolygon))
|
||||
return kPenaltyScore;
|
||||
|
||||
auto const rhsPolygon = toPolygonConverter(rhs);
|
||||
if (boost::geometry::is_empty(rhsPolygon))
|
||||
return kPenaltyScore;
|
||||
|
||||
return GetIntersectionScore(lhsPolygon, rhsPolygon);
|
||||
}
|
||||
|
||||
/// Throws NotAPolygonException exception.
|
||||
/// For detailed info see comment for
|
||||
/// double GetIntersectionScore(LPolygon const & lhs, RPolygon const & rhs).
|
||||
/// |lhs| and |rhs| are any standard containers of m2::Point with random access iterator.
|
||||
template <typename Container>
|
||||
double GetIntersectionScoreForPoints(Container const & lhs, Container const & rhs)
|
||||
{
|
||||
return GetIntersectionScore<Container>(lhs, rhs, PointsToPolygon<Container>);
|
||||
}
|
||||
|
||||
/// Throws NotAPolygonException exception.
|
||||
/// For detailed info see comment for
|
||||
/// double GetIntersectionScore(LPolygon const & lhs, RPolygon const & rhs).
|
||||
/// |lhs| and |rhs| are any standard containers of m2::Point with random access iterator.
|
||||
template <typename Container>
|
||||
double GetIntersectionScoreForTriangulated(Container const & lhs, Container const & rhs)
|
||||
{
|
||||
return GetIntersectionScore<Container>(lhs, rhs, TrianglesToPolygon<Container>);
|
||||
}
|
||||
|
||||
/// |points| is any standard container of m2::Point with random access iterator.
|
||||
template <typename Container>
|
||||
impl::Polygon PointsToPolygon(Container const & points)
|
||||
{
|
||||
impl::Polygon polygon;
|
||||
for (auto const & point : points)
|
||||
polygon.outer().push_back(impl::PointXY(point.x, point.y));
|
||||
boost::geometry::correct(polygon);
|
||||
if (!boost::geometry::is_valid(polygon))
|
||||
MYTHROW(geometry::NotAPolygonException, ("The points is not valid polygon"));
|
||||
|
||||
return polygon;
|
||||
}
|
||||
|
||||
/// |points| is any standard container of m2::Point with random access iterator.
|
||||
template <typename Container>
|
||||
impl::MultiPolygon TrianglesToPolygon(Container const & points)
|
||||
{
|
||||
size_t constexpr kTriangleSize = 3;
|
||||
if (points.size() % kTriangleSize != 0)
|
||||
MYTHROW(geometry::NotAPolygonException, ("Count of points must be multiple of", kTriangleSize));
|
||||
|
||||
std::vector<impl::MultiPolygon> polygons;
|
||||
for (size_t i = 0; i < points.size(); i += kTriangleSize)
|
||||
{
|
||||
impl::MultiPolygon polygon;
|
||||
polygon.resize(1);
|
||||
auto & p = polygon[0];
|
||||
auto & outer = p.outer();
|
||||
for (size_t j = i; j < i + kTriangleSize; ++j)
|
||||
outer.push_back(impl::PointXY(points[j].x, points[j].y));
|
||||
boost::geometry::correct(p);
|
||||
if (!boost::geometry::is_valid(polygon))
|
||||
MYTHROW(geometry::NotAPolygonException, ("The triangle is not valid"));
|
||||
polygons.push_back(polygon);
|
||||
}
|
||||
|
||||
if (polygons.empty())
|
||||
return {};
|
||||
|
||||
auto & result = polygons[0];
|
||||
for (size_t i = 1; i < polygons.size(); ++i)
|
||||
{
|
||||
impl::MultiPolygon u;
|
||||
boost::geometry::union_(result, polygons[i], u);
|
||||
u.swap(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace geometry
|
||||
37
libs/geometry/latlon.cpp
Normal file
37
libs/geometry/latlon.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#include "latlon.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
|
||||
namespace ms
|
||||
{
|
||||
bool LatLon::operator==(ms::LatLon const & rhs) const
|
||||
{
|
||||
return m_lat == rhs.m_lat && m_lon == rhs.m_lon;
|
||||
}
|
||||
|
||||
bool LatLon::operator<(ms::LatLon const & rhs) const
|
||||
{
|
||||
return std::tie(m_lat, m_lon) < std::tie(rhs.m_lat, rhs.m_lon);
|
||||
}
|
||||
|
||||
bool LatLon::EqualDxDy(LatLon const & p, double eps) const
|
||||
{
|
||||
return (::AlmostEqualAbs(m_lat, p.m_lat, eps) && ::AlmostEqualAbs(m_lon, p.m_lon, eps));
|
||||
}
|
||||
|
||||
std::string DebugPrint(LatLon const & t)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out.precision(9); // <3>.<6> digits is enough here
|
||||
out << "ms::LatLon(" << t.m_lat << ", " << t.m_lon << ")";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
bool AlmostEqualAbs(LatLon const & ll1, LatLon const & ll2, double eps)
|
||||
{
|
||||
return ::AlmostEqualAbs(ll1.m_lat, ll2.m_lat, eps) && ::AlmostEqualAbs(ll1.m_lon, ll2.m_lon, eps);
|
||||
}
|
||||
} // namespace ms
|
||||
44
libs/geometry/latlon.hpp
Normal file
44
libs/geometry/latlon.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ms
|
||||
{
|
||||
/// \brief Class for representing WGS point.
|
||||
class LatLon
|
||||
{
|
||||
public:
|
||||
static double constexpr kMinLat = -90.0;
|
||||
static double constexpr kMaxLat = 90.0;
|
||||
static double constexpr kMinLon = -180.0;
|
||||
static double constexpr kMaxLon = 180.0;
|
||||
static double constexpr kInvalid = -1000.0;
|
||||
|
||||
// Default values are invalid.
|
||||
double m_lat = kInvalid;
|
||||
double m_lon = kInvalid;
|
||||
|
||||
constexpr LatLon() = default;
|
||||
constexpr LatLon(double lat, double lon) : m_lat(lat), m_lon(lon) {}
|
||||
|
||||
static constexpr LatLon Invalid() { return LatLon(kInvalid, kInvalid); }
|
||||
static constexpr LatLon Zero() { return LatLon(0.0, 0.0); }
|
||||
|
||||
bool constexpr IsValid() const { return m_lat != kInvalid && m_lon != kInvalid; }
|
||||
bool operator==(LatLon const & rhs) const;
|
||||
bool operator<(LatLon const & rhs) const;
|
||||
|
||||
bool EqualDxDy(LatLon const & p, double eps) const;
|
||||
|
||||
struct Hash
|
||||
{
|
||||
size_t operator()(ms::LatLon const & p) const { return math::Hash(p.m_lat, p.m_lon); }
|
||||
};
|
||||
};
|
||||
|
||||
bool AlmostEqualAbs(LatLon const & ll1, LatLon const & ll2, double eps);
|
||||
|
||||
std::string DebugPrint(LatLon const & t);
|
||||
} // namespace ms
|
||||
49
libs/geometry/line2d.cpp
Normal file
49
libs/geometry/line2d.cpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#include "geometry/line2d.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool Collinear(PointD const & a, PointD const & b, double eps)
|
||||
{
|
||||
return std::fabs(CrossProduct(a, b)) < eps;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string DebugPrint(Line2D const & line)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "Line2D [ ";
|
||||
os << "point: " << DebugPrint(line.m_point) << ", ";
|
||||
os << "direction: " << DebugPrint(line.m_direction);
|
||||
os << " ]";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
IntersectionResult Intersect(Line2D const & lhs, Line2D const & rhs, double eps)
|
||||
{
|
||||
auto const & a = lhs.m_point;
|
||||
auto const & ab = lhs.m_direction;
|
||||
|
||||
auto const & c = rhs.m_point;
|
||||
auto const & cd = rhs.m_direction;
|
||||
|
||||
if (Collinear(ab, cd, eps))
|
||||
{
|
||||
if (Collinear(c - a, cd, eps))
|
||||
return IntersectionResult(IntersectionResult::Type::Infinity);
|
||||
return IntersectionResult(IntersectionResult::Type::Zero);
|
||||
}
|
||||
|
||||
auto const ac = c - a;
|
||||
|
||||
auto const n = CrossProduct(ac, cd);
|
||||
auto const d = CrossProduct(ab, cd);
|
||||
auto const scale = n / d;
|
||||
|
||||
return IntersectionResult(a + ab * scale);
|
||||
}
|
||||
} // namespace m2
|
||||
28
libs/geometry/line2d.hpp
Normal file
28
libs/geometry/line2d.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/segment2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
|
||||
struct Line2D
|
||||
{
|
||||
Line2D() = default;
|
||||
|
||||
explicit Line2D(Segment2D const & segment) : m_point(segment.m_u), m_direction(segment.Dir()) {}
|
||||
|
||||
Line2D(PointD const & point, PointD const & direction) : m_point(point), m_direction(direction) {}
|
||||
|
||||
PointD m_point;
|
||||
PointD m_direction;
|
||||
};
|
||||
|
||||
IntersectionResult Intersect(Line2D const & lhs, Line2D const & rhs, double eps);
|
||||
|
||||
std::string DebugPrint(Line2D const & line);
|
||||
} // namespace m2
|
||||
121
libs/geometry/mercator.cpp
Normal file
121
libs/geometry/mercator.cpp
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include "geometry/area_on_earth.hpp"
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace mercator
|
||||
{
|
||||
m2::RectD MetersToXY(double lon, double lat, double lonMetersR, double latMetersR)
|
||||
{
|
||||
using std::cos, std::fabs, std::max, std::min;
|
||||
|
||||
double const latDegreeOffset = latMetersR * Bounds::kDegreesInMeter;
|
||||
double const minLat = max(-90.0, lat - latDegreeOffset);
|
||||
double const maxLat = min(90.0, lat + latDegreeOffset);
|
||||
|
||||
double const cosL = max(cos(math::DegToRad(max(fabs(minLat), fabs(maxLat)))), 0.00001);
|
||||
ASSERT_GREATER(cosL, 0.0, ());
|
||||
|
||||
double const lonDegreeOffset = lonMetersR * Bounds::kDegreesInMeter / cosL;
|
||||
double const minLon = max(-180.0, lon - lonDegreeOffset);
|
||||
double const maxLon = min(180.0, lon + lonDegreeOffset);
|
||||
|
||||
return {FromLatLon(minLat, minLon), FromLatLon(maxLat, maxLon)};
|
||||
}
|
||||
|
||||
m2::PointD GetSmPoint(m2::PointD const & pt, double lonMetersR, double latMetersR)
|
||||
{
|
||||
using std::max, std::min;
|
||||
|
||||
double const lat = YToLat(pt.y);
|
||||
double const lon = XToLon(pt.x);
|
||||
|
||||
double const latDegreeOffset = latMetersR * Bounds::kDegreesInMeter;
|
||||
double const newLat = min(90.0, max(-90.0, lat + latDegreeOffset));
|
||||
|
||||
double const cosL = max(cos(math::DegToRad(newLat)), 0.00001);
|
||||
ASSERT_GREATER(cosL, 0.0, ());
|
||||
|
||||
double const lonDegreeOffset = lonMetersR * Bounds::kDegreesInMeter / cosL;
|
||||
double const newLon = min(180.0, max(-180.0, lon + lonDegreeOffset));
|
||||
|
||||
return FromLatLon(newLat, newLon);
|
||||
}
|
||||
|
||||
double DistanceOnEarth(m2::PointD const & p1, m2::PointD const & p2)
|
||||
{
|
||||
return ms::DistanceOnEarth(ToLatLon(p1), ToLatLon(p2));
|
||||
}
|
||||
|
||||
double AreaOnEarth(m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3)
|
||||
{
|
||||
return ms::AreaOnEarth(ToLatLon(p1), ToLatLon(p2), ToLatLon(p3));
|
||||
}
|
||||
|
||||
double AreaOnEarth(m2::RectD const & rect)
|
||||
{
|
||||
return AreaOnEarth(rect.LeftTop(), rect.LeftBottom(), rect.RightBottom()) +
|
||||
AreaOnEarth(rect.LeftTop(), rect.RightTop(), rect.RightBottom());
|
||||
}
|
||||
|
||||
void ClampPoint(m2::PointD & pt)
|
||||
{
|
||||
pt.x = ClampX(pt.x);
|
||||
pt.y = ClampY(pt.y);
|
||||
}
|
||||
|
||||
double YToLat(double y)
|
||||
{
|
||||
return math::RadToDeg(2.0 * atan(tanh(0.5 * math::DegToRad(y))));
|
||||
}
|
||||
|
||||
double LatToY(double lat)
|
||||
{
|
||||
double const sinx = sin(math::DegToRad(math::Clamp(lat, -86.0, 86.0)));
|
||||
double const res = math::RadToDeg(0.5 * log((1.0 + sinx) / (1.0 - sinx)));
|
||||
return ClampY(res);
|
||||
}
|
||||
|
||||
m2::RectD MetersToXY(double lon, double lat, double metersR)
|
||||
{
|
||||
return MetersToXY(lon, lat, metersR, metersR);
|
||||
}
|
||||
|
||||
m2::RectD RectByCenterXYAndSizeInMeters(double centerX, double centerY, double sizeX, double sizeY)
|
||||
{
|
||||
ASSERT_GREATER_OR_EQUAL(sizeX, 0, ());
|
||||
ASSERT_GREATER_OR_EQUAL(sizeY, 0, ());
|
||||
|
||||
return MetersToXY(XToLon(centerX), YToLat(centerY), sizeX, sizeY);
|
||||
}
|
||||
|
||||
m2::RectD RectByCenterXYAndSizeInMeters(m2::PointD const & center, double size)
|
||||
{
|
||||
return RectByCenterXYAndSizeInMeters(center.x, center.y, size, size);
|
||||
}
|
||||
|
||||
m2::RectD RectByCenterXYAndOffset(m2::PointD const & center, double offset)
|
||||
{
|
||||
return {ClampX(center.x - offset), ClampY(center.y - offset), ClampX(center.x + offset), ClampY(center.y + offset)};
|
||||
}
|
||||
|
||||
m2::RectD RectByCenterLatLonAndSizeInMeters(double lat, double lon, double size)
|
||||
{
|
||||
return RectByCenterXYAndSizeInMeters(FromLatLon(lat, lon), size);
|
||||
}
|
||||
|
||||
m2::RectD FromLatLon(m2::RectD const & rect)
|
||||
{
|
||||
return {FromLatLon(rect.minY(), rect.minX()), FromLatLon(rect.maxY(), rect.maxX())};
|
||||
}
|
||||
|
||||
m2::RectD ToLatLon(m2::RectD const & rect)
|
||||
{
|
||||
return {YToLat(rect.minY()), XToLon(rect.minX()), YToLat(rect.maxY()), XToLon(rect.maxX())};
|
||||
}
|
||||
} // namespace mercator
|
||||
119
libs/geometry/mercator.hpp
Normal file
119
libs/geometry/mercator.hpp
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
namespace mercator
|
||||
{
|
||||
// Use to compare/match lat lon coordinates.
|
||||
static double constexpr kPointEqualityEps = 1e-7;
|
||||
struct Bounds
|
||||
{
|
||||
static double constexpr kMinX = -180.0;
|
||||
static double constexpr kMaxX = 180.0;
|
||||
static double constexpr kMinY = -180.0;
|
||||
static double constexpr kMaxY = 180.0;
|
||||
static double constexpr kRangeX = kMaxX - kMinX;
|
||||
static double constexpr kRangeY = kMaxY - kMinY;
|
||||
|
||||
// The denominator is the Earth circumference at the Equator in meters.
|
||||
// The value is a bit off for some reason; 40075160 seems to be correct.
|
||||
static double constexpr kDegreesInMeter = 360.0 / 40008245.0;
|
||||
static double constexpr kMetersInDegree = 40008245.0 / 360.0;
|
||||
|
||||
static m2::RectD FullRect() { return m2::RectD(kMinX, kMinY, kMaxX, kMaxY); }
|
||||
};
|
||||
|
||||
inline bool ValidLon(double d)
|
||||
{
|
||||
return math::Between(-180.0, 180.0, d);
|
||||
}
|
||||
inline bool ValidLat(double d)
|
||||
{
|
||||
return math::Between(-90.0, 90.0, d);
|
||||
}
|
||||
|
||||
inline bool ValidX(double d)
|
||||
{
|
||||
return math::Between(Bounds::kMinX, Bounds::kMaxX, d);
|
||||
}
|
||||
inline bool ValidY(double d)
|
||||
{
|
||||
return math::Between(Bounds::kMinY, Bounds::kMaxY, d);
|
||||
}
|
||||
|
||||
inline double ClampX(double d)
|
||||
{
|
||||
return math::Clamp(d, Bounds::kMinX, Bounds::kMaxX);
|
||||
}
|
||||
inline double ClampY(double d)
|
||||
{
|
||||
return math::Clamp(d, Bounds::kMinY, Bounds::kMaxY);
|
||||
}
|
||||
|
||||
void ClampPoint(m2::PointD & pt);
|
||||
|
||||
double YToLat(double y);
|
||||
double LatToY(double lat);
|
||||
|
||||
inline double XToLon(double x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
inline double LonToX(double lon)
|
||||
{
|
||||
return lon;
|
||||
}
|
||||
|
||||
inline double MetersToMercator(double meters)
|
||||
{
|
||||
return meters * Bounds::kDegreesInMeter;
|
||||
}
|
||||
inline double MercatorToMeters(double mercator)
|
||||
{
|
||||
return mercator * Bounds::kMetersInDegree;
|
||||
}
|
||||
|
||||
/// @name Get rect for center point (lon, lat) and dimensions in meters.
|
||||
/// @return mercator rect.
|
||||
m2::RectD MetersToXY(double lon, double lat, double lonMetersR, double latMetersR);
|
||||
m2::RectD MetersToXY(double lon, double lat, double metersR);
|
||||
|
||||
m2::RectD RectByCenterXYAndSizeInMeters(double centerX, double centerY, double sizeX, double sizeY);
|
||||
|
||||
m2::RectD RectByCenterXYAndSizeInMeters(m2::PointD const & center, double size);
|
||||
|
||||
m2::RectD RectByCenterXYAndOffset(m2::PointD const & center, double offset);
|
||||
|
||||
m2::PointD GetSmPoint(m2::PointD const & pt, double lonMetersR, double latMetersR);
|
||||
|
||||
inline m2::PointD FromLatLon(double lat, double lon)
|
||||
{
|
||||
return m2::PointD(LonToX(lon), LatToY(lat));
|
||||
}
|
||||
inline m2::PointD FromLatLon(ms::LatLon const & point)
|
||||
{
|
||||
return FromLatLon(point.m_lat, point.m_lon);
|
||||
}
|
||||
|
||||
m2::RectD RectByCenterLatLonAndSizeInMeters(double lat, double lon, double size);
|
||||
|
||||
inline ms::LatLon ToLatLon(m2::PointD const & point)
|
||||
{
|
||||
return {YToLat(point.y), XToLon(point.x)};
|
||||
}
|
||||
|
||||
m2::RectD FromLatLon(m2::RectD const & rect);
|
||||
m2::RectD ToLatLon(m2::RectD const & rect);
|
||||
|
||||
/// Calculates distance on Earth in meters between two mercator points.
|
||||
double DistanceOnEarth(m2::PointD const & p1, m2::PointD const & p2);
|
||||
|
||||
/// Calculates area of a triangle on Earth in m² by three mercator points.
|
||||
double AreaOnEarth(m2::PointD const & p1, m2::PointD const & p2, m2::PointD const & p3);
|
||||
/// Calculates area on Earth in m².
|
||||
double AreaOnEarth(m2::RectD const & mercatorRect);
|
||||
} // namespace mercator
|
||||
19
libs/geometry/meter.hpp
Normal file
19
libs/geometry/meter.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
namespace m2
|
||||
{
|
||||
using Meter = double;
|
||||
|
||||
inline namespace literals
|
||||
{
|
||||
inline constexpr Meter operator"" _m(long double value) noexcept
|
||||
{
|
||||
return static_cast<double>(value);
|
||||
}
|
||||
|
||||
inline constexpr Meter operator"" _km(long double value) noexcept
|
||||
{
|
||||
return static_cast<double>(value * 1000.0);
|
||||
}
|
||||
} // namespace literals
|
||||
} // namespace m2
|
||||
50
libs/geometry/nearby_points_sweeper.cpp
Normal file
50
libs/geometry/nearby_points_sweeper.cpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#include "geometry/nearby_points_sweeper.hpp"
|
||||
|
||||
namespace m2
|
||||
{
|
||||
// NearbyPointsSweeper::Event ----------------------------------------------------------------------
|
||||
NearbyPointsSweeper::Event::Event(Type type, double y, double x, size_t index, uint8_t priority)
|
||||
: m_type(type)
|
||||
, m_y(y)
|
||||
, m_x(x)
|
||||
, m_index(index)
|
||||
, m_priority(priority)
|
||||
{}
|
||||
|
||||
bool NearbyPointsSweeper::Event::operator<(Event const & rhs) const
|
||||
{
|
||||
if (m_y != rhs.m_y)
|
||||
return m_y < rhs.m_y;
|
||||
|
||||
if (m_type != rhs.m_type)
|
||||
return m_type < rhs.m_type;
|
||||
|
||||
if (m_x != rhs.m_x)
|
||||
return m_x < rhs.m_x;
|
||||
|
||||
if (m_index != rhs.m_index)
|
||||
return m_index < rhs.m_index;
|
||||
|
||||
return m_priority < rhs.m_priority;
|
||||
}
|
||||
|
||||
// NearbyPointsSweeper -----------------------------------------------------------------------------
|
||||
NearbyPointsSweeper::NearbyPointsSweeper(double eps) : m_xEps(eps), m_yEps(eps)
|
||||
{
|
||||
CHECK_GREATER_OR_EQUAL(m_xEps, 0.0, ());
|
||||
CHECK_GREATER_OR_EQUAL(m_yEps, 0.0, ());
|
||||
}
|
||||
|
||||
NearbyPointsSweeper::NearbyPointsSweeper(double xEps, double yEps) : m_xEps(xEps), m_yEps(yEps)
|
||||
{
|
||||
CHECK_GREATER_OR_EQUAL(m_xEps, 0.0, ());
|
||||
CHECK_GREATER_OR_EQUAL(m_yEps, 0.0, ());
|
||||
}
|
||||
|
||||
void NearbyPointsSweeper::Add(double x, double y, size_t index, uint8_t priority)
|
||||
{
|
||||
auto const yEpsHalf = 0.5 * m_yEps;
|
||||
m_events.emplace_back(Event::TYPE_SEGMENT_START, y - yEpsHalf, x, index, priority);
|
||||
m_events.emplace_back(Event::TYPE_SEGMENT_END, y + yEpsHalf, x, index, priority);
|
||||
}
|
||||
} // namespace m2
|
||||
159
libs/geometry/nearby_points_sweeper.hpp
Normal file
159
libs/geometry/nearby_points_sweeper.hpp
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
// This class can be used to greedily sweep points on a plane that are
|
||||
// too close to each other. Two points are considered to be "too
|
||||
// close" when distance along axes between them is less than or equal
|
||||
// to some preselected epsilon. Different epsilons can be used for different axes.
|
||||
// Note, the result is not the largest subset of points that can be selected,
|
||||
// but it can be computed quite fast and gives satisfactory results.
|
||||
//
|
||||
// *NOTE* The class is NOT thread-safe.
|
||||
class NearbyPointsSweeper
|
||||
{
|
||||
public:
|
||||
explicit NearbyPointsSweeper(double eps);
|
||||
|
||||
NearbyPointsSweeper(double xEps, double yEps);
|
||||
|
||||
// Adds a new point (|x|, |y|) on the plane. |index| is used to
|
||||
// identify individual points, and will be reported for survived
|
||||
// points during the Sweep phase.
|
||||
// Points with higher |priority| will be preferred during the Sweep phase.
|
||||
void Add(double x, double y, size_t index, uint8_t priority);
|
||||
|
||||
// Emits indexes of all survived points. Complexity: O(n * log(n)),
|
||||
// where n is the number of already added points.
|
||||
template <typename TEmitter>
|
||||
void Sweep(TEmitter && emitter)
|
||||
{
|
||||
std::sort(m_events.begin(), m_events.end());
|
||||
|
||||
std::set<LineEvent> line;
|
||||
|
||||
for (auto const & event : m_events)
|
||||
{
|
||||
switch (event.m_type)
|
||||
{
|
||||
case Event::TYPE_SEGMENT_START:
|
||||
{
|
||||
if (line.empty())
|
||||
{
|
||||
line.insert({event.m_x, event.m_index, event.m_priority});
|
||||
break;
|
||||
}
|
||||
|
||||
auto it =
|
||||
line.upper_bound({event.m_x, std::numeric_limits<size_t>::max(), std::numeric_limits<uint8_t>::max()});
|
||||
|
||||
bool add = true;
|
||||
while (true)
|
||||
{
|
||||
if (line.empty())
|
||||
break;
|
||||
|
||||
if (it == line.end())
|
||||
{
|
||||
--it;
|
||||
continue;
|
||||
}
|
||||
|
||||
double const x = it->m_x;
|
||||
if (fabs(x - event.m_x) <= m_xEps)
|
||||
{
|
||||
if (it->m_priority >= event.m_priority)
|
||||
{
|
||||
add = false;
|
||||
break;
|
||||
}
|
||||
// New event has higher priority than an existing event nearby.
|
||||
it = line.erase(it);
|
||||
}
|
||||
|
||||
if (x + m_xEps < event.m_x)
|
||||
break;
|
||||
|
||||
if (it == line.begin())
|
||||
break;
|
||||
|
||||
--it;
|
||||
}
|
||||
|
||||
if (add)
|
||||
line.insert({event.m_x, event.m_index, event.m_priority});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Event::TYPE_SEGMENT_END:
|
||||
{
|
||||
auto it = line.find({event.m_x, event.m_index, event.m_priority});
|
||||
if (it != line.end())
|
||||
{
|
||||
emitter(event.m_index);
|
||||
line.erase(it);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ASSERT(line.empty(), ("Sweep line must be empty after events processing:", line));
|
||||
}
|
||||
|
||||
private:
|
||||
struct Event
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
TYPE_SEGMENT_START,
|
||||
TYPE_SEGMENT_END
|
||||
};
|
||||
|
||||
Event(Type type, double y, double x, size_t index, uint8_t priority);
|
||||
|
||||
bool operator<(Event const & rhs) const;
|
||||
|
||||
Type m_type;
|
||||
|
||||
double m_y;
|
||||
double m_x;
|
||||
size_t m_index;
|
||||
uint8_t m_priority;
|
||||
};
|
||||
|
||||
struct LineEvent
|
||||
{
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(LineEvent, visitor(m_x, "x"), visitor(m_index, "index"),
|
||||
visitor(m_priority, "priority"))
|
||||
bool operator<(LineEvent const & rhs) const
|
||||
{
|
||||
if (m_x != rhs.m_x)
|
||||
return m_x < rhs.m_x;
|
||||
if (m_index != rhs.m_index)
|
||||
return m_index < rhs.m_index;
|
||||
return m_priority < rhs.m_priority;
|
||||
}
|
||||
|
||||
double m_x;
|
||||
size_t m_index;
|
||||
uint8_t m_priority;
|
||||
};
|
||||
|
||||
std::vector<Event> m_events;
|
||||
double const m_xEps;
|
||||
double const m_yEps;
|
||||
};
|
||||
} // namespace m2
|
||||
105
libs/geometry/oblate_spheroid.cpp
Normal file
105
libs/geometry/oblate_spheroid.cpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#include "geometry/oblate_spheroid.hpp"
|
||||
#include "geometry/distance_on_sphere.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace oblate_spheroid
|
||||
{
|
||||
// Length of semi-major axis of the spheroid WGS-84.
|
||||
static double constexpr kA = 6378137.0;
|
||||
|
||||
// Flattening of the spheroid.
|
||||
static double constexpr kF = 1.0 / 298.257223563;
|
||||
|
||||
// Length of semi-minor axis of the spheroid ~ 6356752.31424.
|
||||
static double constexpr kB = (1.0 - kF) * kA;
|
||||
|
||||
// Desired degree of accuracy for convergence of Vincenty's formulae.
|
||||
static double constexpr kEps = 1e-10;
|
||||
|
||||
// Maximum iterations of distance evaluation.
|
||||
static int constexpr kIterations = 10;
|
||||
|
||||
/// \brief Calculates latitude on the auxiliary sphere for |angleRad| latitude on a spheroid.
|
||||
static double ReducedLatitude(double angleRad)
|
||||
{
|
||||
return std::atan((1.0 - kF) * std::tan(angleRad));
|
||||
}
|
||||
|
||||
double GetDistance(ms::LatLon const & point1, ms::LatLon const & point2)
|
||||
{
|
||||
using namespace base;
|
||||
using namespace std;
|
||||
using math::DegToRad, math::Pow2;
|
||||
|
||||
m2::PointD const p1 = {DegToRad(point1.m_lon), DegToRad(point1.m_lat)};
|
||||
m2::PointD const p2 = {DegToRad(point2.m_lon), DegToRad(point2.m_lat)};
|
||||
double const U1 = ReducedLatitude(p1.y);
|
||||
double const U2 = ReducedLatitude(p2.y);
|
||||
double const sinU1 = sin(U1);
|
||||
double const cosU1 = cos(U1);
|
||||
double const sinU2 = sin(U2);
|
||||
double const cosU2 = cos(U2);
|
||||
|
||||
// Difference in longitude of two points.
|
||||
double const L = p2.x - p1.x;
|
||||
// Difference in longitude on the auxiliary sphere.
|
||||
double lambda = L;
|
||||
double lambdaPrev = std::numeric_limits<double>::max();
|
||||
int iterations = kIterations;
|
||||
double sinSigma = 1.;
|
||||
double cosSigma = 0.;
|
||||
double sigma = 0.;
|
||||
double cosAlphaSquare = 0.;
|
||||
double cosDoubleSigmaMid = 0.;
|
||||
double cosDoubleSigmaMidSquare = 0.;
|
||||
|
||||
while (iterations-- > 0 && !AlmostEqualAbs(lambda, lambdaPrev, kEps))
|
||||
{
|
||||
sinSigma = sqrt(Pow2(cosU2 * sin(lambda)) + Pow2(cosU1 * sinU2 - sinU1 * cosU2 * cos(lambda)));
|
||||
cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cos(lambda);
|
||||
sigma = atan2(sinSigma, cosSigma);
|
||||
double const sinAlpha = cosU1 * cosU2 * sin(lambda) / sinSigma;
|
||||
cosAlphaSquare = 1 - Pow2(sinAlpha);
|
||||
|
||||
// Cosine of SigmaMid - angular separation between the midpoint of the line and the equator.
|
||||
if (fabs(cosAlphaSquare) < DBL_EPSILON)
|
||||
cosDoubleSigmaMid = 0;
|
||||
else
|
||||
cosDoubleSigmaMid = cos(sigma) - 2 * sinU1 * sinU2 / cosAlphaSquare;
|
||||
cosDoubleSigmaMidSquare = Pow2(cosDoubleSigmaMid);
|
||||
|
||||
double const C = (kF / 16.0) * cosAlphaSquare * (4.0 + kF * (4.0 - 3.0 * cosAlphaSquare));
|
||||
|
||||
lambdaPrev = lambda;
|
||||
lambda = L + (1 - C) * kF * sinAlpha *
|
||||
(sigma + C * sinSigma * (cosDoubleSigmaMid + C * cosSigma * (-1 + 2 * cosDoubleSigmaMidSquare)));
|
||||
}
|
||||
|
||||
// Fallback solution.
|
||||
if (!AlmostEqualAbs(lambda, lambdaPrev, kEps))
|
||||
return DistanceOnEarth(point1, point2);
|
||||
|
||||
double constexpr aSquare = kA * kA;
|
||||
double constexpr bSquare = kB * kB;
|
||||
|
||||
double const uSquare = cosAlphaSquare * (aSquare - bSquare) / bSquare;
|
||||
|
||||
double const A = 1.0 + (uSquare / 16384.0) * (4096.0 + uSquare * (-768.0 + uSquare * (320.0 - 175.0 * uSquare)));
|
||||
|
||||
double const B = (uSquare / 1024.0) * (256.0 + uSquare * (-128.0 + uSquare * (74.0 - 47 * uSquare)));
|
||||
|
||||
double const deltaSigma = B * sinSigma *
|
||||
(cosDoubleSigmaMid + 0.25 * B *
|
||||
(cosSigma * (-1.0 + 2.0 * cosDoubleSigmaMidSquare) -
|
||||
(B / 6.0) * cosDoubleSigmaMid * (-3.0 + 4.0 * Pow2(sinSigma)) *
|
||||
(-3.0 + 4 * cosDoubleSigmaMidSquare)));
|
||||
|
||||
return kB * A * (sigma - deltaSigma);
|
||||
}
|
||||
} // namespace oblate_spheroid
|
||||
12
libs/geometry/oblate_spheroid.hpp
Normal file
12
libs/geometry/oblate_spheroid.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
namespace oblate_spheroid
|
||||
{
|
||||
/// \brief Calculates the distance in meters between two points on an ellipsoidal earth model.
|
||||
/// Implements Vincenty’s formula for the "distance between points" problem.
|
||||
/// Vincenty's solution is much slower but more accurate than ms::DistanceOnEarth from [geometry].
|
||||
/// https://en.wikipedia.org/wiki/Vincenty%27s_formulae
|
||||
double GetDistance(ms::LatLon const & point1, ms::LatLon const & point2);
|
||||
} // namespace oblate_spheroid
|
||||
182
libs/geometry/packer.cpp
Normal file
182
libs/geometry/packer.cpp
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
#include "geometry/packer.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
namespace m2
|
||||
{
|
||||
Packer::Packer()
|
||||
: m_currentX(0)
|
||||
, m_currentY(0)
|
||||
, m_yStep(0)
|
||||
, m_width(0)
|
||||
, m_height(0)
|
||||
, m_currentHandle(0)
|
||||
, m_maxHandle(0)
|
||||
, m_invalidHandle(0x00FFFFFF)
|
||||
{}
|
||||
|
||||
Packer::Packer(unsigned width, unsigned height, uint32_t maxHandle)
|
||||
: m_currentX(0)
|
||||
, m_currentY(0)
|
||||
, m_yStep(0)
|
||||
, m_width(width)
|
||||
, m_height(height)
|
||||
, m_currentHandle(0)
|
||||
, m_maxHandle(maxHandle)
|
||||
, m_invalidHandle(0x00FFFFFF)
|
||||
{}
|
||||
|
||||
uint32_t Packer::invalidHandle() const
|
||||
{
|
||||
return m_invalidHandle;
|
||||
}
|
||||
|
||||
Packer::handle_t Packer::pack(unsigned width, unsigned height)
|
||||
{
|
||||
ASSERT(width <= m_width, ());
|
||||
ASSERT(height <= m_height, ());
|
||||
|
||||
if ((width > m_width) || (height > m_height))
|
||||
return m_invalidHandle;
|
||||
|
||||
/// checking whether there is enough space on X axis
|
||||
|
||||
if (m_currentX + width > m_width)
|
||||
{
|
||||
m_currentY += m_yStep;
|
||||
m_currentX = 0;
|
||||
m_yStep = 0;
|
||||
}
|
||||
|
||||
/// checking whether there is enough space on Y axis
|
||||
if (m_currentY + height > m_height)
|
||||
{
|
||||
/// texture overflow detected. all packed rects should be forgotten.
|
||||
callOverflowFns();
|
||||
reset();
|
||||
}
|
||||
/// check handleOverflow
|
||||
if (m_currentHandle == m_maxHandle)
|
||||
{
|
||||
/// handles overflow detected. all packed rects should be forgotten.
|
||||
callOverflowFns();
|
||||
reset();
|
||||
m_currentHandle = 0;
|
||||
}
|
||||
|
||||
/// can pack
|
||||
m_yStep = std::max(height, m_yStep);
|
||||
handle_t curHandle = m_currentHandle++;
|
||||
m_rects[curHandle] = m2::RectU(m_currentX, m_currentY, m_currentX + width, m_currentY + height);
|
||||
m_currentX += width;
|
||||
|
||||
return curHandle;
|
||||
}
|
||||
|
||||
Packer::handle_t Packer::freeHandle()
|
||||
{
|
||||
if (m_currentHandle == m_maxHandle)
|
||||
{
|
||||
callOverflowFns();
|
||||
reset();
|
||||
m_currentHandle = 0;
|
||||
}
|
||||
|
||||
return m_currentHandle++;
|
||||
}
|
||||
|
||||
bool Packer::hasRoom(unsigned width, unsigned height) const
|
||||
{
|
||||
return ((m_width >= width) && (m_height - m_currentY - m_yStep >= height)) ||
|
||||
((m_width - m_currentX >= width) && (m_height - m_currentY >= height));
|
||||
}
|
||||
|
||||
bool Packer::hasRoom(m2::PointU const * sizes, size_t cnt) const
|
||||
{
|
||||
unsigned currentX = m_currentX;
|
||||
unsigned currentY = m_currentY;
|
||||
unsigned yStep = m_yStep;
|
||||
|
||||
for (unsigned i = 0; i < cnt; ++i)
|
||||
{
|
||||
unsigned width = sizes[i].x;
|
||||
unsigned height = sizes[i].y;
|
||||
|
||||
if (width <= m_width - currentX)
|
||||
{
|
||||
if (height <= m_height - currentY)
|
||||
{
|
||||
yStep = std::max(height, yStep);
|
||||
currentX += width;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentX = 0;
|
||||
currentY += yStep;
|
||||
yStep = 0;
|
||||
|
||||
if (width <= m_width - currentX)
|
||||
{
|
||||
if (height <= m_height - currentY)
|
||||
{
|
||||
yStep = std::max(height, yStep);
|
||||
currentX += width;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Packer::isPacked(handle_t handle)
|
||||
{
|
||||
return m_rects.find(handle) != m_rects.end();
|
||||
}
|
||||
|
||||
Packer::find_result_t Packer::find(handle_t handle) const
|
||||
{
|
||||
rects_t::const_iterator it = m_rects.find(handle);
|
||||
std::pair<bool, m2::RectU> res;
|
||||
res.first = (it != m_rects.end());
|
||||
if (res.first)
|
||||
res.second = it->second;
|
||||
return res;
|
||||
}
|
||||
|
||||
void Packer::remove(handle_t handle)
|
||||
{
|
||||
m_rects.erase(handle);
|
||||
}
|
||||
|
||||
void Packer::reset()
|
||||
{
|
||||
m_rects.clear();
|
||||
m_currentX = 0;
|
||||
m_currentY = 0;
|
||||
m_yStep = 0;
|
||||
m_currentHandle = 0;
|
||||
}
|
||||
|
||||
void Packer::addOverflowFn(overflowFn fn, int priority)
|
||||
{
|
||||
m_overflowFns.push(std::pair<size_t, overflowFn>(priority, fn));
|
||||
}
|
||||
|
||||
void Packer::callOverflowFns()
|
||||
{
|
||||
LOG(LDEBUG, ("Texture|Handles Overflow"));
|
||||
overflowFns handlersCopy = m_overflowFns;
|
||||
while (!handlersCopy.empty())
|
||||
{
|
||||
handlersCopy.top().second();
|
||||
handlersCopy.pop();
|
||||
}
|
||||
}
|
||||
} // namespace m2
|
||||
94
libs/geometry/packer.hpp
Normal file
94
libs/geometry/packer.hpp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
template <typename pair_t>
|
||||
struct first_less
|
||||
{
|
||||
bool operator()(pair_t const & first, pair_t const & second) { return first.first < second.first; }
|
||||
};
|
||||
|
||||
/// The simplest row-by-row packer.
|
||||
/// When there is no free room all the packing
|
||||
/// rect is cleared and the process is started again.
|
||||
class Packer
|
||||
{
|
||||
public:
|
||||
using overflowFn = std::function<void()>;
|
||||
|
||||
using overflowFns = std::priority_queue<std::pair<size_t, overflowFn>, std::vector<std::pair<size_t, overflowFn>>,
|
||||
first_less<std::pair<size_t, overflowFn>>>;
|
||||
|
||||
using handle_t = uint32_t;
|
||||
using find_result_t = std::pair<bool, m2::RectU>;
|
||||
|
||||
Packer();
|
||||
|
||||
/// create a packer on a rectangular area of (0, 0, width, height) dimensions
|
||||
Packer(unsigned width, unsigned height, uint32_t maxHandle = 0xFFFF - 1);
|
||||
|
||||
/// reset the state of the packer
|
||||
void reset();
|
||||
|
||||
/// add overflow handler.
|
||||
/// @param priority handlers with higher priority value are called first.
|
||||
void addOverflowFn(overflowFn fn, int priority);
|
||||
|
||||
/// pack the rect with dimensions width X height on a free area.
|
||||
/// when there is no free area - find it somehow(depending on a packer implementation,
|
||||
/// the simplest one will just clear the whole rect and start the packing process again).
|
||||
handle_t pack(unsigned width, unsigned height);
|
||||
|
||||
/// return free handle
|
||||
handle_t freeHandle();
|
||||
|
||||
/// Does we have room to pack another rectangle?
|
||||
bool hasRoom(unsigned width, unsigned height) const;
|
||||
|
||||
/// Does we have room to pack a sequence of rectangles?
|
||||
bool hasRoom(m2::PointU const * sizes, size_t cnt) const;
|
||||
|
||||
/// is the handle present on the texture.
|
||||
bool isPacked(handle_t handle);
|
||||
|
||||
/// find the packed area by the handle.
|
||||
find_result_t find(handle_t handle) const;
|
||||
|
||||
/// remove the handle from the list of active handles.
|
||||
void remove(handle_t handle);
|
||||
|
||||
/// get an invalid handle
|
||||
uint32_t invalidHandle() const;
|
||||
|
||||
private:
|
||||
using rects_t = std::map<handle_t, m2::RectU>;
|
||||
|
||||
void callOverflowFns();
|
||||
|
||||
unsigned m_currentX;
|
||||
unsigned m_currentY;
|
||||
unsigned m_yStep;
|
||||
|
||||
unsigned m_width;
|
||||
unsigned m_height;
|
||||
|
||||
overflowFns m_overflowFns;
|
||||
|
||||
handle_t m_currentHandle;
|
||||
|
||||
rects_t m_rects;
|
||||
|
||||
uint32_t m_maxHandle;
|
||||
uint32_t m_invalidHandle;
|
||||
};
|
||||
} // namespace m2
|
||||
99
libs/geometry/parametrized_segment.hpp
Normal file
99
libs/geometry/parametrized_segment.hpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
// This class holds a parametrization of the
|
||||
// line segment between two points p0 and p1.
|
||||
// The parametrization is of the form
|
||||
// p(t) = p0 + t * dir.
|
||||
// Other conditions:
|
||||
// dir is the normalized (p1 - p0) vector.
|
||||
// length(dir) = 1.
|
||||
// p(0) = p0.
|
||||
// p(T) = p1 with T = length(p1 - p0).
|
||||
//
|
||||
// The points with t in [0, T] are the points of the segment.
|
||||
template <typename Point>
|
||||
class ParametrizedSegment
|
||||
{
|
||||
public:
|
||||
static_assert(std::numeric_limits<typename Point::value_type>::is_signed, "Unsigned points are not supported");
|
||||
|
||||
ParametrizedSegment(Point const & p0, Point const & p1) : m_p0(p0), m_p1(p1)
|
||||
{
|
||||
m_d = m_p1 - m_p0;
|
||||
m_length = m_d.Length();
|
||||
if (m_d.IsAlmostZero())
|
||||
m_d = Point::Zero();
|
||||
else
|
||||
m_d = m_d / m_length;
|
||||
}
|
||||
|
||||
// Returns the squared (euclidean) distance from the segment to |p|.
|
||||
double SquaredDistanceToPoint(Point const & p) const
|
||||
{
|
||||
m2::PointD const diff(p - m_p0);
|
||||
double const t = DotProduct(m_d, diff);
|
||||
|
||||
if (t <= 0)
|
||||
return diff.SquaredLength();
|
||||
|
||||
if (t >= m_length)
|
||||
return (p - m_p1).SquaredLength();
|
||||
|
||||
// Closest point is between |m_p0| and |m_p1|.
|
||||
return math::Pow2(CrossProduct(diff, m_d));
|
||||
}
|
||||
|
||||
// Returns the point of the segment that is closest to |p|.
|
||||
m2::PointD ClosestPointTo(Point const & p) const
|
||||
{
|
||||
m2::PointD const diff(p - m_p0);
|
||||
double const t = DotProduct(m_d, diff);
|
||||
|
||||
if (t <= 0)
|
||||
return m_p0;
|
||||
|
||||
if (t >= m_length)
|
||||
return m_p1;
|
||||
|
||||
return m_p0 + m_d * t;
|
||||
}
|
||||
|
||||
Point const & GetP0() const { return m_p0; }
|
||||
Point const & GetP1() const { return m_p1; }
|
||||
|
||||
private:
|
||||
Point m_p0;
|
||||
Point m_p1;
|
||||
m2::PointD m_d;
|
||||
double m_length;
|
||||
};
|
||||
|
||||
// This functor is here only for backward compatibility. It is not obvious
|
||||
// when looking at a call site whether x should be the first or the last parameter to the fuction.
|
||||
// For readability, consider creating a parametrized segment and using its methods instead
|
||||
// of using this functor.
|
||||
struct SquaredDistanceFromSegmentToPoint
|
||||
{
|
||||
/// @return Squared distance from the segment [a, b] to the point x.
|
||||
double operator()(m2::PointD const & a, m2::PointD const & b, m2::PointD const & x) const
|
||||
{
|
||||
ParametrizedSegment<m2::PointD> segment(a, b);
|
||||
return segment.SquaredDistanceToPoint(x);
|
||||
}
|
||||
|
||||
template <class PointT>
|
||||
double operator()(PointT const & a, PointT const & b, PointT const & x) const
|
||||
{
|
||||
return this->operator()(m2::PointD(a), m2::PointD(b), m2::PointD(x));
|
||||
}
|
||||
};
|
||||
} // namespace m2
|
||||
288
libs/geometry/point2d.hpp
Normal file
288
libs/geometry/point2d.hpp
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/math.hpp"
|
||||
#include "base/matrix.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <typeinfo>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
template <typename T>
|
||||
class Point
|
||||
{
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
T x, y;
|
||||
|
||||
Point() = default;
|
||||
constexpr Point(T x_, T y_) : x(x_), y(y_) {}
|
||||
|
||||
template <typename U>
|
||||
explicit constexpr Point(Point<U> const & u) : x(u.x)
|
||||
, y(u.y)
|
||||
{}
|
||||
|
||||
static Point<T> Zero() { return Point<T>(0, 0); }
|
||||
static Point<T> Max() { return Point<T>(std::numeric_limits<T>::max(), std::numeric_limits<T>::max()); }
|
||||
|
||||
bool EqualDxDy(Point<T> const & p, T eps) const { return ((fabs(x - p.x) < eps) && (fabs(y - p.y) < eps)); }
|
||||
|
||||
T SquaredLength(Point<T> const & p) const { return math::Pow2(x - p.x) + math::Pow2(y - p.y); }
|
||||
double Length(Point<T> const & p) const { return std::sqrt(SquaredLength(p)); }
|
||||
|
||||
bool IsAlmostZero() const { return AlmostEqualULPs(*this, Point<T>(0, 0)); }
|
||||
|
||||
Point<T> Move(T len, T ang) const { return Point<T>(x + len * cos(ang), y + len * sin(ang)); }
|
||||
|
||||
Point<T> Move(T len, T angSin, T angCos) const { return m2::Point<T>(x + len * angCos, y + len * angSin); }
|
||||
|
||||
Point<T> const & operator-=(Point<T> const & a)
|
||||
{
|
||||
x -= a.x;
|
||||
y -= a.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Point<T> const & operator+=(Point<T> const & a)
|
||||
{
|
||||
x += a.x;
|
||||
y += a.y;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
Point<T> const & operator*=(U const & k)
|
||||
{
|
||||
x = static_cast<T>(x * k);
|
||||
y = static_cast<T>(y * k);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
Point<T> const & operator=(Point<U> const & a)
|
||||
{
|
||||
x = static_cast<T>(a.x);
|
||||
y = static_cast<T>(a.y);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(m2::Point<T> const & p) const { return x == p.x && y == p.y; }
|
||||
|
||||
bool operator!=(m2::Point<T> const & p) const { return !(*this == p); }
|
||||
|
||||
m2::Point<T> operator+(m2::Point<T> const & pt) const { return m2::Point<T>(x + pt.x, y + pt.y); }
|
||||
|
||||
m2::Point<T> operator-(m2::Point<T> const & pt) const { return m2::Point<T>(x - pt.x, y - pt.y); }
|
||||
|
||||
m2::Point<T> operator-() const { return m2::Point<T>(-x, -y); }
|
||||
|
||||
m2::Point<T> operator*(T scale) const { return m2::Point<T>(x * scale, y * scale); }
|
||||
|
||||
m2::Point<T> const operator*(math::Matrix<T, 3, 3> const & m) const
|
||||
{
|
||||
m2::Point<T> res;
|
||||
res.x = x * m(0, 0) + y * m(1, 0) + m(2, 0);
|
||||
res.y = x * m(0, 1) + y * m(1, 1) + m(2, 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
m2::Point<T> operator/(T scale) const { return m2::Point<T>(x / scale, y / scale); }
|
||||
|
||||
m2::Point<T> Mid(m2::Point<T> const & p) const { return m2::Point<T>((x + p.x) * 0.5, (y + p.y) * 0.5); }
|
||||
|
||||
/// @name VectorOperationsOnPoint
|
||||
/// @{
|
||||
T SquaredLength() const { return x * x + y * y; }
|
||||
double Length() const { return std::sqrt(SquaredLength()); }
|
||||
|
||||
Point<T> Normalize() const
|
||||
{
|
||||
if (IsAlmostZero())
|
||||
return Zero();
|
||||
|
||||
double const length = this->Length();
|
||||
return Point<T>(x / length, y / length);
|
||||
}
|
||||
|
||||
std::pair<Point<T>, Point<T>> Normals(T prolongationFactor = 1) const
|
||||
{
|
||||
T const prolongatedX = prolongationFactor * x;
|
||||
T const prolongatedY = prolongationFactor * y;
|
||||
return std::pair<Point<T>, Point<T>>(Point<T>(static_cast<T>(-prolongatedY), static_cast<T>(prolongatedX)),
|
||||
Point<T>(static_cast<T>(prolongatedY), static_cast<T>(-prolongatedX)));
|
||||
}
|
||||
|
||||
m2::Point<T> const & operator*=(math::Matrix<T, 3, 3> const & m)
|
||||
{
|
||||
T tempX = x;
|
||||
x = tempX * m(0, 0) + y * m(1, 0) + m(2, 0);
|
||||
y = tempX * m(0, 1) + y * m(1, 1) + m(2, 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Rotate(double angle)
|
||||
{
|
||||
T cosAngle = cos(angle);
|
||||
T sinAngle = sin(angle);
|
||||
T oldX = x;
|
||||
x = cosAngle * oldX - sinAngle * y;
|
||||
y = sinAngle * oldX + cosAngle * y;
|
||||
}
|
||||
|
||||
// Returns vector rotated 90 degrees counterclockwise.
|
||||
Point Ort() const { return Point(-y, x); }
|
||||
|
||||
void Transform(m2::Point<T> const & org, m2::Point<T> const & dx, m2::Point<T> const & dy)
|
||||
{
|
||||
T oldX = x;
|
||||
x = org.x + oldX * dx.x + y * dy.x;
|
||||
y = org.y + oldX * dx.y + y * dy.y;
|
||||
}
|
||||
/// @}
|
||||
|
||||
struct Hash
|
||||
{
|
||||
size_t operator()(Point const & p) const { return math::Hash(p.x, p.y); }
|
||||
};
|
||||
};
|
||||
|
||||
using PointF = Point<float>;
|
||||
using PointD = Point<double>;
|
||||
using PointU = Point<uint32_t>;
|
||||
using PointU64 = Point<uint64_t>;
|
||||
using PointI = Point<int32_t>;
|
||||
using PointI64 = Point<int64_t>;
|
||||
|
||||
template <typename T>
|
||||
Point<T> operator-(Point<T> const & a, Point<T> const & b)
|
||||
{
|
||||
return Point<T>(a.x - b.x, a.y - b.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Point<T> operator+(Point<T> const & a, Point<T> const & b)
|
||||
{
|
||||
return Point<T>(a.x + b.x, a.y + b.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T DotProduct(Point<T> const & a, Point<T> const & b)
|
||||
{
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T CrossProduct(Point<T> const & a, Point<T> const & b)
|
||||
{
|
||||
return a.x * b.y - a.y * b.x;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Point<T> Rotate(Point<T> const & pt, T a)
|
||||
{
|
||||
Point<T> res(pt);
|
||||
res.Rotate(a);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
Point<T> Shift(Point<T> const & pt, U const & dx, U const & dy)
|
||||
{
|
||||
return Point<T>(pt.x + dx, pt.y + dy);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
Point<T> Shift(Point<T> const & pt, Point<U> const & offset)
|
||||
{
|
||||
return Shift(pt, offset.x, offset.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Point<T> Floor(Point<T> const & pt)
|
||||
{
|
||||
Point<T> res;
|
||||
res.x = floor(pt.x);
|
||||
res.y = floor(pt.y);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string DebugPrint(Point<T> const & p)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out.precision(20);
|
||||
out << "m2::Point<" << typeid(T).name() << ">(" << p.x << ", " << p.y << ")";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool AlmostEqualAbs(Point<T> const & a, Point<T> const & b, double eps)
|
||||
{
|
||||
return ::AlmostEqualAbs(a.x, b.x, static_cast<T>(eps)) && ::AlmostEqualAbs(a.y, b.y, static_cast<T>(eps));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool AlmostEqualULPs(Point<T> const & a, Point<T> const & b, unsigned int maxULPs = 256)
|
||||
{
|
||||
return ::AlmostEqualULPs(a.x, b.x, maxULPs) && ::AlmostEqualULPs(a.y, b.y, maxULPs);
|
||||
}
|
||||
|
||||
/// Calculate three points of a triangle (p1, p2 and p3) which give an arrow that
|
||||
/// presents an equilateral triangle with the median
|
||||
/// starting at point b and having direction b,e.
|
||||
/// The height of the equilateral triangle is l and the base of the triangle is 2 * w
|
||||
template <typename T, typename TT, typename PointT = Point<T>>
|
||||
void GetArrowPoints(PointT const & b, PointT const & e, T w, T l, std::array<Point<TT>, 3> & arr)
|
||||
{
|
||||
ASSERT(!AlmostEqualULPs(b, e), ());
|
||||
|
||||
PointT const beVec = e - b;
|
||||
PointT beNormalizedVec = beVec.Normalize();
|
||||
std::pair<PointT, PointT> beNormVecs = beNormalizedVec.Normals(w);
|
||||
|
||||
arr[0] = e + beNormVecs.first;
|
||||
arr[1] = e + beNormalizedVec * l;
|
||||
arr[2] = e + beNormVecs.second;
|
||||
}
|
||||
|
||||
/// Returns a point which is belonged to the segment p1, p2 with respet the indent shiftFromP1 from
|
||||
/// p1. If shiftFromP1 is more the distance between (p1, p2) it returns p2. If shiftFromP1 is less
|
||||
/// or equal zero it returns p1.
|
||||
template <typename T>
|
||||
Point<T> PointAtSegment(Point<T> const & p1, Point<T> const & p2, T shiftFromP1)
|
||||
{
|
||||
Point<T> p12 = p2 - p1;
|
||||
shiftFromP1 = math::Clamp(shiftFromP1, static_cast<T>(0.0), static_cast<T>(p12.Length()));
|
||||
return p1 + p12.Normalize() * shiftFromP1;
|
||||
}
|
||||
|
||||
template <class TArchive, class PointT>
|
||||
TArchive & operator>>(TArchive & ar, Point<PointT> & pt)
|
||||
{
|
||||
ar >> pt.x;
|
||||
ar >> pt.y;
|
||||
return ar;
|
||||
}
|
||||
|
||||
template <class TArchive, class PointT>
|
||||
TArchive & operator<<(TArchive & ar, Point<PointT> const & pt)
|
||||
{
|
||||
ar << pt.x;
|
||||
ar << pt.y;
|
||||
return ar;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator<(Point<T> const & l, Point<T> const & r)
|
||||
{
|
||||
if (l.x != r.x)
|
||||
return l.x < r.x;
|
||||
return l.y < r.y;
|
||||
}
|
||||
} // namespace m2
|
||||
108
libs/geometry/point3d.hpp
Normal file
108
libs/geometry/point3d.hpp
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace m3
|
||||
{
|
||||
template <typename T>
|
||||
class Point
|
||||
{
|
||||
public:
|
||||
Point() = default;
|
||||
constexpr Point(T x_, T y_, T z_) : x(x_), y(y_), z(z_) {}
|
||||
Point<T> operator+(Point<T> const & obj) { return {x + obj.x, y + obj.y, z + obj.z}; }
|
||||
Point<T> operator-(Point<T> const & obj) { return {x - obj.x, y - obj.y, z - obj.z}; }
|
||||
|
||||
Point<T> operator*(T const & obj) { return {x * obj, y * obj, z * obj}; }
|
||||
|
||||
Point<T> operator/(T const & obj) { return {x / obj, y / obj, z / obj}; }
|
||||
|
||||
T Length() const { return std::sqrt(x * x + y * y + z * z); }
|
||||
|
||||
Point RotateAroundX(double angleDegree) const;
|
||||
Point RotateAroundY(double angleDegree) const;
|
||||
Point RotateAroundZ(double angleDegree) const;
|
||||
|
||||
bool operator==(Point<T> const & rhs) const;
|
||||
|
||||
T x;
|
||||
T y;
|
||||
T z;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Point<T> Point<T>::RotateAroundX(double angleDegree) const
|
||||
{
|
||||
double const angleRad = math::DegToRad(angleDegree);
|
||||
Point<T> res;
|
||||
res.x = x;
|
||||
res.y = y * cos(angleRad) - z * sin(angleRad);
|
||||
res.z = y * sin(angleRad) + z * cos(angleRad);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Point<T> Point<T>::RotateAroundY(double angleDegree) const
|
||||
{
|
||||
double const angleRad = math::DegToRad(angleDegree);
|
||||
Point<T> res;
|
||||
res.x = x * cos(angleRad) + z * sin(angleRad);
|
||||
res.y = y;
|
||||
res.z = -x * sin(angleRad) + z * cos(angleRad);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Point<T> Point<T>::RotateAroundZ(double angleDegree) const
|
||||
{
|
||||
double const angleRad = math::DegToRad(angleDegree);
|
||||
Point<T> res;
|
||||
res.x = x * cos(angleRad) - y * sin(angleRad);
|
||||
res.y = x * sin(angleRad) + y * cos(angleRad);
|
||||
res.z = z;
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool Point<T>::operator==(m3::Point<T> const & rhs) const
|
||||
{
|
||||
return x == rhs.x && y == rhs.y && z == rhs.z;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T DotProduct(Point<T> const & a, Point<T> const & b)
|
||||
{
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Point<T> CrossProduct(Point<T> const & a, Point<T> const & b)
|
||||
{
|
||||
auto const x = a.y * b.z - a.z * b.y;
|
||||
auto const y = a.z * b.x - a.x * b.z;
|
||||
auto const z = a.x * b.y - a.y * b.x;
|
||||
return Point<T>(x, y, z);
|
||||
}
|
||||
|
||||
using PointF = Point<float>;
|
||||
using PointD = Point<double>;
|
||||
|
||||
template <typename T>
|
||||
std::string DebugPrint(Point<T> const & p)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out.precision(20);
|
||||
out << "m3::Point<" << typeid(T).name() << ">(" << p.x << ", " << p.y << ", " << p.z << ")";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool AlmostEqualAbs(Point<T> const & p1, Point<T> const & p2, double const & eps)
|
||||
{
|
||||
return ::AlmostEqualAbs(p1.x, p2.x, eps) && ::AlmostEqualAbs(p1.y, p2.y, eps) && ::AlmostEqualAbs(p1.z, p2.z, eps);
|
||||
}
|
||||
} // namespace m3
|
||||
64
libs/geometry/point_with_altitude.cpp
Normal file
64
libs/geometry/point_with_altitude.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include "geometry/point_with_altitude.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
|
||||
#include <boost/functional/hash.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace geometry
|
||||
{
|
||||
PointWithAltitude::PointWithAltitude() : m_point(m2::PointD::Zero()), m_altitude(kDefaultAltitudeMeters) {}
|
||||
|
||||
PointWithAltitude::PointWithAltitude(m2::PointD const & point, Altitude altitude /* = kDefaultAltitudeMeters */)
|
||||
: m_point(point)
|
||||
, m_altitude(altitude)
|
||||
{}
|
||||
|
||||
bool PointWithAltitude::operator==(PointWithAltitude const & r) const
|
||||
{
|
||||
return m_point == r.m_point && m_altitude == r.m_altitude;
|
||||
}
|
||||
|
||||
bool PointWithAltitude::operator<(PointWithAltitude const & r) const
|
||||
{
|
||||
if (m_point != r.m_point)
|
||||
return m_point < r.m_point;
|
||||
|
||||
return m_altitude < r.m_altitude;
|
||||
}
|
||||
|
||||
ms::LatLon PointWithAltitude::ToLatLon() const
|
||||
{
|
||||
return ms::LatLon(mercator::YToLat(m_point.y), mercator::XToLon(m_point.x));
|
||||
}
|
||||
|
||||
std::string DebugPrint(PointWithAltitude const & r)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "PointWithAltitude{point:" << DebugPrint(r.m_point) << ", altitude:" << r.GetAltitude() << "}";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
PointWithAltitude MakePointWithAltitudeForTesting(m2::PointD const & point)
|
||||
{
|
||||
return PointWithAltitude(point, kDefaultAltitudeMeters);
|
||||
}
|
||||
|
||||
bool AlmostEqualAbs(PointWithAltitude const & lhs, PointWithAltitude const & rhs, double eps)
|
||||
{
|
||||
return AlmostEqualAbs(lhs.GetPoint(), rhs.GetPoint(), eps) && lhs.GetAltitude() == rhs.GetAltitude();
|
||||
}
|
||||
} // namespace geometry
|
||||
|
||||
namespace std
|
||||
{
|
||||
size_t hash<geometry::PointWithAltitude>::operator()(geometry::PointWithAltitude const & point) const
|
||||
{
|
||||
size_t seed = 0;
|
||||
boost::hash_combine(seed, point.GetPoint().x);
|
||||
boost::hash_combine(seed, point.GetPoint().y);
|
||||
boost::hash_combine(seed, point.GetAltitude());
|
||||
return seed;
|
||||
}
|
||||
} // namespace std
|
||||
76
libs/geometry/point_with_altitude.hpp
Normal file
76
libs/geometry/point_with_altitude.hpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace geometry
|
||||
{
|
||||
using Altitude = int16_t;
|
||||
using Altitudes = std::vector<Altitude>;
|
||||
|
||||
Altitude constexpr kInvalidAltitude = std::numeric_limits<Altitude>::min();
|
||||
Altitude constexpr kDefaultAltitudeMeters = 0;
|
||||
|
||||
class PointWithAltitude
|
||||
{
|
||||
public:
|
||||
PointWithAltitude();
|
||||
PointWithAltitude(m2::PointD const & point, Altitude altitude = kDefaultAltitudeMeters);
|
||||
operator m2::PointD() const { return m_point; }
|
||||
|
||||
bool operator==(PointWithAltitude const & r) const;
|
||||
bool operator!=(PointWithAltitude const & r) const { return !(*this == r); }
|
||||
bool operator<(PointWithAltitude const & r) const;
|
||||
|
||||
m2::PointD const & GetPoint() const { return m_point; }
|
||||
ms::LatLon ToLatLon() const;
|
||||
Altitude GetAltitude() const { return m_altitude; }
|
||||
|
||||
void SetPoint(m2::PointD const & point) { m_point = point; }
|
||||
void SetAltitude(Altitude altitude) { m_altitude = altitude; }
|
||||
|
||||
/// @param[in] f in range [0, 1]
|
||||
PointWithAltitude Interpolate(PointWithAltitude const & to, double f) const
|
||||
{
|
||||
return PointWithAltitude(m_point + (to.m_point - m_point) * f,
|
||||
math::iround(m_altitude + (to.m_altitude - m_altitude) * f));
|
||||
}
|
||||
|
||||
private:
|
||||
friend std::string DebugPrint(PointWithAltitude const & r);
|
||||
|
||||
m2::PointD m_point;
|
||||
Altitude m_altitude;
|
||||
};
|
||||
|
||||
std::string DebugPrint(PointWithAltitude const & r);
|
||||
|
||||
template <typename T>
|
||||
m2::Point<T> GetPoint(m2::Point<T> const & point)
|
||||
{
|
||||
return point;
|
||||
}
|
||||
inline m2::PointD GetPoint(PointWithAltitude const & pwa)
|
||||
{
|
||||
return pwa.GetPoint();
|
||||
}
|
||||
|
||||
PointWithAltitude MakePointWithAltitudeForTesting(m2::PointD const & point);
|
||||
|
||||
bool AlmostEqualAbs(PointWithAltitude const & lhs, PointWithAltitude const & rhs, double eps);
|
||||
} // namespace geometry
|
||||
|
||||
namespace std
|
||||
{
|
||||
template <>
|
||||
struct hash<geometry::PointWithAltitude>
|
||||
{
|
||||
size_t operator()(geometry::PointWithAltitude const & point) const;
|
||||
};
|
||||
} // namespace std
|
||||
188
libs/geometry/polygon.hpp
Normal file
188
libs/geometry/polygon.hpp
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/robust_orientation.hpp"
|
||||
#include "geometry/segment2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/base.hpp"
|
||||
#include "base/math.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
template <typename IsVisibleF>
|
||||
bool FindSingleStripForIndex(size_t i, size_t n, IsVisibleF isVisible)
|
||||
{
|
||||
// Searching for a strip only in a single direction, because the opposite direction
|
||||
// is traversed from the last vertex of the possible strip.
|
||||
size_t a = math::PrevModN(i, n);
|
||||
size_t b = math::NextModN(i, n);
|
||||
for (size_t j = 2; j < n; ++j)
|
||||
{
|
||||
ASSERT_NOT_EQUAL(a, b, ());
|
||||
if (!isVisible(a, b))
|
||||
return false;
|
||||
if (j & 1)
|
||||
a = math::PrevModN(a, n);
|
||||
else
|
||||
b = math::NextModN(b, n);
|
||||
}
|
||||
|
||||
ASSERT_EQUAL(a, b, ());
|
||||
return true;
|
||||
}
|
||||
|
||||
// If polygon with n vertices is a single strip, return the start index of the strip or n otherwise.
|
||||
template <typename IsVisibleF>
|
||||
size_t FindSingleStrip(size_t n, IsVisibleF isVisible)
|
||||
{
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
if (FindSingleStripForIndex(i, n, isVisible))
|
||||
return i;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
template <typename IterT>
|
||||
bool TestPolygonPreconditions(IterT beg, IterT end)
|
||||
{
|
||||
ASSERT_GREATER(std::distance(beg, end), 2, ());
|
||||
ASSERT(!AlmostEqualULPs(*beg, *(--end)), ());
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Is polygon [beg, end) has CCW orientation.
|
||||
template <typename IterT>
|
||||
bool IsPolygonCCW(IterT beg, IterT end)
|
||||
{
|
||||
ASSERT(TestPolygonPreconditions(beg, end), ());
|
||||
|
||||
// find the most down (left) point
|
||||
double minY = std::numeric_limits<double>::max();
|
||||
IterT iRes;
|
||||
for (IterT i = beg; i != end; ++i)
|
||||
{
|
||||
if ((*i).y < minY || ((*i).y == minY && (*i).x < (*iRes).x))
|
||||
{
|
||||
iRes = i;
|
||||
minY = (*i).y;
|
||||
}
|
||||
}
|
||||
|
||||
double cp =
|
||||
m2::robust::OrientedS(*base::PrevIterInCycle(iRes, beg, end), *iRes, *base::NextIterInCycle(iRes, beg, end));
|
||||
if (cp != 0.0)
|
||||
return (cp > 0.0);
|
||||
|
||||
// find the most up (left) point
|
||||
double maxY = std::numeric_limits<double>::min();
|
||||
for (IterT i = beg; i != end; ++i)
|
||||
{
|
||||
if ((*i).y > maxY || ((*i).y == maxY && (*i).x < (*iRes).x))
|
||||
{
|
||||
iRes = i;
|
||||
maxY = (*i).y;
|
||||
}
|
||||
}
|
||||
|
||||
IterT iPrev = base::PrevIterInCycle(iRes, beg, end);
|
||||
IterT iNext = base::NextIterInCycle(iRes, beg, end);
|
||||
cp = m2::robust::OrientedS(*iPrev, *iRes, *iNext);
|
||||
|
||||
// Feel free to comment this assert when debugging generator tool.
|
||||
// It fires on degenerated polygons which a lot in OSM.
|
||||
ASSERT_NOT_EQUAL(cp, 0.0, (*iPrev, *iRes, *iNext));
|
||||
return (cp > 0.0);
|
||||
}
|
||||
|
||||
/// Is diagonal (i0, i1) visible in polygon [beg, end).
|
||||
/// @precondition Orientation CCW!!
|
||||
template <typename IterT>
|
||||
bool IsDiagonalVisible(IterT beg, IterT end, IterT i0, IterT i1)
|
||||
{
|
||||
ASSERT(IsPolygonCCW(beg, end), ());
|
||||
ASSERT(TestPolygonPreconditions(beg, end), ());
|
||||
ASSERT(i0 != i1, ());
|
||||
|
||||
IterT const prev = base::PrevIterInCycle(i0, beg, end);
|
||||
IterT const next = base::NextIterInCycle(i0, beg, end);
|
||||
if (prev == i1 || next == i1)
|
||||
return true;
|
||||
|
||||
if (!m2::robust::IsSegmentInCone(*i0, *i1, *prev, *next))
|
||||
return false;
|
||||
|
||||
for (IterT j0 = beg, j1 = base::PrevIterInCycle(beg, beg, end); j0 != end; j1 = j0++)
|
||||
if (j0 != i0 && j0 != i1 && j1 != i0 && j1 != i1 && m2::SegmentsIntersect(*i0, *i1, *j0, *j1))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename IterT>
|
||||
class IsDiagonalVisibleFunctor
|
||||
{
|
||||
IterT m_Beg, m_End;
|
||||
|
||||
public:
|
||||
IsDiagonalVisibleFunctor(IterT beg, IterT end) : m_Beg(beg), m_End(end) {}
|
||||
|
||||
bool operator()(size_t a, size_t b) const { return IsDiagonalVisible(m_Beg, m_End, m_Beg + a, m_Beg + b); }
|
||||
};
|
||||
|
||||
namespace polygon_detail
|
||||
{
|
||||
template <typename F>
|
||||
class StripEmitter
|
||||
{
|
||||
F & m_f;
|
||||
int m_order;
|
||||
|
||||
public:
|
||||
StripEmitter(F & f) : m_f(f), m_order(0) {}
|
||||
|
||||
bool operator()(size_t a, size_t b)
|
||||
{
|
||||
if (m_order == 0)
|
||||
{
|
||||
m_f(b);
|
||||
m_f(a);
|
||||
m_order = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_f(m_order == 1 ? b : a);
|
||||
m_order = -m_order;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
} // namespace polygon_detail
|
||||
|
||||
/// Make single strip for the range of points [beg, end), started with index = i.
|
||||
template <typename F>
|
||||
void MakeSingleStripFromIndex(size_t i, size_t n, F && f)
|
||||
{
|
||||
ASSERT_LESS(i, n, ());
|
||||
f(i);
|
||||
FindSingleStripForIndex(i, n, polygon_detail::StripEmitter<F>(f));
|
||||
}
|
||||
|
||||
template <class TIter>
|
||||
double GetPolygonArea(TIter beg, TIter end)
|
||||
{
|
||||
double area = 0.0;
|
||||
|
||||
TIter curr = beg;
|
||||
while (curr != end)
|
||||
{
|
||||
TIter next = base::NextIterInCycle(curr, beg, end);
|
||||
area += ((*curr).x * (*next).y - (*curr).y * (*next).x);
|
||||
++curr;
|
||||
}
|
||||
|
||||
return 0.5 * fabs(area);
|
||||
}
|
||||
179
libs/geometry/polyline2d.hpp
Normal file
179
libs/geometry/polyline2d.hpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/parametrized_segment.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/point_with_altitude.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "base/internal/message.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
/// \returns a pair of minimum squared distance from |point| to the closest segment and
|
||||
/// a zero-based closest segment index in points in range [|beginIt|, |endIt|).
|
||||
template <typename It, typename T>
|
||||
std::pair<double, uint32_t> CalcMinSquaredDistance(It beginIt, It endIt, m2::Point<T> const & point)
|
||||
{
|
||||
CHECK_GREATER(std::distance(beginIt, endIt), 1, ());
|
||||
auto squaredClosestSegDist = std::numeric_limits<double>::max();
|
||||
|
||||
auto i = beginIt;
|
||||
auto closestSeg = beginIt;
|
||||
for (auto j = i + 1; j != endIt; ++i, ++j)
|
||||
{
|
||||
m2::ParametrizedSegment<m2::Point<T>> seg(geometry::GetPoint(*i), geometry::GetPoint(*j));
|
||||
auto const squaredSegDist = seg.SquaredDistanceToPoint(point);
|
||||
if (squaredSegDist < squaredClosestSegDist)
|
||||
{
|
||||
closestSeg = i;
|
||||
squaredClosestSegDist = squaredSegDist;
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(squaredClosestSegDist, static_cast<uint32_t>(std::distance(beginIt, closestSeg)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class Polyline
|
||||
{
|
||||
std::vector<Point<T>> m_points;
|
||||
|
||||
public:
|
||||
using Container = std::vector<Point<T>>;
|
||||
using Iter = typename Container::const_iterator;
|
||||
|
||||
Polyline() {}
|
||||
Polyline(std::initializer_list<Point<T>> const & points) : m_points(points)
|
||||
{
|
||||
ASSERT_GREATER(m_points.size(), 1, ());
|
||||
}
|
||||
|
||||
explicit Polyline(std::vector<Point<T>> const & points) : m_points(points) { ASSERT_GREATER(m_points.size(), 1, ()); }
|
||||
|
||||
explicit Polyline(std::vector<Point<T>> && points) : m_points(std::move(points))
|
||||
{
|
||||
ASSERT_GREATER(m_points.size(), 1, ());
|
||||
}
|
||||
|
||||
template <class Iter>
|
||||
Polyline(Iter beg, Iter end) : m_points(beg, end)
|
||||
{
|
||||
ASSERT_GREATER(m_points.size(), 1, ());
|
||||
}
|
||||
|
||||
double GetLength() const
|
||||
{
|
||||
// @todo add cached value and lazy init
|
||||
double dist = 0;
|
||||
for (size_t i = 1; i < m_points.size(); ++i)
|
||||
dist += m_points[i - 1].Length(m_points[i]);
|
||||
return dist;
|
||||
}
|
||||
|
||||
double GetLength(size_t pointIndex) const
|
||||
{
|
||||
double dist = 0;
|
||||
for (size_t i = 0; i < std::min(pointIndex, m_points.size() - 1); ++i)
|
||||
dist += m_points[i].Length(m_points[i + 1]);
|
||||
return dist;
|
||||
}
|
||||
|
||||
std::pair<double, uint32_t> CalcMinSquaredDistance(m2::Point<T> const & point) const
|
||||
{
|
||||
return m2::CalcMinSquaredDistance(m_points.begin(), m_points.end(), point);
|
||||
}
|
||||
|
||||
Rect<T> GetLimitRect() const
|
||||
{
|
||||
// @todo add cached value and lazy init
|
||||
m2::Rect<T> rect;
|
||||
for (size_t i = 0; i < m_points.size(); ++i)
|
||||
rect.Add(m_points[i]);
|
||||
return rect;
|
||||
}
|
||||
|
||||
void Clear() { m_points.clear(); }
|
||||
void Add(Point<T> const & pt) { m_points.push_back(pt); }
|
||||
void Append(Polyline const & poly) { m_points.insert(m_points.end(), poly.m_points.cbegin(), poly.m_points.cend()); }
|
||||
|
||||
template <class Iter>
|
||||
void Append(Iter beg, Iter end)
|
||||
{
|
||||
m_points.insert(m_points.end(), beg, end);
|
||||
}
|
||||
|
||||
void PopBack()
|
||||
{
|
||||
ASSERT(!m_points.empty(), ());
|
||||
m_points.pop_back();
|
||||
}
|
||||
|
||||
void Swap(Polyline & rhs) { m_points.swap(rhs.m_points); }
|
||||
size_t GetSize() const { return m_points.size(); }
|
||||
bool operator==(Polyline const & rhs) const { return m_points == rhs.m_points; }
|
||||
Iter Begin() const { return m_points.begin(); }
|
||||
Iter End() const { return m_points.end(); }
|
||||
Point<T> const & Front() const { return m_points.front(); }
|
||||
Point<T> const & Back() const { return m_points.back(); }
|
||||
|
||||
Point<T> const & GetPoint(size_t idx) const
|
||||
{
|
||||
ASSERT_LESS(idx, m_points.size(), ());
|
||||
return m_points[idx];
|
||||
}
|
||||
|
||||
Point<T> GetPointByDistance(T distance) const
|
||||
{
|
||||
if (distance < 0)
|
||||
return m_points.front();
|
||||
|
||||
T dist = 0;
|
||||
for (size_t i = 1; i < m_points.size(); ++i)
|
||||
{
|
||||
T const oldDist = dist;
|
||||
dist += m_points[i - 1].Length(m_points[i]);
|
||||
if (distance <= dist)
|
||||
{
|
||||
T const t = (distance - oldDist) / (dist - oldDist);
|
||||
return m_points[i - 1] * (1 - t) + m_points[i] * t;
|
||||
}
|
||||
}
|
||||
|
||||
return m_points.back();
|
||||
}
|
||||
|
||||
std::vector<Point<T>> ExtractSegment(size_t segmentIndex, bool reversed) const
|
||||
{
|
||||
if (segmentIndex + 1 >= m_points.size())
|
||||
return std::vector<Point<T>>();
|
||||
|
||||
return reversed ? std::vector<Point<T>>{m_points[segmentIndex + 1], m_points[segmentIndex]}
|
||||
: std::vector<Point<T>>{m_points[segmentIndex], m_points[segmentIndex + 1]};
|
||||
}
|
||||
|
||||
std::vector<Point<T>> ExtractSegment(size_t startPointIndex, size_t endPointIndex) const
|
||||
{
|
||||
if (startPointIndex > endPointIndex || startPointIndex >= m_points.size() || endPointIndex >= m_points.size())
|
||||
return std::vector<Point<T>>();
|
||||
|
||||
std::vector<Point<T>> result(endPointIndex - startPointIndex + 1);
|
||||
for (size_t i = startPointIndex, j = 0; i <= endPointIndex; ++i, ++j)
|
||||
result[j] = m_points[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Point<T>> const & GetPoints() const { return m_points; }
|
||||
friend std::string DebugPrint(Polyline const & p) { return ::DebugPrint(p.m_points); }
|
||||
};
|
||||
|
||||
using PolylineD = Polyline<double>;
|
||||
} // namespace m2
|
||||
392
libs/geometry/rect2d.hpp
Normal file
392
libs/geometry/rect2d.hpp
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/internal/message.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
namespace impl
|
||||
{
|
||||
template <typename T, bool has_sign>
|
||||
struct min_max_value;
|
||||
|
||||
template <typename T>
|
||||
struct min_max_value<T, true>
|
||||
{
|
||||
T get_min() { return -get_max(); }
|
||||
T get_max() { return std::numeric_limits<T>::max(); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct min_max_value<T, false>
|
||||
{
|
||||
T get_min() { return std::numeric_limits<T>::min(); }
|
||||
T get_max() { return std::numeric_limits<T>::max(); }
|
||||
};
|
||||
} // namespace impl
|
||||
|
||||
template <typename T>
|
||||
class Rect
|
||||
{
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
Rect() { MakeEmpty(); }
|
||||
|
||||
constexpr Rect(T minX, T minY, T maxX, T maxY) : m_minX(minX), m_minY(minY), m_maxX(maxX), m_maxY(maxY)
|
||||
{
|
||||
ASSERT(minX <= maxX && minY <= maxY, (minX, maxX, minY, maxY));
|
||||
}
|
||||
|
||||
Rect(Point<T> const & p1, Point<T> const & p2)
|
||||
: m_minX(std::min(p1.x, p2.x))
|
||||
, m_minY(std::min(p1.y, p2.y))
|
||||
, m_maxX(std::max(p1.x, p2.x))
|
||||
, m_maxY(std::max(p1.y, p2.y))
|
||||
{}
|
||||
|
||||
template <typename U>
|
||||
explicit Rect(Rect<U> const & src) : m_minX(src.minX())
|
||||
, m_minY(src.minY())
|
||||
, m_maxX(src.maxX())
|
||||
, m_maxY(src.maxY())
|
||||
{}
|
||||
|
||||
static Rect GetEmptyRect() { return Rect(); }
|
||||
|
||||
static Rect GetInfiniteRect()
|
||||
{
|
||||
Rect r;
|
||||
r.MakeInfinite();
|
||||
return r;
|
||||
}
|
||||
|
||||
void MakeEmpty()
|
||||
{
|
||||
m_minX = m_minY = impl::min_max_value<T, IsSigned>().get_max();
|
||||
m_maxX = m_maxY = impl::min_max_value<T, IsSigned>().get_min();
|
||||
}
|
||||
|
||||
void MakeInfinite()
|
||||
{
|
||||
m_minX = m_minY = impl::min_max_value<T, IsSigned>().get_min();
|
||||
m_maxX = m_maxY = impl::min_max_value<T, IsSigned>().get_max();
|
||||
}
|
||||
|
||||
bool IsValid() const { return (m_minX <= m_maxX && m_minY <= m_maxY); }
|
||||
|
||||
bool IsEmptyInterior() const { return m_minX >= m_maxX || m_minY >= m_maxY; }
|
||||
|
||||
void Add(m2::Point<T> const & p)
|
||||
{
|
||||
m_minX = std::min(p.x, m_minX);
|
||||
m_minY = std::min(p.y, m_minY);
|
||||
m_maxX = std::max(p.x, m_maxX);
|
||||
m_maxY = std::max(p.y, m_maxY);
|
||||
}
|
||||
|
||||
void Add(m2::Rect<T> const & r)
|
||||
{
|
||||
m_minX = std::min(r.m_minX, m_minX);
|
||||
m_minY = std::min(r.m_minY, m_minY);
|
||||
m_maxX = std::max(r.m_maxX, m_maxX);
|
||||
m_maxY = std::max(r.m_maxY, m_maxY);
|
||||
}
|
||||
|
||||
void Offset(m2::Point<T> const & p)
|
||||
{
|
||||
m_minX += p.x;
|
||||
m_minY += p.y;
|
||||
m_maxX += p.x;
|
||||
m_maxY += p.y;
|
||||
}
|
||||
|
||||
void Offset(T const & dx, T const & dy)
|
||||
{
|
||||
m_minX += dx;
|
||||
m_minY += dy;
|
||||
m_maxX += dx;
|
||||
m_maxY += dy;
|
||||
}
|
||||
|
||||
Point<T> LeftTop() const { return Point<T>(m_minX, m_maxY); }
|
||||
Point<T> RightTop() const { return Point<T>(m_maxX, m_maxY); }
|
||||
Point<T> RightBottom() const { return Point<T>(m_maxX, m_minY); }
|
||||
Point<T> LeftBottom() const { return Point<T>(m_minX, m_minY); }
|
||||
|
||||
template <typename Fn>
|
||||
void ForEachCorner(Fn && fn) const
|
||||
{
|
||||
fn(LeftTop());
|
||||
fn(LeftBottom());
|
||||
fn(RightBottom());
|
||||
fn(RightTop());
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
void ForEachSide(Fn && fn) const
|
||||
{
|
||||
fn(LeftTop(), LeftBottom());
|
||||
fn(LeftBottom(), RightBottom());
|
||||
fn(RightBottom(), RightTop());
|
||||
fn(RightTop(), LeftTop());
|
||||
}
|
||||
|
||||
bool IsIntersect(Rect const & r) const
|
||||
{
|
||||
return !((m_maxX < r.m_minX) || (m_minX > r.m_maxX) || (m_maxY < r.m_minY) || (m_minY > r.m_maxY));
|
||||
}
|
||||
|
||||
bool IsPointInside(Point<T> const & pt) const
|
||||
{
|
||||
return !(m_minX > pt.x || pt.x > m_maxX || m_minY > pt.y || pt.y > m_maxY);
|
||||
}
|
||||
|
||||
bool IsRectInside(Rect<T> const & rect) const
|
||||
{
|
||||
return (IsPointInside(Point<T>(rect.minX(), rect.minY())) && IsPointInside(Point<T>(rect.maxX(), rect.maxY())));
|
||||
}
|
||||
|
||||
Point<T> Center() const { return Point<T>((m_minX + m_maxX) / 2.0, (m_minY + m_maxY) / 2.0); }
|
||||
T SizeX() const { return std::max(static_cast<T>(0), m_maxX - m_minX); }
|
||||
T SizeY() const { return std::max(static_cast<T>(0), m_maxY - m_minY); }
|
||||
T Area() const { return SizeX() * SizeY(); }
|
||||
|
||||
void DivideByGreaterSize(Rect & r1, Rect & r2) const
|
||||
{
|
||||
if (SizeX() > SizeY())
|
||||
{
|
||||
T const pivot = (m_minX + m_maxX) / 2;
|
||||
r1 = Rect<T>(m_minX, m_minY, pivot, m_maxY);
|
||||
r2 = Rect<T>(pivot, m_minY, m_maxX, m_maxY);
|
||||
}
|
||||
else
|
||||
{
|
||||
T const pivot = (m_minY + m_maxY) / 2;
|
||||
r1 = Rect<T>(m_minX, m_minY, m_maxX, pivot);
|
||||
r2 = Rect<T>(m_minX, pivot, m_maxX, m_maxY);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSizes(T dx, T dy)
|
||||
{
|
||||
ASSERT_GREATER(dx, 0, ());
|
||||
ASSERT_GREATER(dy, 0, ());
|
||||
|
||||
dx /= 2;
|
||||
dy /= 2;
|
||||
|
||||
Point<T> const c = Center();
|
||||
m_minX = c.x - dx;
|
||||
m_minY = c.y - dy;
|
||||
m_maxX = c.x + dx;
|
||||
m_maxY = c.y + dy;
|
||||
}
|
||||
|
||||
void SetSizesToIncludePoint(Point<T> const & pt)
|
||||
{
|
||||
Point<T> const c = Center();
|
||||
T const dx = math::Abs(pt.x - c.x);
|
||||
T const dy = math::Abs(pt.y - c.y);
|
||||
|
||||
m_minX = c.x - dx;
|
||||
m_minY = c.y - dy;
|
||||
m_maxX = c.x + dx;
|
||||
m_maxY = c.y + dy;
|
||||
}
|
||||
|
||||
void SetCenter(m2::Point<T> const & p) { Offset(p - Center()); }
|
||||
|
||||
T minX() const { return m_minX; }
|
||||
T minY() const { return m_minY; }
|
||||
T maxX() const { return m_maxX; }
|
||||
T maxY() const { return m_maxY; }
|
||||
|
||||
void setMinX(T minX) { m_minX = minX; }
|
||||
void setMinY(T minY) { m_minY = minY; }
|
||||
void setMaxX(T maxX) { m_maxX = maxX; }
|
||||
void setMaxY(T maxY) { m_maxY = maxY; }
|
||||
|
||||
void Scale(T scale)
|
||||
{
|
||||
ASSERT_GREATER(scale, 0, ());
|
||||
scale *= 0.5;
|
||||
|
||||
m2::Point<T> const center = Center();
|
||||
T const halfSizeX = SizeX() * scale;
|
||||
T const halfSizeY = SizeY() * scale;
|
||||
m_minX = center.x - halfSizeX;
|
||||
m_minY = center.y - halfSizeY;
|
||||
m_maxX = center.x + halfSizeX;
|
||||
m_maxY = center.y + halfSizeY;
|
||||
}
|
||||
|
||||
void Inflate(T dx, T dy)
|
||||
{
|
||||
m_minX -= dx;
|
||||
m_maxX += dx;
|
||||
m_minY -= dy;
|
||||
m_maxY += dy;
|
||||
}
|
||||
|
||||
bool Intersect(m2::Rect<T> const & r)
|
||||
{
|
||||
T newMinX = std::max(m_minX, r.minX());
|
||||
T newMaxX = std::min(m_maxX, r.maxX());
|
||||
|
||||
if (newMinX > newMaxX)
|
||||
return false;
|
||||
|
||||
T newMinY = std::max(m_minY, r.minY());
|
||||
T newMaxY = std::min(m_maxY, r.maxY());
|
||||
|
||||
if (newMinY > newMaxY)
|
||||
return false;
|
||||
|
||||
m_minX = newMinX;
|
||||
m_minY = newMinY;
|
||||
m_maxX = newMaxX;
|
||||
m_maxY = newMaxY;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator==(m2::Rect<T> const & r) const
|
||||
{
|
||||
return m_minX == r.m_minX && m_minY == r.m_minY && m_maxX == r.m_maxX && m_maxY == r.m_maxY;
|
||||
}
|
||||
|
||||
bool operator!=(m2::Rect<T> const & r) const { return !(*this == r); }
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
IsSigned = std::numeric_limits<T>::is_signed
|
||||
};
|
||||
|
||||
template <class TArchive, class TPoint>
|
||||
friend TArchive & operator<<(TArchive & ar, Rect<TPoint> const & rect);
|
||||
|
||||
template <class TArchive, class TPoint>
|
||||
friend TArchive & operator>>(TArchive & ar, Rect<TPoint> & rect);
|
||||
|
||||
T m_minX, m_minY, m_maxX, m_maxY;
|
||||
};
|
||||
|
||||
using RectF = Rect<float>;
|
||||
using RectD = Rect<double>;
|
||||
using RectU = Rect<unsigned>;
|
||||
using RectU32 = Rect<uint32_t>;
|
||||
using RectI = Rect<int>;
|
||||
|
||||
template <typename T>
|
||||
bool AlmostEqualAbs(Rect<T> const & a, Rect<T> const & b, double eps)
|
||||
{
|
||||
return AlmostEqualAbs(a.LeftTop(), b.LeftTop(), eps) && AlmostEqualAbs(a.RightBottom(), b.RightBottom(), eps);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsEqual(Rect<T> const & r1, Rect<T> const & r2, double epsX, double epsY)
|
||||
{
|
||||
Rect<T> r = r1;
|
||||
r.Inflate(epsX, epsY);
|
||||
if (!r.IsRectInside(r2))
|
||||
return false;
|
||||
|
||||
r = r2;
|
||||
r.Inflate(epsX, epsY);
|
||||
if (!r.IsRectInside(r1))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsEqualSize(Rect<T> const & r1, Rect<T> const & r2, double epsX, double epsY)
|
||||
{
|
||||
return fabs(r1.SizeX() - r2.SizeX()) < epsX && fabs(r1.SizeY() - r2.SizeY()) < epsY;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
m2::Rect<T> const Add(m2::Rect<T> const & r, m2::Point<T> const & p)
|
||||
{
|
||||
return m2::Rect<T>(std::min(p.x, r.minX()), std::min(p.y, r.minY()), std::max(p.x, r.maxX()),
|
||||
std::max(p.y, r.maxY()));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
m2::Rect<T> const Add(m2::Rect<T> const & r1, m2::Rect<T> const & r2)
|
||||
{
|
||||
return m2::Rect<T>(std::min(r2.minX(), r1.minX()), std::min(r2.minY(), r1.minY()), std::max(r2.maxX(), r1.maxX()),
|
||||
std::max(r2.maxY(), r1.maxY()));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
m2::Rect<T> const Offset(m2::Rect<T> const & r1, m2::Point<T> const & p)
|
||||
{
|
||||
return m2::Rect<T>(r1.minX() + p.x, r1.minY() + p.y, r1.maxX() + p.x, r1.maxY() + p.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
m2::Rect<T> const Inflate(m2::Rect<T> const & r, T const & dx, T const & dy)
|
||||
{
|
||||
return m2::Rect<T>(r.minX() - dx, r.minY() - dy, r.maxX() + dx, r.maxY() + dy);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
m2::Rect<T> const Inflate(m2::Rect<T> const & r, m2::Point<T> const & p)
|
||||
{
|
||||
return Inflate(r, p.x, p.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
m2::Rect<T> const Offset(m2::Rect<T> const & r1, T const & dx, T const & dy)
|
||||
{
|
||||
return m2::Rect<T>(r1.minX() + dx, r1.minY() + dy, r1.maxX() + dx, r1.maxY() + dy);
|
||||
}
|
||||
|
||||
template <typename T, typename TCollection>
|
||||
bool HasIntersection(m2::Rect<T> const & rect, TCollection const & geometry)
|
||||
{
|
||||
for (auto const & g : geometry)
|
||||
if (rect.IsIntersect(g))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class TArchive, class PointT>
|
||||
TArchive & operator>>(TArchive & ar, m2::Rect<PointT> & rect)
|
||||
{
|
||||
ar >> rect.m_minX;
|
||||
ar >> rect.m_minY;
|
||||
ar >> rect.m_maxX;
|
||||
ar >> rect.m_maxY;
|
||||
return ar;
|
||||
}
|
||||
|
||||
template <class TArchive, class PointT>
|
||||
TArchive & operator<<(TArchive & ar, m2::Rect<PointT> const & rect)
|
||||
{
|
||||
ar << rect.m_minX;
|
||||
ar << rect.m_minY;
|
||||
ar << rect.m_maxX;
|
||||
ar << rect.m_maxY;
|
||||
return ar;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string DebugPrint(m2::Rect<T> const & r)
|
||||
{
|
||||
std::ostringstream out;
|
||||
out.precision(20);
|
||||
out << "m2::Rect(" << r.minX() << ", " << r.minY() << ", " << r.maxX() << ", " << r.maxY() << ")";
|
||||
return out.str();
|
||||
}
|
||||
} // namespace m2
|
||||
106
libs/geometry/rect_intersect.hpp
Normal file
106
libs/geometry/rect_intersect.hpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
namespace m2
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
// bit masks for code kinds
|
||||
static int const LEFT = 1;
|
||||
static int const RIGHT = 2;
|
||||
static int const BOT = 4;
|
||||
static int const TOP = 8;
|
||||
|
||||
// return accumulated bit mask for point-rect interaction
|
||||
template <class T>
|
||||
int vcode(m2::Rect<T> const & r, m2::Point<T> const & p)
|
||||
{
|
||||
return ((p.x < r.minX() ? LEFT : 0) + (p.x > r.maxX() ? RIGHT : 0) + (p.y < r.minY() ? BOT : 0) +
|
||||
(p.y > r.maxY() ? TOP : 0));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <class T>
|
||||
bool Intersect(m2::Rect<T> const & r, m2::Point<T> & p1, m2::Point<T> & p2, int & code1, int & code2)
|
||||
{
|
||||
code1 = code2 = 0;
|
||||
int codeClip[2] = {0, 0};
|
||||
int code[2] = {detail::vcode(r, p1), detail::vcode(r, p2)};
|
||||
|
||||
// do while one of the point is out of rect
|
||||
while (code[0] || code[1])
|
||||
{
|
||||
if (code[0] & code[1])
|
||||
{
|
||||
// both point area on the one side of rect
|
||||
return false;
|
||||
}
|
||||
|
||||
// choose point with non-zero code
|
||||
m2::Point<T> * pp;
|
||||
int i;
|
||||
if (code[0])
|
||||
{
|
||||
i = 0;
|
||||
pp = &p1;
|
||||
}
|
||||
else
|
||||
{
|
||||
i = 1;
|
||||
pp = &p2;
|
||||
}
|
||||
|
||||
// added points compare to avoid NAN numbers
|
||||
if (code[i] & detail::LEFT)
|
||||
{
|
||||
if (p1 == p2)
|
||||
return false;
|
||||
pp->y += (p1.y - p2.y) * (r.minX() - pp->x) / (p1.x - p2.x);
|
||||
pp->x = r.minX();
|
||||
codeClip[i] = detail::LEFT;
|
||||
}
|
||||
else if (code[i] & detail::RIGHT)
|
||||
{
|
||||
if (p1 == p2)
|
||||
return false;
|
||||
pp->y += (p1.y - p2.y) * (r.maxX() - pp->x) / (p1.x - p2.x);
|
||||
pp->x = r.maxX();
|
||||
codeClip[i] = detail::RIGHT;
|
||||
}
|
||||
|
||||
if (code[i] & detail::BOT)
|
||||
{
|
||||
if (p1 == p2)
|
||||
return false;
|
||||
pp->x += (p1.x - p2.x) * (r.minY() - pp->y) / (p1.y - p2.y);
|
||||
pp->y = r.minY();
|
||||
codeClip[i] = detail::BOT;
|
||||
}
|
||||
else if (code[i] & detail::TOP)
|
||||
{
|
||||
if (p1 == p2)
|
||||
return false;
|
||||
pp->x += (p1.x - p2.x) * (r.maxY() - pp->y) / (p1.y - p2.y);
|
||||
pp->y = r.maxY();
|
||||
codeClip[i] = detail::TOP;
|
||||
}
|
||||
|
||||
// update code with new point
|
||||
code[i] = detail::vcode(r, *pp);
|
||||
}
|
||||
|
||||
code1 = codeClip[0];
|
||||
code2 = codeClip[1];
|
||||
// both codes are equal to zero => points area inside rect
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool Intersect(m2::Rect<T> const & r, m2::Point<T> & p1, m2::Point<T> & p2)
|
||||
{
|
||||
int code1, code2;
|
||||
return Intersect(r, p1, p2, code1, code2);
|
||||
}
|
||||
} // namespace m2
|
||||
409
libs/geometry/region2d.hpp
Normal file
409
libs/geometry/region2d.hpp
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/parametrized_segment.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
struct DefEqualFloat
|
||||
{
|
||||
// 1e-9 is two orders of magnitude more accurate than our OSM source data.
|
||||
static double constexpr kPrecision = 1e-9;
|
||||
|
||||
template <typename Point>
|
||||
bool EqualPoints(Point const & p1, Point const & p2) const
|
||||
{
|
||||
static_assert(std::is_floating_point<typename Point::value_type>::value);
|
||||
return AlmostEqualAbs(p1, p2, kPrecision);
|
||||
}
|
||||
|
||||
template <typename Coord>
|
||||
bool EqualZeroSquarePrecision(Coord val) const
|
||||
{
|
||||
static_assert(std::is_floating_point<Coord>::value);
|
||||
|
||||
return ::AlmostEqualAbs(val, 0.0, kPrecision * kPrecision);
|
||||
}
|
||||
// Determines if value of a val lays between a p1 and a p2 values with some precision.
|
||||
bool IsAlmostBetween(double val, double p1, double p2) const
|
||||
{
|
||||
return (val >= p1 - kPrecision && val <= p2 + kPrecision) || (val <= p1 + kPrecision && val >= p2 - kPrecision);
|
||||
}
|
||||
};
|
||||
|
||||
struct DefEqualInt
|
||||
{
|
||||
template <typename Point>
|
||||
bool EqualPoints(Point const & p1, Point const & p2) const
|
||||
{
|
||||
return p1 == p2;
|
||||
}
|
||||
|
||||
template <typename Coord>
|
||||
bool EqualZeroSquarePrecision(Coord val) const
|
||||
{
|
||||
return val == 0;
|
||||
}
|
||||
|
||||
bool IsAlmostBetween(double val, double left, double right) const
|
||||
{
|
||||
return (val >= left && val <= right) || (val <= left && val >= right);
|
||||
}
|
||||
};
|
||||
|
||||
template <int floating>
|
||||
struct TraitsType;
|
||||
|
||||
template <>
|
||||
struct TraitsType<1>
|
||||
{
|
||||
typedef DefEqualFloat EqualType;
|
||||
typedef double BigType;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TraitsType<0>
|
||||
{
|
||||
typedef DefEqualInt EqualType;
|
||||
typedef int64_t BigType;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename Point>
|
||||
class Region
|
||||
{
|
||||
public:
|
||||
using Value = Point;
|
||||
using Coord = typename Point::value_type;
|
||||
using Container = std::vector<Point>;
|
||||
using Traits = detail::TraitsType<std::is_floating_point<Coord>::value>;
|
||||
|
||||
/// @name Needed for boost region concept.
|
||||
//@{
|
||||
using IteratorT = typename Container::const_iterator;
|
||||
IteratorT Begin() const { return m_points.begin(); }
|
||||
IteratorT End() const { return m_points.end(); }
|
||||
size_t Size() const { return m_points.size(); }
|
||||
//@}
|
||||
|
||||
Region() = default;
|
||||
|
||||
template <typename Points, typename = std::enable_if_t<std::is_constructible<Container, Points>::value>>
|
||||
explicit Region(Points && points) : m_points(std::forward<Points>(points))
|
||||
{
|
||||
CalcLimitRect();
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
Region(Iter first, Iter last) : m_points(first, last)
|
||||
{
|
||||
CalcLimitRect();
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
void Assign(Iter first, Iter last)
|
||||
{
|
||||
m_points.assign(first, last);
|
||||
CalcLimitRect();
|
||||
}
|
||||
|
||||
template <typename Iter, typename Fn>
|
||||
void AssignEx(Iter first, Iter last, Fn fn)
|
||||
{
|
||||
m_points.reserve(distance(first, last));
|
||||
|
||||
while (first != last)
|
||||
m_points.push_back(fn(*first++));
|
||||
|
||||
CalcLimitRect();
|
||||
}
|
||||
|
||||
void AddPoint(Point const & pt)
|
||||
{
|
||||
m_points.push_back(pt);
|
||||
m_rect.Add(pt);
|
||||
}
|
||||
|
||||
template <typename ToDo>
|
||||
void ForEachPoint(ToDo && toDo) const
|
||||
{
|
||||
for_each(m_points.begin(), m_points.end(), std::forward<ToDo>(toDo));
|
||||
}
|
||||
|
||||
m2::Rect<Coord> const & GetRect() const { return m_rect; }
|
||||
size_t GetPointsCount() const { return m_points.size(); }
|
||||
bool IsValid() const { return GetPointsCount() > 2; }
|
||||
|
||||
void Swap(Region<Point> & rhs)
|
||||
{
|
||||
m_points.swap(rhs.m_points);
|
||||
std::swap(m_rect, rhs.m_rect);
|
||||
}
|
||||
|
||||
Container const & Data() const { return m_points; }
|
||||
Container & MutableData() { return m_points; }
|
||||
|
||||
template <typename EqualFn>
|
||||
static bool IsIntersect(Coord const & x11, Coord const & y11, Coord const & x12, Coord const & y12, Coord const & x21,
|
||||
Coord const & y21, Coord const & x22, Coord const & y22, EqualFn && equalF, Point & pt)
|
||||
{
|
||||
double const divider = ((y12 - y11) * (x22 - x21) - (x12 - x11) * (y22 - y21));
|
||||
if (equalF.EqualZeroSquarePrecision(divider))
|
||||
return false;
|
||||
double v = ((x12 - x11) * (y21 - y11) + (y12 - y11) * (x11 - x21)) / divider;
|
||||
Point p(x21 + (x22 - x21) * v, y21 + (y22 - y21) * v);
|
||||
|
||||
if (!equalF.IsAlmostBetween(p.x, x11, x12))
|
||||
return false;
|
||||
if (!equalF.IsAlmostBetween(p.x, x21, x22))
|
||||
return false;
|
||||
if (!equalF.IsAlmostBetween(p.y, y11, y12))
|
||||
return false;
|
||||
if (!equalF.IsAlmostBetween(p.y, y21, y22))
|
||||
return false;
|
||||
|
||||
pt = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsIntersect(Point const & p1, Point const & p2, Point const & p3, Point const & p4, Point & pt)
|
||||
{
|
||||
return IsIntersect(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, typename Traits::EqualType(), pt);
|
||||
}
|
||||
|
||||
/// Taken from Computational Geometry in C and modified
|
||||
template <typename EqualFn>
|
||||
bool Contains(Point const & pt, EqualFn equalF) const
|
||||
{
|
||||
if (!m_rect.IsPointInside(pt))
|
||||
return false;
|
||||
|
||||
int rCross = 0; /* number of right edge/ray crossings */
|
||||
int lCross = 0; /* number of left edge/ray crossings */
|
||||
|
||||
size_t const numPoints = m_points.size();
|
||||
|
||||
using BigCoord = typename Traits::BigType;
|
||||
using BigPoint = ::m2::Point<BigCoord>;
|
||||
|
||||
BigPoint prev = BigPoint(m_points[numPoints - 1]) - BigPoint(pt);
|
||||
for (size_t i = 0; i < numPoints; ++i)
|
||||
{
|
||||
if (equalF.EqualPoints(m_points[i], pt))
|
||||
return true;
|
||||
|
||||
BigPoint const curr = BigPoint(m_points[i]) - BigPoint(pt);
|
||||
|
||||
bool const rCheck = ((curr.y > 0) != (prev.y > 0));
|
||||
bool const lCheck = ((curr.y < 0) != (prev.y < 0));
|
||||
|
||||
if (rCheck || lCheck)
|
||||
{
|
||||
ASSERT_NOT_EQUAL(curr.y, prev.y, ());
|
||||
|
||||
BigCoord const delta = prev.y - curr.y;
|
||||
BigCoord const cp = CrossProduct(curr, prev);
|
||||
|
||||
// Squared precision is needed here because of comparison between cross product of two
|
||||
// std::vectors and zero. It's impossible to compare them relatively, so they're compared
|
||||
// absolutely, and, as cross product is proportional to product of lengths of both
|
||||
// operands precision must be squared too.
|
||||
if (!equalF.EqualZeroSquarePrecision(cp))
|
||||
{
|
||||
bool const PrevGreaterCurr = delta > 0.0;
|
||||
|
||||
if (rCheck && ((cp > 0) == PrevGreaterCurr))
|
||||
++rCross;
|
||||
if (lCheck && ((cp > 0) != PrevGreaterCurr))
|
||||
++lCross;
|
||||
}
|
||||
}
|
||||
|
||||
prev = curr;
|
||||
}
|
||||
|
||||
/* q on the edge if left and right cross are not the same parity. */
|
||||
if ((rCross & 1) != (lCross & 1))
|
||||
return true; // on the edge
|
||||
|
||||
/* q inside if an odd number of crossings. */
|
||||
if (rCross & 1)
|
||||
return true; // inside
|
||||
else
|
||||
return false; // outside
|
||||
}
|
||||
|
||||
bool Contains(Point const & pt) const { return Contains(pt, typename Traits::EqualType()); }
|
||||
|
||||
/// Finds point of intersection with the section.
|
||||
bool FindIntersection(Point const & point1, Point const & point2, Point & result) const
|
||||
{
|
||||
if (m_points.empty())
|
||||
return false;
|
||||
Point const * prev = &m_points.back();
|
||||
for (Point const & curr : m_points)
|
||||
{
|
||||
if (IsIntersect(point1, point2, *prev, curr, result))
|
||||
return true;
|
||||
prev = &curr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Slow check that point lies at the border.
|
||||
template <typename EqualFn>
|
||||
bool AtBorder(Point const & pt, double const delta, EqualFn equalF) const
|
||||
{
|
||||
auto const rectDelta = static_cast<Coord>(delta);
|
||||
if (!Inflate(m_rect, rectDelta, rectDelta).IsPointInside(pt))
|
||||
return false;
|
||||
|
||||
double const squaredDelta = delta * delta;
|
||||
size_t const numPoints = m_points.size();
|
||||
|
||||
Point prev = m_points[numPoints - 1];
|
||||
|
||||
for (size_t i = 0; i < numPoints; ++i)
|
||||
{
|
||||
Point const curr = m_points[i];
|
||||
|
||||
// Borders often have same points with ways
|
||||
if (equalF.EqualPoints(m_points[i], pt))
|
||||
return true;
|
||||
|
||||
ParametrizedSegment<Point> segment(prev, curr);
|
||||
if (segment.SquaredDistanceToPoint(pt) < squaredDelta)
|
||||
return true;
|
||||
|
||||
prev = curr;
|
||||
}
|
||||
|
||||
return false; // Point lies outside the border.
|
||||
}
|
||||
|
||||
bool AtBorder(Point const & pt, double const delta) const
|
||||
{
|
||||
return AtBorder(pt, delta, typename Traits::EqualType());
|
||||
}
|
||||
|
||||
typename Traits::BigType CalculateArea() const
|
||||
{
|
||||
// Use BigType to prevent CrossProduct overflow.
|
||||
using BigCoord = typename Traits::BigType;
|
||||
using BigPoint = ::m2::Point<BigCoord>;
|
||||
BigCoord area = 0;
|
||||
|
||||
if (m_points.empty())
|
||||
return area;
|
||||
|
||||
size_t const numPoints = m_points.size();
|
||||
BigPoint prev(m_points[numPoints - 1]);
|
||||
for (size_t i = 0; i < numPoints; ++i)
|
||||
{
|
||||
BigPoint const curr(m_points[i]);
|
||||
area += CrossProduct(prev, curr);
|
||||
prev = curr;
|
||||
}
|
||||
area = math::Abs(area) / 2;
|
||||
|
||||
return area;
|
||||
}
|
||||
|
||||
template <typename Engine>
|
||||
Point GetRandomPoint(Engine & engine) const
|
||||
{
|
||||
CHECK(m_rect.IsValid(), ());
|
||||
CHECK(!m_rect.IsEmptyInterior(), ());
|
||||
|
||||
std::uniform_real_distribution<> distrX(m_rect.minX(), m_rect.maxX());
|
||||
std::uniform_real_distribution<> distrY(m_rect.minY(), m_rect.maxY());
|
||||
|
||||
size_t constexpr kMaxIterations = 1000;
|
||||
for (size_t it = 0; it < kMaxIterations; ++it)
|
||||
{
|
||||
auto const x = distrX(engine);
|
||||
auto const y = distrY(engine);
|
||||
Point const pt(x, y);
|
||||
if (Contains(pt))
|
||||
return pt;
|
||||
}
|
||||
|
||||
LOG(LWARNING, ("Could not sample a random point from m2::Region"));
|
||||
return m_points[0];
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Archive, typename Pt>
|
||||
friend Archive & operator<<(Archive & ar, Region<Pt> const & region);
|
||||
|
||||
template <typename Archive, typename Pt>
|
||||
friend Archive & operator>>(Archive & ar, Region<Pt> & region);
|
||||
|
||||
template <typename T>
|
||||
friend std::string DebugPrint(Region<T> const &);
|
||||
|
||||
void CalcLimitRect()
|
||||
{
|
||||
m_rect.MakeEmpty();
|
||||
for (size_t i = 0; i < m_points.size(); ++i)
|
||||
m_rect.Add(m_points[i]);
|
||||
}
|
||||
|
||||
Container m_points;
|
||||
m2::Rect<Coord> m_rect;
|
||||
};
|
||||
|
||||
template <typename Point>
|
||||
void swap(Region<Point> & r1, Region<Point> & r2)
|
||||
{
|
||||
r1.Swap(r2);
|
||||
}
|
||||
|
||||
template <typename Archive, typename Point>
|
||||
Archive & operator>>(Archive & ar, Region<Point> & region)
|
||||
{
|
||||
ar >> region.m_rect;
|
||||
ar >> region.m_points;
|
||||
return ar;
|
||||
}
|
||||
|
||||
template <typename Archive, typename Point>
|
||||
Archive & operator<<(Archive & ar, Region<Point> const & region)
|
||||
{
|
||||
ar << region.m_rect;
|
||||
ar << region.m_points;
|
||||
return ar;
|
||||
}
|
||||
|
||||
template <typename Point>
|
||||
std::string DebugPrint(Region<Point> const & r)
|
||||
{
|
||||
return (DebugPrint(r.m_rect) + ::DebugPrint(r.m_points));
|
||||
}
|
||||
|
||||
template <typename Point>
|
||||
bool RegionsContain(std::vector<Region<Point>> const & regions, Point const & point)
|
||||
{
|
||||
for (auto const & region : regions)
|
||||
if (region.Contains(point))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
using RegionD = Region<m2::PointD>;
|
||||
using RegionI = Region<m2::PointI>;
|
||||
using RegionU = Region<m2::PointU>;
|
||||
} // namespace m2
|
||||
52
libs/geometry/region2d/binary_operators.cpp
Normal file
52
libs/geometry/region2d/binary_operators.cpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#include "geometry/region2d/binary_operators.hpp"
|
||||
#include "geometry/region2d/boost_concept.hpp"
|
||||
|
||||
namespace m2
|
||||
{
|
||||
void SpliceRegions(std::vector<RegionI> & src, std::vector<RegionI> & res)
|
||||
{
|
||||
for (size_t i = 0; i < src.size(); ++i)
|
||||
{
|
||||
res.push_back(RegionI());
|
||||
res.back().Swap(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void IntersectRegions(RegionI const & r1, RegionI const & r2, MultiRegionI & res)
|
||||
{
|
||||
MultiRegionI local;
|
||||
using namespace boost::polygon::operators;
|
||||
local += (r1 * r2);
|
||||
SpliceRegions(local, res);
|
||||
}
|
||||
|
||||
MultiRegionI IntersectRegions(RegionI const & r1, MultiRegionI const & r2)
|
||||
{
|
||||
MultiRegionI local;
|
||||
using namespace boost::polygon::operators;
|
||||
local += (r1 * r2);
|
||||
return local;
|
||||
}
|
||||
|
||||
void DiffRegions(RegionI const & r1, RegionI const & r2, MultiRegionI & res)
|
||||
{
|
||||
MultiRegionI local;
|
||||
using namespace boost::polygon::operators;
|
||||
local += boost::polygon::operators::operator-(r1, r2);
|
||||
SpliceRegions(local, res);
|
||||
}
|
||||
|
||||
void AddRegion(RegionI const & r, MultiRegionI & res)
|
||||
{
|
||||
using namespace boost::polygon::operators;
|
||||
res += r;
|
||||
}
|
||||
|
||||
uint64_t Area(MultiRegionI const & rgn)
|
||||
{
|
||||
uint64_t area = 0;
|
||||
for (auto const & r : rgn)
|
||||
area += r.CalculateArea();
|
||||
return area;
|
||||
}
|
||||
} // namespace m2
|
||||
23
libs/geometry/region2d/binary_operators.hpp
Normal file
23
libs/geometry/region2d/binary_operators.hpp
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/region2d.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
using MultiRegionI = std::vector<RegionI>;
|
||||
|
||||
/// @name Next functions work in _append_ mode to the result.
|
||||
/// @{
|
||||
void IntersectRegions(RegionI const & r1, RegionI const & r2, MultiRegionI & res);
|
||||
void DiffRegions(RegionI const & r1, RegionI const & r2, MultiRegionI & res);
|
||||
/// @}
|
||||
|
||||
MultiRegionI IntersectRegions(RegionI const & r1, MultiRegionI const & r2);
|
||||
|
||||
/// Union \a r with \a res and save to \a res.
|
||||
void AddRegion(RegionI const & r, MultiRegionI & res);
|
||||
|
||||
uint64_t Area(MultiRegionI const & rgn);
|
||||
} // namespace m2
|
||||
155
libs/geometry/region2d/boost_concept.hpp
Normal file
155
libs/geometry/region2d/boost_concept.hpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/region2d.hpp"
|
||||
|
||||
#include <boost/polygon/detail/polygon_sort_adaptor.hpp>
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wreturn-std-move"
|
||||
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
|
||||
#endif
|
||||
#include <boost/polygon/polygon.hpp>
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace boost
|
||||
{
|
||||
namespace polygon
|
||||
{
|
||||
typedef int32_t my_coord_t;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Point concept.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
typedef m2::PointI my_point_t;
|
||||
|
||||
template <>
|
||||
struct geometry_concept<my_point_t>
|
||||
{
|
||||
typedef point_concept type;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct point_traits<my_point_t>
|
||||
{
|
||||
typedef my_point_t::value_type coordinate_type;
|
||||
|
||||
static inline coordinate_type get(my_point_t const & p, orientation_2d o) { return ((o == HORIZONTAL) ? p.x : p.y); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct point_mutable_traits<my_point_t>
|
||||
{
|
||||
typedef my_point_t::value_type Coord;
|
||||
|
||||
static inline void set(my_point_t & p, orientation_2d o, Coord v)
|
||||
{
|
||||
if (o == HORIZONTAL)
|
||||
p.x = v;
|
||||
else
|
||||
p.y = v;
|
||||
}
|
||||
|
||||
static inline my_point_t construct(Coord x, Coord y) { return my_point_t(x, y); }
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Polygon concept.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
typedef m2::RegionI my_region_t;
|
||||
|
||||
template <>
|
||||
struct geometry_concept<my_region_t>
|
||||
{
|
||||
typedef polygon_concept type;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_traits<my_region_t>
|
||||
{
|
||||
typedef my_region_t::Coord coordinate_type;
|
||||
typedef my_region_t::IteratorT iterator_type;
|
||||
typedef my_region_t::Value point_type;
|
||||
|
||||
// Get the begin iterator
|
||||
static inline iterator_type begin_points(my_region_t const & t) { return t.Begin(); }
|
||||
|
||||
// Get the end iterator
|
||||
static inline iterator_type end_points(my_region_t const & t) { return t.End(); }
|
||||
|
||||
// Get the number of sides of the polygon
|
||||
static inline size_t size(my_region_t const & t) { return t.Size(); }
|
||||
|
||||
// Get the winding direction of the polygon
|
||||
static inline winding_direction winding(my_region_t const & /*t*/) { return unknown_winding; }
|
||||
};
|
||||
|
||||
struct my_point_getter
|
||||
{
|
||||
my_point_t operator()(point_data<my_coord_t> const & t) { return my_point_t(t.x(), t.y()); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_mutable_traits<my_region_t>
|
||||
{
|
||||
// expects stl style iterators
|
||||
template <typename IterT>
|
||||
static inline my_region_t & set_points(my_region_t & t, IterT b, IterT e)
|
||||
{
|
||||
t.AssignEx(b, e, my_point_getter());
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Polygon set concept.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
typedef std::vector<my_region_t> my_region_set_t;
|
||||
|
||||
template <>
|
||||
struct geometry_concept<my_region_set_t>
|
||||
{
|
||||
typedef polygon_set_concept type;
|
||||
};
|
||||
|
||||
// next we map to the concept through traits
|
||||
template <>
|
||||
struct polygon_set_traits<my_region_set_t>
|
||||
{
|
||||
typedef my_coord_t coordinate_type;
|
||||
typedef my_region_set_t::const_iterator iterator_type;
|
||||
typedef my_region_set_t operator_arg_type;
|
||||
|
||||
static inline iterator_type begin(my_region_set_t const & t) { return t.begin(); }
|
||||
|
||||
static inline iterator_type end(my_region_set_t const & t) { return t.end(); }
|
||||
|
||||
// don't worry about these, just return false from them
|
||||
static inline bool clean(my_region_set_t const & /*t*/) { return false; }
|
||||
static inline bool sorted(my_region_set_t const & /*t*/) { return false; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_set_mutable_traits<my_region_set_t>
|
||||
{
|
||||
template <typename IterT>
|
||||
static inline void set(my_region_set_t & poly_set, IterT b, IterT e)
|
||||
{
|
||||
poly_set.clear();
|
||||
|
||||
// this is kind of cheesy. I am copying the unknown input geometry
|
||||
// into my own polygon set and then calling get to populate the std::vector
|
||||
polygon_set_data<my_coord_t> ps;
|
||||
ps.insert(b, e);
|
||||
ps.get(poly_set);
|
||||
|
||||
// if you had your own odd-ball polygon set you would probably have
|
||||
// to iterate through each polygon at this point and do something extra
|
||||
}
|
||||
};
|
||||
} // namespace polygon
|
||||
} // namespace boost
|
||||
59
libs/geometry/robust_orientation.cpp
Normal file
59
libs/geometry/robust_orientation.cpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#include "geometry/robust_orientation.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wconditional-uninitialized"
|
||||
#include "3party/robust/predicates.c"
|
||||
#pragma clang diagnostic pop
|
||||
#else
|
||||
#include "3party/robust/predicates.c"
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace m2::robust
|
||||
{
|
||||
bool Init()
|
||||
{
|
||||
exactinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
double OrientedS(PointD const & p1, PointD const & p2, PointD const & p)
|
||||
{
|
||||
static bool const res = Init();
|
||||
ASSERT_EQUAL(res, true, ());
|
||||
UNUSED_VALUE(res);
|
||||
|
||||
double a[] = {p1.x, p1.y};
|
||||
double b[] = {p2.x, p2.y};
|
||||
double c[] = {p.x, p.y};
|
||||
|
||||
return orient2d(a, b, c);
|
||||
}
|
||||
|
||||
bool IsSegmentInCone(PointD const & v, PointD const & v1, PointD const & vPrev, PointD const & vNext)
|
||||
{
|
||||
double const cpLR = OrientedS(vPrev, vNext, v);
|
||||
|
||||
if (cpLR == 0.0)
|
||||
{
|
||||
// Points vPrev, v, vNext placed on one line;
|
||||
// use property that polygon has CCW orientation.
|
||||
return OrientedS(vPrev, vNext, v1) > 0.0;
|
||||
}
|
||||
|
||||
if (cpLR < 0.0)
|
||||
{
|
||||
// vertex is concave
|
||||
return OrientedS(v, vPrev, v1) < 0.0 && OrientedS(v, vNext, v1) > 0.0;
|
||||
}
|
||||
// vertex is convex
|
||||
return OrientedS(v, vPrev, v1) < 0.0 || OrientedS(v, vNext, v1) > 0.0;
|
||||
}
|
||||
} // namespace m2::robust
|
||||
139
libs/geometry/robust_orientation.hpp
Normal file
139
libs/geometry/robust_orientation.hpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
namespace robust
|
||||
{
|
||||
bool Init();
|
||||
|
||||
/// @return > 0, (p1, p2, p) - is CCW (left oriented)
|
||||
/// < 0, (p1, p2, p) - is CW (right oriented)
|
||||
/// Same as CrossProduct(p1 - p, p2 - p), but uses robust calculations.
|
||||
double OrientedS(PointD const & p1, PointD const & p2, PointD const & p);
|
||||
|
||||
/// Is segment (v, v1) in cone (vPrev, v, vNext)?
|
||||
/// @precondition (vPrev, v, vNext) is CCW.
|
||||
bool IsSegmentInCone(PointD const & v, PointD const & v1, PointD const & vPrev, PointD const & vNext);
|
||||
|
||||
template <typename T>
|
||||
bool Between(T a, T b, T c)
|
||||
{
|
||||
return std::min(a, b) <= c && c <= std::max(a, b);
|
||||
}
|
||||
|
||||
template <typename Point>
|
||||
bool IsInSection(Point const & p1, Point const & p2, Point const & p)
|
||||
{
|
||||
return Between(p1.x, p2.x, p.x) && Between(p1.y, p2.y, p.y);
|
||||
}
|
||||
|
||||
template <typename Iter>
|
||||
bool CheckPolygonSelfIntersections(Iter beg, Iter end)
|
||||
{
|
||||
Iter last = end;
|
||||
--last;
|
||||
|
||||
for (Iter i = beg; i != last; ++i)
|
||||
{
|
||||
for (Iter j = i; j != end; ++j)
|
||||
{
|
||||
// do not check intersection of neibour segments
|
||||
if (std::distance(i, j) <= 1 || (i == beg && j == last))
|
||||
continue;
|
||||
|
||||
Iter ii = base::NextIterInCycle(i, beg, end);
|
||||
Iter jj = base::NextIterInCycle(j, beg, end);
|
||||
PointD a = *i, b = *ii, c = *j, d = *jj;
|
||||
|
||||
// check for rect intersection
|
||||
if (std::max(a.x, b.x) < std::min(c.x, d.x) || std::min(a.x, b.x) > std::max(c.x, d.x) ||
|
||||
std::max(a.y, b.y) < std::min(c.y, d.y) || std::min(a.y, b.y) > std::max(c.y, d.y))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
double const s1 = OrientedS(a, b, c);
|
||||
double const s2 = OrientedS(a, b, d);
|
||||
double const s3 = OrientedS(c, d, a);
|
||||
double const s4 = OrientedS(c, d, b);
|
||||
|
||||
// check if sections have any intersection
|
||||
if (s1 * s2 > 0.0 || s3 * s4 > 0.0)
|
||||
continue;
|
||||
|
||||
// Common principle if any point lay exactly on section, check 2 variants:
|
||||
// - only touching (><) - don't return as intersection;
|
||||
// - 'X'-crossing - return as intersection;
|
||||
// 'X'-crossing defines when points lay in different cones.
|
||||
|
||||
if (s1 == 0.0 && IsInSection(a, b, c))
|
||||
{
|
||||
PointD const prev = *base::PrevIterInCycle(j, beg, end);
|
||||
|
||||
PointD test[] = {a, b};
|
||||
if (a == c)
|
||||
test[0] = *base::PrevIterInCycle(i, beg, end);
|
||||
if (b == c)
|
||||
test[1] = *base::NextIterInCycle(ii, beg, end);
|
||||
|
||||
if (IsSegmentInCone(c, test[0], prev, d) == IsSegmentInCone(c, test[1], prev, d))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s2 == 0.0 && IsInSection(a, b, d))
|
||||
{
|
||||
PointD const next = *base::NextIterInCycle(jj, beg, end);
|
||||
|
||||
PointD test[] = {a, b};
|
||||
if (a == d)
|
||||
test[0] = *base::PrevIterInCycle(i, beg, end);
|
||||
if (b == d)
|
||||
test[1] = *base::NextIterInCycle(ii, beg, end);
|
||||
|
||||
if (IsSegmentInCone(d, test[0], c, next) == IsSegmentInCone(d, test[1], c, next))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s3 == 0.0 && IsInSection(c, d, a))
|
||||
{
|
||||
PointD const prev = *base::PrevIterInCycle(i, beg, end);
|
||||
|
||||
PointD test[] = {c, d};
|
||||
if (c == a)
|
||||
test[0] = *base::PrevIterInCycle(j, beg, end);
|
||||
if (d == a)
|
||||
test[1] = *base::NextIterInCycle(jj, beg, end);
|
||||
|
||||
if (IsSegmentInCone(a, test[0], prev, b) == IsSegmentInCone(a, test[1], prev, b))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s4 == 0.0 && IsInSection(c, d, b))
|
||||
{
|
||||
PointD const next = *base::NextIterInCycle(ii, beg, end);
|
||||
|
||||
PointD test[] = {c, d};
|
||||
if (c == b)
|
||||
test[0] = *base::PrevIterInCycle(j, beg, end);
|
||||
if (d == b)
|
||||
test[1] = *base::NextIterInCycle(jj, beg, end);
|
||||
|
||||
if (IsSegmentInCone(b, test[0], a, next) == IsSegmentInCone(b, test[1], a, next))
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace robust
|
||||
} // namespace m2
|
||||
534
libs/geometry/screenbase.cpp
Normal file
534
libs/geometry/screenbase.cpp
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
#include "geometry/screenbase.hpp"
|
||||
#include "geometry/angles.hpp"
|
||||
#include "geometry/transformations.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
double constexpr kPerspectiveAngleFOV = math::pi / 3.0;
|
||||
double constexpr kMaxPerspectiveAngle1 = math::pi4;
|
||||
double constexpr kMaxPerspectiveAngle2 = math::pi * 55.0 / 180.0;
|
||||
|
||||
double constexpr kStartPerspectiveScale1 = 1.7e-5;
|
||||
double constexpr kEndPerspectiveScale1 = 0.3e-5;
|
||||
double constexpr kEndPerspectiveScale2 = 0.13e-5;
|
||||
|
||||
ScreenBase::ScreenBase()
|
||||
: m_Org(320, 240)
|
||||
, m_GlobalRect(m_Org, ang::AngleD(0), m2::RectD(-320, -240, 320, 240))
|
||||
, m_ClipRect(m_GlobalRect.GetGlobalRect())
|
||||
, m_ViewportRect(0, 0, 640, 480)
|
||||
, m_PixelRect(m_ViewportRect)
|
||||
, m_Scale(0.1)
|
||||
, m_Angle(0.0)
|
||||
, m_3dFOV(kPerspectiveAngleFOV)
|
||||
, m_3dNearZ(0.1)
|
||||
, m_3dFarZ(0.0)
|
||||
, m_3dAngleX(0.0)
|
||||
, m_3dMaxAngleX(0.0)
|
||||
, m_3dScale(1.0)
|
||||
, m_isPerspective(false)
|
||||
, m_isAutoPerspective(false)
|
||||
{
|
||||
m_GtoP = math::Identity<double, 3>();
|
||||
m_PtoG = math::Identity<double, 3>();
|
||||
}
|
||||
|
||||
ScreenBase::ScreenBase(m2::RectI const & pxRect, m2::AnyRectD const & glbRect)
|
||||
{
|
||||
OnSize(pxRect);
|
||||
SetFromRect(glbRect);
|
||||
}
|
||||
|
||||
ScreenBase::ScreenBase(ScreenBase const & s, m2::PointD const & org, double scale, double angle)
|
||||
: m_Org(org)
|
||||
, m_ViewportRect(s.m_ViewportRect)
|
||||
, m_Scale(scale)
|
||||
, m_Angle(angle)
|
||||
{
|
||||
ASSERT_GREATER(m_Scale, 0.0, ());
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::UpdateDependentParameters()
|
||||
{
|
||||
m_PixelRect = CalculatePixelRect(m_Scale);
|
||||
|
||||
m_PtoG = math::Shift( /// 5. shifting on (E0, N0)
|
||||
math::Rotate( /// 4. rotating on the screen angle
|
||||
math::Scale( /// 3. scaling to translate pixel sizes to global
|
||||
math::Scale( /// 2. swapping the Y axis??? why??? supposed to be a rotation on -pi /
|
||||
/// 2 here.
|
||||
math::Shift( /// 1. shifting for the pixel center to become (0, 0)
|
||||
math::Identity<double, 3>(), -m_PixelRect.Center()),
|
||||
1, -1),
|
||||
m_Scale, m_Scale),
|
||||
m_Angle.cos(), m_Angle.sin()),
|
||||
m_Org);
|
||||
|
||||
m_GtoP = math::Inverse(m_PtoG);
|
||||
|
||||
m2::PointD const pxC = m_PixelRect.Center();
|
||||
m2::PointD const glbC = PtoG(pxC);
|
||||
double const szX = PtoG(m2::PointD(m_PixelRect.maxX(), pxC.y)).Length(glbC);
|
||||
double const szY = PtoG(m2::PointD(pxC.x, m_PixelRect.minY())).Length(glbC);
|
||||
|
||||
m_GlobalRect = m2::AnyRectD(m_Org, m_Angle, m2::RectD(-szX, -szY, szX, szY));
|
||||
m_ClipRect = m_GlobalRect.GetGlobalRect();
|
||||
|
||||
double constexpr kEps = 1.0E-5;
|
||||
double const angle = CalculatePerspectiveAngle(m_Scale);
|
||||
m_isPerspective = angle > 0.0;
|
||||
if (std::fabs(angle - m_3dAngleX) > kEps)
|
||||
{
|
||||
m_3dMaxAngleX = angle;
|
||||
m_3dScale = CalculateScale3d(angle);
|
||||
SetRotationAngle(angle);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
double ScreenBase::CalculateAutoPerspectiveAngle(double scale)
|
||||
{
|
||||
if (scale > kStartPerspectiveScale1)
|
||||
return 0.0;
|
||||
|
||||
if (scale > kEndPerspectiveScale1)
|
||||
{
|
||||
double const k = (kStartPerspectiveScale1 - scale) / (kStartPerspectiveScale1 - kEndPerspectiveScale1);
|
||||
return kMaxPerspectiveAngle1 * k;
|
||||
}
|
||||
|
||||
if (scale > kEndPerspectiveScale2)
|
||||
{
|
||||
double const k = (kEndPerspectiveScale1 - scale) / (kEndPerspectiveScale1 - kEndPerspectiveScale2);
|
||||
return kMaxPerspectiveAngle1 + (kMaxPerspectiveAngle2 - kMaxPerspectiveAngle1) * k;
|
||||
}
|
||||
|
||||
return kMaxPerspectiveAngle2 * 0.99;
|
||||
}
|
||||
|
||||
// static
|
||||
double ScreenBase::GetStartPerspectiveScale()
|
||||
{
|
||||
return kStartPerspectiveScale1;
|
||||
}
|
||||
|
||||
double ScreenBase::CalculatePerspectiveAngle(double scale) const
|
||||
{
|
||||
if (!m_isAutoPerspective)
|
||||
return m_3dAngleX;
|
||||
|
||||
return CalculateAutoPerspectiveAngle(scale);
|
||||
}
|
||||
|
||||
void ScreenBase::SetAutoPerspective(bool isAutoPerspective)
|
||||
{
|
||||
m_isAutoPerspective = isAutoPerspective;
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::SetFromRects(m2::AnyRectD const & glbRect, m2::RectD const & pxRect)
|
||||
{
|
||||
m2::RectD const & lRect = glbRect.GetLocalRect();
|
||||
ASSERT(lRect.IsValid(), (lRect));
|
||||
ASSERT(!pxRect.IsEmptyInterior(), (pxRect));
|
||||
|
||||
double const hScale = lRect.SizeX() / pxRect.SizeX();
|
||||
double const vScale = lRect.SizeY() / pxRect.SizeY();
|
||||
m_Scale = std::max(hScale, vScale);
|
||||
ASSERT_GREATER(m_Scale, 0.0, ());
|
||||
|
||||
m_Angle = glbRect.Angle();
|
||||
m_Org = glbRect.GlobalCenter();
|
||||
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::SetFromRect(m2::AnyRectD const & glbRect)
|
||||
{
|
||||
SetFromRects(glbRect, m_PixelRect);
|
||||
}
|
||||
|
||||
void ScreenBase::SetFromParams(m2::PointD const & org, double angle, double scale)
|
||||
{
|
||||
ASSERT_GREATER(scale, 0.0, ());
|
||||
m_Scale = scale;
|
||||
m_Angle = ang::Angle<double>(angle);
|
||||
m_Org = org;
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::MatchGandP(m2::PointD const & g, m2::PointD const & p)
|
||||
{
|
||||
m2::PointD g_current = PtoG(p);
|
||||
SetOrg(m_Org - g_current + g);
|
||||
}
|
||||
|
||||
void ScreenBase::MatchGandP3d(m2::PointD const & g, m2::PointD const & p3d)
|
||||
{
|
||||
m2::PointD g_current = PtoG(P3dtoP(p3d));
|
||||
SetOrg(m_Org - g_current + g);
|
||||
}
|
||||
|
||||
void ScreenBase::SetOrg(m2::PointD const & p)
|
||||
{
|
||||
m_Org = p;
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::Move(double dx, double dy)
|
||||
{
|
||||
m_Org = PtoG(GtoP(m_Org) - m2::PointD(dx, dy));
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::MoveG(m2::PointD const & p)
|
||||
{
|
||||
m_Org -= p;
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::Scale(double scale)
|
||||
{
|
||||
ASSERT_GREATER(scale, 0, ());
|
||||
m_Scale /= scale;
|
||||
ASSERT_GREATER(m_Scale, 0.0, ());
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::Rotate(double angle)
|
||||
{
|
||||
m_Angle = ang::AngleD(m_Angle.val() + angle);
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::OnSize(m2::RectI const & r)
|
||||
{
|
||||
m_ViewportRect = m2::RectD(r);
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::OnSize(int x0, int y0, int w, int h)
|
||||
{
|
||||
OnSize(m2::RectI(x0, y0, x0 + w, y0 + h));
|
||||
}
|
||||
|
||||
void ScreenBase::SetScale(double scale)
|
||||
{
|
||||
ASSERT_GREATER(scale, 0.0, ());
|
||||
m_Scale = scale;
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::SetAngle(double angle)
|
||||
{
|
||||
m_Angle = ang::AngleD(angle);
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
int ScreenBase::GetWidth() const
|
||||
{
|
||||
return math::iround(m_PixelRect.SizeX());
|
||||
}
|
||||
|
||||
int ScreenBase::GetHeight() const
|
||||
{
|
||||
return math::iround(m_PixelRect.SizeY());
|
||||
}
|
||||
|
||||
ScreenBase::MatrixT ScreenBase::CalcTransform(m2::PointD const & oldPt1, m2::PointD const & oldPt2,
|
||||
m2::PointD const & newPt1, m2::PointD const & newPt2, bool allowRotate,
|
||||
bool allowScale)
|
||||
{
|
||||
double const s = allowScale ? newPt1.Length(newPt2) / oldPt1.Length(oldPt2) : 1.0;
|
||||
double const a = allowRotate ? ang::AngleTo(newPt1, newPt2) - ang::AngleTo(oldPt1, oldPt2) : 0.0;
|
||||
|
||||
MatrixT m =
|
||||
math::Shift(math::Scale(math::Rotate(math::Shift(math::Identity<double, 3>(), -oldPt1.x, -oldPt1.y), a), s, s),
|
||||
newPt1.x, newPt1.y);
|
||||
return m;
|
||||
}
|
||||
|
||||
void ScreenBase::SetGtoPMatrix(MatrixT const & m)
|
||||
{
|
||||
m_GtoP = m;
|
||||
m_PtoG = math::Inverse(m_GtoP);
|
||||
/// Extracting transformation params, assuming that the matrix
|
||||
/// somehow represent a valid screen transformation
|
||||
/// into m_PixelRectangle
|
||||
double dx, dy, a, s;
|
||||
ExtractGtoPParams(m, a, s, dx, dy);
|
||||
m_Angle = ang::AngleD(-a);
|
||||
ASSERT_GREATER(s, 0.0, ());
|
||||
m_Scale = 1 / s;
|
||||
ASSERT_GREATER(m_Scale, 0.0, ());
|
||||
m_Org = PtoG(m_PixelRect.Center());
|
||||
|
||||
UpdateDependentParameters();
|
||||
}
|
||||
|
||||
void ScreenBase::GtoP(m2::RectD const & glbRect, m2::RectD & pxRect) const
|
||||
{
|
||||
pxRect = m2::RectD(GtoP(glbRect.LeftTop()), GtoP(glbRect.RightBottom()));
|
||||
}
|
||||
|
||||
void ScreenBase::PtoG(m2::RectD const & pxRect, m2::RectD & glbRect) const
|
||||
{
|
||||
glbRect = m2::RectD(PtoG(pxRect.LeftTop()), PtoG(pxRect.RightBottom()));
|
||||
}
|
||||
|
||||
void ScreenBase::GetTouchRect(m2::PointD const & pixPoint, double pixRadius, m2::AnyRectD & glbRect) const
|
||||
{
|
||||
double const r = pixRadius * m_Scale;
|
||||
glbRect = m2::AnyRectD(PtoG(pixPoint), m_Angle, m2::RectD(-r, -r, r, r));
|
||||
}
|
||||
|
||||
void ScreenBase::GetTouchRect(m2::PointD const & pixPoint, double const pxWidth, double const pxHeight,
|
||||
m2::AnyRectD & glbRect) const
|
||||
{
|
||||
double const width = pxWidth * m_Scale;
|
||||
double const height = pxHeight * m_Scale;
|
||||
glbRect = m2::AnyRectD(PtoG(pixPoint), m_Angle, m2::RectD(-width, -height, width, height));
|
||||
}
|
||||
|
||||
bool IsPanningAndRotate(ScreenBase const & s1, ScreenBase const & s2)
|
||||
{
|
||||
m2::RectD const & r1 = s1.GlobalRect().GetLocalRect();
|
||||
m2::RectD const & r2 = s2.GlobalRect().GetLocalRect();
|
||||
|
||||
m2::PointD c1 = r1.Center();
|
||||
m2::PointD c2 = r2.Center();
|
||||
|
||||
m2::PointD globPt(c1.x - r1.minX(), c1.y - r1.minY());
|
||||
|
||||
m2::PointD p1 = s1.GtoP(s1.GlobalRect().ConvertFrom(c1)) - s1.GtoP(s1.GlobalRect().ConvertFrom(c1 + globPt));
|
||||
m2::PointD p2 = s2.GtoP(s2.GlobalRect().ConvertFrom(c2)) - s2.GtoP(s2.GlobalRect().ConvertFrom(c2 + globPt));
|
||||
|
||||
return p1.EqualDxDy(p2, 0.00001);
|
||||
}
|
||||
|
||||
void ScreenBase::ExtractGtoPParams(MatrixT const & m, double & a, double & s, double & dx, double & dy)
|
||||
{
|
||||
s = sqrt(m(0, 0) * m(0, 0) + m(0, 1) * m(0, 1));
|
||||
|
||||
a = ang::AngleIn2PI(atan2(-m(0, 1), m(0, 0)));
|
||||
|
||||
dx = m(2, 0);
|
||||
dy = m(2, 1);
|
||||
}
|
||||
|
||||
double ScreenBase::CalculateScale3d(double rotationAngle) const
|
||||
{
|
||||
double const halfFOV = m_3dFOV / 2.0;
|
||||
double const cameraZ = 1.0 / tan(halfFOV);
|
||||
|
||||
// Ratio of the expanded plane's size to the original size.
|
||||
double const y3dScale = cos(rotationAngle) + sin(rotationAngle) * tan(halfFOV + rotationAngle);
|
||||
double const x3dScale = 1.0 + 2 * sin(rotationAngle) * cos(halfFOV) / (cameraZ * cos(halfFOV + rotationAngle));
|
||||
|
||||
return std::max(x3dScale, y3dScale);
|
||||
}
|
||||
|
||||
m2::RectD ScreenBase::CalculatePixelRect(double scale) const
|
||||
{
|
||||
double const angle = CalculatePerspectiveAngle(scale);
|
||||
if (angle > 0.0)
|
||||
{
|
||||
double const scale3d = CalculateScale3d(angle);
|
||||
|
||||
return m2::RectD(m2::PointD(0.0, 0.0), m2::PointD(m_ViewportRect.maxX(), m_ViewportRect.maxY()) * scale3d);
|
||||
}
|
||||
|
||||
return m_ViewportRect;
|
||||
}
|
||||
|
||||
// Place the camera at the distance, where it gives the same view of plane as the
|
||||
// orthogonal projection does. Calculate what part of the map would be visible,
|
||||
// when it is rotated through maxRotationAngle around its near horizontal side.
|
||||
void ScreenBase::ApplyPerspective(double currentRotationAngle, double maxRotationAngle, double angleFOV)
|
||||
{
|
||||
ASSERT_GREATER(angleFOV, 0.0, ());
|
||||
ASSERT_LESS(angleFOV, math::pi2, ());
|
||||
ASSERT_GREATER_OR_EQUAL(maxRotationAngle, 0.0, ());
|
||||
ASSERT_LESS(maxRotationAngle, math::pi2, ());
|
||||
|
||||
m_isPerspective = true;
|
||||
m_isAutoPerspective = false;
|
||||
|
||||
m_3dMaxAngleX = maxRotationAngle;
|
||||
m_3dAngleX = currentRotationAngle;
|
||||
m_3dFOV = angleFOV;
|
||||
|
||||
double const old_dy = m_ViewportRect.SizeY() * (m_3dScale - 1.0);
|
||||
|
||||
m_3dScale = CalculateScale3d(m_3dMaxAngleX);
|
||||
double const new_dy = m_ViewportRect.SizeY() * (m_3dScale - 1.0);
|
||||
|
||||
SetRotationAngle(currentRotationAngle);
|
||||
|
||||
Move(0.0, (new_dy - old_dy) / 2.0);
|
||||
}
|
||||
|
||||
// Place the camera at the distance, where it gives the same view of plane as the
|
||||
// orthogonal projection does and rotate the map plane around its near horizontal side.
|
||||
void ScreenBase::SetRotationAngle(double rotationAngle)
|
||||
{
|
||||
ASSERT(rotationAngle == 0.0 || (rotationAngle > 0.0 && m_isPerspective), ());
|
||||
ASSERT_GREATER_OR_EQUAL(rotationAngle, 0.0, ());
|
||||
ASSERT_LESS_OR_EQUAL(rotationAngle, m_3dMaxAngleX, ());
|
||||
|
||||
if (rotationAngle > m_3dMaxAngleX)
|
||||
rotationAngle = m_3dMaxAngleX;
|
||||
|
||||
m_3dAngleX = rotationAngle;
|
||||
|
||||
double const halfFOV = m_3dFOV / 2.0;
|
||||
double const cameraZ = 1.0 / tan(halfFOV);
|
||||
|
||||
double const offsetZ = cameraZ + sin(m_3dAngleX) * m_3dScale;
|
||||
double const offsetY = cos(m_3dAngleX) * m_3dScale - 1.0;
|
||||
|
||||
Matrix3dT scaleM = math::Identity<double, 4>();
|
||||
scaleM(0, 0) = m_3dScale;
|
||||
scaleM(1, 1) = m_3dScale;
|
||||
|
||||
Matrix3dT rotateM = math::Identity<double, 4>();
|
||||
rotateM(1, 1) = cos(m_3dAngleX);
|
||||
rotateM(1, 2) = sin(m_3dAngleX);
|
||||
rotateM(2, 1) = -sin(m_3dAngleX);
|
||||
rotateM(2, 2) = cos(m_3dAngleX);
|
||||
|
||||
Matrix3dT translateM = math::Identity<double, 4>();
|
||||
translateM(3, 1) = offsetY;
|
||||
translateM(3, 2) = offsetZ;
|
||||
|
||||
Matrix3dT projectionM = math::Zero<double, 4>();
|
||||
m_3dFarZ = cameraZ + 2.0 * sin(m_3dAngleX) * m_3dScale;
|
||||
projectionM(0, 0) = projectionM(1, 1) = cameraZ;
|
||||
projectionM(2, 2) = m_3dAngleX != 0.0 ? (m_3dFarZ + m_3dNearZ) / (m_3dFarZ - m_3dNearZ) : 0.0;
|
||||
projectionM(2, 3) = 1.0;
|
||||
projectionM(3, 2) = m_3dAngleX != 0.0 ? -2.0 * m_3dFarZ * m_3dNearZ / (m_3dFarZ - m_3dNearZ) : 0.0;
|
||||
|
||||
m_Pto3d = scaleM * rotateM * translateM * projectionM;
|
||||
m_3dtoP = math::Inverse(m_Pto3d);
|
||||
}
|
||||
|
||||
void ScreenBase::ResetPerspective()
|
||||
{
|
||||
m_isPerspective = false;
|
||||
m_isAutoPerspective = false;
|
||||
|
||||
double const old_dy = m_ViewportRect.SizeY() * (m_3dScale - 1.0);
|
||||
|
||||
m_3dScale = 1.0;
|
||||
m_3dAngleX = 0.0;
|
||||
m_3dMaxAngleX = 0.0;
|
||||
|
||||
Move(0.0, -old_dy / 2.0);
|
||||
}
|
||||
|
||||
m2::PointD ScreenBase::PtoP3d(m2::PointD const & pt) const
|
||||
{
|
||||
return PtoP3d(pt, 0.0);
|
||||
}
|
||||
|
||||
double ScreenBase::GetZScale() const
|
||||
{
|
||||
double const averageScale3d = m_isPerspective ? 2.7 : 1.0;
|
||||
return 2.0 / (m_Scale * m_ViewportRect.SizeY() * averageScale3d);
|
||||
}
|
||||
|
||||
m2::PointD ScreenBase::PtoP3d(m2::PointD const & pt, double ptZ) const
|
||||
{
|
||||
if (!m_isPerspective)
|
||||
return pt;
|
||||
Vector3dT const normalizedPoint{float(2.0 * pt.x / m_PixelRect.SizeX() - 1.0),
|
||||
-float(2.0 * pt.y / m_PixelRect.SizeY() - 1.0), float(ptZ * GetZScale()), 1.0};
|
||||
|
||||
Vector3dT const perspectivePoint = normalizedPoint * m_Pto3d;
|
||||
|
||||
m2::RectD const viewport = PixelRectIn3d();
|
||||
m2::PointD const pixelPointPerspective(
|
||||
(perspectivePoint(0, 0) / perspectivePoint(0, 3) + 1.0) * viewport.SizeX() / 2.0,
|
||||
(-perspectivePoint(0, 1) / perspectivePoint(0, 3) + 1.0) * viewport.SizeY() / 2.0);
|
||||
|
||||
return pixelPointPerspective;
|
||||
}
|
||||
|
||||
m2::PointD ScreenBase::P3dtoP(m2::PointD const & pt) const
|
||||
{
|
||||
return P3dtoP(pt, 0.0 /* ptZ */);
|
||||
}
|
||||
|
||||
m2::PointD ScreenBase::P3dtoP(m2::PointD const & pt, double ptZ) const
|
||||
{
|
||||
if (!m_isPerspective)
|
||||
return pt;
|
||||
|
||||
double const normalizedX = 2.0 * pt.x / PixelRectIn3d().SizeX() - 1.0;
|
||||
double const normalizedY = -2.0 * pt.y / PixelRectIn3d().SizeY() + 1.0;
|
||||
|
||||
double normalizedZ = 0.0;
|
||||
|
||||
if (m_3dAngleX != 0.0)
|
||||
{
|
||||
double const halfFOV = m_3dFOV / 2.0;
|
||||
double const cameraZ = 1.0 / tan(halfFOV);
|
||||
|
||||
double const tanX = tan(m_3dAngleX);
|
||||
double cameraDistanceZ = cameraZ * (1.0 + (normalizedY + 1.0) * tanX / (cameraZ - normalizedY * tanX));
|
||||
|
||||
if (ptZ != 0.0)
|
||||
{
|
||||
double const t = sqrt(cameraZ * cameraZ + normalizedY * normalizedY);
|
||||
double const cosBeta = cameraZ / t;
|
||||
double const sinBeta = normalizedY / t;
|
||||
double const dz = ptZ * GetZScale() * cosBeta / (cos(m_3dAngleX) * cosBeta - sin(m_3dAngleX) * sinBeta);
|
||||
|
||||
cameraDistanceZ -= dz;
|
||||
}
|
||||
|
||||
double const a = (m_3dFarZ + m_3dNearZ) / (m_3dFarZ - m_3dNearZ);
|
||||
double const b = -2.0 * m_3dFarZ * m_3dNearZ / (m_3dFarZ - m_3dNearZ);
|
||||
normalizedZ = a + b / cameraDistanceZ;
|
||||
}
|
||||
|
||||
Vector3dT const normalizedPoint{normalizedX, normalizedY, normalizedZ, 1.0};
|
||||
|
||||
Vector3dT const originalPoint = normalizedPoint * m_3dtoP;
|
||||
|
||||
m2::PointD const pixelPointOriginal =
|
||||
m2::PointD((originalPoint(0, 0) / originalPoint(0, 3) + 1.0) * PixelRect().SizeX() / 2.0,
|
||||
(-originalPoint(0, 1) / originalPoint(0, 3) + 1.0) * PixelRect().SizeY() / 2.0);
|
||||
|
||||
return pixelPointOriginal;
|
||||
}
|
||||
|
||||
bool ScreenBase::IsReverseProjection3d(m2::PointD const & pt) const
|
||||
{
|
||||
if (!m_isPerspective)
|
||||
return false;
|
||||
|
||||
Vector3dT const normalizedPoint{float(2.0 * pt.x / m_PixelRect.SizeX() - 1.0),
|
||||
-float(2.0 * pt.y / m_PixelRect.SizeY() - 1.0), 0.0, 1.0};
|
||||
|
||||
Vector3dT const perspectivePoint = normalizedPoint * m_Pto3d;
|
||||
return perspectivePoint(0, 3) < 0.0;
|
||||
}
|
||||
|
||||
ScreenBase::Matrix3dT ScreenBase::GetModelView() const
|
||||
{
|
||||
return ScreenBase::Matrix3dT{
|
||||
m_GtoP(0, 0), m_GtoP(1, 0), 0, m_GtoP(2, 0), m_GtoP(0, 1), m_GtoP(1, 1), 0, m_GtoP(2, 1), 0, 0, 1, 0, 0, 0, 0, 1};
|
||||
}
|
||||
|
||||
ScreenBase::Matrix3dT ScreenBase::GetModelView(m2::PointD const & pivot, double scalar) const
|
||||
{
|
||||
MatrixT const & m = m_GtoP;
|
||||
double const s = 1.0 / scalar;
|
||||
return ScreenBase::Matrix3dT{s * m(0, 0), s * m(1, 0), 0, m(2, 0) + pivot.x * m(0, 0) + pivot.y * m(1, 0),
|
||||
s * m(0, 1), s * m(1, 1), 0, m(2, 1) + pivot.x * m(0, 1) + pivot.y * m(1, 1),
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1};
|
||||
}
|
||||
187
libs/geometry/screenbase.hpp
Normal file
187
libs/geometry/screenbase.hpp
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
#pragma once
|
||||
|
||||
#include "geometry/any_rect2d.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/rect2d.hpp"
|
||||
|
||||
#include "base/matrix.hpp"
|
||||
|
||||
class ScreenBase
|
||||
{
|
||||
public:
|
||||
using MatrixT = math::Matrix<double, 3, 3>;
|
||||
using Matrix3dT = math::Matrix<double, 4, 4>;
|
||||
using Vector3dT = math::Matrix<double, 1, 4>;
|
||||
|
||||
ScreenBase();
|
||||
ScreenBase(m2::RectI const & pxRect, m2::AnyRectD const & glbRect);
|
||||
ScreenBase(ScreenBase const & s, m2::PointD const & org, double scale, double angle);
|
||||
|
||||
void SetFromRect(m2::AnyRectD const & rect);
|
||||
|
||||
void SetFromParams(m2::PointD const & org, double angle, double scale);
|
||||
void SetFromRects(m2::AnyRectD const & glbRect, m2::RectD const & pxRect);
|
||||
void SetOrg(m2::PointD const & p);
|
||||
|
||||
void Move(double dx, double dy);
|
||||
void MoveG(m2::PointD const & p);
|
||||
|
||||
/// scale global rect
|
||||
void Scale(double scale);
|
||||
void Rotate(double angle);
|
||||
|
||||
void OnSize(m2::RectI const & r);
|
||||
void OnSize(int x0, int y0, int w, int h);
|
||||
|
||||
double GetScale() const
|
||||
{
|
||||
ASSERT_GREATER(m_Scale, 0.0, ());
|
||||
return m_Scale;
|
||||
}
|
||||
void SetScale(double scale);
|
||||
|
||||
double GetAngle() const { return m_Angle.val(); }
|
||||
void SetAngle(double angle);
|
||||
|
||||
m2::PointD const & GetOrg() const { return m_Org; }
|
||||
|
||||
int GetWidth() const;
|
||||
int GetHeight() const;
|
||||
|
||||
m2::PointD GtoP(m2::PointD const & pt) const { return pt * m_GtoP; }
|
||||
|
||||
m2::PointD PtoG(m2::PointD const & pt) const { return pt * m_PtoG; }
|
||||
|
||||
void GtoP(double & x, double & y) const
|
||||
{
|
||||
double tempX = x;
|
||||
x = tempX * m_GtoP(0, 0) + y * m_GtoP(1, 0) + m_GtoP(2, 0);
|
||||
y = tempX * m_GtoP(1, 0) + y * m_GtoP(1, 1) + m_GtoP(2, 1);
|
||||
}
|
||||
|
||||
void PtoG(double & x, double & y) const
|
||||
{
|
||||
double const tempX = x;
|
||||
x = tempX * m_PtoG(0, 0) + y * m_PtoG(1, 0) + m_PtoG(2, 0);
|
||||
y = tempX * m_PtoG(0, 1) + y * m_PtoG(1, 1) + m_PtoG(2, 1);
|
||||
}
|
||||
|
||||
void GtoP(m2::RectD const & gr, m2::RectD & sr) const;
|
||||
void PtoG(m2::RectD const & pr, m2::RectD & gr) const;
|
||||
|
||||
void MatchGandP(m2::PointD const & g, m2::PointD const & p);
|
||||
void MatchGandP3d(m2::PointD const & g, m2::PointD const & p3d);
|
||||
|
||||
void GetTouchRect(m2::PointD const & pixPoint, double pixRadius, m2::AnyRectD & glbRect) const;
|
||||
void GetTouchRect(m2::PointD const & pixPoint, double const pxWidth, double const pxHeight,
|
||||
m2::AnyRectD & glbRect) const;
|
||||
|
||||
MatrixT const & GtoPMatrix() const { return m_GtoP; }
|
||||
MatrixT const & PtoGMatrix() const { return m_PtoG; }
|
||||
|
||||
m2::RectD const & PixelRect() const { return m_PixelRect; }
|
||||
m2::AnyRectD const & GlobalRect() const { return m_GlobalRect; }
|
||||
m2::RectD const & ClipRect() const { return m_ClipRect; }
|
||||
|
||||
void ApplyPerspective(double currentRotationAngle, double maxRotationAngle, double angleFOV);
|
||||
void ResetPerspective();
|
||||
|
||||
void SetRotationAngle(double rotationAngle);
|
||||
double GetRotationAngle() const { return m_3dAngleX; }
|
||||
double GetMaxRotationAngle() const { return m_3dMaxAngleX; }
|
||||
double GetAngleFOV() const { return m_3dFOV; }
|
||||
double GetScale3d() const { return m_3dScale; }
|
||||
double GetZScale() const;
|
||||
|
||||
double GetDepth3d() const { return m_3dFarZ - m_3dNearZ; }
|
||||
|
||||
m2::PointD P3dtoP(m2::PointD const & pt) const;
|
||||
m2::PointD P3dtoP(m2::PointD const & pt, double ptZ) const;
|
||||
|
||||
Matrix3dT const & Pto3dMatrix() const { return m_Pto3d; }
|
||||
bool isPerspective() const { return m_isPerspective; }
|
||||
bool isAutoPerspective() const { return m_isAutoPerspective; }
|
||||
void SetAutoPerspective(bool isAutoPerspective);
|
||||
|
||||
bool IsReverseProjection3d(m2::PointD const & pt) const;
|
||||
|
||||
m2::PointD PtoP3d(m2::PointD const & pt) const;
|
||||
m2::PointD PtoP3d(m2::PointD const & pt, double ptZ) const;
|
||||
|
||||
m2::RectD const & PixelRectIn3d() const { return m_ViewportRect; }
|
||||
|
||||
double CalculateScale3d(double rotationAngle) const;
|
||||
m2::RectD CalculatePixelRect(double scale) const;
|
||||
double CalculatePerspectiveAngle(double scale) const;
|
||||
|
||||
Matrix3dT GetModelView() const;
|
||||
Matrix3dT GetModelView(m2::PointD const & pivot, double scalar) const;
|
||||
|
||||
static double CalculateAutoPerspectiveAngle(double scale);
|
||||
static double GetStartPerspectiveScale();
|
||||
|
||||
/// Compute arbitrary pixel transformation, that translates the (oldPt1, oldPt2) -> (newPt1,
|
||||
/// newPt2)
|
||||
static MatrixT CalcTransform(m2::PointD const & oldPt1, m2::PointD const & oldPt2, m2::PointD const & newPt1,
|
||||
m2::PointD const & newPt2, bool allowRotate, bool allowScale);
|
||||
|
||||
/// Setting GtoP matrix extracts the Angle and m_Org parameters, leaving PixelRect intact
|
||||
void SetGtoPMatrix(MatrixT const & m);
|
||||
|
||||
/// Extracting parameters from matrix, that is supposed to represent GtoP transformation
|
||||
static void ExtractGtoPParams(MatrixT const & m, double & a, double & s, double & dx, double & dy);
|
||||
|
||||
bool operator!=(ScreenBase const & src) const { return !(*this == src); }
|
||||
|
||||
bool operator==(ScreenBase const & src) const { return (m_GtoP == src.m_GtoP) && (m_PtoG == src.m_PtoG); }
|
||||
|
||||
private:
|
||||
// Used when initializing m_GlobalRect.
|
||||
m2::PointD m_Org;
|
||||
|
||||
protected:
|
||||
// Update dependent parameters from base parameters.
|
||||
// Must be called when base parameters changed.
|
||||
void UpdateDependentParameters();
|
||||
|
||||
/// @group Dependent parameters
|
||||
/// Global to Pixel conversion matrix.
|
||||
/// |a11 a12 0|
|
||||
/// |x, y, 1| * |a21 a22 0| = |x', y', 1|
|
||||
/// |a31 a32 1|
|
||||
/// @{
|
||||
MatrixT m_GtoP;
|
||||
|
||||
/// Pixel to Global conversion matrix. Inverted GtoP matrix.
|
||||
MatrixT m_PtoG;
|
||||
|
||||
/// Global Rect
|
||||
m2::AnyRectD m_GlobalRect;
|
||||
|
||||
/// X-axis aligned global rect used for clipping
|
||||
m2::RectD m_ClipRect;
|
||||
|
||||
/// @}
|
||||
|
||||
Matrix3dT m_Pto3d;
|
||||
Matrix3dT m_3dtoP;
|
||||
|
||||
private:
|
||||
m2::RectD m_ViewportRect;
|
||||
m2::RectD m_PixelRect;
|
||||
|
||||
double m_Scale;
|
||||
ang::AngleD m_Angle;
|
||||
|
||||
double m_3dFOV;
|
||||
double m_3dNearZ;
|
||||
double m_3dFarZ;
|
||||
double m_3dAngleX;
|
||||
double m_3dMaxAngleX;
|
||||
double m_3dScale;
|
||||
bool m_isPerspective;
|
||||
bool m_isAutoPerspective;
|
||||
};
|
||||
|
||||
/// checking whether the s1 transforms into s2 without scaling, only with shift and rotation
|
||||
bool IsPanningAndRotate(ScreenBase const & s1, ScreenBase const & s2);
|
||||
100
libs/geometry/segment2d.cpp
Normal file
100
libs/geometry/segment2d.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include "geometry/segment2d.hpp"
|
||||
|
||||
#include "geometry/line2d.hpp"
|
||||
#include "geometry/robust_orientation.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace m2
|
||||
{
|
||||
bool IsPointOnSegmentEps(PointD const & pt, PointD const & p1, PointD const & p2, double eps)
|
||||
{
|
||||
double const t = robust::OrientedS(p1, p2, pt);
|
||||
|
||||
if (std::fabs(t) > eps)
|
||||
return false;
|
||||
|
||||
double minX = p1.x;
|
||||
double maxX = p2.x;
|
||||
if (maxX < minX)
|
||||
std::swap(maxX, minX);
|
||||
|
||||
double minY = p1.y;
|
||||
double maxY = p2.y;
|
||||
if (maxY < minY)
|
||||
std::swap(maxY, minY);
|
||||
|
||||
return pt.x >= minX - eps && pt.x <= maxX + eps && pt.y >= minY - eps && pt.y <= maxY + eps;
|
||||
}
|
||||
|
||||
bool IsPointOnSegment(PointD const & pt, PointD const & p1, PointD const & p2)
|
||||
{
|
||||
// The epsilon here is chosen quite arbitrarily, to pass paranoid
|
||||
// tests and to match our real-data geometry precision. If you have
|
||||
// better ideas how to check whether pt belongs to (p1, p2) segment
|
||||
// more precisely or without kEps, feel free to submit a pull
|
||||
// request.
|
||||
double constexpr kEps = 1e-100;
|
||||
return IsPointOnSegmentEps(pt, p1, p2, kEps);
|
||||
}
|
||||
|
||||
bool SegmentsIntersect(PointD const & a, PointD const & b, PointD const & c, PointD const & d)
|
||||
{
|
||||
using std::max, std::min;
|
||||
return max(a.x, b.x) >= min(c.x, d.x) && min(a.x, b.x) <= max(c.x, d.x) && max(a.y, b.y) >= min(c.y, d.y) &&
|
||||
min(a.y, b.y) <= max(c.y, d.y) && robust::OrientedS(a, b, c) * robust::OrientedS(a, b, d) <= 0.0 &&
|
||||
robust::OrientedS(c, d, a) * robust::OrientedS(c, d, b) <= 0.0;
|
||||
}
|
||||
|
||||
IntersectionResult Intersect(Segment2D const & seg1, Segment2D const & seg2, double eps)
|
||||
{
|
||||
if (!SegmentsIntersect(seg1.m_u, seg1.m_v, seg2.m_u, seg2.m_v))
|
||||
return IntersectionResult(IntersectionResult::Type::Zero);
|
||||
|
||||
Line2D const line1(seg1);
|
||||
Line2D const line2(seg2);
|
||||
auto const lineIntersection = Intersect(line1, line2, eps);
|
||||
if (lineIntersection.m_type != IntersectionResult::Type::One)
|
||||
return lineIntersection;
|
||||
|
||||
if (IsPointOnSegmentEps(lineIntersection.m_point, seg1.m_u, seg1.m_v, eps) &&
|
||||
IsPointOnSegmentEps(lineIntersection.m_point, seg2.m_u, seg2.m_v, eps))
|
||||
{
|
||||
return lineIntersection;
|
||||
}
|
||||
|
||||
return IntersectionResult(IntersectionResult::Type::Zero);
|
||||
}
|
||||
|
||||
std::string DebugPrint(Segment2D const & segment)
|
||||
{
|
||||
return "(" + DebugPrint(segment.m_u) + ", " + DebugPrint(segment.m_v) + ")";
|
||||
}
|
||||
|
||||
std::string DebugPrint(IntersectionResult::Type type)
|
||||
{
|
||||
using Type = IntersectionResult::Type;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Type::Zero: return "Zero";
|
||||
case Type::One: return "One";
|
||||
case Type::Infinity: return "Infinity";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::string DebugPrint(IntersectionResult const & result)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "Result [";
|
||||
if (result.m_type == IntersectionResult::Type::One)
|
||||
os << DebugPrint(result.m_point);
|
||||
else
|
||||
os << DebugPrint(result.m_type);
|
||||
os << "]";
|
||||
return os.str();
|
||||
}
|
||||
} // namespace m2
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue