mirror of
https://github.com/hyprwm/hyprutils.git
synced 2024-12-22 10:49:48 +01:00
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:
parent
ff343e0279
commit
7a2c2c96ec
4 changed files with 262 additions and 106 deletions
|
@ -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();
|
||||
};
|
||||
}
|
|
@ -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)};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue