/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bli * * Euler rotations are represented as a triple of angle representing a rotation around each basis * vector. The order in which the three rotations are applied changes the resulting orientation. * * A `blender::math::EulerXYZ` represent an Euler triple with fixed axis order (XYZ). * A `blender::math::Euler3` represents an Euler triple with arbitrary axis order. * * They are prone to gimbal lock and are not suited for many applications. However they are more * intuitive than other rotation types. Their main use is for converting user facing rotation * values to other rotation types. * * The rotation values can still be reinterpreted like this: * `Euler3(float3(my_euler3_zyx_rot), EulerOrder::XYZ)` * This will swap the X and Z rotation order and will likely not produce the same rotation matrix. * * If the goal is to convert (keep the same orientation) to `Euler3` then you have to do an * assignment. * eg: `Euler3 my_euler(EulerOrder::XYZ); my_euler = my_quaternion:` */ #include #include "BLI_math_angle_types.hh" #include "BLI_math_base.hh" #include "BLI_math_basis_types.hh" namespace blender::math { /* WARNING: must match the #eRotationModes in `DNA_action_types.h` * order matters - types are saved to file. */ enum EulerOrder { XYZ = 1, XZY, YXZ, YZX, ZXY, ZYX, }; std::ostream &operator<<(std::ostream &stream, EulerOrder order); /* -------------------------------------------------------------------- */ /** \name EulerBase * \{ */ template struct EulerBase { using AngleT = AngleRadianBase; protected: /** * Container for the rotation values. They are always stored as XYZ order. * Rotation values are stored without parity flipping. */ VecBase xyz_; EulerBase() = default; EulerBase(const AngleT &x, const AngleT &y, const AngleT &z) : xyz_(x, y, z){}; EulerBase(const VecBase &vec) : xyz_(vec){}; EulerBase(const VecBase &vec) : xyz_(vec.x, vec.y, vec.z){}; public: /** Static functions. */ /** Conversions. */ explicit operator VecBase() const { return this->xyz_; } explicit operator VecBase() const { return {T(x()), T(y()), T(z())}; } /** Methods. */ VecBase &xyz() { return this->xyz_; } const VecBase &xyz() const { return this->xyz_; } const AngleT &x() const { return this->xyz_.x; } const AngleT &y() const { return this->xyz_.y; } const AngleT &z() const { return this->xyz_.z; } AngleT &x() { return this->xyz_.x; } AngleT &y() { return this->xyz_.y; } AngleT &z() { return this->xyz_.z; } }; /** \} */ /* -------------------------------------------------------------------- */ /** \name EulerXYZ * \{ */ template struct EulerXYZBase : public EulerBase { using AngleT = AngleRadianBase; public: EulerXYZBase() = default; /** * Create an euler x,y,z rotation from a triple of radian angle. */ template EulerXYZBase(const VecBase &vec) : EulerBase(vec){}; EulerXYZBase(const AngleT &x, const AngleT &y, const AngleT &z) : EulerBase(x, y, z){}; /** * Create a rotation from an basis axis and an angle. * This sets a single component of the euler triple, the others are left to 0. */ EulerXYZBase(const Axis axis, const AngleT &angle) { *this = identity(); this->xyz_[axis] = angle; } /** Static functions. */ static EulerXYZBase identity() { return {AngleRadianBase::identity(), AngleRadianBase::identity(), AngleRadianBase::identity()}; } /** Methods. */ /** * Return this euler orientation but with angles wrapped inside [-pi..pi] range. */ EulerXYZBase wrapped() const; /** * Return this euler orientation but wrapped around \a reference. * * This means the interpolation between the returned value and \a reference will always take the * shortest path. The angle between them will not be more than pi. */ EulerXYZBase wrapped_around(const EulerXYZBase &reference) const; /** Operators. */ friend EulerXYZBase operator-(const EulerXYZBase &a) { return {-a.xyz_.x, -a.xyz_.y, -a.xyz_.z}; } friend bool operator==(const EulerXYZBase &a, const EulerXYZBase &b) { return a.xyz_ == b.xyz_; } friend std::ostream &operator<<(std::ostream &stream, const EulerXYZBase &rot) { return stream << "EulerXYZ" << static_cast>(rot); } }; /** \} */ /* -------------------------------------------------------------------- */ /** \name Euler3 * \{ */ template struct Euler3Base : public EulerBase { using AngleT = AngleRadianBase; private: /** Axes order from applying the rotation. */ EulerOrder order_; /** * Swizzle structure allowing to rotation ordered assignment. */ class Swizzle { private: Euler3Base &eul_; public: explicit Swizzle(Euler3Base &eul) : eul_(eul){}; Euler3Base &operator=(const VecBase &angles) { eul_.xyz_.x = angles[eul_.i_index()]; eul_.xyz_.y = angles[eul_.j_index()]; eul_.xyz_.z = angles[eul_.k_index()]; return eul_; } operator VecBase() const { return {eul_.i(), eul_.j(), eul_.k()}; } explicit operator VecBase() const { return {T(eul_.i()), T(eul_.j()), T(eul_.k())}; } }; public: Euler3Base() = delete; /** * Create an euler rotation with \a order rotation ordering * from a triple of radian angles in XYZ order. * eg: If \a order is `EulerOrder::ZXY` then `angles.z` will be the angle of the first rotation. */ template Euler3Base(const VecBase &angles_xyz, EulerOrder order) : EulerBase(angles_xyz), order_(order){}; Euler3Base(const AngleT &x, const AngleT &y, const AngleT &z, EulerOrder order) : EulerBase(x, y, z), order_(order){}; /** * Create a rotation around a single euler axis and an angle. */ Euler3Base(const Axis axis, AngleT angle, EulerOrder order) : EulerBase(), order_(order) { this->xyz_[axis] = angle; } /** * Defines rotation order but not the rotation values. * Used for conversion from other rotation types. */ Euler3Base(EulerOrder order) : order_(order){}; /** Methods. */ const EulerOrder &order() const { return order_; } /** * Returns the rotations angle in rotation order. * eg: if rotation `order` is `YZX` then `i` is the `Y` rotation. */ Swizzle ijk() { return Swizzle{*this}; } const VecBase ijk() const { return {i(), j(), k()}; } /** * Returns the rotations angle in rotation order. * eg: if rotation `order` is `YZX` then `i` is the `Y` rotation. */ const AngleT &i() const { return this->xyz_[i_index()]; } const AngleT &j() const { return this->xyz_[j_index()]; } const AngleT &k() const { return this->xyz_[k_index()]; } AngleT &i() { return this->xyz_[i_index()]; } AngleT &j() { return this->xyz_[j_index()]; } AngleT &k() { return this->xyz_[k_index()]; } /** * Return this euler orientation but wrapped around \a reference. * * This means the interpolation between the returned value and \a reference will always take the * shortest path. The angle between them will not be more than pi. */ Euler3Base wrapped_around(const Euler3Base &reference) const { return {VecBase(EulerXYZBase(this->xyz_).wrapped_around(reference.xyz_)), order_}; } /** Operators. */ friend Euler3Base operator-(const Euler3Base &a) { return {-a.xyz_, a.order_}; } friend bool operator==(const Euler3Base &a, const Euler3Base &b) { return a.xyz_ == b.xyz_ && a.order_ == b.order_; } friend std::ostream &operator<<(std::ostream &stream, const Euler3Base &rot) { return stream << "Euler3_" << rot.order_ << rot.xyz_; } /* Utilities for conversions and functions operating on Euler3. * This should be private in theory. */ /** * Parity of axis permutation. * It is considered even if axes are not shuffled (X followed by Y which in turn followed by Z). * Return `true` if odd (shuffled) and `false` if even (non-shuffled). */ bool parity() const { switch (order_) { default: BLI_assert_unreachable(); return false; case XYZ: case ZXY: case YZX: return false; case XZY: case YXZ: case ZYX: return true; } } /** * Source Axis of the 1st axis rotation. */ int i_index() const { switch (order_) { default: BLI_assert_unreachable(); return 0; case XYZ: case XZY: return 0; case YXZ: case YZX: return 1; case ZXY: case ZYX: return 2; } } /** * Source Axis of the 2nd axis rotation. */ int j_index() const { switch (order_) { default: BLI_assert_unreachable(); return 0; case YXZ: case ZXY: return 0; case XYZ: case ZYX: return 1; case XZY: case YZX: return 2; } } /** * Source Axis of the 3rd axis rotation. */ int k_index() const { switch (order_) { default: BLI_assert_unreachable(); return 0; case YZX: case ZYX: return 0; case XZY: case ZXY: return 1; case XYZ: case YXZ: return 2; } } }; /** \} */ using EulerXYZ = EulerXYZBase; using Euler3 = Euler3Base; } // namespace blender::math