#pragma once #include #include #include #ifdef BUILD_TESTING #include #endif #include "engine/direction.hpp" #include "engine/displacement.hpp" #include "utils/attributes.h" namespace devilution { template struct PointOf; using Point = PointOf; template constexpr DisplacementOf operator-(PointOf a, PointOf b); template struct PointOf { CoordT x; CoordT y; PointOf() = default; template DVL_ALWAYS_INLINE constexpr PointOf(PointOf other) : x(other.x) , y(other.y) { } DVL_ALWAYS_INLINE constexpr PointOf(CoordT x, CoordT y) : x(x) , y(y) { } template DVL_ALWAYS_INLINE explicit constexpr PointOf(DisplacementOf other) : x(other.deltaX) , y(other.deltaY) { } template DVL_ALWAYS_INLINE constexpr bool operator==(const PointOf &other) const { return x == other.x && y == other.y; } template DVL_ALWAYS_INLINE constexpr bool operator!=(const PointOf &other) const { return !(*this == other); } template DVL_ALWAYS_INLINE constexpr PointOf &operator+=(const DisplacementOf &displacement) { x += displacement.deltaX; y += displacement.deltaY; return *this; } DVL_ALWAYS_INLINE constexpr PointOf &operator+=(Direction direction) { return (*this) += DisplacementOf::type>(direction); } template DVL_ALWAYS_INLINE constexpr PointOf &operator-=(const DisplacementOf &displacement) { x -= displacement.deltaX; y -= displacement.deltaY; return *this; } DVL_ALWAYS_INLINE constexpr PointOf &operator*=(const float factor) { x = static_cast(x * factor); y = static_cast(y * factor); return *this; } DVL_ALWAYS_INLINE constexpr PointOf &operator*=(const int factor) { x *= factor; y *= factor; return *this; } DVL_ALWAYS_INLINE constexpr PointOf &operator/=(const int factor) { x /= factor; y /= factor; return *this; } DVL_ALWAYS_INLINE constexpr PointOf operator-() const { static_assert(std::is_signed::value, "CoordT must be signed"); return { -x, -y }; } /** * @brief Fast approximate distance between two points, using only integer arithmetic, with less than ~5% error * @param other Pointer to which we want the distance * @return Magnitude of vector this -> other */ template constexpr int ApproxDistance(PointOf other) const { const Displacement offset = abs(Point(*this) - Point(other)); const auto [min, max] = std::minmax(offset.deltaX, offset.deltaY); int approx = max * 1007 + min * 441; if (max < (min * 16)) approx -= max * 40; return (approx + 512) / 1024; } /** * @brief Calculates the exact distance between two points (as accurate as the closest integer representation) * * In practice it is likely that ApproxDistance gives the same result, especially for nearby points. * @param other Point to which we want the distance * @return Exact magnitude of vector this -> other */ template int ExactDistance(PointOf other) const { const Displacement vector = Point(*this) - Point(other); // No need to call abs() as we square the values anyway // Casting multiplication operands to a wide type to address overflow warnings return static_cast(std::sqrt(static_cast(vector.deltaX) * vector.deltaX + static_cast(vector.deltaY) * vector.deltaY)); } template DVL_ALWAYS_INLINE constexpr int ManhattanDistance(PointOf other) const { return std::abs(static_cast(x) - static_cast(other.x)) + std::abs(static_cast(y) - static_cast(other.y)); } template DVL_ALWAYS_INLINE constexpr int WalkingDistance(PointOf other) const { return std::max( std::abs(static_cast(x) - static_cast(other.x)), std::abs(static_cast(y) - static_cast(other.y))); } /** * @brief Converts a coordinate in megatiles to the northmost of the 4 corresponding world tiles */ DVL_ALWAYS_INLINE constexpr PointOf megaToWorld() const { return { static_cast(16 + 2 * x), static_cast(16 + 2 * y) }; } /** * @brief Converts a coordinate in world tiles back to the corresponding megatile */ DVL_ALWAYS_INLINE constexpr PointOf worldToMega() const { return { static_cast((x - 16) / 2), static_cast((y - 16) / 2) }; } }; #ifdef BUILD_TESTING /** * @brief Format points nicely in test failure messages * @param stream output stream, expected to have overloads for int and char* * @param point Object to display * @return the stream, to allow chaining */ template std::ostream &operator<<(std::ostream &stream, const PointOf &point) { return stream << "(x: " << point.x << ", y: " << point.y << ")"; } #endif template DVL_ALWAYS_INLINE constexpr PointOf operator+(PointOf a, DisplacementOf displacement) { a += displacement; return a; } template DVL_ALWAYS_INLINE constexpr PointOf operator+(PointOf a, Direction direction) { a += direction; return a; } template DVL_ALWAYS_INLINE constexpr DisplacementOf operator-(PointOf a, PointOf b) { static_assert(std::is_signed::value == std::is_signed::value, "points must have the same signedness"); return { static_cast(a.x - b.x), static_cast(a.y - b.y) }; } template DVL_ALWAYS_INLINE constexpr PointOf operator-(PointOf a, DisplacementOf displacement) { a -= displacement; return a; } template DVL_ALWAYS_INLINE constexpr PointOf operator*(PointOf a, const float factor) { a *= factor; return a; } template DVL_ALWAYS_INLINE constexpr PointOf operator*(PointOf a, const int factor) { a *= factor; return a; } template DVL_ALWAYS_INLINE constexpr PointOf abs(PointOf a) { return { std::abs(a.x), std::abs(a.y) }; } } // namespace devilution