480 lines
13 KiB
C++
480 lines
13 KiB
C++
/* 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 <iosfwd>
|
|
|
|
#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<Value>(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<Value>(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<Value>(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<typename T> 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<typename T> 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<AxisSigned, 3> 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<typename T>
|
|
[[nodiscard]] inline VecBase<T, 3> transform_point(const CartesianBasis &basis,
|
|
const VecBase<T, 3> &v)
|
|
{
|
|
VecBase<T, 3> 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
|