Skip to content

Commit 0b38732

Browse files
authored
feat: add utility functions for the eskf code (#14)
1 parent 1144d73 commit 0b38732

3 files changed

Lines changed: 151 additions & 0 deletions

File tree

cpp_test/test_math.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,48 @@ TEST(get_transformation_matrix_attitude, test_transformation_matrix_zeros) {
5252
EXPECT_NEAR(0.0, matrix_norm_diff(expected, transformation_matrix), 0.01);
5353
}
5454

55+
TEST(get_transformation_matrix_attitude_quat,
56+
test_transformation_matrix_unit_quat) {
57+
Eigen::Quaterniond quat = Eigen::Quaterniond::Identity();
58+
Eigen::Matrix<double, 4, 3> transformation_matrix{
59+
get_transformation_matrix_attitude_quat(quat)};
60+
Eigen::Matrix<double, 4, 3> expected = Eigen::Matrix<double, 4, 3>::Zero();
61+
expected.bottomRightCorner<3, 3>() = 0.5 * Eigen::Matrix3d::Identity();
62+
EXPECT_NEAR(0.0, matrix_norm_diff(expected, transformation_matrix), 0.01);
63+
}
64+
65+
TEST(eigen_vector3d_to_quaternion, zero_vector_returns_identity) {
66+
const Eigen::Vector3d v = Eigen::Vector3d::Zero();
67+
68+
const Eigen::Quaterniond q = eigen_vector3d_to_quaternion(v);
69+
70+
EXPECT_NEAR(q.w(), 1.0, 1e-15);
71+
EXPECT_NEAR(q.x(), 0.0, 1e-15);
72+
EXPECT_NEAR(q.y(), 0.0, 1e-15);
73+
EXPECT_NEAR(q.z(), 0.0, 1e-15);
74+
EXPECT_NEAR(q.norm(), 1.0, 1e-15);
75+
}
76+
77+
TEST(eigen_vector3d_to_quaternion, general_vector_matches_axis_angle_formula) {
78+
const Eigen::Vector3d v(0.3, -0.4, 0.5);
79+
const double angle = v.norm();
80+
const Eigen::Vector3d axis = v / angle;
81+
82+
const double half = 0.5 * angle;
83+
const double c = cos(half);
84+
const double s = sin(half);
85+
const Eigen::Quaterniond expected(c, axis.x() * s, axis.y() * s,
86+
axis.z() * s);
87+
88+
const Eigen::Quaterniond q = eigen_vector3d_to_quaternion(v);
89+
90+
EXPECT_NEAR(q.w(), expected.w(), 1e-12);
91+
EXPECT_NEAR(q.x(), expected.x(), 1e-12);
92+
EXPECT_NEAR(q.y(), expected.y(), 1e-12);
93+
EXPECT_NEAR(q.z(), expected.z(), 1e-12);
94+
EXPECT_NEAR(q.norm(), 1.0, 1e-12);
95+
}
96+
5597
// Test that the identity quaternion correctly maps to 0, 0, 0
5698
TEST(quat_to_euler, test_quat_to_euler_1) {
5799
Eigen::Quaterniond q(1.0, 0.0, 0.0, 0.0);
@@ -168,4 +210,69 @@ TEST(euler_to_quat, test_euler_to_quat_5) {
168210
}
169211
}
170212

213+
// Test that zero euler angles construct the correct quaternion
214+
TEST(euler_to_quat_vec3, zero_angles_identity_quaternion) {
215+
const Eigen::Vector3d euler(0.0, 0.0, 0.0); // roll, pitch, yaw
216+
const Eigen::Quaterniond q = euler_to_quat(euler);
217+
218+
const Eigen::Vector4d result{q.x(), q.y(), q.z(), q.w()};
219+
const Eigen::Vector4d expected(0.0, 0.0, 0.0, 1.0);
220+
221+
for (int i = 0; i < 4; ++i) {
222+
EXPECT_NEAR(expected[i], result[i], 0.01);
223+
}
224+
}
225+
226+
// Test that non-zero roll constructs the correct quaternion
227+
TEST(euler_to_quat_vec3, roll_only) {
228+
const Eigen::Vector3d euler(1.0, 0.0, 0.0); // roll, pitch, yaw
229+
const Eigen::Quaterniond q = euler_to_quat(euler);
230+
231+
const Eigen::Vector4d result{q.x(), q.y(), q.z(), q.w()};
232+
const Eigen::Vector4d expected(0.479, 0.0, 0.0, 0.877);
233+
234+
for (int i = 0; i < 4; ++i) {
235+
EXPECT_NEAR(expected[i], result[i], 0.01);
236+
}
237+
}
238+
239+
// Test that non-zero pitch constructs the correct quaternion
240+
TEST(euler_to_quat_vec3, pitch_only) {
241+
const Eigen::Vector3d euler(0.0, 1.0, 0.0);
242+
const Eigen::Quaterniond q = euler_to_quat(euler);
243+
244+
const Eigen::Vector4d result{q.x(), q.y(), q.z(), q.w()};
245+
const Eigen::Vector4d expected(0.0, 0.479, 0.0, 0.877);
246+
247+
for (int i = 0; i < 4; ++i) {
248+
EXPECT_NEAR(expected[i], result[i], 0.01);
249+
}
250+
}
251+
252+
// Test that non-zero yaw constructs the correct quaternion
253+
TEST(euler_to_quat_vec3, yaw_only) {
254+
const Eigen::Vector3d euler(0.0, 0.0, 1.0);
255+
const Eigen::Quaterniond q = euler_to_quat(euler);
256+
257+
const Eigen::Vector4d result{q.x(), q.y(), q.z(), q.w()};
258+
const Eigen::Vector4d expected(0.0, 0.0, 0.479, 0.877);
259+
260+
for (int i = 0; i < 4; ++i) {
261+
EXPECT_NEAR(expected[i], result[i], 0.01);
262+
}
263+
}
264+
265+
// Test that non-zero euler angles construct the correct quaternion
266+
TEST(euler_to_quat_vec3, roll_pitch_yaw_all_nonzero) {
267+
const Eigen::Vector3d euler(1.0, 1.0, 1.0);
268+
const Eigen::Quaterniond q = euler_to_quat(euler);
269+
270+
const Eigen::Vector4d result{q.x(), q.y(), q.z(), q.w()};
271+
const Eigen::Vector4d expected(0.1675, 0.5709, 0.1675, 0.786);
272+
273+
for (int i = 0; i < 4; ++i) {
274+
EXPECT_NEAR(expected[i], result[i], 0.01);
275+
}
276+
}
277+
171278
} // namespace vortex::utils::math

include/vortex/utils/math.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ Eigen::Matrix3d get_rotation_matrix(const double roll,
2828
Eigen::Matrix3d get_transformation_matrix_attitude(const double roll,
2929
const double pitch);
3030

31+
// @brief Fossen, 2021 eq. 2.78
32+
Eigen::Matrix<double, 4, 3> get_transformation_matrix_attitude_quat(
33+
const Eigen::Quaterniond& quat);
34+
35+
// @brief Converts an Eigen::Vector3d with euler angles to Eigen::Quaterniond
36+
Eigen::Quaterniond eigen_vector3d_to_quaternion(const Eigen::Vector3d& vector);
37+
3138
// @brief Converts a quaternion to Euler angles.
3239
Eigen::Vector3d quat_to_euler(const Eigen::Quaterniond& q);
3340

@@ -36,6 +43,9 @@ Eigen::Quaterniond euler_to_quat(const double roll,
3643
const double pitch,
3744
const double yaw);
3845

46+
// @brief Converts Eigen::Vector3d with euler angles to quaternion
47+
Eigen::Quaterniond euler_to_quat(const Eigen::Vector3d& euler);
48+
3949
} // namespace vortex::utils::math
4050

4151
#endif // VORTEX_UTILS_MATH_HPP

src/math.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,32 @@ Eigen::Matrix3d get_rotation_matrix(const double roll,
2525
return rotation_matrix;
2626
}
2727

28+
Eigen::Matrix<double, 4, 3> get_transformation_matrix_attitude_quat(
29+
const Eigen::Quaterniond& quat) {
30+
Eigen::Matrix<double, 4, 3> T_q = Eigen::Matrix<double, 4, 3>::Zero();
31+
double qw{quat.w()};
32+
double qx{quat.x()};
33+
double qy{quat.y()};
34+
double qz{quat.z()};
35+
36+
T_q << -qx, -qy, -qz, qw, -qz, qy, qz, qw, -qx, -qy, qx, qw;
37+
38+
T_q *= 0.5;
39+
return T_q;
40+
}
41+
42+
Eigen::Quaterniond eigen_vector3d_to_quaternion(const Eigen::Vector3d& vector) {
43+
double angle{vector.norm()};
44+
if (angle < 1e-8) {
45+
return Eigen::Quaterniond(1.0, 0.0, 0.0, 0.0);
46+
} else {
47+
Eigen::Vector3d axis = vector / angle;
48+
Eigen::Quaterniond quat =
49+
Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis));
50+
return quat.normalized();
51+
}
52+
}
53+
2854
Eigen::Matrix3d get_transformation_matrix_attitude(const double roll,
2955
const double pitch) {
3056
double sin_r = sin(roll);
@@ -69,4 +95,12 @@ Eigen::Quaterniond euler_to_quat(const double roll,
6995
return q.normalized();
7096
}
7197

98+
Eigen::Quaterniond euler_to_quat(const Eigen::Vector3d& euler) {
99+
Eigen::Quaterniond q;
100+
q = Eigen::AngleAxisd(euler.z(), Eigen::Vector3d::UnitZ()) *
101+
Eigen::AngleAxisd(euler.y(), Eigen::Vector3d::UnitY()) *
102+
Eigen::AngleAxisd(euler.x(), Eigen::Vector3d::UnitX());
103+
return q.normalized();
104+
}
105+
72106
} // namespace vortex::utils::math

0 commit comments

Comments
 (0)