Search Unity

Resolved Encoding Vector2 and Vector3 variables into single int or float and back

Discussion in 'Multiplayer' started by emotitron, Dec 28, 2016.

  1. emotitron

    emotitron

    Joined:
    Oct 9, 2016
    Posts:
    41
    I am starting to optimize a game for mobile pvp, and to that end I am looking to reduce my traffic as much as possible.

    The maps will rarely exceed a cube of (-50, -50, -50) to (50, 50, 50) units with a required resolution of 1 decimal point. Vector3 data for each projectile fire needs even less data. That means I could nicely fit my coordinates into 10bit chunks that give me a range of values from -51.2 to 51.2 for each axis.

    Integers are 32bits, and I need 30bits per vector.

    So my first easiest path seems to be to strip all V3 data down to a 3 digit integer (the decimal removed by multiplying times 10) and then crush all of that into a single integer for transmission, and then decode it back to a V3 on the receiving end.

    So before I go doing this, my question is - am I recreating someone else's work I can just download and can I save myself a couple hours of learning how to bitwise encode/decode this? If not, does anyone have some code snippets they would recommend for doing this efficiently? I am sure I will not be the last person looking to do this.
     
  2. Apparaten_

    Apparaten_

    Joined:
    Jul 9, 2013
    Posts:
    45
  3. emotitron

    emotitron

    Joined:
    Oct 9, 2016
    Posts:
    41
    It is intended to be people in a small space moving at higher than should work speeds for PvP over mobile, so if I can cut my V3 data in a third - it should be of value. It might not be noticeable on its own, but as I start piling up syncvars and command/rpcs every little bit is likely going to matter. I need every millisecond I can get. I also will be relying less on player authority than games of this time for hiding the latency. Player movement is likely going to be the only item under player authority - the rest is all server - so all latency will be felt and seen.

    I intend to squeeze every bit down as much as humanly possible. Thanks for the link, will add that to my reading when I attack this.
     
    Apparaten_ likes this.
  4. emotitron

    emotitron

    Joined:
    Oct 9, 2016
    Posts:
    41
    Code (CSharp):
    1.     public int encodeVector3ToInt(Vector3 v) {
    2.         //Vectors must stay within the -512 to 512 range per axis - no error handling coded here
    3.         //Add 512 to get numbers into the 0-1024 range rather than -512 to 512 range
    4.         //Multiply by 10 to save one decimal place from rounding
    5.         int xcomp = Mathf.RoundToInt((v.x * 10)) + 512;
    6.         int ycomp = Mathf.RoundToInt((v.y * 10)) + 512;
    7.         int zcomp = Mathf.RoundToInt((v.z * 10)) + 512;
    8.         return xcomp + ycomp * 1024 + zcomp * 1048576;
    9.     }
    10.     public Vector3 decodeVector3FromInt(int i) {
    11.         //Get the leftmost bits first. The fractional remains are the bits to the right.
    12.         // 1024 is 2 ^ 10 - 1048576 is 2 ^ 20 - just saving some calculation time doing that in advance
    13.         float z = Mathf.Floor(i / 1048576);
    14.         float y = Mathf.Floor ((i - z * 1048576) / 1024);
    15.         float x = (i - y * 1024 - z * 1048576);
    16.         // subtract 512 to move numbers back into the -512 to 512 range rather than 0 - 1024
    17.         return new Vector3 ((x - 512) / 10, (y - 512) / 10, (z - 512) / 10);
    18.     }
    This is my dirty working code to solve the problem. If you have a world that is smaller than 102.4x102.4x102.4 units and a resolution of .1 units is good enough - this will reduce your network traffic. If anyone has faster/cleaner code methods to do this same thing - PLEASE pass them along.

    There are also two unused bits on the left of the int. So they could be used to double the x & z limits to 204.8 units if your level is more wide than tall. Just replace the 1048576 and 1024 numbers with the appropriate 2^X result for the number of bits you want for each axis.
     
    Last edited: Dec 28, 2016
  5. emotitron

    emotitron

    Joined:
    Oct 9, 2016
    Posts:
    41
    The resolution was a bit low, the alternative is to use shorts as a cheap half-float. That knocks 12 bytes per tick down to 6 (plus whatever unet overhead there is in each serialized command).

    Code (CSharp):
    1.     [Command(channel = Channels.DefaultUnreliable)]
    2.     private void CmdSendPositionShort(short x, short y, short z)
    3.     {
    4.         _lastPosition.Set(
    5.             (float)x / 100f,
    6.             (float)y / 100f,
    7.             (float)z / 100f);
    8.     }
    9.  
    10.         CmdSendPositionShort (
    11.                 (short)(transform.position.x * 100),
    12.                 (short)(transform.position.y * 100),
    13.                 (short)(transform.position.z * 100));
     
  6. emotitron

    emotitron

    Joined:
    Oct 9, 2016
    Posts:
    41
    And my latest resting place for truncating vectors and encoding them into a single variable (and back). Note I am only using 3/4s of the ulongs 64bits. 16 bits per axis. The remaining unused flags I may use to pass other info later such things as animation states and other flags.

    Code (CSharp):
    1.     public ulong encodeVector3ToULong(Vector3 v) {
    2.         //Vectors must stay within the -320.00 to 320.00 range per axis - no error handling is coded here
    3.         //Adds 32768 to get numbers into the 0-65536 range rather than -32768 to 32768 range to allow unsigned
    4.         //Multiply by 100 to get two decimal place
    5.         ulong xcomp = (ulong)(Mathf.RoundToInt((v.x * 100f)) + 32768);
    6.         ulong ycomp = (ulong)(Mathf.RoundToInt((v.y * 100f)) + 32768);
    7.         ulong zcomp = (ulong)(Mathf.RoundToInt((v.z * 100f)) + 32768);
    8.         //Debug.Log ("comps " + xcomp + " " + ycomp + " " + zcomp);
    9.         return xcomp + ycomp * 65536 + zcomp * 4294967296;
    10.     }
    11.     public Vector3 decodeVector3FromULong(ulong i) {
    12.         //Debug.Log ("ulong " +i);
    13.         //Get the leftmost bits first. The fractional remains are the bits to the right.
    14.         // 1024 is 2 ^ 10 - 1048576 is 2 ^ 20 - just saving some calculation time doing that in advance
    15.         ulong z = (ulong)(i / 4294967296);
    16.         ulong y = (ulong)((i - z * 4294967296) / 65536);
    17.         ulong x = (ulong)(i - y * 65536 - z * 4294967296);
    18.         //Debug.Log (x + " " + y + " " + z);
    19.         // subtract 512 to move numbers back into the -512 to 512 range rather than 0 - 1024
    20.         return new Vector3 (((float)x - 32768f) / 100f, ((float)y - 32768f) / 100f, ((float)z - 32768f) / 100f);
    21.     }
    Hopefully this wasn't all for nothing - my understanding of what goes on with Syncvar behind the scenes is limited so I don't know if regular network compression would be condensing a V3 to a comparable size. Again, there must be a more efficient way of doing this than decimal math, but right now my concern is crushing network usage more than sparing the CPU.
     
    Stiffx likes this.
  7. abhayaagrawal

    abhayaagrawal

    Joined:
    Sep 30, 2016
    Posts:
    8
    @emotitron
    this works for me. but I am unable to understand why are you multiplying
    return xcomp + ycomp * 65536 + zcomp * 4294967296;
    in case of encoding.

    and in case of decoding how are you getting value of z and y

    1. ulong z = (ulong)(i / 4294967296);
    2. ulong y = (ulong)((i - z * 4294967296) / 65536);

      it will be great if you can explain the logic
     
  8. criyessei

    criyessei

    Joined:
    May 26, 2023
    Posts:
    1
    Thank you emotitron

    I have created a dynamic class by advancing your solution.


    Code (CSharp):
    1. abstract class Compressor
    2. {
    3.     public static UInt64 Encode(float[] values, int resolution)
    4.     {
    5.         sbyte parts = (sbyte)values.Length;
    6.         sbyte k = (sbyte)(64 / parts);
    7.         UInt32 halfKVal = 1U << (k - 1);
    8.  
    9.         float validAbsValue = (1 << (k - 1)) / resolution;
    10.         if (values.Any(val => Math.Abs(val) > validAbsValue))
    11.             throw new Exception(String.Format("Values({0}) must be between -{1} and {1} with resolution of {2}", string.Join(", ", values), validAbsValue, 1 / resolution));
    12.  
    13.         UInt64 sum = 0;
    14.         for (int i = 0; i < parts; i++)
    15.         {
    16.             UInt64 comp = (UInt64)(Mathf.RoundToInt(values[i] * resolution) + halfKVal);
    17.             sum += comp << (i * k);
    18.         }
    19.  
    20.         return sum;
    21.     }
    22.     public static float[] Decode(UInt64 sum, sbyte parts, int resolution)
    23.     {
    24.         sbyte k = (sbyte)(64 / parts);
    25.         UInt32 kVal = 1U << k;
    26.         UInt32 halfKVal = kVal >> 1;
    27.         UInt32 kValComplement = (kVal - 1);
    28.  
    29.         float[] values = new float[parts];
    30.  
    31.         for (int i = 0; i < parts; i++)
    32.         {
    33.             ulong comp = (sum >> (i * k)) & kValComplement;
    34.             values[i] = ((float)comp - halfKVal) / resolution;
    35.         }
    36.         return values;
    37.     }
    38. }

    Code (CSharp):
    1. sealed class Vector3Compressor1048_576 : Compressor // [-1048.576, 1048.576] (Resolution 0,001)
    2. {
    3.     public static readonly int resolution = 1000; // 3 digit
    4.     public static readonly sbyte parts = 3;
    5.     public static readonly float max =  1048.576f;
    6.     public static readonly float min = -1048.576f;
    7.  
    8.     public static UInt64 Encode(Vector3 vector)=> Encode(new float[] { vector.X, vector.Y, vector.Z }, resolution);
    9.     public static Vector3 Decode(UInt64 sum)
    10.     {
    11.         float[] values = Decode(sum, parts, resolution);
    12.         return new Vector3(values[0], values[1], values[2]);
    13.     }
    14. }
    15.  
    16. sealed class QuaternionCompressor1_6384 : Compressor // [-1.6384, 1.6384] (Resolution 0,0001)
    17. {
    18.     public static readonly int resolution = 10000; // 4 digit
    19.     public static readonly sbyte parts = 4;
    20.     public static readonly float max =  1.6384f;
    21.     public static readonly float min = -1.6384f;
    22.  
    23.     public static UInt64 Encode(Quaternion vector) => Encode(new float[] { vector.X, vector.Y, vector.Z, vector.W }, resolution);
    24.     public static Quaternion Decode(UInt64 sum)
    25.     {
    26.         float[] values = Decode(sum, parts, resolution);
    27.         return new Quaternion(values[0], values[1], values[2], values[3]);
    28.     }
    29. }
     
    Last edited: Dec 10, 2023