Skip to main content

Quaternion Pack

Work In Progress

This page is a work in progress

Quaternion Rules

Quaternions can be packed using some of the mathematical rules they follow

Quaternions should be normalized

x^2 + y^2 + z^2 + w^2 = 1

Because of this we only need to send the smallest 3 values as we can calculate the largest one again on the other side.

We also need to send the index to say which of the 4 elements was the largest

largest = sqrt(1 - a^2 + b^2 + c^2)

Positive and Negative Quaternions represent the same rotation

Q(x,y,z,w) === Q(-x,-y,-z,-w)

If the largest element is negative we would have to send its sign in order to calculate the correct rotation.

However, because Q=-Q, if the largest element is negative we can just flip the sign of all 4 elements instead.

Max of the second largest element

The value of the 2nd largest element is when it is also equal to the largest so we have

L^2 + L^2 = 1

From this, we can find the max value for the 2nd largest is

L = +- 1 / sqrt(2) = +- ~0.707

This allows us to pack the smallest 3 elements in the range -0.707 to +0.707 instead of -1 to +1

Result

Combining all this we can send each of the smallest 3 elements with 9 bits, and 2 bits for the index of the largest element. Which reduces the size of a Quaternion from 128 bits unpacked to only 29 bits.

The precision of the smallest 3 can in increased or decreased to change the bit counts by multiples of 3. eg 10 bits per element will result in 32 bits total.

Supported Types

  • Quaternion

Example 1

public class MyNetworkBehaviour : NetworkBehaviour 
{
[SyncVar, QuaternionPack(9)]
public Quaternion direction;
}

Generated Code

Source:

[SyncVar, QuaternionPack(9)]
public int myValue;

Generated:


private QuaternionPacker myValue__Packer = new QuaternionPacker(9);

public override bool SerializeSyncVars(NetworkWriter writer, bool initialState)
{
ulong syncVarDirtyBits = base.SyncVarDirtyBits;
bool result = base.SerializeSyncVars(writer, initialize);

if (initialState)
{
myValue__Packer.Pack(writer, this.myValue);
return true;
}

writer.Write(syncVarDirtyBits, 1);
if ((syncVarDirtyBits & 1UL) != 0UL)
{
myValue__Packer.Pack(writer, this.myValue);
result = true;
}

return result;
}

public override void DeserializeSyncVars(NetworkReader reader, bool initialState)
{
base.DeserializeSyncVars(reader, initialState);

if (initialState)
{
this.myValue = myValue__Packer.Unpack(reader);
return;
}

ulong dirtyMask = reader.Read(1);
if ((dirtyMask & 1UL) != 0UL)
{
this.myValue = myValue__Packer.Unpack(reader);
}
}

last updated for Mirage v101.8.0