Program Listing for File NuMagSANSlib_RotationMatrix.h#
↰ Return to documentation for file (src/helper/NuMagSANSlib_RotationMatrix.h)
#pragma once
#include <algorithm>
#include <array>
#include <cctype>
#include <cmath>
#include <stdexcept>
#include <string>
inline std::string NormalizeEulerConvention(std::string convention) {
std::transform(convention.begin(), convention.end(), convention.begin(),
[](unsigned char character) { return static_cast<char>(std::tolower(character)); });
return convention;
}
inline bool IsSupportedEulerConvention(const std::string& convention) {
static constexpr std::array<const char*, 12> supported = {
"xyx", "xzx", "yxy", "yzy", "zxz", "zyz", "xyz", "xzy", "yxz", "yzx", "zxy", "zyx",
};
const std::string normalized = NormalizeEulerConvention(convention);
return std::find_if(supported.begin(), supported.end(),
[&](const char* candidate) { return normalized == candidate; }) != supported.end();
}
inline void RotationMatrix_x(float angle, float* rotation_matrix) {
const float cosine = std::cos(angle);
const float sine = std::sin(angle);
// Column-major storage: R(row, column) = R[row + 3 * column].
rotation_matrix[0] = 1.0f;
rotation_matrix[1] = 0.0f;
rotation_matrix[2] = 0.0f;
rotation_matrix[3] = 0.0f;
rotation_matrix[4] = cosine;
rotation_matrix[5] = sine;
rotation_matrix[6] = 0.0f;
rotation_matrix[7] = -sine;
rotation_matrix[8] = cosine;
}
inline void RotationMatrix_y(float angle, float* rotation_matrix) {
const float cosine = std::cos(angle);
const float sine = std::sin(angle);
rotation_matrix[0] = cosine;
rotation_matrix[1] = 0.0f;
rotation_matrix[2] = -sine;
rotation_matrix[3] = 0.0f;
rotation_matrix[4] = 1.0f;
rotation_matrix[5] = 0.0f;
rotation_matrix[6] = sine;
rotation_matrix[7] = 0.0f;
rotation_matrix[8] = cosine;
}
inline void RotationMatrix_z(float angle, float* rotation_matrix) {
const float cosine = std::cos(angle);
const float sine = std::sin(angle);
rotation_matrix[0] = cosine;
rotation_matrix[1] = sine;
rotation_matrix[2] = 0.0f;
rotation_matrix[3] = -sine;
rotation_matrix[4] = cosine;
rotation_matrix[5] = 0.0f;
rotation_matrix[6] = 0.0f;
rotation_matrix[7] = 0.0f;
rotation_matrix[8] = 1.0f;
}
inline void RotationMatrix_axis(char axis, float angle, float* rotation_matrix) {
switch (static_cast<char>(std::tolower(static_cast<unsigned char>(axis)))) {
case 'x':
RotationMatrix_x(angle, rotation_matrix);
return;
case 'y':
RotationMatrix_y(angle, rotation_matrix);
return;
case 'z':
RotationMatrix_z(angle, rotation_matrix);
return;
default:
throw std::invalid_argument("Euler rotation axis must be x, y, or z.");
}
}
inline void LeftMultiply3x3(float* left, const float* multiplier) {
// Updates left in place as left <- multiplier * left. Matrices use
// column-major storage: R(row, column) = R[row + 3 * column].
float product[9];
for (int column = 0; column < 3; ++column) {
for (int row = 0; row < 3; ++row) {
product[row + 3 * column] = multiplier[row + 3 * 0] * left[0 + 3 * column] +
multiplier[row + 3 * 1] * left[1 + 3 * column] +
multiplier[row + 3 * 2] * left[2 + 3 * column];
}
}
for (int index = 0; index < 9; ++index) {
left[index] = product[index];
}
}
inline void Multiply_RotmatEuler_3x3(float angle_1, float angle_2, float angle_3, const std::string& convention,
float* rotation_matrix) {
const std::string normalized = NormalizeEulerConvention(convention);
if (!IsSupportedEulerConvention(normalized)) {
throw std::invalid_argument("Unsupported Euler rotation convention: " + convention);
}
float first_axis_rotation[9];
float second_axis_rotation[9];
float third_axis_rotation[9];
RotationMatrix_axis(normalized[0], angle_1, first_axis_rotation);
RotationMatrix_axis(normalized[1], angle_2, second_axis_rotation);
RotationMatrix_axis(normalized[2], angle_3, third_axis_rotation);
// Starting from identity, this produces
// R = R_axis1(angle_1) * R_axis2(angle_2) * R_axis3(angle_3).
LeftMultiply3x3(rotation_matrix, third_axis_rotation);
LeftMultiply3x3(rotation_matrix, second_axis_rotation);
LeftMultiply3x3(rotation_matrix, first_axis_rotation);
}
inline void ComputeEulerRotationMatrixDegrees_3x3(float angle_1_degrees, float angle_2_degrees, float angle_3_degrees,
const std::string& convention, float* rotation_matrix) {
constexpr float degrees_to_radians = 3.14159265358979323846f / 180.0f;
// Global RotMat angles use degrees, whereas object-wise RotData angles use
// radians. Both interfaces share the same Euler convention and angle order.
rotation_matrix[0] = 1.0f;
rotation_matrix[1] = 0.0f;
rotation_matrix[2] = 0.0f;
rotation_matrix[3] = 0.0f;
rotation_matrix[4] = 1.0f;
rotation_matrix[5] = 0.0f;
rotation_matrix[6] = 0.0f;
rotation_matrix[7] = 0.0f;
rotation_matrix[8] = 1.0f;
Multiply_RotmatEuler_3x3(angle_1_degrees * degrees_to_radians, angle_2_degrees * degrees_to_radians,
angle_3_degrees * degrees_to_radians, convention, rotation_matrix);
}