Mirror Networking
Mirror.Compression Class Reference

Functions to Compress Quaternions and Floats More...

Static Public Member Functions

static int LargestAbsoluteComponentIndex (Vector4 value, out float largestAbs, out Vector3 withoutLargest)
 
static ushort ScaleFloatToUShort (float value, float minValue, float maxValue, ushort minTarget, ushort maxTarget)
 
static float ScaleUShortToFloat (ushort value, ushort minValue, ushort maxValue, float minTarget, float maxTarget)
 
static uint CompressQuaternion (Quaternion q)
 
static Quaternion DecompressQuaternion (uint data)
 
static void CompressVarUInt (NetworkWriter writer, ulong value)
 
static void CompressVarInt (NetworkWriter writer, long i)
 
static ulong DecompressVarUInt (NetworkReader reader)
 
static long DecompressVarInt (NetworkReader reader)
 

Detailed Description

Functions to Compress Quaternions and Floats

Definition at line 9 of file Compression.cs.

Member Function Documentation

◆ CompressQuaternion()

static uint Mirror.Compression.CompressQuaternion ( Quaternion  q)
static

Definition at line 95 of file Compression.cs.

96 {
97 // note: assuming normalized quaternions is enough. no need to force
98 // normalize here. we already normalize when decompressing.
99
100 // find the largest component index [0,3] + value
101 int largestIndex = LargestAbsoluteComponentIndex(new Vector4(q.x, q.y, q.z, q.w), out float _, out Vector3 withoutLargest);
102
103 // from here on, we work with the 3 components without largest!
104
105 // "You might think you need to send a sign bit for [largest] in
106 // case it is negative, but you don’t, because you can make
107 // [largest] always positive by negating the entire quaternion if
108 // [largest] is negative. in quaternion space (x,y,z,w) and
109 // (-x,-y,-z,-w) represent the same rotation."
110 if (QuaternionElement(q, largestIndex) < 0)
111 withoutLargest = -withoutLargest;
112
113 // put index & three floats into one integer.
114 // => index is 2 bits (4 values require 2 bits to store them)
115 // => the three floats are between [-0.707107,+0.707107] because:
116 // "If v is the absolute value of the largest quaternion
117 // component, the next largest possible component value occurs
118 // when two components have the same absolute value and the
119 // other two components are zero. The length of that quaternion
120 // (v,v,0,0) is 1, therefore v^2 + v^2 = 1, 2v^2 = 1,
121 // v = 1/sqrt(2). This means you can encode the smallest three
122 // components in [-0.707107,+0.707107] instead of [-1,+1] giving
123 // you more precision with the same number of bits."
124 // => the article recommends storing each float in 9 bits
125 // => our uint has 32 bits, so we might as well store in (32-2)/3=10
126 // 10 bits max value: 1023=0x3FF (use OSX calc to flip 10 bits)
127 ushort aScaled = ScaleFloatToUShort(withoutLargest.x, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
128 ushort bScaled = ScaleFloatToUShort(withoutLargest.y, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
129 ushort cScaled = ScaleFloatToUShort(withoutLargest.z, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
130
131 // now we just need to pack them into one integer
132 // -> index is 2 bit and needs to be shifted to 31..32
133 // -> a is 10 bit and needs to be shifted 20..30
134 // -> b is 10 bit and needs to be shifted 10..20
135 // -> c is 10 bit and needs to be at 0..10
136 return (uint)(largestIndex << 30 | aScaled << 20 | bScaled << 10 | cScaled);
137 }

◆ CompressVarInt()

static void Mirror.Compression.CompressVarInt ( NetworkWriter  writer,
long  i 
)
static

Definition at line 287 of file Compression.cs.

288 {
289 ulong zigzagged = (ulong)((i >> 63) ^ (i << 1));
290 CompressVarUInt(writer, zigzagged);
291 }

◆ CompressVarUInt()

static void Mirror.Compression.CompressVarUInt ( NetworkWriter  writer,
ulong  value 
)
static

Definition at line 200 of file Compression.cs.

201 {
202 if (value <= 240)
203 {
204 writer.WriteByte((byte)value);
205 return;
206 }
207 if (value <= 2287)
208 {
209 writer.WriteByte((byte)(((value - 240) >> 8) + 241));
210 writer.WriteByte((byte)((value - 240) & 0xFF));
211 return;
212 }
213 if (value <= 67823)
214 {
215 writer.WriteByte((byte)249);
216 writer.WriteByte((byte)((value - 2288) >> 8));
217 writer.WriteByte((byte)((value - 2288) & 0xFF));
218 return;
219 }
220 if (value <= 16777215)
221 {
222 writer.WriteByte((byte)250);
223 writer.WriteByte((byte)(value & 0xFF));
224 writer.WriteByte((byte)((value >> 8) & 0xFF));
225 writer.WriteByte((byte)((value >> 16) & 0xFF));
226 return;
227 }
228 if (value <= 4294967295)
229 {
230 writer.WriteByte((byte)251);
231 writer.WriteByte((byte)(value & 0xFF));
232 writer.WriteByte((byte)((value >> 8) & 0xFF));
233 writer.WriteByte((byte)((value >> 16) & 0xFF));
234 writer.WriteByte((byte)((value >> 24) & 0xFF));
235 return;
236 }
237 if (value <= 1099511627775)
238 {
239 writer.WriteByte((byte)252);
240 writer.WriteByte((byte)(value & 0xFF));
241 writer.WriteByte((byte)((value >> 8) & 0xFF));
242 writer.WriteByte((byte)((value >> 16) & 0xFF));
243 writer.WriteByte((byte)((value >> 24) & 0xFF));
244 writer.WriteByte((byte)((value >> 32) & 0xFF));
245 return;
246 }
247 if (value <= 281474976710655)
248 {
249 writer.WriteByte((byte)253);
250 writer.WriteByte((byte)(value & 0xFF));
251 writer.WriteByte((byte)((value >> 8) & 0xFF));
252 writer.WriteByte((byte)((value >> 16) & 0xFF));
253 writer.WriteByte((byte)((value >> 24) & 0xFF));
254 writer.WriteByte((byte)((value >> 32) & 0xFF));
255 writer.WriteByte((byte)((value >> 40) & 0xFF));
256 return;
257 }
258 if (value <= 72057594037927935)
259 {
260 writer.WriteByte((byte)254);
261 writer.WriteByte((byte)(value & 0xFF));
262 writer.WriteByte((byte)((value >> 8) & 0xFF));
263 writer.WriteByte((byte)((value >> 16) & 0xFF));
264 writer.WriteByte((byte)((value >> 24) & 0xFF));
265 writer.WriteByte((byte)((value >> 32) & 0xFF));
266 writer.WriteByte((byte)((value >> 40) & 0xFF));
267 writer.WriteByte((byte)((value >> 48) & 0xFF));
268 return;
269 }
270
271 // all others
272 {
273 writer.WriteByte((byte)255);
274 writer.WriteByte((byte)(value & 0xFF));
275 writer.WriteByte((byte)((value >> 8) & 0xFF));
276 writer.WriteByte((byte)((value >> 16) & 0xFF));
277 writer.WriteByte((byte)((value >> 24) & 0xFF));
278 writer.WriteByte((byte)((value >> 32) & 0xFF));
279 writer.WriteByte((byte)((value >> 40) & 0xFF));
280 writer.WriteByte((byte)((value >> 48) & 0xFF));
281 writer.WriteByte((byte)((value >> 56) & 0xFF));
282 }
283 }

◆ DecompressQuaternion()

static Quaternion Mirror.Compression.DecompressQuaternion ( uint  data)
static

Definition at line 156 of file Compression.cs.

157 {
158 // get cScaled which is at 0..10 and ignore the rest
159 ushort cScaled = (ushort)(data & TenBitsMax);
160
161 // get bScaled which is at 10..20 and ignore the rest
162 ushort bScaled = (ushort)((data >> 10) & TenBitsMax);
163
164 // get aScaled which is at 20..30 and ignore the rest
165 ushort aScaled = (ushort)((data >> 20) & TenBitsMax);
166
167 // get 2 bit largest index, which is at 31..32
168 int largestIndex = (int)(data >> 30);
169
170 // scale back to floats
171 float a = ScaleUShortToFloat(aScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
172 float b = ScaleUShortToFloat(bScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
173 float c = ScaleUShortToFloat(cScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
174
175 // calculate the omitted component based on a²+b²+c²+d²=1
176 float d = Mathf.Sqrt(1 - a*a - b*b - c*c);
177
178 // reconstruct based on largest index
179 Vector4 value;
180 switch (largestIndex)
181 {
182 case 0: value = new Vector4(d, a, b, c); break;
183 case 1: value = new Vector4(a, d, b, c); break;
184 case 2: value = new Vector4(a, b, d, c); break;
185 default: value = new Vector4(a, b, c, d); break;
186 }
187
188 // ECS Rotation only works with normalized quaternions.
189 // make sure that's always the case here to avoid ECS bugs where
190 // everything stops moving if the quaternion isn't normalized.
191 // => NormalizeSafe returns a normalized quaternion even if we pass
192 // in NaN from deserializing invalid values!
193 return QuaternionNormalizeSafe(new Quaternion(value.x, value.y, value.z, value.w));
194 }

◆ DecompressVarInt()

static long Mirror.Compression.DecompressVarInt ( NetworkReader  reader)
static

Definition at line 355 of file Compression.cs.

356 {
357 ulong data = DecompressVarUInt(reader);
358 return ((long)(data >> 1)) ^ -((long)data & 1);
359 }

◆ DecompressVarUInt()

static ulong Mirror.Compression.DecompressVarUInt ( NetworkReader  reader)
static

Definition at line 294 of file Compression.cs.

295 {
296 byte a0 = reader.ReadByte();
297 if (a0 < 241)
298 {
299 return a0;
300 }
301
302 byte a1 = reader.ReadByte();
303 if (a0 <= 248)
304 {
305 return 240 + ((a0 - (ulong)241) << 8) + a1;
306 }
307
308 byte a2 = reader.ReadByte();
309 if (a0 == 249)
310 {
311 return 2288 + ((ulong)a1 << 8) + a2;
312 }
313
314 byte a3 = reader.ReadByte();
315 if (a0 == 250)
316 {
317 return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16);
318 }
319
320 byte a4 = reader.ReadByte();
321 if (a0 == 251)
322 {
323 return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24);
324 }
325
326 byte a5 = reader.ReadByte();
327 if (a0 == 252)
328 {
329 return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32);
330 }
331
332 byte a6 = reader.ReadByte();
333 if (a0 == 253)
334 {
335 return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40);
336 }
337
338 byte a7 = reader.ReadByte();
339 if (a0 == 254)
340 {
341 return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40) + (((ulong)a7) << 48);
342 }
343
344 byte a8 = reader.ReadByte();
345 if (a0 == 255)
346 {
347 return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40) + (((ulong)a7) << 48) + (((ulong)a8) << 56);
348 }
349
350 throw new IndexOutOfRangeException($"DecompressVarInt failure: {a0}");
351 }

◆ LargestAbsoluteComponentIndex()

static int Mirror.Compression.LargestAbsoluteComponentIndex ( Vector4  value,
out float  largestAbs,
out Vector3  withoutLargest 
)
static

Definition at line 17 of file Compression.cs.

18 {
19 // convert to abs
20 Vector4 abs = new Vector4(Mathf.Abs(value.x), Mathf.Abs(value.y), Mathf.Abs(value.z), Mathf.Abs(value.w));
21
22 // set largest to first abs (x)
23 largestAbs = abs.x;
24 withoutLargest = new Vector3(value.y, value.z, value.w);
25 int largestIndex = 0;
26
27 // compare to the others, starting at second value
28 // performance for 100k calls
29 // for-loop: 25ms
30 // manual checks: 22ms
31 if (abs.y > largestAbs)
32 {
33 largestIndex = 1;
34 largestAbs = abs.y;
35 withoutLargest = new Vector3(value.x, value.z, value.w);
36 }
37 if (abs.z > largestAbs)
38 {
39 largestIndex = 2;
40 largestAbs = abs.z;
41 withoutLargest = new Vector3(value.x, value.y, value.w);
42 }
43 if (abs.w > largestAbs)
44 {
45 largestIndex = 3;
46 largestAbs = abs.w;
47 withoutLargest = new Vector3(value.x, value.y, value.z);
48 }
49
50 return largestIndex;
51 }

◆ ScaleFloatToUShort()

static ushort Mirror.Compression.ScaleFloatToUShort ( float  value,
float  minValue,
float  maxValue,
ushort  minTarget,
ushort  maxTarget 
)
static

Definition at line 55 of file Compression.cs.

56 {
57 // note: C# ushort - ushort => int, hence so many casts
58 // max ushort - min ushort only fits into something bigger
59 int targetRange = maxTarget - minTarget;
60 float valueRange = maxValue - minValue;
61 float valueRelative = value - minValue;
62 return (ushort)(minTarget + (ushort)(valueRelative / valueRange * targetRange));
63 }

◆ ScaleUShortToFloat()

static float Mirror.Compression.ScaleUShortToFloat ( ushort  value,
ushort  minValue,
ushort  maxValue,
float  minTarget,
float  maxTarget 
)
static

Definition at line 67 of file Compression.cs.

68 {
69 // note: C# ushort - ushort => int, hence so many casts
70 float targetRange = maxTarget - minTarget;
71 ushort valueRange = (ushort)(maxValue - minValue);
72 ushort valueRelative = (ushort)(value - minValue);
73 return minTarget + (valueRelative / (float)valueRange * targetRange);
74 }