Vulcan
Aerospace Engineering Utilities Built on Janus
Loading...
Searching...
No Matches
EulerSequences.hpp
Go to the documentation of this file.
1// Vulcan Euler Angle Sequences
2// All 12 Euler angle sequences with DCM and quaternion conversions
3#pragma once
4
6
7#include <janus/math/Quaternion.hpp>
8#include <janus/math/Rotations.hpp>
9
10#include <array>
11
12namespace vulcan {
13
14// =============================================================================
15// Euler Sequence Enumeration
16// =============================================================================
17
32enum class EulerSequence {
33 // Tait-Bryan (3 distinct axes)
40
41 // Proper Euler (symmetric axes)
48};
49
50// =============================================================================
51// Sequence Utilities
52// =============================================================================
53
58constexpr std::array<int, 3> euler_axes(EulerSequence seq) {
59 switch (seq) {
61 return {0, 1, 2};
63 return {0, 2, 1};
65 return {1, 0, 2};
67 return {1, 2, 0};
69 return {2, 0, 1};
71 return {2, 1, 0};
73 return {0, 1, 0};
75 return {0, 2, 0};
77 return {1, 0, 1};
79 return {1, 2, 1};
81 return {2, 0, 2};
83 return {2, 1, 2};
84 default:
85 return {2, 1, 0}; // Default to ZYX
86 }
87}
88
90constexpr bool is_proper_euler(EulerSequence seq) {
91 auto axes = euler_axes(seq);
92 return axes[0] == axes[2];
93}
94
96constexpr const char *euler_sequence_name(EulerSequence seq) {
97 switch (seq) {
99 return "XYZ";
101 return "XZY";
103 return "YXZ";
105 return "YZX";
107 return "ZXY";
109 return "ZYX";
111 return "XYX";
113 return "XZX";
115 return "YXY";
117 return "YZY";
119 return "ZXZ";
121 return "ZYZ";
122 default:
123 return "Unknown";
124 }
125}
126
127// =============================================================================
128// DCM from Euler Angles
129// =============================================================================
130
142template <typename Scalar>
143Mat3<Scalar> dcm_from_euler(Scalar e1, Scalar e2, Scalar e3,
144 EulerSequence seq) {
145 auto axes = euler_axes(seq);
146
147 // R = R_axis[0](e1) * R_axis[1](e2) * R_axis[2](e3)
148 // For intrinsic rotations, compose left-to-right
149 // janus::rotation_matrix_3d gives R such that v' = R * v
150 Mat3<Scalar> R1 = janus::rotation_matrix_3d(e1, axes[0]);
151 Mat3<Scalar> R2 = janus::rotation_matrix_3d(e2, axes[1]);
152 Mat3<Scalar> R3 = janus::rotation_matrix_3d(e3, axes[2]);
153
154 return R1 * R2 * R3;
155}
156
157// =============================================================================
158// Quaternion from Euler Angles
159// =============================================================================
160
169template <typename Scalar>
170janus::Quaternion<Scalar> quaternion_from_euler(Scalar e1, Scalar e2, Scalar e3,
171 EulerSequence seq) {
172 // For ZYX, we can use the optimized Janus implementation
173 if (seq == EulerSequence::ZYX) {
174 // Janus from_euler takes (roll, pitch, yaw) for ZYX intrinsic
175 return janus::Quaternion<Scalar>::from_euler(e3, e2, e1);
176 }
177
178 // For other sequences, build via DCM and convert
179 Mat3<Scalar> R = dcm_from_euler(e1, e2, e3, seq);
180 return janus::Quaternion<Scalar>::from_rotation_matrix(R);
181}
182
183// =============================================================================
184// Euler Angles from DCM - Tait-Bryan Sequences
185// =============================================================================
186
187namespace detail {
188
196template <typename Scalar>
197Vec3<Scalar> euler_from_dcm_zyx(const Mat3<Scalar> &R) {
198 Scalar sin_e2 = -R(2, 0);
199 Scalar e2 = janus::asin(sin_e2);
200
201 Scalar eps = Scalar(1e-6);
202 Scalar cos_e2 = janus::cos(e2);
203 Scalar is_gimbal = janus::abs(cos_e2) < eps;
204
205 // Normal case
206 Scalar e1_normal = janus::atan2(R(1, 0), R(0, 0));
207 Scalar e3_normal = janus::atan2(R(2, 1), R(2, 2));
208
209 // Gimbal lock: set e3 = 0, compute e1 from other elements
210 Scalar e3_gimbal = Scalar(0);
211 Scalar e1_gimbal = janus::atan2(-R(0, 1), R(1, 1));
212
213 Vec3<Scalar> euler;
214 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
215 euler(1) = e2;
216 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
217
218 return euler;
219}
220
228template <typename Scalar>
229Vec3<Scalar> euler_from_dcm_xyz(const Mat3<Scalar> &R) {
230 Scalar sin_e2 = R(0, 2);
231 Scalar e2 = janus::asin(sin_e2);
232
233 Scalar eps = Scalar(1e-6);
234 Scalar cos_e2 = janus::cos(e2);
235 Scalar is_gimbal = janus::abs(cos_e2) < eps;
236
237 // Normal case
238 Scalar e1_normal = janus::atan2(-R(1, 2), R(2, 2));
239 Scalar e3_normal = janus::atan2(-R(0, 1), R(0, 0));
240
241 // Gimbal lock: set e3 = 0
242 Scalar e3_gimbal = Scalar(0);
243 Scalar e1_gimbal = janus::atan2(R(2, 1), R(1, 1));
244
245 Vec3<Scalar> euler;
246 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
247 euler(1) = e2;
248 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
249
250 return euler;
251}
252
254template <typename Scalar>
255Vec3<Scalar> euler_from_dcm_xzy(const Mat3<Scalar> &R) {
256 Scalar sin_e2 = -R(0, 1);
257 Scalar e2 = janus::asin(sin_e2);
258
259 Scalar eps = Scalar(1e-6);
260 Scalar cos_e2 = janus::cos(e2);
261 Scalar is_gimbal = janus::abs(cos_e2) < eps;
262
263 Scalar e1_normal = janus::atan2(R(2, 1), R(1, 1));
264 Scalar e3_normal = janus::atan2(R(0, 2), R(0, 0));
265
266 Scalar e3_gimbal = Scalar(0);
267 Scalar e1_gimbal = janus::atan2(-R(1, 2), R(2, 2));
268
269 Vec3<Scalar> euler;
270 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
271 euler(1) = e2;
272 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
273
274 return euler;
275}
276
278template <typename Scalar>
279Vec3<Scalar> euler_from_dcm_yxz(const Mat3<Scalar> &R) {
280 Scalar sin_e2 = -R(1, 2);
281 Scalar e2 = janus::asin(sin_e2);
282
283 Scalar eps = Scalar(1e-6);
284 Scalar cos_e2 = janus::cos(e2);
285 Scalar is_gimbal = janus::abs(cos_e2) < eps;
286
287 Scalar e1_normal = janus::atan2(R(0, 2), R(2, 2));
288 Scalar e3_normal = janus::atan2(R(1, 0), R(1, 1));
289
290 Scalar e3_gimbal = Scalar(0);
291 Scalar e1_gimbal = janus::atan2(-R(2, 0), R(0, 0));
292
293 Vec3<Scalar> euler;
294 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
295 euler(1) = e2;
296 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
297
298 return euler;
299}
300
302template <typename Scalar>
303Vec3<Scalar> euler_from_dcm_yzx(const Mat3<Scalar> &R) {
304 Scalar sin_e2 = R(1, 0);
305 Scalar e2 = janus::asin(sin_e2);
306
307 Scalar eps = Scalar(1e-6);
308 Scalar cos_e2 = janus::cos(e2);
309 Scalar is_gimbal = janus::abs(cos_e2) < eps;
310
311 Scalar e1_normal = janus::atan2(-R(2, 0), R(0, 0));
312 Scalar e3_normal = janus::atan2(-R(1, 2), R(1, 1));
313
314 Scalar e3_gimbal = Scalar(0);
315 Scalar e1_gimbal = janus::atan2(R(0, 2), R(2, 2));
316
317 Vec3<Scalar> euler;
318 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
319 euler(1) = e2;
320 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
321
322 return euler;
323}
324
326template <typename Scalar>
327Vec3<Scalar> euler_from_dcm_zxy(const Mat3<Scalar> &R) {
328 Scalar sin_e2 = R(2, 1);
329 Scalar e2 = janus::asin(sin_e2);
330
331 Scalar eps = Scalar(1e-6);
332 Scalar cos_e2 = janus::cos(e2);
333 Scalar is_gimbal = janus::abs(cos_e2) < eps;
334
335 Scalar e1_normal = janus::atan2(-R(0, 1), R(1, 1));
336 Scalar e3_normal = janus::atan2(-R(2, 0), R(2, 2));
337
338 Scalar e3_gimbal = Scalar(0);
339 Scalar e1_gimbal = janus::atan2(R(1, 0), R(0, 0));
340
341 Vec3<Scalar> euler;
342 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
343 euler(1) = e2;
344 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
345
346 return euler;
347}
348
349// =============================================================================
350// Euler Angles from DCM - Proper Euler Sequences
351// =============================================================================
352
357template <typename Scalar>
358Vec3<Scalar> euler_from_dcm_zxz(const Mat3<Scalar> &R) {
359 Scalar cos_e2 = R(2, 2);
360 Scalar e2 = janus::acos(cos_e2);
361
362 Scalar eps = Scalar(1e-6);
363 Scalar sin_e2 = janus::sin(e2);
364 Scalar is_gimbal = janus::abs(sin_e2) < eps;
365
366 // Normal case
367 Scalar e1_normal = janus::atan2(R(0, 2), -R(1, 2));
368 Scalar e3_normal = janus::atan2(R(2, 0), R(2, 1));
369
370 // Gimbal lock: set e3 = 0
371 Scalar e3_gimbal = Scalar(0);
372 // At e2 = 0: e1 + e3 = atan2(-R[0,1], R[0,0])
373 // At e2 = π: e1 - e3 = atan2(R[0,1], R[0,0])
374 Scalar e1_gimbal = janus::atan2(-R(0, 1), R(0, 0));
375
376 Vec3<Scalar> euler;
377 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
378 euler(1) = e2;
379 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
380
381 return euler;
382}
383
385template <typename Scalar>
386Vec3<Scalar> euler_from_dcm_zyz(const Mat3<Scalar> &R) {
387 Scalar cos_e2 = R(2, 2);
388 Scalar e2 = janus::acos(cos_e2);
389
390 Scalar eps = Scalar(1e-6);
391 Scalar sin_e2 = janus::sin(e2);
392 Scalar is_gimbal = janus::abs(sin_e2) < eps;
393
394 Scalar e1_normal = janus::atan2(R(1, 2), R(0, 2));
395 Scalar e3_normal = janus::atan2(R(2, 1), -R(2, 0));
396
397 Scalar e3_gimbal = Scalar(0);
398 Scalar e1_gimbal = janus::atan2(R(1, 0), R(0, 0));
399
400 Vec3<Scalar> euler;
401 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
402 euler(1) = e2;
403 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
404
405 return euler;
406}
407
409template <typename Scalar>
410Vec3<Scalar> euler_from_dcm_xyx(const Mat3<Scalar> &R) {
411 Scalar cos_e2 = R(0, 0);
412 Scalar e2 = janus::acos(cos_e2);
413
414 Scalar eps = Scalar(1e-6);
415 Scalar sin_e2 = janus::sin(e2);
416 Scalar is_gimbal = janus::abs(sin_e2) < eps;
417
418 Scalar e1_normal = janus::atan2(R(1, 0), -R(2, 0));
419 Scalar e3_normal = janus::atan2(R(0, 1), R(0, 2));
420
421 Scalar e3_gimbal = Scalar(0);
422 Scalar e1_gimbal = janus::atan2(-R(1, 2), R(1, 1));
423
424 Vec3<Scalar> euler;
425 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
426 euler(1) = e2;
427 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
428
429 return euler;
430}
431
433template <typename Scalar>
434Vec3<Scalar> euler_from_dcm_xzx(const Mat3<Scalar> &R) {
435 Scalar cos_e2 = R(0, 0);
436 Scalar e2 = janus::acos(cos_e2);
437
438 Scalar eps = Scalar(1e-6);
439 Scalar sin_e2 = janus::sin(e2);
440 Scalar is_gimbal = janus::abs(sin_e2) < eps;
441
442 Scalar e1_normal = janus::atan2(R(2, 0), R(1, 0));
443 Scalar e3_normal = janus::atan2(R(0, 2), -R(0, 1));
444
445 Scalar e3_gimbal = Scalar(0);
446 Scalar e1_gimbal = janus::atan2(R(2, 1), R(2, 2));
447
448 Vec3<Scalar> euler;
449 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
450 euler(1) = e2;
451 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
452
453 return euler;
454}
455
457template <typename Scalar>
458Vec3<Scalar> euler_from_dcm_yxy(const Mat3<Scalar> &R) {
459 Scalar cos_e2 = R(1, 1);
460 Scalar e2 = janus::acos(cos_e2);
461
462 Scalar eps = Scalar(1e-6);
463 Scalar sin_e2 = janus::sin(e2);
464 Scalar is_gimbal = janus::abs(sin_e2) < eps;
465
466 Scalar e1_normal = janus::atan2(R(0, 1), R(2, 1));
467 Scalar e3_normal = janus::atan2(R(1, 0), -R(1, 2));
468
469 Scalar e3_gimbal = Scalar(0);
470 Scalar e1_gimbal = janus::atan2(R(0, 2), R(0, 0));
471
472 Vec3<Scalar> euler;
473 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
474 euler(1) = e2;
475 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
476
477 return euler;
478}
479
481template <typename Scalar>
482Vec3<Scalar> euler_from_dcm_yzy(const Mat3<Scalar> &R) {
483 Scalar cos_e2 = R(1, 1);
484 Scalar e2 = janus::acos(cos_e2);
485
486 Scalar eps = Scalar(1e-6);
487 Scalar sin_e2 = janus::sin(e2);
488 Scalar is_gimbal = janus::abs(sin_e2) < eps;
489
490 Scalar e1_normal = janus::atan2(R(2, 1), -R(0, 1));
491 Scalar e3_normal = janus::atan2(R(1, 2), R(1, 0));
492
493 Scalar e3_gimbal = Scalar(0);
494 Scalar e1_gimbal = janus::atan2(-R(2, 0), R(2, 2));
495
496 Vec3<Scalar> euler;
497 euler(0) = janus::where(is_gimbal, e1_gimbal, e1_normal);
498 euler(1) = e2;
499 euler(2) = janus::where(is_gimbal, e3_gimbal, e3_normal);
500
501 return euler;
502}
503
504} // namespace detail
505
506// =============================================================================
507// Euler Angles from DCM - Public Interface
508// =============================================================================
509
519template <typename Scalar>
520Vec3<Scalar> euler_from_dcm(const Mat3<Scalar> &R, EulerSequence seq) {
521 switch (seq) {
546 default:
548 }
549}
550
557template <typename Scalar>
558Vec3<Scalar> euler_from_quaternion(const janus::Quaternion<Scalar> &q,
559 EulerSequence seq) {
560 // For ZYX, we can use the optimized Janus implementation
561 if (seq == EulerSequence::ZYX) {
562 auto euler_rpy = q.to_euler(); // Returns [roll, pitch, yaw]
563 Vec3<Scalar> euler;
564 euler(0) = euler_rpy(2); // yaw (e1)
565 euler(1) = euler_rpy(1); // pitch (e2)
566 euler(2) = euler_rpy(0); // roll (e3)
567 return euler;
568 }
569
570 // For other sequences, convert via DCM
571 Mat3<Scalar> R = q.to_rotation_matrix();
572 return euler_from_dcm(R, seq);
573}
574
575} // namespace vulcan
Definition EulerSequences.hpp:187
Vec3< Scalar > euler_from_dcm_yzy(const Mat3< Scalar > &R)
Extract Euler angles from DCM for YZY sequence.
Definition EulerSequences.hpp:482
Vec3< Scalar > euler_from_dcm_xyx(const Mat3< Scalar > &R)
Extract Euler angles from DCM for XYX sequence.
Definition EulerSequences.hpp:410
Vec3< Scalar > euler_from_dcm_xyz(const Mat3< Scalar > &R)
Definition EulerSequences.hpp:229
Vec3< Scalar > euler_from_dcm_yzx(const Mat3< Scalar > &R)
Extract Euler angles from DCM for YZX sequence.
Definition EulerSequences.hpp:303
Vec3< Scalar > euler_from_dcm_yxy(const Mat3< Scalar > &R)
Extract Euler angles from DCM for YXY sequence.
Definition EulerSequences.hpp:458
Vec3< Scalar > euler_from_dcm_zyz(const Mat3< Scalar > &R)
Extract Euler angles from DCM for ZYZ sequence.
Definition EulerSequences.hpp:386
Vec3< Scalar > euler_from_dcm_yxz(const Mat3< Scalar > &R)
Extract Euler angles from DCM for YXZ sequence.
Definition EulerSequences.hpp:279
Vec3< Scalar > euler_from_dcm_zyx(const Mat3< Scalar > &R)
Definition EulerSequences.hpp:197
Vec3< Scalar > euler_from_dcm_xzx(const Mat3< Scalar > &R)
Extract Euler angles from DCM for XZX sequence.
Definition EulerSequences.hpp:434
Vec3< Scalar > euler_from_dcm_xzy(const Mat3< Scalar > &R)
Extract Euler angles from DCM for XZY sequence.
Definition EulerSequences.hpp:255
Vec3< Scalar > euler_from_dcm_zxy(const Mat3< Scalar > &R)
Extract Euler angles from DCM for ZXY sequence.
Definition EulerSequences.hpp:327
Vec3< Scalar > euler_from_dcm_zxz(const Mat3< Scalar > &R)
Definition EulerSequences.hpp:358
Definition Aerodynamics.hpp:11
constexpr const char * euler_sequence_name(EulerSequence seq)
Get sequence name as string (for debugging/logging).
Definition EulerSequences.hpp:96
Mat3< Scalar > dcm_from_euler(Scalar e1, Scalar e2, Scalar e3, EulerSequence seq)
Definition EulerSequences.hpp:143
Vec3< Scalar > euler_from_dcm(const Mat3< Scalar > &R, EulerSequence seq)
Definition EulerSequences.hpp:520
janus::Quaternion< Scalar > quaternion_from_euler(Scalar e1, Scalar e2, Scalar e3, EulerSequence seq)
Definition EulerSequences.hpp:170
EulerSequence
Definition EulerSequences.hpp:32
@ ZXY
Definition EulerSequences.hpp:38
@ XYX
Definition EulerSequences.hpp:42
@ ZYZ
Definition EulerSequences.hpp:47
@ YXZ
Definition EulerSequences.hpp:36
@ ZYX
Yaw-Pitch-Roll (aerospace standard).
Definition EulerSequences.hpp:39
@ XZY
Definition EulerSequences.hpp:35
@ YXY
Definition EulerSequences.hpp:44
@ ZXZ
Classical mechanics (precession-nutation-spin).
Definition EulerSequences.hpp:46
@ YZX
Definition EulerSequences.hpp:37
@ XYZ
Roll-Pitch-Yaw (robotics convention).
Definition EulerSequences.hpp:34
@ XZX
Definition EulerSequences.hpp:43
@ YZY
Definition EulerSequences.hpp:45
constexpr bool is_proper_euler(EulerSequence seq)
Check if sequence is proper Euler (symmetric: first axis == third axis).
Definition EulerSequences.hpp:90
Vec3< Scalar > euler_from_quaternion(const janus::Quaternion< Scalar > &q, EulerSequence seq)
Definition EulerSequences.hpp:558
constexpr std::array< int, 3 > euler_axes(EulerSequence seq)
Definition EulerSequences.hpp:58