From d322427bc5451090993b1a4a3f183650617f5820 Mon Sep 17 00:00:00 2001 From: "alan (NyxTrail)" Date: Mon, 1 Jul 2024 05:29:41 +0000 Subject: [PATCH] New upstream version 0.1.5 --- .github/workflows/arch.yml | 4 +- CMakeLists.txt | 17 ++- README.md | 3 + include/hyprutils/math/Box.hpp | 182 ++++++++++++++++++++++ include/hyprutils/math/Misc.hpp | 16 ++ include/hyprutils/math/Region.hpp | 70 +++++++++ include/hyprutils/math/Vector2D.hpp | 161 ++++++++++++++++++++ nix/default.nix | 11 +- src/math/Box.cpp | 225 ++++++++++++++++++++++++++++ src/math/Region.cpp | 204 +++++++++++++++++++++++++ src/math/Vector2D.cpp | 60 ++++++++ tests/math.cpp | 89 +++++++++++ tests/shared.hpp | 12 ++ 13 files changed, 1047 insertions(+), 7 deletions(-) create mode 100644 include/hyprutils/math/Box.hpp create mode 100644 include/hyprutils/math/Misc.hpp create mode 100644 include/hyprutils/math/Region.hpp create mode 100644 include/hyprutils/math/Vector2D.hpp create mode 100644 src/math/Box.cpp create mode 100644 src/math/Region.cpp create mode 100644 src/math/Vector2D.cpp create mode 100644 tests/math.cpp diff --git a/.github/workflows/arch.yml b/.github/workflows/arch.yml index f8a4a0e..80556e4 100644 --- a/.github/workflows/arch.yml +++ b/.github/workflows/arch.yml @@ -17,7 +17,7 @@ jobs: run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman - name: Build hyprutils with gcc run: | @@ -44,7 +44,7 @@ jobs: run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman - name: Build hyprutils with clang run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 95e9bd4..fa1f426 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.19) -set(HYPRUTILS_VERSION "0.1.3") +set(HYPRUTILS_VERSION "0.1.5") add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}") project(hyprutils @@ -30,6 +30,9 @@ endif() file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp") +find_package(PkgConfig REQUIRED) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET pixman-1) + add_library(hyprutils SHARED ${SRCFILES}) target_include_directories( hyprutils PUBLIC "./include" @@ -39,25 +42,31 @@ set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION} SOVERSION 0 ) +target_link_libraries(hyprutils PkgConfig::deps) # tests add_custom_target(tests) add_executable(hyprutils_memory "tests/memory.cpp") -target_link_libraries(hyprutils_memory PRIVATE hyprutils) +target_link_libraries(hyprutils_memory PRIVATE hyprutils PkgConfig::deps) add_test(NAME "Memory" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_memory "memory") add_dependencies(tests hyprutils_memory) add_executable(hyprutils_string "tests/string.cpp") -target_link_libraries(hyprutils_string PRIVATE hyprutils) +target_link_libraries(hyprutils_string PRIVATE hyprutils PkgConfig::deps) add_test(NAME "String" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_string "string") add_dependencies(tests hyprutils_string) add_executable(hyprutils_signal "tests/signal.cpp") -target_link_libraries(hyprutils_signal PRIVATE hyprutils) +target_link_libraries(hyprutils_signal PRIVATE hyprutils PkgConfig::deps) add_test(NAME "Signal" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_signal "signal") add_dependencies(tests hyprutils_signal) +add_executable(hyprutils_math "tests/math.cpp") +target_link_libraries(hyprutils_math PRIVATE hyprutils PkgConfig::deps) +add_test(NAME "Math" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_math "math") +add_dependencies(tests hyprutils_math) + # Installation install(TARGETS hyprutils) install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/README.md b/README.md index 54e62a7..b4c6335 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ Hyprutils depends on the ABI stability of the stdlib implementation of your comp ## Building ```sh +git clone https://github.com/hyprwm/hyprutils.git +cd hyprutils/ cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF` +sudo cmake --install build ``` diff --git a/include/hyprutils/math/Box.hpp b/include/hyprutils/math/Box.hpp new file mode 100644 index 0000000..db10428 --- /dev/null +++ b/include/hyprutils/math/Box.hpp @@ -0,0 +1,182 @@ +#pragma once + +#include "./Vector2D.hpp" +#include "./Misc.hpp" + +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); + 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; + }; + + 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(); + }; +} \ No newline at end of file diff --git a/include/hyprutils/math/Misc.hpp b/include/hyprutils/math/Misc.hpp new file mode 100644 index 0000000..33f446f --- /dev/null +++ b/include/hyprutils/math/Misc.hpp @@ -0,0 +1,16 @@ +#pragma once + +namespace Hyprutils { + namespace Math { + enum eTransform { + HYPRUTILS_TRANSFORM_NORMAL = 0, + HYPRUTILS_TRANSFORM_90 = 1, + HYPRUTILS_TRANSFORM_180 = 2, + HYPRUTILS_TRANSFORM_270 = 3, + HYPRUTILS_TRANSFORM_FLIPPED = 4, + HYPRUTILS_TRANSFORM_FLIPPED_90 = 5, + HYPRUTILS_TRANSFORM_FLIPPED_180 = 6, + HYPRUTILS_TRANSFORM_FLIPPED_270 = 7, + }; + } +} \ No newline at end of file diff --git a/include/hyprutils/math/Region.hpp b/include/hyprutils/math/Region.hpp new file mode 100644 index 0000000..856489b --- /dev/null +++ b/include/hyprutils/math/Region.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include "Vector2D.hpp" +#include "Box.hpp" + +namespace Hyprutils { + namespace Math { + class CRegion { + public: + /* Create an empty region */ + CRegion(); + /* Create from a reference. Copies, does not own. */ + CRegion(const pixman_region32_t* const ref); + /* Create from a box */ + CRegion(double x, double y, double w, double h); + /* Create from a CBox */ + CRegion(const CBox& box); + /* Create from a pixman_box32_t */ + CRegion(pixman_box32_t* box); + + CRegion(const CRegion&); + CRegion(CRegion&&); + + ~CRegion(); + + CRegion& operator=(CRegion&& other) { + pixman_region32_copy(&m_rRegion, other.pixman()); + return *this; + } + + CRegion& operator=(CRegion& other) { + pixman_region32_copy(&m_rRegion, other.pixman()); + return *this; + } + + CRegion& clear(); + CRegion& set(const CRegion& other); + CRegion& add(const CRegion& other); + CRegion& add(double x, double y, double w, double h); + CRegion& add(const CBox& other); + CRegion& subtract(const CRegion& other); + CRegion& intersect(const CRegion& other); + CRegion& intersect(double x, double y, double w, double h); + CRegion& translate(const Vector2D& vec); + CRegion& transform(const eTransform t, double w, double h); + CRegion& invert(pixman_box32_t* box); + CRegion& invert(const CBox& box); + CRegion& scale(float scale); + CRegion& scale(const Vector2D& scale); + CRegion& rationalize(); + CBox getExtents(); + bool containsPoint(const Vector2D& vec) const; + bool empty() const; + Vector2D closestPoint(const Vector2D& vec) const; + CRegion copy() const; + + std::vector getRects() const; + + // + pixman_region32_t* pixman() { + return &m_rRegion; + } + + private: + pixman_region32_t m_rRegion; + }; + } +} diff --git a/include/hyprutils/math/Vector2D.hpp b/include/hyprutils/math/Vector2D.hpp new file mode 100644 index 0000000..4cc0402 --- /dev/null +++ b/include/hyprutils/math/Vector2D.hpp @@ -0,0 +1,161 @@ +#pragma once + +#include +#include + +namespace Hyprutils { + namespace Math { + class Vector2D { + public: + Vector2D(double, double); + Vector2D(int, int); + Vector2D(); + ~Vector2D(); + + double x = 0; + double y = 0; + + // returns the scale + double normalize(); + + Vector2D operator+(const Vector2D& a) const { + return Vector2D(this->x + a.x, this->y + a.y); + } + Vector2D operator-(const Vector2D& a) const { + return Vector2D(this->x - a.x, this->y - a.y); + } + Vector2D operator-() const { + return Vector2D(-this->x, -this->y); + } + Vector2D operator*(const double& a) const { + return Vector2D(this->x * a, this->y * a); + } + Vector2D operator/(const double& a) const { + return Vector2D(this->x / a, this->y / a); + } + + bool operator==(const Vector2D& a) const { + return a.x == x && a.y == y; + } + + bool operator!=(const Vector2D& a) const { + return a.x != x || a.y != y; + } + + Vector2D operator*(const Vector2D& a) const { + return Vector2D(this->x * a.x, this->y * a.y); + } + + Vector2D operator/(const Vector2D& a) const { + return Vector2D(this->x / a.x, this->y / a.y); + } + + bool operator>(const Vector2D& a) const { + return this->x > a.x && this->y > a.y; + } + + bool operator<(const Vector2D& a) const { + return this->x < a.x && this->y < a.y; + } + Vector2D& operator+=(const Vector2D& a) { + this->x += a.x; + this->y += a.y; + return *this; + } + Vector2D& operator-=(const Vector2D& a) { + this->x -= a.x; + this->y -= a.y; + return *this; + } + Vector2D& operator*=(const Vector2D& a) { + this->x *= a.x; + this->y *= a.y; + return *this; + } + Vector2D& operator/=(const Vector2D& a) { + this->x /= a.x; + this->y /= a.y; + return *this; + } + Vector2D& operator*=(const double& a) { + this->x *= a; + this->y *= a; + return *this; + } + Vector2D& operator/=(const double& a) { + this->x /= a; + this->y /= a; + return *this; + } + + double distance(const Vector2D& other) const; + double distanceSq(const Vector2D& other) const; + double size() const; + Vector2D clamp(const Vector2D& min, const Vector2D& max = Vector2D{-1, -1}) const; + + Vector2D floor() const; + Vector2D round() const; + + Vector2D getComponentMax(const Vector2D& other) const; + }; + } +} + +// absolutely ridiculous formatter spec parsing +#define AQ_FORMAT_PARSE(specs__, type__) \ + template \ + constexpr auto parse(FormatContext& ctx) { \ + auto it = ctx.begin(); \ + for (; it != ctx.end() && *it != '}'; it++) { \ + switch (*it) { specs__ default : throw std::format_error("invalid format specification"); } \ + } \ + return it; \ + } + +#define AQ_FORMAT_FLAG(spec__, flag__) \ + case spec__: (flag__) = true; break; + +#define AQ_FORMAT_NUMBER(buf__) \ + case '0': \ + case '1': \ + case '2': \ + case '3': \ + case '4': \ + case '5': \ + case '6': \ + case '7': \ + case '8': \ + case '9': (buf__).push_back(*it); break; + +/** + format specification + - 'j', as json array + - 'X', same as std::format("{}x{}", vec.x, vec.y) + - number, floating point precision, use `0` to format as integer +*/ +template +struct std::formatter : std::formatter { + bool formatJson = false; + bool formatX = false; + std::string precision = ""; + AQ_FORMAT_PARSE(AQ_FORMAT_FLAG('j', formatJson) // + AQ_FORMAT_FLAG('X', formatX) // + AQ_FORMAT_NUMBER(precision), + Hyprutils::Math::Vector2D) + + template + auto format(const Hyprutils::Math::Vector2D& vec, FormatContext& ctx) const { + std::string formatString = precision.empty() ? "{}" : std::format("{{:.{}f}}", precision); + + if (formatJson) + formatString = std::format("[{0}, {0}]", formatString); + else if (formatX) + formatString = std::format("{0}x{0}", formatString); + else + formatString = std::format("[Vector2D: x: {0}, y: {0}]", formatString); + try { + string buf = std::vformat(formatString, std::make_format_args(vec.x, vec.y)); + return std::format_to(ctx.out(), "{}", buf); + } catch (std::format_error& e) { return std::format_to(ctx.out(), "[{}, {}]", vec.x, vec.y); } + } +}; diff --git a/nix/default.nix b/nix/default.nix index c5ce83e..99b53bc 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -2,6 +2,8 @@ lib, stdenv, cmake, + pkg-config, + pixman, version ? "git", doCheck ? false, }: @@ -10,7 +12,14 @@ stdenv.mkDerivation { inherit version doCheck; src = ../.; - nativeBuildInputs = [cmake]; + nativeBuildInputs = [ + cmake + pkg-config + ]; + + buildInputs = [ + pixman + ]; outputs = ["out" "dev"]; diff --git a/src/math/Box.cpp b/src/math/Box.cpp new file mode 100644 index 0000000..21e8fe5 --- /dev/null +++ b/src/math/Box.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include + +#define VECINRECT(vec, x1, y1, x2, y2) ((vec).x >= (x1) && (vec).x < (x2) && (vec).y >= (y1) && (vec).y < (y2)) + +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; + w *= scale; + h *= scale; + + return *this; +} + +CBox& Hyprutils::Math::CBox::scale(const Vector2D& scale) { + x *= scale.x; + y *= scale.y; + w *= scale.x; + h *= scale.y; + + return *this; +} + +CBox& Hyprutils::Math::CBox::translate(const Vector2D& vec) { + x += vec.x; + y += vec.y; + + return *this; +} + +Vector2D Hyprutils::Math::CBox::middle() const { + 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 std::fabs(w) < EPSILON || std::fabs(h) < EPSILON; +} + +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); + + return *this; +} + +CBox& Hyprutils::Math::CBox::transform(const eTransform t, double w, double h) { + + CBox temp = *this; + + if (t % 2 == 0) { + width = temp.width; + height = temp.height; + } else { + width = temp.height; + height = temp.width; + } + + switch (t) { + case HYPRUTILS_TRANSFORM_NORMAL: + x = temp.x; + y = temp.y; + break; + case HYPRUTILS_TRANSFORM_90: + x = h - temp.y - temp.height; + y = temp.x; + break; + case HYPRUTILS_TRANSFORM_180: + x = w - temp.x - temp.width; + y = h - temp.y - temp.height; + break; + case HYPRUTILS_TRANSFORM_270: + x = temp.y; + y = w - temp.x - temp.width; + break; + case HYPRUTILS_TRANSFORM_FLIPPED: + x = w - temp.x - temp.width; + y = temp.y; + break; + case HYPRUTILS_TRANSFORM_FLIPPED_90: + x = temp.y; + y = temp.x; + break; + case HYPRUTILS_TRANSFORM_FLIPPED_180: + x = temp.x; + y = h - temp.y - temp.height; + break; + case HYPRUTILS_TRANSFORM_FLIPPED_270: + x = h - temp.y - temp.height; + y = w - temp.x - temp.width; + break; + } + + return *this; +} + +CBox& Hyprutils::Math::CBox::addExtents(const SBoxExtents& e) { + x -= e.topLeft.x; + y -= e.topLeft.y; + w += e.topLeft.x + e.bottomRight.x; + h += e.topLeft.y + e.bottomRight.y; + + return *this; +} + +CBox& Hyprutils::Math::CBox::scaleFromCenter(double scale) { + double oldW = w, oldH = h; + + w *= scale; + h *= scale; + + x -= (w - oldW) * HALF; + y -= (h - oldH) * HALF; + + return *this; +} + +CBox& Hyprutils::Math::CBox::expand(const double& value) { + x -= value; + y -= value; + w += value * DOUBLE; + h += value * DOUBLE; + + if (w <= EPSILON || h <= EPSILON) { + w = 0; + h = 0; + } + + return *this; +} + +CBox& Hyprutils::Math::CBox::noNegativeSize() { + w = std::clamp(w, 0.0, std::numeric_limits::infinity()); + h = std::clamp(h, 0.0, std::numeric_limits::infinity()); + + return *this; +} + +CBox Hyprutils::Math::CBox::intersection(const CBox& other) const { + 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; + } + + return {newX, newY, newW, newH}; +} + +bool Hyprutils::Math::CBox::overlaps(const CBox& other) const { + return (other.x + other.w >= x) && (x + w >= other.x) && (other.y + other.h >= y) && (y + h >= other.y); +} + +bool Hyprutils::Math::CBox::inside(const CBox& bound) const { + return bound.x < x && bound.y < y && x + w < bound.x + bound.w && y + h < bound.y + bound.h; +} + +CBox Hyprutils::Math::CBox::roundInternal() { + double flooredX = std::floor(x); + double flooredY = std::floor(y); + double newW = x + w - flooredX; + double newH = y + h - flooredY; + + return CBox{flooredX, flooredY, std::floor(newW), std::floor(newH)}; +} + +CBox Hyprutils::Math::CBox::copy() const { + return CBox{*this}; +} + +Vector2D Hyprutils::Math::CBox::pos() const { + return {x, y}; +} + +Vector2D Hyprutils::Math::CBox::size() const { + return {w, h}; +} + +Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const { + if (containsPoint(vec)) + return vec; + + 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; +} + +SBoxExtents Hyprutils::Math::CBox::extentsFrom(const CBox& small) { + return {{small.x - x, small.y - y}, {w - small.w - (small.x - x), h - small.h - (small.y - y)}}; +} diff --git a/src/math/Region.cpp b/src/math/Region.cpp new file mode 100644 index 0000000..81eda8b --- /dev/null +++ b/src/math/Region.cpp @@ -0,0 +1,204 @@ +#include +#include + +using namespace Hyprutils::Math; + +constexpr const int64_t MAX_REGION_SIDE = 10000000; + +Hyprutils::Math::CRegion::CRegion() { + pixman_region32_init(&m_rRegion); +} + +Hyprutils::Math::CRegion::CRegion(const pixman_region32_t* const ref) { + pixman_region32_init(&m_rRegion); + pixman_region32_copy(&m_rRegion, ref); +} + +Hyprutils::Math::CRegion::CRegion(double x, double y, double w, double h) { + pixman_region32_init_rect(&m_rRegion, x, y, w, h); +} + +Hyprutils::Math::CRegion::CRegion(const CBox& box) { + pixman_region32_init_rect(&m_rRegion, box.x, box.y, box.w, box.h); +} + +Hyprutils::Math::CRegion::CRegion(pixman_box32_t* box) { + pixman_region32_init_rect(&m_rRegion, box->x1, box->y1, box->x2 - box->x1, box->y2 - box->y1); +} + +Hyprutils::Math::CRegion::CRegion(const CRegion& other) { + pixman_region32_init(&m_rRegion); + pixman_region32_copy(&m_rRegion, const_cast(&other)->pixman()); +} + +Hyprutils::Math::CRegion::CRegion(CRegion&& other) { + pixman_region32_init(&m_rRegion); + pixman_region32_copy(&m_rRegion, other.pixman()); +} + +Hyprutils::Math::CRegion::~CRegion() { + pixman_region32_fini(&m_rRegion); +} + +CRegion& Hyprutils::Math::CRegion::clear() { + pixman_region32_clear(&m_rRegion); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::set(const CRegion& other) { + pixman_region32_copy(&m_rRegion, const_cast(&other)->pixman()); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::add(const CRegion& other) { + pixman_region32_union(&m_rRegion, &m_rRegion, const_cast(&other)->pixman()); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::add(double x, double y, double w, double h) { + pixman_region32_union_rect(&m_rRegion, &m_rRegion, x, y, w, h); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::add(const CBox& other) { + pixman_region32_union_rect(&m_rRegion, &m_rRegion, other.x, other.y, other.w, other.h); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::subtract(const CRegion& other) { + pixman_region32_subtract(&m_rRegion, &m_rRegion, const_cast(&other)->pixman()); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::intersect(const CRegion& other) { + pixman_region32_intersect(&m_rRegion, &m_rRegion, const_cast(&other)->pixman()); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::intersect(double x, double y, double w, double h) { + pixman_region32_intersect_rect(&m_rRegion, &m_rRegion, x, y, w, h); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::invert(pixman_box32_t* box) { + pixman_region32_inverse(&m_rRegion, &m_rRegion, box); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::invert(const CBox& box) { + pixman_box32 pixmanBox = {(int32_t)box.x, (int32_t)box.y, (int32_t)box.w + (int32_t)box.x, (int32_t)box.h + (int32_t)box.y}; + return this->invert(&pixmanBox); +} + +CRegion& Hyprutils::Math::CRegion::translate(const Vector2D& vec) { + pixman_region32_translate(&m_rRegion, vec.x, vec.y); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::transform(const eTransform t, double w, double h) { + if (t == HYPRUTILS_TRANSFORM_NORMAL) + return *this; + + auto rects = getRects(); + + clear(); + + for (auto& r : rects) { + CBox xfmd{(double)r.x1, (double)r.y1, (double)r.x2 - r.x1, (double)r.y2 - r.y1}; + xfmd.transform(t, w, h); + add(xfmd); + } + + return *this; +} + +CRegion& Hyprutils::Math::CRegion::rationalize() { + intersect(CBox{-MAX_REGION_SIDE, -MAX_REGION_SIDE, MAX_REGION_SIDE * 2, MAX_REGION_SIDE * 2}); + return *this; +} + +CRegion Hyprutils::Math::CRegion::copy() const { + return CRegion(*this); +} + +CRegion& Hyprutils::Math::CRegion::scale(float scale_) { + scale({scale_, scale_}); + return *this; +} + +CRegion& Hyprutils::Math::CRegion::scale(const Vector2D& scale) { + if (scale == Vector2D{1, 1}) + return *this; + + auto rects = getRects(); + + clear(); + + for (auto& r : rects) { + r.x1 = std::floor(r.x1 * scale.x); + r.y1 = std::floor(r.y1 * scale.x); + r.x2 = std::ceil(r.x2 * scale.x); + r.y2 = std::ceil(r.y2 * scale.x); + add(&r); + } + + return *this; +} + +std::vector Hyprutils::Math::CRegion::getRects() const { + std::vector result; + + int rectsNum = 0; + const auto RECTSARR = pixman_region32_rectangles(&m_rRegion, &rectsNum); + + result.assign(RECTSARR, RECTSARR + rectsNum); + + return result; +} + +CBox Hyprutils::Math::CRegion::getExtents() { + pixman_box32_t* box = pixman_region32_extents(&m_rRegion); + return {(double)box->x1, (double)box->y1, (double)box->x2 - box->x1, (double)box->y2 - box->y1}; +} + +bool Hyprutils::Math::CRegion::containsPoint(const Vector2D& vec) const { + return pixman_region32_contains_point(&m_rRegion, vec.x, vec.y, nullptr); +} + +bool Hyprutils::Math::CRegion::empty() const { + return !pixman_region32_not_empty(&m_rRegion); +} + +Vector2D Hyprutils::Math::CRegion::closestPoint(const Vector2D& vec) const { + if (containsPoint(vec)) + return vec; + + double bestDist = __FLT_MAX__; + Vector2D leader = vec; + + for (auto& box : getRects()) { + double x = 0, y = 0; + + if (vec.x >= box.x2) + x = box.x2 - 1; + else if (vec.x < box.x1) + x = box.x1; + else + x = vec.x; + + if (vec.y >= box.y2) + y = box.y2 - 1; + else if (vec.y < box.y1) + y = box.y1; + else + y = vec.y; + + double distance = pow(x, 2) + pow(y, 2); + if (distance < bestDist) { + bestDist = distance; + leader = {x, y}; + } + } + + return leader; +} \ No newline at end of file diff --git a/src/math/Vector2D.cpp b/src/math/Vector2D.cpp new file mode 100644 index 0000000..a7f709a --- /dev/null +++ b/src/math/Vector2D.cpp @@ -0,0 +1,60 @@ +#include +#include +#include + +using namespace Hyprutils::Math; + +Hyprutils::Math::Vector2D::Vector2D(double xx, double yy) { + x = xx; + y = yy; +} + +Hyprutils::Math::Vector2D::Vector2D(int xx, int yy) { + x = (double)xx; + y = (double)yy; +} + +Hyprutils::Math::Vector2D::Vector2D() { + x = 0; + y = 0; +} + +Hyprutils::Math::Vector2D::~Vector2D() {} + +double Hyprutils::Math::Vector2D::normalize() { + // get max abs + const auto max = std::abs(x) > std::abs(y) ? std::abs(x) : std::abs(y); + + x /= max; + y /= max; + + return max; +} + +Vector2D Hyprutils::Math::Vector2D::floor() const { + return Vector2D(std::floor(x), std::floor(y)); +} + +Vector2D Hyprutils::Math::Vector2D::round() const { + return Vector2D(std::round(x), std::round(y)); +} + +Vector2D Hyprutils::Math::Vector2D::clamp(const Vector2D& min, const Vector2D& max) const { + return Vector2D(std::clamp(this->x, min.x, max.x < min.x ? INFINITY : max.x), std::clamp(this->y, min.y, max.y < min.y ? INFINITY : max.y)); +} + +double Hyprutils::Math::Vector2D::distance(const Vector2D& other) const { + return std::sqrt(distanceSq(other)); +} + +double Hyprutils::Math::Vector2D::distanceSq(const Vector2D& other) const { + return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y); +} + +double Hyprutils::Math::Vector2D::size() const { + return std::sqrt(x * x + y * y); +} + +Vector2D Hyprutils::Math::Vector2D::getComponentMax(const Vector2D& other) const { + return Vector2D(std::max(this->x, other.x), std::max(this->y, other.y)); +} diff --git a/tests/math.cpp b/tests/math.cpp new file mode 100644 index 0000000..8a29774 --- /dev/null +++ b/tests/math.cpp @@ -0,0 +1,89 @@ +#include +#include "shared.hpp" + +using namespace Hyprutils::Math; + +int main(int argc, char** argv, char** envp) { + CRegion rg = {0, 0, 100, 100}; + rg.add(CBox{{}, {20, 200}}); + + int ret = 0; + + 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; +} \ No newline at end of file diff --git a/tests/shared.hpp b/tests/shared.hpp index ce3d771..e738e5a 100644 --- a/tests/shared.hpp +++ b/tests/shared.hpp @@ -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)