BLI math: Do not use BLI_ASSERT_ C-style checks in modern cpp code.

The old C-style `BLI_ASSERT_UNIT_V...` assert macros have a few issues:
* They are named `unit`, but also consider a zero-length vector as valid.
* They use a fairly high epsilon value, which was defined because
  vertex normals used to be stored as shorts.

Fortunately, these are used only in one place in the modern BLI_math C++
code AFAICS, which is `math::rotate_direction_around_axis`.

This commit adds some utils to check for vectors being (almost) unit
or zero length, using more modern bases for epsilon values (from
`std::numeric_limits`).
* `is_zero` keeps its existing default arror of `0` (i.e. strictly null
  vector by default). That way, current behavior is not changed, and in
  most cases null vectors are explicitely created as exactly null.
* `is_unit` uses a default 10 times the type's epsilon, as a zero
  epsilon would virtually never succeed here.

And it modifies `rotate_direction_around_axis` to:
* Assert that `axis` is a unit vector.
* Early-out in case given `direction` is a null vector, or rotating
  angle is zero.
* Assert about `direction` being a unit vector otherwise.

Note that this will make `rotate_direction_around_axis` use much
stricter epsilon error factors. This does not seem to affect any of the
files that triggered asserts prior to recent fix in e18dd894b8 though.

Pull Request: https://projects.blender.org/blender/blender/pulls/122482
This commit is contained in:
Bastien Montagne 2024-05-30 11:44:57 +02:00 committed by Gitea
parent 43a47f39f9
commit dd3222d2f8
2 changed files with 49 additions and 29 deletions

@ -18,32 +18,6 @@
namespace blender::math {
/**
* Returns true if all components are exactly equal to 0.
*/
template<typename T, int Size> [[nodiscard]] inline bool is_zero(const VecBase<T, Size> &a)
{
for (int i = 0; i < Size; i++) {
if (a[i] != T(0)) {
return false;
}
}
return true;
}
/**
* Returns true if at least one component is exactly equal to 0.
*/
template<typename T, int Size> [[nodiscard]] inline bool is_any_zero(const VecBase<T, Size> &a)
{
for (int i = 0; i < Size; i++) {
if (a[i] == T(0)) {
return true;
}
}
return false;
}
/**
* Returns true if the given vectors are equal within the given epsilon.
* The epsilon is scaled for each component by magnitude of the matching component of `a`.
@ -745,6 +719,48 @@ template<typename T, int Size>
return true;
}
/**
* Return true if the absolute values of all components are smaller than given epsilon (0 by
* default).
*
* \note Does not compute the actual length of the vector, for performance.
*/
template<typename T, int Size>
[[nodiscard]] inline bool is_zero(const VecBase<T, Size> &a, const T epsilon = T(0))
{
for (int i = 0; i < Size; i++) {
if (math::abs(a[i]) > epsilon) {
return false;
}
}
return true;
}
/**
* Returns true if at least one component is exactly equal to 0.
*/
template<typename T, int Size> [[nodiscard]] inline bool is_any_zero(const VecBase<T, Size> &a)
{
for (int i = 0; i < Size; i++) {
if (a[i] == T(0)) {
return true;
}
}
return false;
}
/**
* Return true if the squared length of the vector is (almost) equal to 1 (with a
* `10 * std::numeric_limits<T>::epsilon()` epsilon error by default).
*/
template<typename T, int Size>
[[nodiscard]] inline bool is_unit(const VecBase<T, Size> &a,
const T epsilon = T(10) * std::numeric_limits<T>::epsilon())
{
const T length = length_squared(a);
return math::abs(length - T(1)) <= epsilon;
}
/** Intersections. */
template<typename T> struct isect_result {

@ -111,9 +111,13 @@ void generate_axes_to_quaternion_switch_cases()
float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, const float angle)
{
BLI_ASSERT_UNIT_V3(direction);
BLI_ASSERT_UNIT_V3(axis);
BLI_assert(!math::is_zero(axis));
BLI_assert(math::is_unit(axis));
if (UNLIKELY(angle == 0.0f || math::is_zero(direction, std::numeric_limits<float>::epsilon()))) {
return direction;
}
BLI_assert(math::is_unit(direction));
const float3 axis_scaled = axis * math::dot(direction, axis);
const float3 diff = direction - axis_scaled;