/* SPDX-FileCopyrightText: 2023 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bli * * Orthonormal rotation and orientation. * * A practical reminder: * - Forward is typically the positive Y direction in Blender. * - Up is typically the positive Z direction in Blender. * - Right is typically the positive X direction in Blender. * - Blender uses right handedness. * - For cross product, forward = thumb, up = index, right = middle finger. * * The basis changes for each space: * - Object: X-right, Y-forward, Z-up * - World: X-right, Y-forward, Z-up * - Armature Bone: X-right, Y-forward, Z-up (with forward being the root to tip direction) * - Curve Tangent-Space: X-left, Y-up, Z-forward */ #include #include "BLI_math_base.hh" #include "BLI_math_vector_types.hh" namespace blender::math { /* -------------------------------------------------------------------- */ /** \name Axes * \{ */ /** * An enum class representing one of the 3 basis axes. * This is implemented using a class to allow operators and methods. * NOTE: While this represents a 3D axis it can still be used to generate 2D basis vectors. */ class Axis { public: enum class Value : int8_t { /* Must start at 0. Used as indices in tables and vectors. */ X = 0, Y, Z, }; constexpr static Value X = Value::X; constexpr static Value Y = Value::Y; constexpr static Value Z = Value::Z; private: Value axis_; public: Axis() = default; constexpr Axis(const Value axis) : axis_(axis){}; /** Convert an uppercase axis character 'X', 'Y' or 'Z' to an enum value. */ constexpr explicit Axis(char axis_char) : axis_(static_cast(axis_char - 'X')) { BLI_assert(Value::X <= axis_ && axis_ <= Value::Z); } /** Allow casting from DNA enums stored as short / int. */ constexpr static Axis from_int(const int axis_int) { const Axis axis = static_cast(axis_int); BLI_assert(Axis::X <= axis && axis <= Axis::Z); return axis; } /* Allow usage in `switch()` statements and comparisons. */ constexpr operator Value() const { return axis_; } constexpr int as_int() const { return int(axis_); } /** Avoid hell. */ explicit operator bool() const = delete; friend std::ostream &operator<<(std::ostream &stream, const Axis axis); }; /** * An enum class representing one of the 6 axis aligned direction. * This is implemented using a class to allow operators and methods. * NOTE: While this represents a 3D axis it can still be used to generate 2D basis vectors. */ class AxisSigned { public: enum class Value : int8_t { /* Match #eTrackToAxis_Modes */ /* Must start at 0. Used as indices in tables and vectors. */ X_POS = 0, Y_POS = 1, Z_POS = 2, X_NEG = 3, Y_NEG = 4, Z_NEG = 5, }; constexpr static Value X_POS = Value::X_POS; constexpr static Value Y_POS = Value::Y_POS; constexpr static Value Z_POS = Value::Z_POS; constexpr static Value X_NEG = Value::X_NEG; constexpr static Value Y_NEG = Value::Y_NEG; constexpr static Value Z_NEG = Value::Z_NEG; private: Value axis_; public: AxisSigned() = default; constexpr AxisSigned(Value axis) : axis_(axis){}; constexpr AxisSigned(Axis axis) : axis_(from_int(axis.as_int())){}; /** Allow casting from DNA enums stored as short / int. */ constexpr static AxisSigned from_int(int axis_int) { const AxisSigned axis = static_cast(axis_int); BLI_assert(AxisSigned::X_POS <= axis && axis <= AxisSigned::Z_NEG); return axis; } /** Return the axis without the sign. It changes the type whereas abs(axis) doesn't. */ constexpr Axis axis() const { return Axis::from_int(this->as_int() % 3); } /** Return the opposing axis. */ AxisSigned operator-() const { return from_int((this->as_int() + 3) % 6); } /** Return next enum value. */ AxisSigned next_after() const { return from_int((this->as_int() + 1) % 6); } /** Allow usage in `switch()` statements and comparisons. */ constexpr operator Value() const { return axis_; } constexpr int as_int() const { return int(axis_); } /** Returns -1 if axis is negative, 1 otherwise. */ constexpr int sign() const { return is_negative() ? -1 : 1; } /** Returns true if axis is negative, false otherwise. */ constexpr bool is_negative() const { return int(axis_) > int(Value::Z_POS); } /** Avoid hell. */ explicit operator bool() const = delete; friend std::ostream &operator<<(std::ostream &stream, const AxisSigned axis); }; constexpr static bool operator<=(const Axis::Value a, const Axis::Value b) { return int(a) <= int(b); } constexpr static bool operator<=(const AxisSigned::Value a, const AxisSigned::Value b) { return int(a) <= int(b); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Axes Utilities * \{ */ template<> inline AxisSigned abs(const AxisSigned &axis) { return axis.axis(); } [[nodiscard]] inline int sign(const AxisSigned &axis) { return axis.sign(); } /** * Returns the cross direction from two basis direction using the right hand rule. * This is much faster than true cross product if the vectors are basis vectors. * Any ill-formed case will return a orthogonal axis to \a a but will also trigger an assert. It is * better to filter these cases upstream. */ [[nodiscard]] inline AxisSigned cross(const AxisSigned a, const AxisSigned b) { BLI_assert_msg(abs(a) != abs(b), "Axes must not be colinear."); switch (a) { case AxisSigned::X_POS: switch (b) { case AxisSigned::X_POS: break; /* Ill-defined. */ case AxisSigned::Y_POS: return AxisSigned::Z_POS; case AxisSigned::Z_POS: return AxisSigned::Y_NEG; case AxisSigned::X_NEG: break; /* Ill-defined. */ case AxisSigned::Y_NEG: return AxisSigned::Z_NEG; case AxisSigned::Z_NEG: return AxisSigned::Y_POS; } break; case AxisSigned::Y_POS: switch (b) { case AxisSigned::X_POS: return AxisSigned::Z_NEG; case AxisSigned::Y_POS: break; /* Ill-defined. */ case AxisSigned::Z_POS: return AxisSigned::X_POS; case AxisSigned::X_NEG: return AxisSigned::Z_POS; case AxisSigned::Y_NEG: break; /* Ill-defined. */ case AxisSigned::Z_NEG: return AxisSigned::X_NEG; } break; case AxisSigned::Z_POS: switch (b) { case AxisSigned::X_POS: return AxisSigned::Y_POS; case AxisSigned::Y_POS: return AxisSigned::X_NEG; case AxisSigned::Z_POS: break; /* Ill-defined. */ case AxisSigned::X_NEG: return AxisSigned::Y_NEG; case AxisSigned::Y_NEG: return AxisSigned::X_POS; case AxisSigned::Z_NEG: break; /* Ill-defined. */ } break; case AxisSigned::X_NEG: switch (b) { case AxisSigned::X_POS: break; /* Ill-defined. */ case AxisSigned::Y_POS: return AxisSigned::Z_NEG; case AxisSigned::Z_POS: return AxisSigned::Y_POS; case AxisSigned::X_NEG: break; /* Ill-defined. */ case AxisSigned::Y_NEG: return AxisSigned::Z_POS; case AxisSigned::Z_NEG: return AxisSigned::Y_NEG; } break; case AxisSigned::Y_NEG: switch (b) { case AxisSigned::X_POS: return AxisSigned::Z_POS; case AxisSigned::Y_POS: break; /* Ill-defined. */ case AxisSigned::Z_POS: return AxisSigned::X_NEG; case AxisSigned::X_NEG: return AxisSigned::Z_NEG; case AxisSigned::Y_NEG: break; /* Ill-defined. */ case AxisSigned::Z_NEG: return AxisSigned::X_POS; } break; case AxisSigned::Z_NEG: switch (b) { case AxisSigned::X_POS: return AxisSigned::Y_NEG; case AxisSigned::Y_POS: return AxisSigned::X_POS; case AxisSigned::Z_POS: break; /* Ill-defined. */ case AxisSigned::X_NEG: return AxisSigned::Y_POS; case AxisSigned::Y_NEG: return AxisSigned::X_NEG; case AxisSigned::Z_NEG: break; /* Ill-defined. */ } break; } return a.next_after(); } /** Create basis vector. */ template T to_vector(const Axis axis) { BLI_assert(axis <= AxisSigned::from_int(T::type_length - 1)); T vec{}; vec[axis] = 1; return vec; } /** Create signed basis vector. */ template T to_vector(const AxisSigned axis) { BLI_assert(abs(axis) <= AxisSigned::from_int(T::type_length - 1)); T vec{}; vec[abs(axis).as_int()] = axis.is_negative() ? -1 : 1; return vec; } /** \} */ /* -------------------------------------------------------------------- */ /** \name CartesianBasis * \{ */ /** * An `blender::math::CartesianBasis` represents an orientation that is aligned with the basis * axes. This type of rotation is fast, precise and adds more meaning to the code that uses it. */ struct CartesianBasis { VecBase axes = {AxisSigned::X_POS, AxisSigned::Y_POS, AxisSigned::Z_POS}; CartesianBasis() = default; /** * Create an arbitrary basis orientation. * Handedness can be flipped but an axis cannot be present twice. */ CartesianBasis(const AxisSigned x, const AxisSigned y, const AxisSigned z) : axes(x, y, z) { BLI_assert(abs(x) != abs(y)); BLI_assert(abs(y) != abs(z)); BLI_assert(abs(z) != abs(x)); } const AxisSigned &x() const { return axes.x; } const AxisSigned &y() const { return axes.y; } const AxisSigned &z() const { return axes.z; } AxisSigned &x() { return axes.x; } AxisSigned &y() { return axes.y; } AxisSigned &z() { return axes.z; } friend std::ostream &operator<<(std::ostream &stream, const CartesianBasis &rot); }; /** * Create an CartesianBasis using two orthogonal axes. * The third axis is chosen by right hand rule to follow blender coordinate system. * \a forward is Y axis in blender coordinate system. * \a up is Z axis in blender coordinate system. * \note \a forward and \a up must be different axes. */ [[nodiscard]] inline CartesianBasis from_orthonormal_axes(const AxisSigned forward, const AxisSigned up) { BLI_assert(math::abs(forward) != math::abs(up)); return {cross(forward, up), forward, up}; } /** * Create an CartesianBasis for converting from \a a orientation to \a b orientation. */ [[nodiscard]] inline CartesianBasis rotation_between(const CartesianBasis &a, const CartesianBasis &b) { CartesianBasis basis; basis.axes[abs(b.x()).as_int()] = (sign(b.x()) != sign(a.x())) ? -abs(a.x()) : abs(a.x()); basis.axes[abs(b.y()).as_int()] = (sign(b.y()) != sign(a.y())) ? -abs(a.y()) : abs(a.y()); basis.axes[abs(b.z()).as_int()] = (sign(b.z()) != sign(a.z())) ? -abs(a.z()) : abs(a.z()); return basis; } /** * Create an CartesianBasis for converting from an \a a orientation defined only by its forward * vector to a \a b orientation defined only by its forward vector. * Rotation is given to be non flipped and deterministic. */ [[nodiscard]] inline CartesianBasis rotation_between(const AxisSigned a_forward, const AxisSigned b_forward) { /* Pick predictable next axis. */ AxisSigned a_up = abs(a_forward.next_after()); AxisSigned b_up = abs(b_forward.next_after()); if (sign(a_forward) != sign(b_forward)) { /* Flip both axis (up and right) so resulting rotation matrix sign remains positive. */ b_up = -b_up; } return rotation_between(from_orthonormal_axes(a_forward, a_up), from_orthonormal_axes(b_forward, b_up)); } template [[nodiscard]] inline VecBase transform_point(const CartesianBasis &basis, const VecBase &v) { VecBase result; result[basis.x().axis().as_int()] = basis.x().is_negative() ? -v[0] : v[0]; result[basis.y().axis().as_int()] = basis.y().is_negative() ? -v[1] : v[1]; result[basis.z().axis().as_int()] = basis.z().is_negative() ? -v[2] : v[2]; return result; } /** * Return the inverse transformation represented by the given basis. * This is conceptually the equivalent to a rotation matrix transpose, but much faster. */ [[nodiscard]] inline CartesianBasis invert(const CartesianBasis &basis) { /* Returns the column where the `axis` is found in. The sign is taken from the axis value. */ auto search_axis = [](const CartesianBasis &basis, const Axis axis) { if (basis.x().axis() == axis) { return basis.x().is_negative() ? AxisSigned::X_NEG : AxisSigned::X_POS; } if (basis.y().axis() == axis) { return basis.y().is_negative() ? AxisSigned::Y_NEG : AxisSigned::Y_POS; } return basis.z().is_negative() ? AxisSigned::Z_NEG : AxisSigned::Z_POS; }; CartesianBasis result; result.x() = search_axis(basis, Axis::X); result.y() = search_axis(basis, Axis::Y); result.z() = search_axis(basis, Axis::Z); return result; } /** \} */ } // namespace blender::math