Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some more box improvements and test cases #3

Merged
merged 7 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 91 additions & 14 deletions include/hyprutils/math/Box.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,97 @@
#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;
w = size.x;
h = size.y;
}

// Geometric operations
CBox& applyFromWlr();
CBox& scale(double scale);
CBox& scaleFromCenter(double scale);
Expand All @@ -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;
Expand All @@ -92,15 +165,19 @@ 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;
}

private:
CBox roundInternal();
};
}
}
}
65 changes: 43 additions & 22 deletions src/math/Box.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

using namespace Hyprutils::Math;

constexpr double HALF = 0.5;
constexpr double DOUBLE = 2.0;
constexpr double EPSILON = 1e-9;

CBox& Hyprutils::Math::CBox::scale(double scale) {
x *= scale;
y *= scale;
Expand All @@ -33,29 +37,33 @@ CBox& Hyprutils::Math::CBox::translate(const Vector2D& vec) {
}

Vector2D Hyprutils::Math::CBox::middle() const {
return Vector2D{x + w / 2.0, y + h / 2.0};
return Vector2D{x + w * HALF, y + h * HALF};
}

bool Hyprutils::Math::CBox::containsPoint(const Vector2D& vec) const {
return VECINRECT(vec, x, y, x + w, y + h);
}

bool Hyprutils::Math::CBox::empty() const {
return w == 0 || h == 0;
return std::fabs(w) < EPSILON || std::fabs(h) < EPSILON;
}

CBox& Hyprutils::Math::CBox::round() {
float newW = x + w - std::round(x);
float newH = y + h - std::round(y);
x = std::round(x);
y = std::round(y);
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);

return *this;
}

CBox& Hyprutils::Math::CBox::transform(const eTransform t, double w, double h) {

CBox temp = *this;

if (t % 2 == 0) {
Expand Down Expand Up @@ -119,19 +127,19 @@ CBox& Hyprutils::Math::CBox::scaleFromCenter(double scale) {
w *= scale;
h *= scale;

x -= (w - oldW) / 2.0;
y -= (h - oldH) / 2.0;
x -= (w - oldW) * HALF;
y -= (h - oldH) * HALF;

return *this;
}

CBox& Hyprutils::Math::CBox::expand(const double& value) {
x -= value;
y -= value;
w += value * 2.0;
h += value * 2.0;
w += value * DOUBLE;
h += value * DOUBLE;

if (w <= 0 || h <= 0) {
if (w <= EPSILON || h <= EPSILON) {
w = 0;
h = 0;
}
Expand All @@ -147,14 +155,14 @@ CBox& Hyprutils::Math::CBox::noNegativeSize() {
}

CBox Hyprutils::Math::CBox::intersection(const CBox& other) const {
const float newX = std::max(x, other.x);
const float newY = std::max(y, other.y);
const float newBottom = std::min(y + h, other.y + other.h);
const float newRight = std::min(x + w, other.x + other.w);
float newW = newRight - newX;
float newH = newBottom - newY;

if (newW <= 0 || newH <= 0) {
const double newX = std::max(x, other.x);
const double newY = std::max(y, other.y);
const double newBottom = std::min(y + h, other.y + other.h);
const double newRight = std::min(x + w, other.x + other.w);
double newW = newRight - newX;
double newH = newBottom - newY;

if (newW <= EPSILON || newH <= EPSILON) {
newW = 0;
newH = 0;
}
Expand All @@ -171,10 +179,12 @@ bool Hyprutils::Math::CBox::inside(const CBox& bound) const {
}

CBox Hyprutils::Math::CBox::roundInternal() {
float newW = x + w - std::floor(x);
float newH = y + h - std::floor(y);
double flooredX = std::floor(x);
double flooredY = std::floor(y);
double newW = x + w - flooredX;
double newH = y + h - flooredY;

return CBox{std::floor(x), std::floor(y), std::floor(newW), std::floor(newH)};
return CBox{flooredX, flooredY, std::floor(newW), std::floor(newH)};
}

CBox Hyprutils::Math::CBox::copy() const {
Expand All @@ -196,6 +206,17 @@ Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const {
Vector2D nv = vec;
nv.x = std::clamp(nv.x, x, x + w);
nv.y = std::clamp(nv.y, y, y + h);

if (std::fabs(nv.x - x) < EPSILON)
nv.x = x;
else if (std::fabs(nv.x - (x + w)) < EPSILON)
nv.x = x + w;

if (std::fabs(nv.y - y) < EPSILON)
nv.y = y;
else if (std::fabs(nv.y - (y + h)) < EPSILON)
nv.y = y + h;

return nv;
}

Expand Down
68 changes: 68 additions & 0 deletions tests/math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Loading
Loading