From 7a2c2c96ec27d6d9a4301d09982bca2e4b28238d Mon Sep 17 00:00:00 2001 From: Jasson Cordones Date: Mon, 24 Jun 2024 19:17:44 -0400 Subject: [PATCH] Math: Some more box improvements and test cases (#3) * Added some constants to handle floating point presicion comparisons and other calculations plus some refactoring * Removed validation * Added comments to understand how box header works * Extended the EXPECT macro to evaluate Vector2D test cases * Added box.cpp test cases * Applied clang-format --- include/hyprutils/math/Box.hpp | 260 +++++++++++++++++++++------------ src/math/Box.cpp | 26 ++-- tests/math.cpp | 70 ++++++++- tests/shared.hpp | 12 ++ 4 files changed, 262 insertions(+), 106 deletions(-) diff --git a/include/hyprutils/math/Box.hpp b/include/hyprutils/math/Box.hpp index ea974c5..db10428 100644 --- a/include/hyprutils/math/Box.hpp +++ b/include/hyprutils/math/Box.hpp @@ -3,104 +3,180 @@ #include "./Vector2D.hpp" #include "./Misc.hpp" -namespace Hyprutils { - namespace Math { - struct SBoxExtents { - Vector2D topLeft; - Vector2D bottomRight; +namespace Hyprutils::Math { - // - SBoxExtents operator*(const double& scale) const { - return SBoxExtents{topLeft * scale, bottomRight * scale}; - } + /** + * @brief Represents the extents of a bounding box. + */ + struct SBoxExtents { + Vector2D topLeft; + Vector2D bottomRight; - SBoxExtents round() { - return {topLeft.round(), bottomRight.round()}; - } + /** + * @brief Scales the extents by a given factor. + * @param scale The scaling factor. + * @return Scaled SBoxExtents. + */ + SBoxExtents operator*(const double& scale) const { + return SBoxExtents{topLeft * scale, bottomRight * scale}; + } + /** + * @brief Rounds the coordinates of the extents. + * @return Rounded SBoxExtents. + */ + SBoxExtents round() { + return {topLeft.round(), bottomRight.round()}; + } + /** + * @brief Checks equality between two SBoxExtents objects. + * @param other Another SBoxExtents object to compare. + * @return True if both SBoxExtents are equal, false otherwise. + */ + bool operator==(const SBoxExtents& other) const { + return topLeft == other.topLeft && bottomRight == other.bottomRight; + } - bool operator==(const SBoxExtents& other) const { - return topLeft == other.topLeft && bottomRight == other.bottomRight; - } + /** + * @brief Adjusts the extents to encompass another SBoxExtents. + * @param other Another SBoxExtents to add to this one. + */ + void addExtents(const SBoxExtents& other) { + topLeft = topLeft.getComponentMax(other.topLeft); + bottomRight = bottomRight.getComponentMax(other.bottomRight); + } + }; - void addExtents(const SBoxExtents& other) { - topLeft = topLeft.getComponentMax(other.topLeft); - bottomRight = bottomRight.getComponentMax(other.bottomRight); - } + /** + * @brief Represents a 2D bounding box. + */ + class CBox { + public: + /** + * @brief Constructs a CBox with specified position and dimensions. + * @param x_ X-coordinate of the top-left corner. + * @param y_ Y-coordinate of the top-left corner. + * @param w_ Width of the box. + * @param h_ Height of the box. + */ + CBox(double x_, double y_, double w_, double h_) { + x = x_; + y = y_; + w = w_; + h = h_; + } + /** + * @brief Default constructor. Initializes an empty box (0 width, 0 height). + */ + CBox() { + w = 0; + h = 0; + } + /** + * @brief Constructs a CBox with uniform dimensions. + * @param d Dimensions to apply uniformly (x, y, width, height). + */ + CBox(const double d) { + x = d; + y = d; + w = d; + h = d; + } + /** + * @brief Constructs a CBox from a position and size vector. + * @param pos Position vector representing the top-left corner. + * @param size Size vector representing width and height. + */ + CBox(const Vector2D& pos, const Vector2D& size) { + x = pos.x; + y = pos.y; + w = size.x; + h = size.y; + } + + // Geometric operations + CBox& applyFromWlr(); + CBox& scale(double scale); + CBox& scaleFromCenter(double scale); + CBox& scale(const Vector2D& scale); + CBox& translate(const Vector2D& vec); + CBox& round(); + CBox& transform(const eTransform t, double w, double h); + CBox& addExtents(const SBoxExtents& e); + CBox& expand(const double& value); + CBox& noNegativeSize(); + + CBox copy() const; + CBox intersection(const CBox& other) const; + bool overlaps(const CBox& other) const; + bool inside(const CBox& bound) const; + + /** + * @brief Computes the extents of the box relative to another box. + * @param small Another CBox to compare against. + * @return SBoxExtents representing the extents of the box relative to 'small'. + */ + SBoxExtents extentsFrom(const CBox&); // this is the big box + + /** + * @brief Calculates the middle point of the box. + * @return Vector2D representing the middle point. + */ + Vector2D middle() const; + + /** + * @brief Retrieves the position of the top-left corner of the box. + * @return Vector2D representing the position. + */ + Vector2D pos() const; + + /** + * @brief Retrieves the size (width and height) of the box. + * @return Vector2D representing the size. + */ + Vector2D size() const; + + /** + * @brief Finds the closest point within the box to a given vector. + * @param vec Vector from which to find the closest point. + * @return Vector2D representing the closest point within the box. + */ + Vector2D closestPoint(const Vector2D& vec) const; + + /** + * @brief Checks if a given point is inside the box. + * @param vec Vector representing the point to check. + * @return True if the point is inside the box, false otherwise. + */ + bool containsPoint(const Vector2D& vec) const; + + /** + * @brief Checks if the box is empty (zero width or height). + * @return True if the box is empty, false otherwise. + */ + bool empty() const; + + double x = 0, y = 0; // Position of the top-left corner of the box. + union { + double w; + double width; + }; + union { + double h; + double height; }; - class CBox { - public: - CBox(double x_, double y_, double w_, double h_) { - x = x_; - y = y_; - w = w_; - h = h_; - } + double rot = 0; //< Rotation angle of the box in radians (counterclockwise). - CBox() { - w = 0; - h = 0; - } + /** + * @brief Checks equality between two CBox objects. + * @param rhs Another CBox object to compare. + * @return True if both CBox objects are equal, false otherwise. + */ + bool operator==(const CBox& rhs) const { + return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h; + } - CBox(const double d) { - x = d; - y = d; - w = d; - h = d; - } - - CBox(const Vector2D& pos, const Vector2D& size) { - x = pos.x; - y = pos.y; - w = size.x; - h = size.y; - } - - CBox& applyFromWlr(); - CBox& scale(double scale); - CBox& scaleFromCenter(double scale); - CBox& scale(const Vector2D& scale); - CBox& translate(const Vector2D& vec); - CBox& round(); - CBox& transform(const eTransform t, double w, double h); - CBox& addExtents(const SBoxExtents& e); - CBox& expand(const double& value); - CBox& noNegativeSize(); - - CBox copy() const; - CBox intersection(const CBox& other) const; - bool overlaps(const CBox& other) const; - bool inside(const CBox& bound) const; - - SBoxExtents extentsFrom(const CBox&); // this is the big box - - Vector2D middle() const; - Vector2D pos() const; - Vector2D size() const; - Vector2D closestPoint(const Vector2D& vec) const; - - bool containsPoint(const Vector2D& vec) const; - bool empty() const; - - double x = 0, y = 0; - union { - double w; - double width; - }; - union { - double h; - double height; - }; - - double rot = 0; /* rad, ccw */ - - // - bool operator==(const CBox& rhs) const { - return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h; - } - - private: - CBox roundInternal(); - }; - } + private: + CBox roundInternal(); + }; } \ No newline at end of file diff --git a/src/math/Box.cpp b/src/math/Box.cpp index 21b5df8..21e8fe5 100644 --- a/src/math/Box.cpp +++ b/src/math/Box.cpp @@ -7,11 +7,11 @@ using namespace Hyprutils::Math; -constexpr double HALF = 0.5; -constexpr double DOUBLE = 2.0; +constexpr double HALF = 0.5; +constexpr double DOUBLE = 2.0; constexpr double EPSILON = 1e-9; -CBox& Hyprutils::Math::CBox::scale(double scale) { +CBox& Hyprutils::Math::CBox::scale(double scale) { x *= scale; y *= scale; w *= scale; @@ -51,13 +51,13 @@ bool Hyprutils::Math::CBox::empty() const { CBox& Hyprutils::Math::CBox::round() { double roundedX = std::round(x); double roundedY = std::round(y); - double newW = x + w - roundedX; - double newH = y + h - roundedY; - - x = roundedX; - y = roundedY; - w = std::round(newW); - h = std::round(newH); + double newW = x + w - roundedX; + double newH = y + h - roundedY; + + x = roundedX; + y = roundedY; + w = std::round(newW); + h = std::round(newH); return *this; } @@ -179,10 +179,10 @@ bool Hyprutils::Math::CBox::inside(const CBox& bound) const { } CBox Hyprutils::Math::CBox::roundInternal() { - double flooredX = std::floor(x); + double flooredX = std::floor(x); double flooredY = std::floor(y); - double newW = x + w - flooredX; - double newH = y + h - flooredY; + double newW = x + w - flooredX; + double newH = y + h - flooredY; return CBox{flooredX, flooredY, std::floor(newW), std::floor(newH)}; } diff --git a/tests/math.cpp b/tests/math.cpp index cb47189..8a29774 100644 --- a/tests/math.cpp +++ b/tests/math.cpp @@ -11,11 +11,79 @@ int main(int argc, char** argv, char** envp) { EXPECT(rg.getExtents().height, 200); EXPECT(rg.getExtents().width, 100); - + rg.intersect(CBox{10, 10, 300, 300}); EXPECT(rg.getExtents().width, 90); EXPECT(rg.getExtents().height, 190); + /*Box.cpp test cases*/ + // Test default constructor and accessors + { + CBox box1; + EXPECT(box1.x, 0); + EXPECT(box1.y, 0); + EXPECT(box1.width, 0); + EXPECT(box1.height, 0); + + // Test parameterized constructor and accessors + CBox box2(10, 20, 30, 40); + EXPECT(box2.x, 10); + EXPECT(box2.y, 20); + EXPECT(box2.width, 30); + EXPECT(box2.height, 40); + + // Test setters and getters + box2.translate(Vector2D(5, -5)); + EXPECT_VECTOR2D(box2.pos(), Vector2D(15, 15)); + } + + //Test Scaling and Transformation + { + CBox box(10, 10, 20, 30); + + // Test scaling + box.scale(2.0); + EXPECT_VECTOR2D(box.size(), Vector2D(40, 60)); + EXPECT_VECTOR2D(box.pos(), Vector2D(20, 20)); + + // Test scaling from center + box.scaleFromCenter(0.5); + EXPECT_VECTOR2D(box.size(), Vector2D(20, 30)); + EXPECT_VECTOR2D(box.pos(), Vector2D(30, 35)); + + // Test transformation + box.transform(HYPRUTILS_TRANSFORM_90, 100, 200); + EXPECT_VECTOR2D(box.pos(), Vector2D(135, 30)); + EXPECT_VECTOR2D(box.size(), Vector2D(30, 20)); + + // Test Intersection and Extents + } + + { + CBox box1(0, 0, 100, 100); + CBox box2(50, 50, 100, 100); + + CBox intersection = box1.intersection(box2); + EXPECT_VECTOR2D(intersection.pos(), Vector2D(50, 50)); + EXPECT_VECTOR2D(intersection.size(), Vector2D(50, 50)); + + SBoxExtents extents = box1.extentsFrom(box2); + EXPECT_VECTOR2D(extents.topLeft, Vector2D(50, 50)); + EXPECT_VECTOR2D(extents.bottomRight, Vector2D(-50, -50)); + } + + // Test Boundary Conditions and Special Cases + { + CBox box(0, 0, 50, 50); + + EXPECT(box.empty(), false); + + EXPECT(box.containsPoint(Vector2D(25, 25)), true); + EXPECT(box.containsPoint(Vector2D(60, 60)), false); + EXPECT(box.overlaps(CBox(25, 25, 50, 50)), true); + EXPECT(box.inside(CBox(0, 0, 100, 100)), false); + } + return ret; } \ No newline at end of file diff --git a/tests/shared.hpp b/tests/shared.hpp index ce3d771..e738e5a 100644 --- a/tests/shared.hpp +++ b/tests/shared.hpp @@ -18,3 +18,15 @@ namespace Colors { } else { \ std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \ } +#define EXPECT_VECTOR2D(expr, val) \ + do { \ + const auto& RESULT = expr; \ + const auto& EXPECTED = val; \ + if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \ + std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected (" << EXPECTED.x << ", " << EXPECTED.y << ") but got (" << RESULT.x << ", " \ + << RESULT.y << ")\n"; \ + ret = 1; \ + } else { \ + std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got (" << RESULT.x << ", " << RESULT.y << ")\n"; \ + } \ + } while (0)