Is there a way to compress Y rotation axis to one byte?

Viewed 243

I am making a Unity Multiplayer game, and I wanted to compress the Y rotation axis from sending the whole Quaternion to just sending one byte.

  1. My first compression attempt:
  • Instead of sending Quaternion, I have just sent a Y-axis float value
  • Result: 16 bytes -> 4 bytes (12 bytes saved overall)
  1. Second compression attempt:
  • I have cached lastSentAxis variable (float) which contains the last Y-axis value that has been sent to the server
  • When a player changes their rotation (looks right/left), then a new Y-axis is compared to the cached one, and a delta value is prepared (delta is guaranteed to be less than 255).
  • Then, I create a new sbyte - which contains rotation way (-1, if turned left, 1, if turned right)
  • Result: 4 bytes -> 2 bytes (2 bytes saved, 14 overall)
  1. Third compression attempt (failed)
  • Define a byte flag instead of creating a separated byte mentioned before (1 - left, 2 - right)
  • Get a delta rotation value (as mentioned previously), but add it to the byte flag
  • PROBLEM: I have looped through 0 to 255 to find which numbers will collide with the byte flag.
  • POTENTIAL SOLUTION: Check if flag + delta is in the colliding number list. If yes, don't send a rotation request.
  • Every X requests, send a correction float value
  • Potential result: 2 bytes -> 1 byte (1 byte saved, 15 overall)

My question is, is it possible to make a third compression attempt in a more... proper way or my potential solution is only possible thing I can achieve?

1 Answers

I would not claim that you saved overall 15 bytes ^^

If you only need one component of the rotation anyway then the first step of syncing a single float (4 bytes) seems actually pretty obvious ;)

I would also say that going beyond that sounds a bit like an unnecessary micro optimization.


The delta sync is quite clever and at first glance is a 100% improvement from 4 bytes to 2 bytes.

But

  • it is also quite error prone and could go desync if only one single transmission fails.

  • this of course lowers the precision down to 1 degree integer steps instead of a full float value.

Honestly I would stick to the 4 bytes just for stability and precision.


2 bytes - about 0.0055° precision

With 2 bytes you can actually go way better than your attempt!

Why waste an entire byte just for the sign of the value?

use a short

  • uses a single bit for the sign
  • still has 15 bits left for the value!

You just would have to map your floating point range of -180 to 180 to the range -32768 to 32767.

Sending

// your delta between -180 and 180
float actualAngleDelta;

var shortAngleDelta = (short)Mathf.RondToInt(actualAngleDelta / 180f * shortMaxValue);
var sendBytes = BitConverter.GetBytes(shortAngleDelta);

Receiving

short shortAngleDelta = BitConverter.ToInt16(receivedBytes);
float actualAngleDelta = (float) shortAngleDelta / (float)short.MaxValue * 360f;

But honestly then you should rather not sync the delta but the actual value.

So, use a ushort!

It covers values from 0 to 65535 so just map the possible 360 degrees on that. Sure you lose a little bit on precision but not down to full degrees ;)

// A value between 0 and 360
float actualAngle;

ushort ushortAngle = (ushort) Mathf.RoundToInt((actualAngle % 360f) / 360f * ushort.MaxValue);
byte[] sendBytes = BitConverter.GetBytes(ushortAngle);

Receiving

ushort ushortAngle = BitConverter.ToUInt16(receivedBytes, 0);
float actualAngle = (float)ushortAngle / (float)ushort.MaxValue * 360f;

Both maintains a precision down to about 0.0055 (= 360/65535) degrees!


Single byte - about 1.41° precision

If a lower precision is an option for you anyway you could however go totally fancy and say you don't sync every exact rotation angle in degrees but rather divide a circle not by 360 but by 256 steps.

Then you could map the delta to your lesser grained "degree" angles and could cover the entire circle in a single byte:

Sending

byte sendByte = (byte)Mathf.RoundToInt((actualAngle % 360f) / 360f * (float)byte.MaxValue); 

receiving

float actualAngle = receivedByte / (float)byte.MaxValue * 360f;

which would have a precision of about 1.4 degrees.


BUT honestly, is all this forth and back calculations really worth the 2/3 saved bytes?

Related