diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index fa5532e..f1aeda5 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -1,9 +1,12 @@ + cmake_minimum_required(VERSION 3.27) -# Define util library sources (header-only for now) -add_library(istd_util INTERFACE) -target_include_directories(istd_util INTERFACE include) -target_compile_features(istd_util INTERFACE cxx_std_23) +# Define util library sources +add_library(istd_util STATIC + src/vec2.cpp +) +target_include_directories(istd_util PUBLIC include) +target_compile_features(istd_util PUBLIC cxx_std_23) if (BUILD_TESTS) add_subdirectory("test/") diff --git a/util/include/istd_util/vec2.h b/util/include/istd_util/vec2.h new file mode 100644 index 0000000..3c4cd6c --- /dev/null +++ b/util/include/istd_util/vec2.h @@ -0,0 +1,159 @@ +#ifndef ISTD_UTIL_VEC2_H +#define ISTD_UTIL_VEC2_H + +#include +#include +#include + +namespace istd { + +/** + * @brief 2D vector with float components. + * + * Provides basic arithmetic, comparison, and utility operations for 2D vectors. + */ +struct Vec2 { + /** + * @brief X component of the vector. + */ + float x; + /** + * @brief Y component of the vector. + */ + float y; + + /** + * @brief Construct a new Vec2 object. + * @param x X component + * @param y Y component + */ + Vec2(float x, float y) noexcept; + /** + * @brief Returns a zero vector (0, 0). + */ + static Vec2 zero() noexcept; + /** + * @brief Returns a vector rotated by the given angle. + * @param rad Angle in radians + * @param len Length of the resulting vector (default 1.0) + */ + static Vec2 rotated(float rad, float len = 1.0) noexcept; + + /** + * @name Symmetric operations + * @{ + */ + /** + * @brief Vector addition. + */ + friend Vec2 operator+(Vec2 a, Vec2 b) noexcept; + /** + * @brief Vector subtraction. + */ + friend Vec2 operator-(Vec2 a, Vec2 b) noexcept; + /** + * @brief Unary negation. + */ + friend Vec2 operator-(Vec2 a) noexcept; + /** + * @brief Scalar multiplication. + */ + friend Vec2 operator*(Vec2 a, float k) noexcept; + /** + * @brief Scalar division. + */ + friend Vec2 operator/(Vec2 a, float k) noexcept; + /** + * @brief Three-way comparison operator. + */ + friend std::strong_ordering operator<=>(Vec2 a, Vec2 b) noexcept; + /** @} */ + + /** + * @name Assignment operations + * @{ + */ + /** + * @brief Adds another vector to this vector. + */ + Vec2 &operator+=(Vec2 b) noexcept; + /** + * @brief Subtracts another vector from this vector. + */ + Vec2 &operator-=(Vec2 b) noexcept; + /** + * @brief Multiplies this vector by a scalar. + */ + Vec2 &operator*=(float k) noexcept; + /** + * @brief Divides this vector by a scalar. + */ + Vec2 &operator/=(float k) noexcept; + /** @} */ + + /** + * @brief Access vector components by index. + * @param i Index (0 for x, 1 for y) + * @return Reference to the component + * @throws std::out_of_range if index is not 0 or 1 + */ + float &operator[](std::size_t i) { + if (i == 0) { + return x; + } + if (i == 1) { + return y; + } + throw std::out_of_range("Index out of range for Vec2"); + } + + /** + * @brief Access vector components by index (const). + * @param i Index (0 for x, 1 for y) + * @return Value of the component + * @throws std::out_of_range if index is not 0 or 1 + */ + float operator[](std::size_t i) const { + if (i == 0) { + return x; + } + if (i == 1) { + return y; + } + throw std::out_of_range("Index out of range for Vec2"); + } + + /** + * @brief Returns the length (magnitude) of the vector. + */ + float length(this const Vec2 self) noexcept; + /** + * @brief Returns the squared length of the vector. + */ + float length_squared(this const Vec2 self) noexcept; + /** + * @brief Returns a normalized (unit length) vector. + */ + Vec2 normalized(this const Vec2 self) noexcept; + /** + * @brief Returns a tuple of floored components. + */ + std::tuple floor(this const Vec2 self) noexcept; + /** + * @brief Returns a tuple of rounded components. + */ + std::tuple round(this const Vec2 self) noexcept; + + /** + * @brief Returns the dot product of two vectors. + */ + friend float dot(Vec2 a, Vec2 b) noexcept; + /** + * @brief Returns the cross product of two vectors. + */ + friend float cross(Vec2 a, Vec2 b) noexcept; +}; + +} // namespace istd + +#endif diff --git a/util/src/vec2.cpp b/util/src/vec2.cpp new file mode 100644 index 0000000..8084ec3 --- /dev/null +++ b/util/src/vec2.cpp @@ -0,0 +1,115 @@ + +#include "istd_util/vec2.h" +#include + +namespace istd { + +Vec2::Vec2(float x_, float y_) noexcept: x(x_), y(y_) {} + +Vec2 Vec2::zero() noexcept { + return Vec2(0.0f, 0.0f); +} + +Vec2 Vec2::rotated(float rad, float len) noexcept { + return Vec2(std::cos(rad) * len, std::sin(rad) * len); +} + +Vec2 operator+(Vec2 a, Vec2 b) noexcept { + return Vec2(a.x + b.x, a.y + b.y); +} + +Vec2 operator-(Vec2 a, Vec2 b) noexcept { + return Vec2(a.x - b.x, a.y - b.y); +} + +Vec2 operator-(Vec2 a) noexcept { + return Vec2(-a.x, -a.y); +} + +Vec2 operator*(Vec2 a, float k) noexcept { + return Vec2(a.x * k, a.y * k); +} + +Vec2 operator/(Vec2 a, float k) noexcept { + return Vec2(a.x / k, a.y / k); +} + +std::strong_ordering operator<=>(Vec2 a, Vec2 b) noexcept { + if (a.x < b.x) { + return std::strong_ordering::less; + } + if (a.x > b.x) { + return std::strong_ordering::greater; + } + if (a.y < b.y) { + return std::strong_ordering::less; + } + if (a.y > b.y) { + return std::strong_ordering::greater; + } + return std::strong_ordering::equal; +} + +Vec2 &Vec2::operator+=(Vec2 b) noexcept { + x += b.x; + y += b.y; + return *this; +} + +Vec2 &Vec2::operator-=(Vec2 b) noexcept { + x -= b.x; + y -= b.y; + return *this; +} + +Vec2 &Vec2::operator*=(float k) noexcept { + x *= k; + y *= k; + return *this; +} + +Vec2 &Vec2::operator/=(float k) noexcept { + x /= k; + y /= k; + return *this; +} + +float Vec2::length(this const Vec2 self) noexcept { + return std::sqrt(self.x * self.x + self.y * self.y); +} + +float Vec2::length_squared(this const Vec2 self) noexcept { + return self.x * self.x + self.y * self.y; +} + +Vec2 Vec2::normalized(this const Vec2 self) noexcept { + float len = self.length(); + if (len == 0.0f) { + return Vec2(0.0f, 0.0f); + } + return Vec2(self.x / len, self.y / len); +} + +std::tuple Vec2::floor(this const Vec2 self) noexcept { + return std::make_tuple( + static_cast(std::floor(self.x)), + static_cast(std::floor(self.y)) + ); +} + +std::tuple Vec2::round(this const Vec2 self) noexcept { + return std::make_tuple( + static_cast(std::round(self.x)), + static_cast(std::round(self.y)) + ); +} + +float dot(Vec2 a, Vec2 b) noexcept { + return a.x * b.x + a.y * b.y; +} + +float cross(Vec2 a, Vec2 b) noexcept { + return a.x * b.y - a.y * b.x; +} + +} // namespace istd diff --git a/util/test/CMakeLists.txt b/util/test/CMakeLists.txt index 8fb31e4..677ed0a 100644 --- a/util/test/CMakeLists.txt +++ b/util/test/CMakeLists.txt @@ -1,9 +1,16 @@ cmake_minimum_required(VERSION 3.27) + add_executable(test_small_map small_map.cpp) target_link_libraries(test_small_map PRIVATE istd_util) target_compile_features(test_small_map PRIVATE cxx_std_23) +add_executable(test_vec2 test_vec2.cpp) +target_link_libraries(test_vec2 PRIVATE istd_util) +target_compile_features(test_vec2 PRIVATE cxx_std_23) + + include(CTest) enable_testing() add_test(NAME util_small_map COMMAND test_small_map) +add_test(NAME util_vec2 COMMAND test_vec2) diff --git a/util/test/test_vec2.cpp b/util/test/test_vec2.cpp new file mode 100644 index 0000000..31253e3 --- /dev/null +++ b/util/test/test_vec2.cpp @@ -0,0 +1,34 @@ +#include "istd_util/vec2.h" +#include +using namespace istd; + +int main() { + Vec2 v1(3.0f, 4.0f); + Vec2 v2(1.0f, 2.0f); + + // Test length and length_squared + assert(v1.length() == 5.0f); + assert(v1.length_squared() == 25.0f); + + // Test normalized + Vec2 n = v1.normalized(); + assert(std::abs(n.length() - 1.0f) < 1e-6); + + // Test addition and subtraction + Vec2 v3 = v1 + v2; + assert(v3.x == 4.0f && v3.y == 6.0f); + Vec2 v4 = v1 - v2; + assert(v4.x == 2.0f && v4.y == 2.0f); + + // Test dot and cross + assert(dot(v1, v2) == 11.0f); + assert(cross(v1, v2) == 2.0f); + + // Test floor and round + Vec2 v5(1.7f, -2.3f); + auto f = v5.floor(); + auto r = v5.round(); + assert(f == std::make_tuple(1, -3)); + assert(r == std::make_tuple(2, -2)); + return 0; +}