mirror of
https://github.com/hyprwm/hyprutils.git
synced 2024-11-16 23:05:58 +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 "./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();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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)};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue