I thought about this in the past. Start with a + bi + cj + dk. The quaternions we are interested in have length 1, and it doesn't matter if you flip their sign. So we can encode in 2 bits which of the coordinates is the largest in absolute value (say it's b) and divide by it. Now we have something like a/b + i + (c/b)j + (d/b)k. It's enough to encode the numbers a/b, c/b and d/b, and they are all between -1 and 1.
So you could use 32 bits to encode a quaternion, using code like this:
using System;
class QuaternionEncoder
{
public static uint Encode3(float x, float y, float z)
{
uint ux = (uint)Math.Round((x + 1.0f) * 511.0f);
uint uy = (uint)Math.Round((y + 1.0f) * 511.0f);
uint uz = (uint)Math.Round((z + 1.0f) * 511.0f);
return (ux << 20) | (uy << 10) | uz;
}
public static uint EncodeQuaternion(float a, float b, float c, float d)
{
float max = Math.Max(Math.Max(Math.Max(Math.Abs(a), Math.Abs(b)), Math.Abs(c)), Math.Abs(d));
if (Math.Abs(a) == max)
return (0u << 30) | Encode3(b / a, c / a, d / a);
if (Math.Abs(b) == max)
return (1u << 30) | Encode3(a / b, c / b, d / b);
if (Math.Abs(c) == max)
return (2u << 30) | Encode3(a / c, b / c, d / c);
return (3u << 30) | Encode3(a / d, b / d, c / d);
}
public static void NormalizeQuaternion(ref float a, ref float b, ref float c, ref float d)
{
float invLength = 1.0f / (float)Math.Sqrt(a * a + b * b + c * c + d * d);
a *= invLength;
b *= invLength;
c *= invLength;
d *= invLength;
}
public static void Decode3(uint u, out float x, out float y, out float z)
{
x = ((u >> 20) & 0x3ffu) / 511.0f - 1.0f;
y = ((u >> 10) & 0x3ffu) / 511.0f - 1.0f;
z = (u & 0x3ffu) / 511.0f - 1.0f;
}
public static void DecodeQuaternion(uint u, out float a, out float b, out float c, out float d)
{
a = b = c = d = 0;
switch (u >> 30)
{
case 0: a = 1.0f; Decode3(u, out b, out c, out d); break;
case 1: b = 1.0f; Decode3(u, out a, out c, out d); break;
case 2: c = 1.0f; Decode3(u, out a, out b, out d); break;
case 3: d = 1.0f; Decode3(u, out a, out b, out c); break;
}
NormalizeQuaternion(ref a, ref b, ref c, ref d);
}
}