This patch re-implement the whole C rotation API into a more type oriented C++ API. See the #104444 design task for more details about the goals. The list of C to C++ equivalent syntax can be found attached. This adds `AngleRadian`, `AngleCartesian` and `AngleFraction` as different angle types with the same interface. Each of them have specific benefits / cons. See inline documentation for detail. This adds `Axis` and `AxisSigned` class wrapped enums to increase type safety with axes selection. This adds `CartesianBasis` to represent orthonormal orientations. Added a weight accumulation to dual-quaternions to make normalization error proof. Creates the overhead of summing the total weight twice (which I think is negligible) and add an extra float. Named the dual-quaternion `DualQuaternion` to avoid naming ambiguity with `DualQuat` which come up quite often (even with namespace). Pull Request: https://projects.blender.org/blender/blender/pulls/104941
153 lines
4.8 KiB
C++
153 lines
4.8 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bli
|
|
*/
|
|
|
|
#include "BLI_math_base.h"
|
|
#include "BLI_math_matrix.hh"
|
|
#include "BLI_math_rotation.hh"
|
|
#include "BLI_math_rotation_legacy.hh"
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_math_vector.hh"
|
|
|
|
namespace blender::math {
|
|
|
|
template EulerXYZ to_euler(const AxisAngle &);
|
|
template EulerXYZ to_euler(const AxisAngleCartesian &);
|
|
template EulerXYZ to_euler(const Quaternion &);
|
|
template Euler3 to_euler(const AxisAngle &, EulerOrder);
|
|
template Euler3 to_euler(const AxisAngleCartesian &, EulerOrder);
|
|
template Euler3 to_euler(const Quaternion &, EulerOrder);
|
|
template Quaternion to_quaternion(const AxisAngle &);
|
|
template Quaternion to_quaternion(const AxisAngleCartesian &);
|
|
template Quaternion to_quaternion(const Euler3 &);
|
|
template Quaternion to_quaternion(const EulerXYZ &);
|
|
template AxisAngleCartesian to_axis_angle(const Euler3 &);
|
|
template AxisAngleCartesian to_axis_angle(const EulerXYZ &);
|
|
template AxisAngleCartesian to_axis_angle(const Quaternion &);
|
|
template AxisAngle to_axis_angle(const Euler3 &);
|
|
template AxisAngle to_axis_angle(const EulerXYZ &);
|
|
template AxisAngle to_axis_angle(const Quaternion &);
|
|
|
|
#if 0 /* Only for reference. */
|
|
void generate_axes_to_quaternion_switch_cases()
|
|
{
|
|
std::cout << "default: *this = identity(); break;" << std::endl;
|
|
/* Go through all 32 cases. Only 23 valid and 1 is identity. */
|
|
for (int i : IndexRange(6)) {
|
|
for (int j : IndexRange(6)) {
|
|
const AxisSigned forward = AxisSigned(i);
|
|
const AxisSigned up = AxisSigned(j);
|
|
/* Filter the 12 invalid cases. Fall inside the default case. */
|
|
if (Axis(forward) == Axis(up)) {
|
|
continue;
|
|
}
|
|
/* Filter the identity case. Fall inside the default case. */
|
|
if (forward == AxisSigned::Y_POS && up == AxisSigned::Z_POS) {
|
|
continue;
|
|
}
|
|
|
|
VecBase<AxisSigned, 3> axes{cross(forward, up), forward, up};
|
|
|
|
float3x3 mat;
|
|
mat.x_axis() = float3(axes.x);
|
|
mat.y_axis() = float3(axes.y);
|
|
mat.z_axis() = float3(axes.z);
|
|
|
|
math::Quaternion q = to_quaternion(mat);
|
|
/* Create a integer value out of the 4 possible component values (+sign). */
|
|
int4 p = int4(round(sign(float4(q)) * min(pow(float4(q), 2.0f), float4(0.75)) * 4.0));
|
|
|
|
auto format_component = [](int value) {
|
|
switch (abs(value)) {
|
|
default:
|
|
case 0:
|
|
return "T(0)";
|
|
case 1:
|
|
return (value > 0) ? "T(0.5)" : "T(-0.5)";
|
|
case 2:
|
|
return (value > 0) ? "T(M_SQRT1_2)" : "T(-M_SQRT1_2)";
|
|
case 3:
|
|
return (value > 0) ? "T(1)" : "T(-1)";
|
|
}
|
|
};
|
|
auto format_axis = [](AxisSigned axis) {
|
|
switch (axis) {
|
|
default:
|
|
case AxisSigned::X_POS:
|
|
return "AxisSigned::X_POS";
|
|
case AxisSigned::Y_POS:
|
|
return "AxisSigned::Y_POS";
|
|
case AxisSigned::Z_POS:
|
|
return "AxisSigned::Z_POS";
|
|
case AxisSigned::X_NEG:
|
|
return "AxisSigned::X_NEG";
|
|
case AxisSigned::Y_NEG:
|
|
return "AxisSigned::Y_NEG";
|
|
case AxisSigned::Z_NEG:
|
|
return "AxisSigned::Z_NEG";
|
|
}
|
|
};
|
|
/* Use same code function as in the switch case. */
|
|
std::cout << "case ";
|
|
std::cout << format_axis(axes.x) << " << 16 | ";
|
|
std::cout << format_axis(axes.y) << " << 8 | ";
|
|
std::cout << format_axis(axes.z);
|
|
std::cout << ": *this = {";
|
|
std::cout << format_component(p.x) << ", ";
|
|
std::cout << format_component(p.y) << ", ";
|
|
std::cout << format_component(p.z) << ", ";
|
|
std::cout << format_component(p.w) << "}; break;";
|
|
std::cout << std::endl;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
float3 rotate_direction_around_axis(const float3 &direction, const float3 &axis, const float angle)
|
|
{
|
|
BLI_ASSERT_UNIT_V3(direction);
|
|
BLI_ASSERT_UNIT_V3(axis);
|
|
|
|
const float3 axis_scaled = axis * math::dot(direction, axis);
|
|
const float3 diff = direction - axis_scaled;
|
|
const float3 cross = math::cross(axis, diff);
|
|
|
|
return axis_scaled + diff * std::cos(angle) + cross * std::sin(angle);
|
|
}
|
|
|
|
float3 rotate_around_axis(const float3 &vector,
|
|
const float3 ¢er,
|
|
const float3 &axis,
|
|
const float angle)
|
|
|
|
{
|
|
float3 result = vector - center;
|
|
float mat[3][3];
|
|
axis_angle_normalized_to_mat3(mat, axis, angle);
|
|
mul_m3_v3(mat, result);
|
|
return result + center;
|
|
}
|
|
|
|
std::ostream &operator<<(std::ostream &stream, EulerOrder order)
|
|
{
|
|
switch (order) {
|
|
default:
|
|
case XYZ:
|
|
return stream << "XYZ";
|
|
case XZY:
|
|
return stream << "XZY";
|
|
case YXZ:
|
|
return stream << "YXZ";
|
|
case YZX:
|
|
return stream << "YZX";
|
|
case ZXY:
|
|
return stream << "ZXY";
|
|
case ZYX:
|
|
return stream << "ZYX";
|
|
}
|
|
}
|
|
|
|
} // namespace blender::math
|