Geometry/EulerAngles: make sure that returned solution has canonical ranges
NB: this is possibly a breaking change because, compared to legacy code, it may return a different dual solution, which is equally valid, but mapped to the respective canonical (standard) Euler angle ranges.
Reference issue
#2617 (initially started out describing an unrelated issue in unsupported/EulerAngles
; this issue does not close #2617)
What does this implement/fix?
Per detailed discussion in #2617, this is an initial implementation of the formulas derived by @evbernardes given in this comment for Tait-Bryan angle sequences and in this comment for proper Euler sequences.
Prior to this fix, Eigen was returning a set of angles in a non-standard set of angle ranges, [0, pi] × [-pi, pi] × [-pi, pi]
, which is inappropriate for e.g. yaw-pitch-roll computations which is probably the most common application of .eulerAngles()
.
Without an angle range restriction, for any given rotation (matrix) and Euler sequence, there are two valid solution sets of Euler angles in the general, non-degenerate (non-gymbal lock) case. This MR applies solution-flipping formulas derived by @evbernardes to flip the solution to the one inside the canonical range for the respective kind of Euler angles:
-
[-pi, pi] × [-pi/2, pi/2] × [-pi, pi]
for Tait-Bryan angles (a0 != a2, e.g. ZXY) -
[-pi, pi] × [0, pi] × [-pi, pi]
for proper Euler angles (a0 == a2, e.g. XYX)
This MR introduces a default-parameter bool canonical = true
which defaults all clients to the new (more correct) behaviour. The existing code for computing angles which has seen long time battle-testing and seems to be robust to degenerate cases has deliberately not been touched; instead we apply at the very end a single solution-flip step when needed, which brings the solution angles into the respective canonical ranges.
If a client wants to keep using legacy Eigen behaviour, they can pass canonical = false
to .eulerAngles()
.