/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bli */ #include "BLI_math_matrix.hh" #include "BLI_math_rotation_types.hh" #include "BLI_math_vector.hh" namespace blender::math { /** * Generic function for implementing slerp * (quaternions and spherical vector coords). * * \param t: factor in [0..1] * \param cosom: dot product from normalized vectors/quats. * \param r_w: calculated weights. */ template inline vec_base interpolate_dot_slerp(const T t, const T cosom) { const T eps = T(1e-4); BLI_assert(IN_RANGE_INCL(cosom, T(-1.0001), T(1.0001))); vec_base w; /* Within [-1..1] range, avoid aligned axis. */ if (LIKELY(math::abs(cosom) < (T(1) - eps))) { const T omega = math::acos(cosom); const T sinom = math::sin(omega); w[0] = math::sin((T(1) - t) * omega) / sinom; w[1] = math::sin(t * omega) / sinom; } else { /* Fallback to lerp */ w[0] = T(1) - t; w[1] = t; } return w; } template inline detail::Quaternion interpolate(const detail::Quaternion &a, const detail::Quaternion &b, T t) { using Vec4T = vec_base; BLI_assert(is_unit_scale(Vec4T(a))); BLI_assert(is_unit_scale(Vec4T(b))); Vec4T quat = Vec4T(a); T cosom = dot(Vec4T(a), Vec4T(b)); /* Rotate around shortest angle. */ if (cosom < T(0)) { cosom = -cosom; quat = -quat; } vec_base w = interpolate_dot_slerp(t, cosom); return detail::Quaternion(w[0] * quat + w[1] * Vec4T(b)); } } // namespace blender::math /* -------------------------------------------------------------------- */ /** \name Template implementations * \{ */ namespace blender::math::detail { #ifdef DEBUG # define BLI_ASSERT_UNIT_QUATERNION(_q) \ { \ auto rot_vec = static_cast>(_q); \ T quat_length = math::length_squared(rot_vec); \ if (!(quat_length == 0 || (math::abs(quat_length - 1) < 0.0001))) { \ std::cout << "Warning! " << __func__ << " called with non-normalized quaternion: size " \ << quat_length << " *** report a bug ***\n"; \ } \ } #else # define BLI_ASSERT_UNIT_QUATERNION(_q) #endif template AxisAngle::AxisAngle(const vec_base &axis, T angle) { T length; axis_ = math::normalize_and_get_length(axis, length); if (length > 0.0f) { angle_cos_ = math::cos(angle); angle_sin_ = math::sin(angle); angle_ = angle; } else { *this = identity(); } } template AxisAngle::AxisAngle(const vec_base &from, const vec_base &to) { BLI_assert(is_unit_scale(from)); BLI_assert(is_unit_scale(to)); /* Avoid calculating the angle. */ angle_cos_ = dot(from, to); axis_ = normalize_and_get_length(cross(from, to), angle_sin_); if (angle_sin_ <= FLT_EPSILON) { if (angle_cos_ > T(0)) { /* Same vectors, zero rotation... */ *this = identity(); } else { /* Colinear but opposed vectors, 180 rotation... */ axis_ = normalize(orthogonal(from)); angle_sin_ = T(0); angle_cos_ = T(-1); } } } template AxisAngleNormalized::AxisAngleNormalized(const vec_base &axis, T angle) : AxisAngle() { BLI_assert(is_unit_scale(axis)); this->axis_ = axis; this->angle_ = angle; this->angle_cos_ = math::cos(angle); this->angle_sin_ = math::sin(angle); } template Quaternion::operator EulerXYZ() const { using Mat3T = MatBase; const Quaternion &quat = *this; BLI_ASSERT_UNIT_QUATERNION(quat) Mat3T unit_mat = math::from_rotation(quat); return math::to_euler(unit_mat); } template EulerXYZ::operator Quaternion() const { const EulerXYZ &eul = *this; const T ti = eul.x * T(0.5); const T tj = eul.y * T(0.5); const T th = eul.z * T(0.5); const T ci = math::cos(ti); const T cj = math::cos(tj); const T ch = math::cos(th); const T si = math::sin(ti); const T sj = math::sin(tj); const T sh = math::sin(th); const T cc = ci * ch; const T cs = ci * sh; const T sc = si * ch; const T ss = si * sh; Quaternion quat; quat.x = cj * cc + sj * ss; quat.y = cj * sc - sj * cs; quat.z = cj * ss + sj * cc; quat.w = cj * cs - sj * sc; return quat; } template Quaternion::operator AxisAngle() const { const Quaternion &quat = *this; BLI_ASSERT_UNIT_QUATERNION(quat) /* Calculate angle/2, and sin(angle/2). */ T ha = math::acos(quat.x); T si = math::sin(ha); /* From half-angle to angle. */ T angle = ha * 2; /* Prevent division by zero for axis conversion. */ if (math::abs(si) < 0.0005) { si = 1.0f; } vec_base axis = vec_base(quat.y, quat.z, quat.w) / si; if (math::is_zero(axis)) { axis[1] = 1.0f; } return AxisAngleNormalized(axis, angle); } template AxisAngle::operator Quaternion() const { BLI_assert(math::is_unit_scale(axis_)); /** Using half angle identities: sin(angle / 2) = sqrt((1 - angle_cos) / 2) */ T sine = math::sqrt(T(0.5) - angle_cos_ * T(0.5)); const T cosine = math::sqrt(T(0.5) + angle_cos_ * T(0.5)); if (angle_sin_ < 0.0) { sine = -sine; } Quaternion quat; quat.x = cosine; quat.y = axis_.x * sine; quat.z = axis_.y * sine; quat.w = axis_.z * sine; return quat; } template EulerXYZ::operator AxisAngle() const { /* Use quaternions as intermediate representation for now... */ return AxisAngle(Quaternion(*this)); } template AxisAngle::operator EulerXYZ() const { /* Use quaternions as intermediate representation for now... */ return EulerXYZ(Quaternion(*this)); } /* Using explicit template instantiations in order to reduce compilation time. */ extern template AxisAngle::operator EulerXYZ() const; extern template AxisAngle::operator Quaternion() const; extern template EulerXYZ::operator AxisAngle() const; extern template EulerXYZ::operator Quaternion() const; extern template Quaternion::operator AxisAngle() const; extern template Quaternion::operator EulerXYZ() const; extern template AxisAngle::operator EulerXYZ() const; extern template AxisAngle::operator Quaternion() const; extern template EulerXYZ::operator AxisAngle() const; extern template EulerXYZ::operator Quaternion() const; extern template Quaternion::operator AxisAngle() const; extern template Quaternion::operator EulerXYZ() const; } // namespace blender::math::detail /** \} */