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
This commit is contained in:
Falk David 2024-03-05 14:32:48 +01:00 committed by Falk David
parent a58a2b0f5d
commit a4e5469cc0
3 changed files with 226 additions and 2 deletions

View File

@ -17,7 +17,9 @@
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
namespace blender::bounds {
namespace blender {
namespace bounds {
template<typename T> [[nodiscard]] inline Bounds<T> merge(const Bounds<T> &a, const Bounds<T> &b)
{
@ -115,4 +117,75 @@ template<typename T, typename RadiusT>
[](const Bounds<T> &a, const Bounds<T> &b) { return merge(a, b); });
}
} // namespace blender::bounds
} // namespace bounds
namespace detail {
template<typename T, int Size>
[[nodiscard]] inline bool less_or_equal_than(const VecBase<T, Size> &a, const VecBase<T, Size> &b)
{
for (int i = 0; i < Size; i++) {
if (a[i] > b[i]) {
return false;
}
}
return true;
}
} // namespace detail
template<typename T> inline bool Bounds<T>::is_empty() const
{
if constexpr (std::is_integral<T>::value || std::is_floating_point<T>::value) {
return this->max <= this->min;
}
else {
return detail::less_or_equal_than(this->max, this->min);
}
}
template<typename T> inline T Bounds<T>::center() const
{
return math::midpoint(this->min, this->max);
}
template<typename T> inline T Bounds<T>::size() const
{
return math::abs(max - min);
}
template<typename T> inline void Bounds<T>::translate(const T &offset)
{
this->min += offset;
this->max += offset;
}
template<typename T> inline void Bounds<T>::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<typename T> inline void Bounds<T>::resize(const T &new_size)
{
this->min = this->center() - (new_size / T(2));
this->max = this->min + new_size;
}
template<typename T> inline void Bounds<T>::recenter(const T &new_center)
{
const T offset = new_center - this->center();
this->translate(offset);
}
template<typename T>
template<typename PaddingT>
inline void Bounds<T>::pad(const PaddingT &padding)
{
this->min = this->min - padding;
this->max = this->max + padding;
}
} // namespace blender

View File

@ -16,6 +16,52 @@ template<typename T> 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<float3> 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<typename PaddingT> void pad(const PaddingT &padding);
};
} // namespace blender

View File

@ -12,6 +12,111 @@
namespace blender::tests {
TEST(bounds, Empty)
{
Bounds<float2> bounds1(float2(0.0f));
Bounds<float2> bounds2(float2(1.0f), float2(-1.0f));
Bounds<float2> 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<int> bounds1(0);
Bounds<int> bounds2(1, -1);
Bounds<int> bounds3(-1, 1);
EXPECT_TRUE(bounds1.is_empty());
EXPECT_TRUE(bounds2.is_empty());
EXPECT_FALSE(bounds3.is_empty());
}
TEST(bounds, Center)
{
Bounds<float2> bounds1(float2(0.0f));
Bounds<float2> bounds2(float2(-1.0f));
Bounds<float2> bounds3(float2(-1.0f), float2(1.0f));
Bounds<float2> 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<float2> bounds1(float2(0.0f));
Bounds<float2> bounds2(float2(-1.0f));
Bounds<float2> 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<float2> bounds1(float2(0.0f));
Bounds<float2> 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<float2> bounds1(float2(0.0f));
Bounds<float2> 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<float2> bounds1(float2(0.0f));
Bounds<float2> 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<float2> bounds1(float2(0.0f));
Bounds<float2> 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<float2> bounds1(float2(0.0f));
Bounds<float2> bounds2(float2(-1.0f), float2(1.0f));
Bounds<float2> 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<float2> empty_span{};
EXPECT_TRUE(empty_span.is_empty());