tornavis/source/blender/blenlib/BLI_math_basis_types.hh

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