### Rise of the Quaternions

Adolphus finally quit messing around and started using a quaternion representation for rotations internally! The quaternion class itself is simple, and conversion to and from rotation matrix and axis-angle representations is fairly straightforward. The magic happens in converting from Euler angles — all twelve valid conventions!

By solving the conversion to quaternion for all twelve possibilities, I managed to squeeze out a pattern that allows me to solve them with a near-minimum of redundant code:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | @staticmethod def from_euler(convention, angles): def qterm(index): bin = lambda x: [(x >> 2) % 2, (x >> 1) % 2, x % 2] return copysign(1, index) * reduce(lambda a, b: a * b, [(bit and sin or cos)(angles[i] / 2.0) \ for i, bit in enumerate(bin(abs(index)))]) # TODO: can these be generated from the convention? eulerquat = {'xyx': [0, -5, 1, 4, 2, 7, 3, -6], 'xyz': [0, -7, 1, 6, 2, -5, 3, 4], 'xzx': [0, -5, 1, 4, -3, 6, 2, 7], 'xzy': [0, 7, 1, -6, -3, 4, 2, 5], 'yxy': [0, -5, 2, 7, 1, 4, -3, 6], 'yxz': [0, 7, 2, 5, 1, -6, -3, 4], 'yzx': [0, -7, 3, 4, 1, 6, 2, -5], 'yzy': [0, -5, 3, -6, 1, 4, 2, 7], 'zxy': [0, -7, 2, -5, 3, 4, 1, 6], 'zxz': [0, -5, 2, 7, 3, -6, 1, 4], 'zyx': [0, 7, -3, 4, 2, 5, 1, -6], 'zyz': [0, -5, -3, 6, 2, 7, 1, 4]} a, b, c, d = [sum([qterm(eulerquat[convention][i + j * 2]) \ for i in range(2)]) for j in range(4)] if a > 0: return Quaternion(a, -b, -c, -d) else: return Quaternion(-a, b, c, d) |

Now, those hard-coded sequences of numbers? I am sure there is some relationship between the patterns and the conventions that would allow a more concise translation from one to the other. Note that, for a sequence [*a*, *b*, *c*, *d*, *e*, *f*, *g*, *h*], any or all of the pairs (*a*, *b*), (*c*, *d*), (*e*, *f*), and (*g*, *h*) can be swapped without changing anything (for example, the sequence for *zyx* could just as easily be [0, 7, 4, -3, 2, 5, -6, 1]). This has the unfortunate effect of making finding a pattern more difficult.

There are a number of tantalizing clues. The first number is always 0. The second is always -5 when two of the rotations are about the same axis, -7 when they are all different and in (rotated) *xyz* order, and 7 when they are all different and in *zyx* order. The 1 is always positive and appears in the second pair when the first axis is *x*, the pair when *y*, and the fourth when *z*. And so on.

I wonder if anyone else has figured out something similar? Adolphus wants a 9-line conversion function for its birthday.