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
This commit is contained in:
Jasson Cordones 2024-06-24 19:17:44 -04:00 committed by GitHub
parent ff343e0279
commit 7a2c2c96ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 262 additions and 106 deletions

View file

@ -3,52 +3,89 @@
#include "./Vector2D.hpp"
#include "./Misc.hpp"
namespace Hyprutils {
namespace Math {
namespace Hyprutils::Math {
/**
* @brief Represents the extents of a bounding box.
*/
struct SBoxExtents {
Vector2D topLeft;
Vector2D bottomRight;
//
/**
* @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;
}
/**
* @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);
}
};
/**
* @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;
@ -56,6 +93,7 @@ namespace Hyprutils {
h = size.y;
}
// Geometric operations
CBox& applyFromWlr();
CBox& scale(double scale);
CBox& scaleFromCenter(double scale);
@ -72,17 +110,52 @@ namespace Hyprutils {
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;
double x = 0, y = 0; // Position of the top-left corner of the box.
union {
double w;
double width;
@ -92,9 +165,13 @@ namespace Hyprutils {
double height;
};
double rot = 0; /* rad, ccw */
double rot = 0; //< Rotation angle of the box in radians (counterclockwise).
//
/**
* @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;
}
@ -103,4 +180,3 @@ namespace Hyprutils {
CBox roundInternal();
};
}
}

View file

@ -17,5 +17,73 @@ int main(int argc, char** argv, char** envp) {
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;
}

View file

@ -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)