From a4e5469cc070cc132a27db59e53ab163d0953b6a Mon Sep 17 00:00:00 2001 From: Falk David Date: Tue, 5 Mar 2024 14:32:48 +0100 Subject: [PATCH] BLI: Bounds: Add basic functions The overall goal is to be able to eventually replace the `BLI_rect.h` C-style API. This only adds come basic functions and tests. Pull Request: https://projects.blender.org/blender/blender/pulls/118964 --- source/blender/blenlib/BLI_bounds.hh | 77 ++++++++++++- source/blender/blenlib/BLI_bounds_types.hh | 46 ++++++++ .../blender/blenlib/tests/BLI_bounds_test.cc | 105 ++++++++++++++++++ 3 files changed, 226 insertions(+), 2 deletions(-) diff --git a/source/blender/blenlib/BLI_bounds.hh b/source/blender/blenlib/BLI_bounds.hh index 37200accc10..35102be81f4 100644 --- a/source/blender/blenlib/BLI_bounds.hh +++ b/source/blender/blenlib/BLI_bounds.hh @@ -17,7 +17,9 @@ #include "BLI_math_vector.hh" #include "BLI_task.hh" -namespace blender::bounds { +namespace blender { + +namespace bounds { template [[nodiscard]] inline Bounds merge(const Bounds &a, const Bounds &b) { @@ -115,4 +117,75 @@ template [](const Bounds &a, const Bounds &b) { return merge(a, b); }); } -} // namespace blender::bounds +} // namespace bounds + +namespace detail { + +template +[[nodiscard]] inline bool less_or_equal_than(const VecBase &a, const VecBase &b) +{ + for (int i = 0; i < Size; i++) { + if (a[i] > b[i]) { + return false; + } + } + return true; +} + +} // namespace detail + +template inline bool Bounds::is_empty() const +{ + if constexpr (std::is_integral::value || std::is_floating_point::value) { + return this->max <= this->min; + } + else { + return detail::less_or_equal_than(this->max, this->min); + } +} + +template inline T Bounds::center() const +{ + return math::midpoint(this->min, this->max); +} + +template inline T Bounds::size() const +{ + return math::abs(max - min); +} + +template inline void Bounds::translate(const T &offset) +{ + this->min += offset; + this->max += offset; +} + +template inline void Bounds::scale_from_center(const T &scale) +{ + const T center = this->center(); + const T new_half_size = this->size() / T(2) * scale; + this->min = center - new_half_size; + this->max = center + new_half_size; +} + +template inline void Bounds::resize(const T &new_size) +{ + this->min = this->center() - (new_size / T(2)); + this->max = this->min + new_size; +} + +template inline void Bounds::recenter(const T &new_center) +{ + const T offset = new_center - this->center(); + this->translate(offset); +} + +template +template +inline void Bounds::pad(const PaddingT &padding) +{ + this->min = this->min - padding; + this->max = this->max + padding; +} + +} // namespace blender diff --git a/source/blender/blenlib/BLI_bounds_types.hh b/source/blender/blenlib/BLI_bounds_types.hh index 5613c5433b4..21cb8d33039 100644 --- a/source/blender/blenlib/BLI_bounds_types.hh +++ b/source/blender/blenlib/BLI_bounds_types.hh @@ -16,6 +16,52 @@ template struct Bounds { Bounds() = default; Bounds(const T &value) : min(value), max(value) {} Bounds(const T &min, const T &max) : min(min), max(max) {} + + /** + * Returns true when the size of the bounds is zero (or negative). + * This matches the behavior of #BLI_rcti_is_empty/#BLI_rctf_is_empty. + */ + bool is_empty() const; + /** + * Return the center (i.e. the midpoint) of the bounds. + * This matches the behavior of #BLI_rctf_cent/#BLI_rcti_cent. + */ + T center() const; + /** + * Return the size of the bounds. + * E.g. for a Bounds this would return the dimensions of bounding box as a float3. + * This matches the behavior of #BLI_rctf_size/#BLI_rcti_size. + */ + T size() const; + + /** + * Translate the bounds by #offset. + * This matches the behavior of #BLI_rctf_translate/#BLI_rcti_translate. + */ + void translate(const T &offset); + /** + * Scale the bounds from the center. + * This matches the behavior of #BLI_rctf_scale/#BLI_rcti_scale. + */ + void scale_from_center(const T &scale); + + /** + * Resize the bounds in-place to ensure their size is #new_size. + * The center of the bounds doesn't change. + * This matches the behavior of #BLI_rctf_resize/#BLI_rcti_resize. + */ + void resize(const T &new_size); + /** + * Translate the bounds such that their center is #new_center. + * This matches the behavior of #BLI_rctf_recenter/#BLI_rcti_recenter. + */ + void recenter(const T &new_center); + + /** + * Adds some padding to the bounds. + * This matches the behavior of #BLI_rcti_pad/#BLI_rctf_pad. + */ + template void pad(const PaddingT &padding); }; } // namespace blender diff --git a/source/blender/blenlib/tests/BLI_bounds_test.cc b/source/blender/blenlib/tests/BLI_bounds_test.cc index 44f6cff9ace..9da09ebd4e7 100644 --- a/source/blender/blenlib/tests/BLI_bounds_test.cc +++ b/source/blender/blenlib/tests/BLI_bounds_test.cc @@ -12,6 +12,111 @@ namespace blender::tests { TEST(bounds, Empty) +{ + Bounds bounds1(float2(0.0f)); + Bounds bounds2(float2(1.0f), float2(-1.0f)); + Bounds bounds3(float2(-1.0f), float2(1.0f)); + EXPECT_TRUE(bounds1.is_empty()); + EXPECT_TRUE(bounds2.is_empty()); + EXPECT_FALSE(bounds3.is_empty()); +} + +TEST(bounds, EmptyInt) +{ + Bounds bounds1(0); + Bounds bounds2(1, -1); + Bounds bounds3(-1, 1); + EXPECT_TRUE(bounds1.is_empty()); + EXPECT_TRUE(bounds2.is_empty()); + EXPECT_FALSE(bounds3.is_empty()); +} + +TEST(bounds, Center) +{ + Bounds bounds1(float2(0.0f)); + Bounds bounds2(float2(-1.0f)); + Bounds bounds3(float2(-1.0f), float2(1.0f)); + Bounds bounds4(float2(-3.0f, -5.0f), float2(2.0f, 4.0f)); + EXPECT_EQ(bounds1.center(), float2(0.0f)); + EXPECT_EQ(bounds2.center(), float2(-1.0f)); + EXPECT_EQ(bounds3.center(), float2(0.0f)); + EXPECT_EQ(bounds4.center(), float2(-0.5f, -0.5f)); +} + +TEST(bounds, Size) +{ + Bounds bounds1(float2(0.0f)); + Bounds bounds2(float2(-1.0f)); + Bounds bounds3(float2(-3.0f, -5.0f), float2(2.0f, 4.0f)); + EXPECT_EQ(bounds1.size(), float2(0.0f)); + EXPECT_EQ(bounds2.size(), float2(0.0f)); + EXPECT_EQ(bounds3.size(), float2(5.0f, 9.0f)); +} + +TEST(bounds, Translate) +{ + Bounds bounds1(float2(0.0f)); + Bounds bounds2(float2(-3.0f, -5.0f), float2(2.0f, 4.0f)); + bounds1.translate(float2(-1.0f)); + bounds2.translate(float2(2.0f)); + EXPECT_EQ(bounds1.min, float2(-1.0f)); + EXPECT_EQ(bounds1.max, float2(-1.0f)); + EXPECT_EQ(bounds2.min, float2(-1.0f, -3.0f)); + EXPECT_EQ(bounds2.max, float2(4.0f, 6.0f)); +} + +TEST(bounds, ScaleFromCenter) +{ + Bounds bounds1(float2(0.0f)); + Bounds bounds2(float2(-3.0f, -5.0f), float2(2.0f, 4.0f)); + bounds1.scale_from_center(float2(2.0f)); + const float2 bound2_size = bounds2.size(); + bounds2.scale_from_center(float2(2.0f, 1.0f)); + EXPECT_EQ(bounds1.min, float2(0.0f)); + EXPECT_EQ(bounds1.max, float2(0.0f)); + EXPECT_EQ(bounds2.min, float2(-5.5f, -5.0f)); + EXPECT_EQ(bounds2.max, float2(4.5f, 4.0f)); + EXPECT_EQ(bounds2.size(), bound2_size * float2(2.0f, 1.0f)); +} + +TEST(bounds, Resize) +{ + Bounds bounds1(float2(0.0f)); + Bounds bounds2(float2(-3.0f, -5.0f), float2(2.0f, 4.0f)); + bounds1.resize(float2(1.0f)); + bounds2.resize(float2(7.0f, 10.0f)); + EXPECT_EQ(bounds1.center(), float2(0.0f)); + EXPECT_EQ(bounds1.size(), float2(1.0f)); + EXPECT_EQ(bounds2.size(), float2(7.0f, 10.0f)); +} + +TEST(bounds, Recenter) +{ + Bounds bounds1(float2(0.0f)); + Bounds bounds2(float2(-3.0f, -5.0f), float2(2.0f, 4.0f)); + bounds1.recenter(float2(-1.0f)); + bounds2.recenter(float2(2.0f, 3.0f)); + EXPECT_EQ(bounds1.center(), float2(-1.0f)); + EXPECT_EQ(bounds2.center(), float2(2.0f, 3.0f)); +} + +TEST(bounds, Pad) +{ + Bounds bounds1(float2(0.0f)); + Bounds bounds2(float2(-1.0f), float2(1.0f)); + Bounds bounds3(float2(-3.0f, -5.0f), float2(2.0f, 4.0f)); + bounds1.pad(float2(1.0f)); + bounds2.pad(1.0f); + bounds3.pad(float2(1.0f, 2.0f)); + EXPECT_EQ(bounds1.min, float2(-1.0f)); + EXPECT_EQ(bounds1.max, float2(1.0f)); + EXPECT_EQ(bounds2.min, float2(-2.0f)); + EXPECT_EQ(bounds2.max, float2(2.0f)); + EXPECT_EQ(bounds3.min, float2(-4.0f, -7.0f)); + EXPECT_EQ(bounds3.max, float2(3.0f, 6.0f)); +} + +TEST(bounds, MinMaxEmpty) { Span empty_span{}; EXPECT_TRUE(empty_span.is_empty());