/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bli */ #include "BLI_math_rotation_types.hh" #include "BLI_math_axis_angle.hh" #include "BLI_math_euler.hh" #include "BLI_math_matrix.hh" #include "BLI_math_quaternion.hh" #include "BLI_math_vector.hh" namespace blender::math { /* -------------------------------------------------------------------- */ /** \name Rotation helpers * \{ */ /** * Rotate \a a by \a b. In other word, insert the \a b rotation before \a a. * * \note Since \a a is a #Quaternion it will cast \a b to a #Quaternion. * This might introduce some precision loss and have performance implication. */ template [[nodiscard]] QuaternionBase rotate(const QuaternionBase &a, const RotT &b); /** * Rotate \a a by \a b. In other word, insert the \a b rotation before \a a. * * \note Since \a a is an #AxisAngle it will cast both \a a and \a b to #Quaternion. * This might introduce some precision loss and have performance implication. */ template [[nodiscard]] AxisAngleBase rotate(const AxisAngleBase &a, const RotT &b); /** * Rotate \a a by \a b. In other word, insert the \a b rotation before \a a. * * \note Since \a a is an #EulerXYZ it will cast both \a a and \a b to #MatBase. * This might introduce some precision loss and have performance implication. */ template [[nodiscard]] EulerXYZBase rotate(const EulerXYZBase &a, const RotT &b); /** * Rotate \a a by \a b. In other word, insert the \a b rotation before \a a. * * \note Since \a a is an #Euler3 it will cast both \a a and \a b to #MatBase. * This might introduce some precision loss and have performance implication. */ template [[nodiscard]] Euler3Base rotate(const Euler3Base &a, const RotT &b); /** * Return rotation from orientation \a a to orientation \a b into another quaternion. */ template [[nodiscard]] QuaternionBase rotation_between(const QuaternionBase &a, const QuaternionBase &b); /** * Create a orientation from a triangle plane and the axis formed by the segment(v1, v2). * Takes pre-computed \a normal from the triangle. * Used for Ngons when their normal is known. */ template [[nodiscard]] QuaternionBase from_triangle(const VecBase &v1, const VecBase &v2, const VecBase &v3, const VecBase &normal); /** * Create a orientation from a triangle plane and the axis formed by the segment(v1, v2). */ template [[nodiscard]] QuaternionBase from_triangle(const VecBase &v1, const VecBase &v2, const VecBase &v3); /** * Create a rotation from a vector and a basis rotation. * Used for tracking. * \a track_flag is supposed to be #Object.trackflag * \a up_flag is supposed to be #Object.upflag */ template [[nodiscard]] QuaternionBase from_vector(const VecBase &vector, const AxisSigned track_flag, const Axis up_flag); /** * Returns a quaternion for converting local space to tracking space. * This is slightly different than from_axis_conversion for legacy reasons. */ template [[nodiscard]] QuaternionBase from_tracking(AxisSigned forward_axis, Axis up_axis); /** * Convert euler rotation to gimbal rotation matrix. */ template [[nodiscard]] MatBase to_gimbal_axis(const Euler3Base &rotation); /** \} */ /* -------------------------------------------------------------------- */ /** \name Angles * \{ */ /** * Extract rotation angle from a unit quaternion. * Returned angle is in [0..2pi] range. * * Unlike the angle between vectors, this does *NOT* return the shortest angle. * See `angle_of_signed` below for this. */ template [[nodiscard]] AngleRadianBase angle_of(const QuaternionBase &q) { BLI_assert(is_unit_scale(q)); return T(2) * math::safe_acos(q.w); } /** * Extract rotation angle from a unit quaternion. Always return the shortest angle. * Returned angle is in [-pi..pi] range. * * `angle_of` with quaternion can exceed PI radians. Having signed versions of these functions * allows to use 'abs(angle_of_signed(...))' to get the shortest angle between quaternions with * higher precision than subtracting 2pi afterwards. */ template [[nodiscard]] AngleRadianBase angle_of_signed(const QuaternionBase &q) { BLI_assert(is_unit_scale(q)); return T(2) * ((q.w >= T(0)) ? math::safe_acos(q.w) : -math::safe_acos(-q.w)); } /** * Extract angle between 2 orientations. * For #Quaternion, the returned angle is in [0..2pi] range. * For other types, the returned angle is in [0..pi] range. * See `angle_of` for more detail. */ template [[nodiscard]] AngleRadianBase angle_between(const QuaternionBase &a, const QuaternionBase &b) { return angle_of(rotation_between(a, b)); } template [[nodiscard]] AngleRadianBase angle_between(const VecBase &a, const VecBase &b) { BLI_assert(is_unit_scale(a)); BLI_assert(is_unit_scale(b)); return math::safe_acos(dot(a, b)); } template [[nodiscard]] AngleFraction angle_between(const AxisSigned a, const AxisSigned b) { if (a == b) { return AngleFraction::identity(); } if (abs(a) == abs(b)) { return AngleFraction::pi(); } return AngleFraction::pi() / 2; } /** * Extract angle between 2 orientations. * Returned angle is in [-pi..pi] range. * See `angle_of_signed` for more detail. */ template [[nodiscard]] AngleRadianBase angle_between_signed(const QuaternionBase &a, const QuaternionBase &b) { return angle_of_signed(rotation_between(a, b)); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Template implementations * \{ */ template [[nodiscard]] QuaternionBase rotate(const QuaternionBase &a, const RotT &b) { return a * QuaternionBase(b); } template [[nodiscard]] AxisAngleBase rotate(const AxisAngleBase &a, const RotT &b) { return AxisAngleBase(QuaternionBase(a) * QuaternionBase(b)); } template [[nodiscard]] EulerXYZBase rotate(const EulerXYZBase &a, const RotT &b) { MatBase tmp = from_rotation>(a) * from_rotation>(b); return to_euler(tmp); } template [[nodiscard]] Euler3Base rotate(const Euler3Base &a, const RotT &b) { const MatBase tmp = from_rotation>(a) * from_rotation>(b); return to_euler(tmp, a.order()); } template [[nodiscard]] QuaternionBase rotation_between(const QuaternionBase &a, const QuaternionBase &b) { return invert(a) * b; } template [[nodiscard]] QuaternionBase from_triangle(const VecBase &v1, const VecBase &v2, const VecBase &v3, const VecBase &normal) { /* Force to used an unused var to avoid the same function signature as the version without * `normal` argument. */ UNUSED_VARS(v3); using Vec3T = VecBase; /* Move z-axis to face-normal. */ const Vec3T z_axis = normal; Vec3T nor = normalize(Vec3T(z_axis.y, -z_axis.x, T(0))); if (is_zero(nor.xy())) { nor.x = T(1); } T angle = T(-0.5) * math::safe_acos(z_axis.z); T si = math::sin(angle); QuaternionBase q1(math::cos(angle), nor.x * si, nor.y * si, T(0)); /* Rotate back line v1-v2. */ Vec3T line = transform_point(conjugate(q1), (v2 - v1)); /* What angle has this line with x-axis? */ line = normalize(Vec3T(line.x, line.y, T(0))); angle = T(0.5) * math::atan2(line.y, line.x); QuaternionBase q2(math::cos(angle), 0.0, 0.0, math::sin(angle)); return q1 * q2; } template [[nodiscard]] QuaternionBase from_triangle(const VecBase &v1, const VecBase &v2, const VecBase &v3) { return from_triangle(v1, v2, v3, normal_tri(v1, v2, v3)); } template [[nodiscard]] QuaternionBase from_vector(const VecBase &vector, const AxisSigned track_flag, const Axis up_flag) { using Vec2T = VecBase; using Vec3T = VecBase; using Vec4T = VecBase; const T vec_len = length(vector); if (UNLIKELY(vec_len == 0.0f)) { return QuaternionBase::identity(); } const Axis axis = track_flag.axis(); const Vec3T vec = track_flag.is_negative() ? vector : -vector; Vec3T rotation_axis; constexpr T eps = T(1e-4); T axis_len; switch (axis) { case Axis::X: rotation_axis = normalize_and_get_length(Vec3T(T(0), -vec.z, vec.y), axis_len); if (axis_len < eps) { rotation_axis = Vec3T(0, 1, 0); } break; case Axis::Y: rotation_axis = normalize_and_get_length(Vec3T(vec.z, T(0), -vec.x), axis_len); if (axis_len < eps) { rotation_axis = Vec3T(0, 0, 1); } break; default: case Axis::Z: rotation_axis = normalize_and_get_length(Vec3T(-vec.y, vec.x, T(0)), axis_len); if (axis_len < eps) { rotation_axis = Vec3T(1, 0, 0); } break; } /* TODO(fclem): Can optimize here by initializing AxisAngle using the cos an sin directly. * Avoiding the need for safe_acos and deriving sin from cos. */ const T rotation_angle = math::safe_acos(vec[axis.as_int()] / vec_len); const QuaternionBase q1 = to_quaternion( AxisAngleBase>(rotation_axis, rotation_angle)); if (axis == up_flag) { /* Nothing else to do. */ return q1; } /* Extract rotation between the up axis of the rotated space and the up axis. */ /* There might be an easier way to get this angle directly from the quaternion representation. */ const Vec3T rotated_up = transform_point(q1, Vec3T(0, 0, 1)); /* Project using axes index instead of arithmetic. It's much faster and more precise. */ const AxisSigned y_axis_signed = math::cross(AxisSigned(axis), AxisSigned(up_flag)); const Axis x_axis = up_flag; const Axis y_axis = y_axis_signed.axis(); Vec2T projected = normalize(Vec2T(rotated_up[x_axis.as_int()], rotated_up[y_axis.as_int()])); /* Flip sign for flipped axis. */ if (y_axis_signed.is_negative()) { projected.y = -projected.y; } /* Not sure if this was a bug or not in the previous implementation. * Carry over this weird behavior to avoid regressions. */ if (axis == Axis::Z) { projected = -projected; } const AngleCartesianBase angle(projected.x, projected.y); const AngleCartesianBase half_angle = angle / T(2); const QuaternionBase q2(Vec4T(half_angle.cos(), vec * (half_angle.sin() / vec_len))); return q2 * q1; } template [[nodiscard]] QuaternionBase from_tracking(AxisSigned forward_axis, Axis up_axis) { BLI_assert(forward_axis.axis() != up_axis); /* Curve have Z forward, Y up, X left. */ return QuaternionBase( rotation_between(from_orthonormal_axes(AxisSigned::Z_POS, AxisSigned::Y_POS), from_orthonormal_axes(forward_axis, AxisSigned(up_axis)))); } template [[nodiscard]] MatBase to_gimbal_axis(const Euler3Base &rotation) { using Mat3T = MatBase; using Vec3T = VecBase; const int i_index = rotation.i_index(); const int j_index = rotation.j_index(); const int k_index = rotation.k_index(); Mat3T result; /* First axis is local. */ result[i_index] = from_rotation(rotation)[i_index]; /* Second axis is local minus first rotation. */ Euler3Base tmp_rot = rotation; tmp_rot.i() = T(0); result[j_index] = from_rotation(tmp_rot)[j_index]; /* Last axis is global. */ result[k_index] = Vec3T(0); result[k_index][k_index] = T(1); return result; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Conversion from Cartesian Basis * \{ */ /** * Creates a quaternion from an axis triple. * This is faster and more precise than converting from another representation. */ template QuaternionBase to_quaternion(const CartesianBasis &rotation) { /** * There are only 6 * 4 = 24 possible valid orthonormal orientations. * We precompute them and store them inside this switch using a key. * Generated using `generate_axes_to_quaternion_switch_cases()`. */ constexpr auto map = [](AxisSigned x, AxisSigned y, AxisSigned z) { return x.as_int() << 16 | y.as_int() << 8 | z.as_int(); }; switch (map(rotation.axes.x, rotation.axes.y, rotation.axes.z)) { default: return QuaternionBase::identity(); case map(AxisSigned::Z_POS, AxisSigned::X_POS, AxisSigned::Y_POS): return QuaternionBase{T(0.5), T(-0.5), T(-0.5), T(-0.5)}; case map(AxisSigned::Y_NEG, AxisSigned::X_POS, AxisSigned::Z_POS): return QuaternionBase{T(M_SQRT1_2), T(0), T(0), T(-M_SQRT1_2)}; case map(AxisSigned::Z_NEG, AxisSigned::X_POS, AxisSigned::Y_NEG): return QuaternionBase{T(0.5), T(0.5), T(0.5), T(-0.5)}; case map(AxisSigned::Y_POS, AxisSigned::X_POS, AxisSigned::Z_NEG): return QuaternionBase{T(0), T(M_SQRT1_2), T(M_SQRT1_2), T(0)}; case map(AxisSigned::Z_NEG, AxisSigned::Y_POS, AxisSigned::X_POS): return QuaternionBase{T(M_SQRT1_2), T(0), T(M_SQRT1_2), T(0)}; case map(AxisSigned::Z_POS, AxisSigned::Y_POS, AxisSigned::X_NEG): return QuaternionBase{T(M_SQRT1_2), T(0), T(-M_SQRT1_2), T(0)}; case map(AxisSigned::X_NEG, AxisSigned::Y_POS, AxisSigned::Z_NEG): return QuaternionBase{T(0), T(0), T(1), T(0)}; case map(AxisSigned::Y_POS, AxisSigned::Z_POS, AxisSigned::X_POS): return QuaternionBase{T(0.5), T(0.5), T(0.5), T(0.5)}; case map(AxisSigned::X_NEG, AxisSigned::Z_POS, AxisSigned::Y_POS): return QuaternionBase{T(0), T(0), T(M_SQRT1_2), T(M_SQRT1_2)}; case map(AxisSigned::Y_NEG, AxisSigned::Z_POS, AxisSigned::X_NEG): return QuaternionBase{T(0.5), T(0.5), T(-0.5), T(-0.5)}; case map(AxisSigned::X_POS, AxisSigned::Z_POS, AxisSigned::Y_NEG): return QuaternionBase{T(M_SQRT1_2), T(M_SQRT1_2), T(0), T(0)}; case map(AxisSigned::Z_NEG, AxisSigned::X_NEG, AxisSigned::Y_POS): return QuaternionBase{T(0.5), T(-0.5), T(0.5), T(0.5)}; case map(AxisSigned::Y_POS, AxisSigned::X_NEG, AxisSigned::Z_POS): return QuaternionBase{T(M_SQRT1_2), T(0), T(0), T(M_SQRT1_2)}; case map(AxisSigned::Z_POS, AxisSigned::X_NEG, AxisSigned::Y_NEG): return QuaternionBase{T(0.5), T(0.5), T(-0.5), T(0.5)}; case map(AxisSigned::Y_NEG, AxisSigned::X_NEG, AxisSigned::Z_NEG): return QuaternionBase{T(0), T(-M_SQRT1_2), T(M_SQRT1_2), T(0)}; case map(AxisSigned::Z_POS, AxisSigned::Y_NEG, AxisSigned::X_POS): return QuaternionBase{T(0), T(M_SQRT1_2), T(0), T(M_SQRT1_2)}; case map(AxisSigned::X_NEG, AxisSigned::Y_NEG, AxisSigned::Z_POS): return QuaternionBase{T(0), T(0), T(0), T(1)}; case map(AxisSigned::Z_NEG, AxisSigned::Y_NEG, AxisSigned::X_NEG): return QuaternionBase{T(0), T(-M_SQRT1_2), T(0), T(M_SQRT1_2)}; case map(AxisSigned::X_POS, AxisSigned::Y_NEG, AxisSigned::Z_NEG): return QuaternionBase{T(0), T(1), T(0), T(0)}; case map(AxisSigned::Y_NEG, AxisSigned::Z_NEG, AxisSigned::X_POS): return QuaternionBase{T(0.5), T(-0.5), T(0.5), T(-0.5)}; case map(AxisSigned::X_POS, AxisSigned::Z_NEG, AxisSigned::Y_POS): return QuaternionBase{T(M_SQRT1_2), T(-M_SQRT1_2), T(0), T(0)}; case map(AxisSigned::Y_POS, AxisSigned::Z_NEG, AxisSigned::X_NEG): return QuaternionBase{T(0.5), T(-0.5), T(-0.5), T(0.5)}; case map(AxisSigned::X_NEG, AxisSigned::Z_NEG, AxisSigned::Y_NEG): return QuaternionBase{T(0), T(0), T(-M_SQRT1_2), T(M_SQRT1_2)}; } } /** \} */ } // namespace blender::math namespace blender::math { /* Using explicit template instantiations in order to reduce compilation time. */ extern template EulerXYZ to_euler(const AxisAngle &); extern template EulerXYZ to_euler(const AxisAngleCartesian &); extern template EulerXYZ to_euler(const Quaternion &); extern template Euler3 to_euler(const AxisAngle &, EulerOrder); extern template Euler3 to_euler(const AxisAngleCartesian &, EulerOrder); extern template Euler3 to_euler(const Quaternion &, EulerOrder); extern template Quaternion to_quaternion(const AxisAngle &); extern template Quaternion to_quaternion(const AxisAngleCartesian &); extern template Quaternion to_quaternion(const Euler3 &); extern template Quaternion to_quaternion(const EulerXYZ &); extern template AxisAngleCartesian to_axis_angle(const Euler3 &); extern template AxisAngleCartesian to_axis_angle(const EulerXYZ &); extern template AxisAngleCartesian to_axis_angle(const Quaternion &); extern template AxisAngle to_axis_angle(const Euler3 &); extern template AxisAngle to_axis_angle(const EulerXYZ &); extern template AxisAngle to_axis_angle(const Quaternion &); } // namespace blender::math /** \} */