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,104 +3,180 @@
#include "./Vector2D.hpp" #include "./Vector2D.hpp"
#include "./Misc.hpp" #include "./Misc.hpp"
namespace Hyprutils { namespace Hyprutils::Math {
namespace Math {
struct SBoxExtents {
Vector2D topLeft;
Vector2D bottomRight;
// /**
SBoxExtents operator*(const double& scale) const { * @brief Represents the extents of a bounding box.
return SBoxExtents{topLeft * scale, bottomRight * scale}; */
} 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); * @brief Represents a 2D bounding box.
bottomRight = bottomRight.getComponentMax(other.bottomRight); */
} 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 { double rot = 0; //< Rotation angle of the box in radians (counterclockwise).
public:
CBox(double x_, double y_, double w_, double h_) {
x = x_;
y = y_;
w = w_;
h = h_;
}
CBox() { /**
w = 0; * @brief Checks equality between two CBox objects.
h = 0; * @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) { private:
x = d; CBox roundInternal();
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();
};
}
} }

View file

@ -7,11 +7,11 @@
using namespace Hyprutils::Math; using namespace Hyprutils::Math;
constexpr double HALF = 0.5; constexpr double HALF = 0.5;
constexpr double DOUBLE = 2.0; constexpr double DOUBLE = 2.0;
constexpr double EPSILON = 1e-9; constexpr double EPSILON = 1e-9;
CBox& Hyprutils::Math::CBox::scale(double scale) { CBox& Hyprutils::Math::CBox::scale(double scale) {
x *= scale; x *= scale;
y *= scale; y *= scale;
w *= scale; w *= scale;
@ -51,13 +51,13 @@ bool Hyprutils::Math::CBox::empty() const {
CBox& Hyprutils::Math::CBox::round() { CBox& Hyprutils::Math::CBox::round() {
double roundedX = std::round(x); double roundedX = std::round(x);
double roundedY = std::round(y); double roundedY = std::round(y);
double newW = x + w - roundedX; double newW = x + w - roundedX;
double newH = y + h - roundedY; double newH = y + h - roundedY;
x = roundedX; x = roundedX;
y = roundedY; y = roundedY;
w = std::round(newW); w = std::round(newW);
h = std::round(newH); h = std::round(newH);
return *this; return *this;
} }
@ -179,10 +179,10 @@ bool Hyprutils::Math::CBox::inside(const CBox& bound) const {
} }
CBox Hyprutils::Math::CBox::roundInternal() { CBox Hyprutils::Math::CBox::roundInternal() {
double flooredX = std::floor(x); double flooredX = std::floor(x);
double flooredY = std::floor(y); double flooredY = std::floor(y);
double newW = x + w - flooredX; double newW = x + w - flooredX;
double newH = y + h - flooredY; double newH = y + h - flooredY;
return CBox{flooredX, flooredY, std::floor(newW), std::floor(newH)}; return CBox{flooredX, flooredY, std::floor(newW), std::floor(newH)};
} }

View file

@ -11,11 +11,79 @@ int main(int argc, char** argv, char** envp) {
EXPECT(rg.getExtents().height, 200); EXPECT(rg.getExtents().height, 200);
EXPECT(rg.getExtents().width, 100); EXPECT(rg.getExtents().width, 100);
rg.intersect(CBox{10, 10, 300, 300}); rg.intersect(CBox{10, 10, 300, 300});
EXPECT(rg.getExtents().width, 90); EXPECT(rg.getExtents().width, 90);
EXPECT(rg.getExtents().height, 190); 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; return ret;
} }

View file

@ -18,3 +18,15 @@ namespace Colors {
} else { \ } else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \ 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)