tornavis/source/blender/blenlib/BLI_math_euler_types.hh

442 lines
9.7 KiB
C++

/* 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 <iostream>
#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<typename T> struct EulerBase {
using AngleT = AngleRadianBase<T>;
protected:
/**
* Container for the rotation values. They are always stored as XYZ order.
* Rotation values are stored without parity flipping.
*/
VecBase<AngleT, 3> xyz_;
EulerBase() = default;
EulerBase(const AngleT &x, const AngleT &y, const AngleT &z) : xyz_(x, y, z){};
EulerBase(const VecBase<AngleT, 3> &vec) : xyz_(vec){};
EulerBase(const VecBase<T, 3> &vec) : xyz_(vec.x, vec.y, vec.z){};
public:
/** Static functions. */
/** Conversions. */
explicit operator VecBase<AngleT, 3>() const
{
return this->xyz_;
}
explicit operator VecBase<T, 3>() const
{
return {T(x()), T(y()), T(z())};
}
/** Methods. */
VecBase<AngleT, 3> &xyz()
{
return this->xyz_;
}
const VecBase<AngleT, 3> &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<typename T> struct EulerXYZBase : public EulerBase<T> {
using AngleT = AngleRadianBase<T>;
public:
EulerXYZBase() = default;
/**
* Create an euler x,y,z rotation from a triple of radian angle.
*/
template<typename AngleU> EulerXYZBase(const VecBase<AngleU, 3> &vec) : EulerBase<T>(vec){};
EulerXYZBase(const AngleT &x, const AngleT &y, const AngleT &z) : EulerBase<T>(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<T>::identity(),
AngleRadianBase<T>::identity(),
AngleRadianBase<T>::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<VecBase<T, 3>>(rot);
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Euler3
* \{ */
template<typename T> struct Euler3Base : public EulerBase<T> {
using AngleT = AngleRadianBase<T>;
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<AngleT, 3> &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<AngleT, 3>() const
{
return {eul_.i(), eul_.j(), eul_.k()};
}
explicit operator VecBase<T, 3>() 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<typename AngleU>
Euler3Base(const VecBase<AngleU, 3> &angles_xyz, EulerOrder order)
: EulerBase<T>(angles_xyz), order_(order){};
Euler3Base(const AngleT &x, const AngleT &y, const AngleT &z, EulerOrder order)
: EulerBase<T>(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<T>(), 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<AngleT, 3> 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<AngleT, 3>(EulerXYZBase<T>(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<float>;
using Euler3 = Euler3Base<float>;
} // namespace blender::math