From fe0278c12f9025bd9c85670b34d96aad19f4da92 Mon Sep 17 00:00:00 2001 From: Danil Belov Date: Sun, 15 Dec 2024 19:28:31 +0200 Subject: [PATCH 1/5] Add mr::within(_ex) functions --- include/mr-math/def.hpp | 46 +++++++++++++++++++++++++++++++++++++++++ tests/main.cpp | 18 ++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/include/mr-math/def.hpp b/include/mr-math/def.hpp index a5a54f0..d58a7c4 100644 --- a/include/mr-math/def.hpp +++ b/include/mr-math/def.hpp @@ -68,6 +68,52 @@ namespace mr { } inline struct UncheckedTag {} unchecked; + + // inclusive range [low, high] + // use operator() to check if value is in range + template + class Range { + public: + Range(const L& low_, const H& high_) + : low(low_), high(high_) {} + + template + requires std::totally_ordered_with && std::totally_ordered_with + bool operator()(const T& value) { return low <= value && value <= high; } + + const L& low; + const H& high; + }; + + // returns inclusive range + // usage: if (mr::within_(1, 10)(x)) + template + constexpr Range within(const L& low, const H& high) { + return {low, high}; + } + + // inclusive range (low, high) + // use operator() to check if value is in range + template + class RangeEx { + public: + RangeEx(const L& low_, const H& high_) + : low(low_), high(high_) {} + + template + requires std::totally_ordered_with && std::totally_ordered_with + bool operator()(const T& value) { return low < value && value < high; } + + const L& low; + const H& high; + }; + + // returns exclusive rang + // usage: if (mr::within_ex(1, 10)(x)) + template + constexpr RangeEx within_ex(const L& low, const H& high) { + return {low, high}; + } } // namespace mr #endif // __def_hpp_ diff --git a/tests/main.cpp b/tests/main.cpp index e423109..02e1141 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -230,3 +230,21 @@ TEST_F(MatrixTest, RotateVector) { } // TODO: camera tests + + +TEST(Utility, Within) { + EXPECT_FALSE(mr::within(1, 10)(0)); + EXPECT_TRUE(mr::within(1, 10)(1)); + EXPECT_TRUE(mr::within(1, 10)(5)); + EXPECT_TRUE(mr::within(1, 10)(10)); + EXPECT_FALSE(mr::within(1, 10)(11)); + + EXPECT_FALSE(mr::within_ex(1, 10)(0)); + EXPECT_FALSE(mr::within_ex(1, 10)(1)); + EXPECT_TRUE(mr::within_ex(1, 10)(5)); + EXPECT_FALSE(mr::within_ex(1, 10)(10)); + EXPECT_FALSE(mr::within_ex(1, 10)(11)); + + EXPECT_TRUE(mr::within(1., 10.f)(5)); + EXPECT_TRUE(mr::within_ex(1., 10.f)(5)); +} From 61288ffcfbce4b08fc44d8d76e45066d6238df0b Mon Sep 17 00:00:00 2001 From: Danil Belov Date: Sun, 15 Dec 2024 20:05:18 +0200 Subject: [PATCH 2/5] Add clamp methods to Vec --- include/mr-math/vec.hpp | 11 +++++++++++ tests/main.cpp | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/include/mr-math/vec.hpp b/include/mr-math/vec.hpp index 090dbcf..9a6656b 100644 --- a/include/mr-math/vec.hpp +++ b/include/mr-math/vec.hpp @@ -258,6 +258,17 @@ namespace mr { return *this; } + constexpr Vec clamped(T low, T high) const noexcept { + assert(low < high); + const auto &data = _data._data; + return {stdx::iif(data <= low, SimdT(low), stdx::iif(data >= high, SimdT(high), data))}; + } + + constexpr Vec & clamp(T low, T high) noexcept { + *this = clamped(low, high); + return *this; + } + constexpr bool operator==(const Vec &other) const noexcept { return _data == other._data; } diff --git a/tests/main.cpp b/tests/main.cpp index 02e1141..6f5fede 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -106,6 +106,12 @@ TEST_F(Vector3DTest, Abs) { EXPECT_EQ(v.abs(), mr::Vec3f(30, 47, 80)); } +TEST_F(Vector3DTest, Clamp) { + mr::Vec3f v{-30, 47, -80}; + EXPECT_EQ(v.clamped(-47, 0), mr::Vec3f(-30, 0, -47)); + EXPECT_EQ(v.clamp(-47, 0), mr::Vec3f(-30, 0, -47)); +} + class MatrixTest : public ::testing::Test { protected: mr::Matr4f m1 { From 147a05f518b6481421970105659c6b4061de0562 Mon Sep 17 00:00:00 2001 From: Danil Belov Date: Sun, 15 Dec 2024 20:51:55 +0200 Subject: [PATCH 3/5] Add mr::Color --- CMakeLists.txt | 1 + include/mr-math/color.hpp | 136 ++++++++++++++++++++++++++++++++++++++ include/mr-math/math.hpp | 1 + tests/main.cpp | 63 +++++++++++++++++- 4 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 include/mr-math/color.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4384834..2727d09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ add_library(${MR_MATH_LIB_NAME} INTERFACE include/mr-math/vec.hpp include/mr-math/math.hpp include/mr-math/bound_box.hpp + include/mr-math/color.hpp ) target_include_directories(${MR_MATH_LIB_NAME} INTERFACE diff --git a/include/mr-math/color.hpp b/include/mr-math/color.hpp new file mode 100644 index 0000000..b080901 --- /dev/null +++ b/include/mr-math/color.hpp @@ -0,0 +1,136 @@ +#ifndef __color_hpp_ +#define __color_hpp_ + +#include "def.hpp" +#include "vec.hpp" + +namespace mr { + + // color in RGBA float format + struct [[nodiscard]] Color { + public: + using ValueT = float; + + template + constexpr Color(T r, T g, T b, T a = 1) noexcept + : _data{Vec4f{r, g, b, a}} {} + + constexpr Color(Vec4f rgba) noexcept + : _data{rgba} {} + + template + constexpr Color(T r, T g, T b, T a = 255) noexcept + : _data{r, g, b, a} { + assert(r <= 255); + assert(g <= 255); + assert(b <= 255); + assert(a <= 255); + _data /= 255; + } + + constexpr explicit Color(uint32_t rgba) noexcept + : Color( + uint8_t((rgba & 0xFF'00'00'00) >> 24), + uint8_t((rgba & 0x00'FF'00'00) >> 16), + uint8_t((rgba & 0x00'00'FF'00) >> 8), + uint8_t((rgba & 0x00'00'00'FF)) + ) {} + + // setters + constexpr void r(ValueT r) noexcept { _data.x(r); } + constexpr void g(ValueT g) noexcept { _data.y(g); } + constexpr void b(ValueT b) noexcept { _data.z(b); } + constexpr void a(ValueT a) noexcept { _data.w(a); } + constexpr void set(size_t i, ValueT value) noexcept { + assert(i < 4); + _data.set(i, value); + } + + // getters + [[nodiscard]] constexpr ValueT r() const noexcept { return _data.x(); } + [[nodiscard]] constexpr ValueT g() const noexcept { return _data.y(); } + [[nodiscard]] constexpr ValueT b() const noexcept { return _data.z(); } + [[nodiscard]] constexpr ValueT a() const noexcept { return _data.w(); } + [[nodiscard]] constexpr ValueT operator[](size_t i) const { + assert(i < 4); + return _data[i]; + } + + // structured binding support + template requires (I < 4) + constexpr ValueT get() const { return _data[I]; } + + constexpr operator Vec4f() noexcept { + return _data; + } + + // format conversions + // TODO: implement using shuffle + constexpr Vec4f argb() const noexcept { + return {a(), r(), g(), b()}; + } + + constexpr Vec4f bgra() const noexcept { + return {b(), g(), r(), a()}; + } + + constexpr Vec4f abgr() const noexcept { + return {a(), b(), g(), r()}; + } + + friend Color operator+(Color lhs, const Color &rhs) noexcept { + lhs += rhs; + return lhs; + } + + Color &operator+=(const Color &other) noexcept { + _data += other._data;; + return *this; + } + + constexpr bool operator==(const Color &other) const noexcept { + return _data == other._data; + } + + constexpr bool equal(const Color &other, ValueT eps = epsilon()) const noexcept { + return _data.equal(other._data, eps); + } + + friend std::ostream & operator<<(std::ostream &s, const Color &color) noexcept { + Vec4u comps = color._data * 255; + comps.clamp(0, 256); + s << '#' << std::hex << std::uppercase << comps[0] << comps[1] << comps[2] << comps[3] << std::nouppercase << std::dec; + return s; + } + + private: + Vec4f _data; + }; + +namespace literals { + + constexpr Color operator"" _rgba(unsigned long long value) { + assert(value <= 0xFF'FF'FF'FF); + return Color{static_cast(value)}; + } + +} // namespace literals + +} // namespace mr + +#ifdef __cpp_structured_bindings +// specializations for structured binding support +namespace std +{ + template <> + struct tuple_size + : std::integral_constant {}; + + template + struct tuple_element { + using type = mr::Color::ValueT; + }; +} +#endif // __cpp_structured_bindings + +#endif // __color_hpp_ diff --git a/include/mr-math/math.hpp b/include/mr-math/math.hpp index 28ddda2..b47ead0 100644 --- a/include/mr-math/math.hpp +++ b/include/mr-math/math.hpp @@ -9,5 +9,6 @@ #include "units.hpp" #include "camera.hpp" #include "bound_box.hpp" +#include "color.hpp" #endif // __math_hpp_ diff --git a/tests/main.cpp b/tests/main.cpp index 6f5fede..a455424 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -237,8 +237,69 @@ TEST_F(MatrixTest, RotateVector) { // TODO: camera tests +TEST(ColorTest, Constructors) { + const mr::Color expected1{0.3, 0.47, 0.8, 1.0}; + EXPECT_EQ(mr::Color(0.3, 0.47, 0.8), expected1); + EXPECT_EQ(mr::Color(mr::Vec4f(0.3, 0.47, 0.8, 1)), expected1); + + const mr::Color expected2{0.2980392156862745, 0.4666666666666667, 0.8, 1.0}; + EXPECT_EQ(mr::Color(76, 119, 204, 255), expected2); + EXPECT_EQ(mr::Color(0x4C'77'CC'FF), expected2); + EXPECT_EQ(0x4C'77'CC'FF_rgba, expected2); +} + +TEST(ColorTest, Formats) { + const auto color = 0x4C'77'CC'FF_rgba; + EXPECT_EQ(color.argb(), 0xFF'4C'77'CC_rgba); + EXPECT_EQ(color.bgra(), 0xCC'77'4c'FF_rgba); + EXPECT_EQ(color.abgr(), 0xFF'CC'77'4c_rgba); +} + +TEST(ColorTest, Getters) { + const auto color = 0x4C'77'CC'FF_rgba; + EXPECT_FLOAT_EQ(color.r(), 0.2980392156862745f); + EXPECT_FLOAT_EQ(color.g(), 0.4666666666666667f); + EXPECT_FLOAT_EQ(color.b(), 0.8); + EXPECT_FLOAT_EQ(color.a(), 1.0); + + EXPECT_EQ(color[0], color.r()); + EXPECT_EQ(color[1], color.g()); + EXPECT_EQ(color[2], color.b()); + EXPECT_EQ(color[3], color.a()); + + const auto[r, g, b, a] = color; + EXPECT_EQ(r, color.r()); + EXPECT_EQ(g, color.g()); + EXPECT_EQ(b, color.b()); + EXPECT_EQ(a, color.a()); +} + +TEST(ColorTest, Setters) { + auto color = 0x4C'77'CC'FF_rgba; + color.r(1.0); + color.set(1, 0.5); + EXPECT_EQ(color, mr::Color(1.0, 0.5, 0.8, 1.0)); +} + +TEST(ColorTest, Equality) { + const auto color1 = 0x4C'77'CC'FF_rgba; + const auto copy = color1; + EXPECT_EQ(color1, copy); + EXPECT_TRUE(color1.equal(copy)); + EXPECT_TRUE(equal(color1, copy)); + + const auto color2 = 0x00'00'00'00_rgba; + EXPECT_NE(color1, color2); + EXPECT_FALSE(color1.equal(color2)); + EXPECT_FALSE(equal(color1, color2)); +} + +TEST(ColorTest, Addition) { + // Values can exeed 1.0 (should they?) + EXPECT_EQ(mr::Color(1.0, 0.0, 0.5, 1.0) + mr::Color(0.0, 1.0, 0.5, 1.0), mr::Color(1.0, 1.0, 1.0, 2.0)); +} -TEST(Utility, Within) { +TEST(UtilityTest, Within) { EXPECT_FALSE(mr::within(1, 10)(0)); EXPECT_TRUE(mr::within(1, 10)(1)); EXPECT_TRUE(mr::within(1, 10)(5)); From 8a0f94dcaf5419291d030865f9aa4f85ee9b31c2 Mon Sep 17 00:00:00 2001 From: Danil Belov Date: Sun, 15 Dec 2024 21:05:46 +0200 Subject: [PATCH 4/5] Remove useless constexpr (Vc's simd isn't constepxr) --- include/mr-math/color.hpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/include/mr-math/color.hpp b/include/mr-math/color.hpp index b080901..ae759c0 100644 --- a/include/mr-math/color.hpp +++ b/include/mr-math/color.hpp @@ -12,14 +12,14 @@ namespace mr { using ValueT = float; template - constexpr Color(T r, T g, T b, T a = 1) noexcept + Color(T r, T g, T b, T a = 1) noexcept : _data{Vec4f{r, g, b, a}} {} - constexpr Color(Vec4f rgba) noexcept + Color(Vec4f rgba) noexcept : _data{rgba} {} template - constexpr Color(T r, T g, T b, T a = 255) noexcept + Color(T r, T g, T b, T a = 255) noexcept : _data{r, g, b, a} { assert(r <= 255); assert(g <= 255); @@ -28,7 +28,7 @@ namespace mr { _data /= 255; } - constexpr explicit Color(uint32_t rgba) noexcept + explicit Color(uint32_t rgba) noexcept : Color( uint8_t((rgba & 0xFF'00'00'00) >> 24), uint8_t((rgba & 0x00'FF'00'00) >> 16), @@ -37,11 +37,11 @@ namespace mr { ) {} // setters - constexpr void r(ValueT r) noexcept { _data.x(r); } - constexpr void g(ValueT g) noexcept { _data.y(g); } - constexpr void b(ValueT b) noexcept { _data.z(b); } - constexpr void a(ValueT a) noexcept { _data.w(a); } - constexpr void set(size_t i, ValueT value) noexcept { + void r(ValueT r) noexcept { _data.x(r); } + void g(ValueT g) noexcept { _data.y(g); } + void b(ValueT b) noexcept { _data.z(b); } + void a(ValueT a) noexcept { _data.w(a); } + void set(size_t i, ValueT value) noexcept { assert(i < 4); _data.set(i, value); } @@ -58,23 +58,23 @@ namespace mr { // structured binding support template requires (I < 4) - constexpr ValueT get() const { return _data[I]; } + ValueT get() const { return _data[I]; } - constexpr operator Vec4f() noexcept { + operator Vec4f() noexcept { return _data; } // format conversions // TODO: implement using shuffle - constexpr Vec4f argb() const noexcept { + Vec4f argb() const noexcept { return {a(), r(), g(), b()}; } - constexpr Vec4f bgra() const noexcept { + Vec4f bgra() const noexcept { return {b(), g(), r(), a()}; } - constexpr Vec4f abgr() const noexcept { + Vec4f abgr() const noexcept { return {a(), b(), g(), r()}; } @@ -88,11 +88,11 @@ namespace mr { return *this; } - constexpr bool operator==(const Color &other) const noexcept { + bool operator==(const Color &other) const noexcept { return _data == other._data; } - constexpr bool equal(const Color &other, ValueT eps = epsilon()) const noexcept { + bool equal(const Color &other, ValueT eps = epsilon()) const noexcept { return _data.equal(other._data, eps); } @@ -109,7 +109,7 @@ namespace mr { namespace literals { - constexpr Color operator"" _rgba(unsigned long long value) { + Color operator"" _rgba(unsigned long long value) { assert(value <= 0xFF'FF'FF'FF); return Color{static_cast(value)}; } From c05ccffb6ba169aee1729706e3dae280e77719b1 Mon Sep 17 00:00:00 2001 From: Danil Belov Date: Mon, 16 Dec 2024 12:52:55 +0200 Subject: [PATCH 5/5] Rename Range(Ex) into Interval(Ex) --- include/mr-math/def.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/mr-math/def.hpp b/include/mr-math/def.hpp index d58a7c4..52a8554 100644 --- a/include/mr-math/def.hpp +++ b/include/mr-math/def.hpp @@ -69,12 +69,12 @@ namespace mr { inline struct UncheckedTag {} unchecked; - // inclusive range [low, high] + // inclusive numeric interval [low, high] // use operator() to check if value is in range template - class Range { + class Interval { public: - Range(const L& low_, const H& high_) + Interval(const L& low_, const H& high_) : low(low_), high(high_) {} template @@ -85,19 +85,19 @@ namespace mr { const H& high; }; - // returns inclusive range + // returns inclusive interval // usage: if (mr::within_(1, 10)(x)) template - constexpr Range within(const L& low, const H& high) { + constexpr Interval within(const L& low, const H& high) { return {low, high}; } - // inclusive range (low, high) + // exclusive numeric interval (low, high) // use operator() to check if value is in range template - class RangeEx { + class IntervalEx { public: - RangeEx(const L& low_, const H& high_) + IntervalEx(const L& low_, const H& high_) : low(low_), high(high_) {} template @@ -108,10 +108,10 @@ namespace mr { const H& high; }; - // returns exclusive rang + // returns exclusive interval // usage: if (mr::within_ex(1, 10)(x)) template - constexpr RangeEx within_ex(const L& low, const H& high) { + constexpr IntervalEx within_ex(const L& low, const H& high) { return {low, high}; } } // namespace mr