Compression.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. // Quaternion compression from DOTSNET
  2. using System;
  3. using System.Runtime.CompilerServices;
  4. using UnityEngine;
  5. namespace Mirror
  6. {
  7. /// <summary>Functions to Compress Quaternions and Floats</summary>
  8. public static class Compression
  9. {
  10. // quaternion compression //////////////////////////////////////////////
  11. // smallest three: https://gafferongames.com/post/snapshot_compression/
  12. // compresses 16 bytes quaternion into 4 bytes
  13. // helper function to find largest absolute element
  14. // returns the index of the largest one
  15. public static int LargestAbsoluteComponentIndex(Vector4 value, out float largestAbs, out Vector3 withoutLargest)
  16. {
  17. // convert to abs
  18. Vector4 abs = new Vector4(Mathf.Abs(value.x), Mathf.Abs(value.y), Mathf.Abs(value.z), Mathf.Abs(value.w));
  19. // set largest to first abs (x)
  20. largestAbs = abs.x;
  21. withoutLargest = new Vector3(value.y, value.z, value.w);
  22. int largestIndex = 0;
  23. // compare to the others, starting at second value
  24. // performance for 100k calls
  25. // for-loop: 25ms
  26. // manual checks: 22ms
  27. if (abs.y > largestAbs)
  28. {
  29. largestIndex = 1;
  30. largestAbs = abs.y;
  31. withoutLargest = new Vector3(value.x, value.z, value.w);
  32. }
  33. if (abs.z > largestAbs)
  34. {
  35. largestIndex = 2;
  36. largestAbs = abs.z;
  37. withoutLargest = new Vector3(value.x, value.y, value.w);
  38. }
  39. if (abs.w > largestAbs)
  40. {
  41. largestIndex = 3;
  42. largestAbs = abs.w;
  43. withoutLargest = new Vector3(value.x, value.y, value.z);
  44. }
  45. return largestIndex;
  46. }
  47. // scale a float within min/max range to an ushort between min/max range
  48. // note: can also use this for byte range from byte.MinValue to byte.MaxValue
  49. public static ushort ScaleFloatToUShort(float value, float minValue, float maxValue, ushort minTarget, ushort maxTarget)
  50. {
  51. // note: C# ushort - ushort => int, hence so many casts
  52. // max ushort - min ushort only fits into something bigger
  53. int targetRange = maxTarget - minTarget;
  54. float valueRange = maxValue - minValue;
  55. float valueRelative = value - minValue;
  56. return (ushort)(minTarget + (ushort)(valueRelative / valueRange * targetRange));
  57. }
  58. // scale an ushort within min/max range to a float between min/max range
  59. // note: can also use this for byte range from byte.MinValue to byte.MaxValue
  60. public static float ScaleUShortToFloat(ushort value, ushort minValue, ushort maxValue, float minTarget, float maxTarget)
  61. {
  62. // note: C# ushort - ushort => int, hence so many casts
  63. float targetRange = maxTarget - minTarget;
  64. ushort valueRange = (ushort)(maxValue - minValue);
  65. ushort valueRelative = (ushort)(value - minValue);
  66. return minTarget + (valueRelative / (float)valueRange * targetRange);
  67. }
  68. const float QuaternionMinRange = -0.707107f;
  69. const float QuaternionMaxRange = 0.707107f;
  70. const ushort TenBitsMax = 0x3FF;
  71. // helper function to access 'nth' component of quaternion
  72. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  73. static float QuaternionElement(Quaternion q, int element)
  74. {
  75. switch (element)
  76. {
  77. case 0: return q.x;
  78. case 1: return q.y;
  79. case 2: return q.z;
  80. case 3: return q.w;
  81. default: return 0;
  82. }
  83. }
  84. // note: assumes normalized quaternions
  85. public static uint CompressQuaternion(Quaternion q)
  86. {
  87. // note: assuming normalized quaternions is enough. no need to force
  88. // normalize here. we already normalize when decompressing.
  89. // find the largest component index [0,3] + value
  90. int largestIndex = LargestAbsoluteComponentIndex(new Vector4(q.x, q.y, q.z, q.w), out float _, out Vector3 withoutLargest);
  91. // from here on, we work with the 3 components without largest!
  92. // "You might think you need to send a sign bit for [largest] in
  93. // case it is negative, but you don’t, because you can make
  94. // [largest] always positive by negating the entire quaternion if
  95. // [largest] is negative. in quaternion space (x,y,z,w) and
  96. // (-x,-y,-z,-w) represent the same rotation."
  97. if (QuaternionElement(q, largestIndex) < 0)
  98. withoutLargest = -withoutLargest;
  99. // put index & three floats into one integer.
  100. // => index is 2 bits (4 values require 2 bits to store them)
  101. // => the three floats are between [-0.707107,+0.707107] because:
  102. // "If v is the absolute value of the largest quaternion
  103. // component, the next largest possible component value occurs
  104. // when two components have the same absolute value and the
  105. // other two components are zero. The length of that quaternion
  106. // (v,v,0,0) is 1, therefore v^2 + v^2 = 1, 2v^2 = 1,
  107. // v = 1/sqrt(2). This means you can encode the smallest three
  108. // components in [-0.707107,+0.707107] instead of [-1,+1] giving
  109. // you more precision with the same number of bits."
  110. // => the article recommends storing each float in 9 bits
  111. // => our uint has 32 bits, so we might as well store in (32-2)/3=10
  112. // 10 bits max value: 1023=0x3FF (use OSX calc to flip 10 bits)
  113. ushort aScaled = ScaleFloatToUShort(withoutLargest.x, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
  114. ushort bScaled = ScaleFloatToUShort(withoutLargest.y, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
  115. ushort cScaled = ScaleFloatToUShort(withoutLargest.z, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
  116. // now we just need to pack them into one integer
  117. // -> index is 2 bit and needs to be shifted to 31..32
  118. // -> a is 10 bit and needs to be shifted 20..30
  119. // -> b is 10 bit and needs to be shifted 10..20
  120. // -> c is 10 bit and needs to be at 0..10
  121. return (uint)(largestIndex << 30 | aScaled << 20 | bScaled << 10 | cScaled);
  122. }
  123. // Quaternion normalizeSAFE from ECS math.normalizesafe()
  124. // => useful to produce valid quaternions even if client sends invalid
  125. // data
  126. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  127. static Quaternion QuaternionNormalizeSafe(Quaternion value)
  128. {
  129. // The smallest positive normal number representable in a float.
  130. const float FLT_MIN_NORMAL = 1.175494351e-38F;
  131. Vector4 v = new Vector4(value.x, value.y, value.z, value.w);
  132. float length = Vector4.Dot(v, v);
  133. return length > FLT_MIN_NORMAL
  134. ? value.normalized
  135. : Quaternion.identity;
  136. }
  137. // note: gives normalized quaternions
  138. public static Quaternion DecompressQuaternion(uint data)
  139. {
  140. // get cScaled which is at 0..10 and ignore the rest
  141. ushort cScaled = (ushort)(data & TenBitsMax);
  142. // get bScaled which is at 10..20 and ignore the rest
  143. ushort bScaled = (ushort)((data >> 10) & TenBitsMax);
  144. // get aScaled which is at 20..30 and ignore the rest
  145. ushort aScaled = (ushort)((data >> 20) & TenBitsMax);
  146. // get 2 bit largest index, which is at 31..32
  147. int largestIndex = (int)(data >> 30);
  148. // scale back to floats
  149. float a = ScaleUShortToFloat(aScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
  150. float b = ScaleUShortToFloat(bScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
  151. float c = ScaleUShortToFloat(cScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
  152. // calculate the omitted component based on a²+b²+c²+d²=1
  153. float d = Mathf.Sqrt(1 - a*a - b*b - c*c);
  154. // reconstruct based on largest index
  155. Vector4 value;
  156. switch (largestIndex)
  157. {
  158. case 0: value = new Vector4(d, a, b, c); break;
  159. case 1: value = new Vector4(a, d, b, c); break;
  160. case 2: value = new Vector4(a, b, d, c); break;
  161. default: value = new Vector4(a, b, c, d); break;
  162. }
  163. // ECS Rotation only works with normalized quaternions.
  164. // make sure that's always the case here to avoid ECS bugs where
  165. // everything stops moving if the quaternion isn't normalized.
  166. // => NormalizeSafe returns a normalized quaternion even if we pass
  167. // in NaN from deserializing invalid values!
  168. return QuaternionNormalizeSafe(new Quaternion(value.x, value.y, value.z, value.w));
  169. }
  170. // varint compression //////////////////////////////////////////////////
  171. // compress ulong varint.
  172. // same result for int, short and byte. only need one function.
  173. // NOT an extension. otherwise weaver might accidentally use it.
  174. public static void CompressVarUInt(NetworkWriter writer, ulong value)
  175. {
  176. if (value <= 240)
  177. {
  178. writer.WriteByte((byte)value);
  179. return;
  180. }
  181. if (value <= 2287)
  182. {
  183. writer.WriteByte((byte)(((value - 240) >> 8) + 241));
  184. writer.WriteByte((byte)((value - 240) & 0xFF));
  185. return;
  186. }
  187. if (value <= 67823)
  188. {
  189. writer.WriteByte((byte)249);
  190. writer.WriteByte((byte)((value - 2288) >> 8));
  191. writer.WriteByte((byte)((value - 2288) & 0xFF));
  192. return;
  193. }
  194. if (value <= 16777215)
  195. {
  196. writer.WriteByte((byte)250);
  197. writer.WriteByte((byte)(value & 0xFF));
  198. writer.WriteByte((byte)((value >> 8) & 0xFF));
  199. writer.WriteByte((byte)((value >> 16) & 0xFF));
  200. return;
  201. }
  202. if (value <= 4294967295)
  203. {
  204. writer.WriteByte((byte)251);
  205. writer.WriteByte((byte)(value & 0xFF));
  206. writer.WriteByte((byte)((value >> 8) & 0xFF));
  207. writer.WriteByte((byte)((value >> 16) & 0xFF));
  208. writer.WriteByte((byte)((value >> 24) & 0xFF));
  209. return;
  210. }
  211. if (value <= 1099511627775)
  212. {
  213. writer.WriteByte((byte)252);
  214. writer.WriteByte((byte)(value & 0xFF));
  215. writer.WriteByte((byte)((value >> 8) & 0xFF));
  216. writer.WriteByte((byte)((value >> 16) & 0xFF));
  217. writer.WriteByte((byte)((value >> 24) & 0xFF));
  218. writer.WriteByte((byte)((value >> 32) & 0xFF));
  219. return;
  220. }
  221. if (value <= 281474976710655)
  222. {
  223. writer.WriteByte((byte)253);
  224. writer.WriteByte((byte)(value & 0xFF));
  225. writer.WriteByte((byte)((value >> 8) & 0xFF));
  226. writer.WriteByte((byte)((value >> 16) & 0xFF));
  227. writer.WriteByte((byte)((value >> 24) & 0xFF));
  228. writer.WriteByte((byte)((value >> 32) & 0xFF));
  229. writer.WriteByte((byte)((value >> 40) & 0xFF));
  230. return;
  231. }
  232. if (value <= 72057594037927935)
  233. {
  234. writer.WriteByte((byte)254);
  235. writer.WriteByte((byte)(value & 0xFF));
  236. writer.WriteByte((byte)((value >> 8) & 0xFF));
  237. writer.WriteByte((byte)((value >> 16) & 0xFF));
  238. writer.WriteByte((byte)((value >> 24) & 0xFF));
  239. writer.WriteByte((byte)((value >> 32) & 0xFF));
  240. writer.WriteByte((byte)((value >> 40) & 0xFF));
  241. writer.WriteByte((byte)((value >> 48) & 0xFF));
  242. return;
  243. }
  244. // all others
  245. {
  246. writer.WriteByte((byte)255);
  247. writer.WriteByte((byte)(value & 0xFF));
  248. writer.WriteByte((byte)((value >> 8) & 0xFF));
  249. writer.WriteByte((byte)((value >> 16) & 0xFF));
  250. writer.WriteByte((byte)((value >> 24) & 0xFF));
  251. writer.WriteByte((byte)((value >> 32) & 0xFF));
  252. writer.WriteByte((byte)((value >> 40) & 0xFF));
  253. writer.WriteByte((byte)((value >> 48) & 0xFF));
  254. writer.WriteByte((byte)((value >> 56) & 0xFF));
  255. }
  256. }
  257. // zigzag encoding https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba
  258. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  259. public static void CompressVarInt(NetworkWriter writer, long i)
  260. {
  261. ulong zigzagged = (ulong)((i >> 63) ^ (i << 1));
  262. CompressVarUInt(writer, zigzagged);
  263. }
  264. // NOT an extension. otherwise weaver might accidentally use it.
  265. public static ulong DecompressVarUInt(NetworkReader reader)
  266. {
  267. byte a0 = reader.ReadByte();
  268. if (a0 < 241)
  269. {
  270. return a0;
  271. }
  272. byte a1 = reader.ReadByte();
  273. if (a0 <= 248)
  274. {
  275. return 240 + ((a0 - (ulong)241) << 8) + a1;
  276. }
  277. byte a2 = reader.ReadByte();
  278. if (a0 == 249)
  279. {
  280. return 2288 + ((ulong)a1 << 8) + a2;
  281. }
  282. byte a3 = reader.ReadByte();
  283. if (a0 == 250)
  284. {
  285. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16);
  286. }
  287. byte a4 = reader.ReadByte();
  288. if (a0 == 251)
  289. {
  290. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24);
  291. }
  292. byte a5 = reader.ReadByte();
  293. if (a0 == 252)
  294. {
  295. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32);
  296. }
  297. byte a6 = reader.ReadByte();
  298. if (a0 == 253)
  299. {
  300. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40);
  301. }
  302. byte a7 = reader.ReadByte();
  303. if (a0 == 254)
  304. {
  305. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40) + (((ulong)a7) << 48);
  306. }
  307. byte a8 = reader.ReadByte();
  308. if (a0 == 255)
  309. {
  310. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40) + (((ulong)a7) << 48) + (((ulong)a8) << 56);
  311. }
  312. throw new IndexOutOfRangeException($"DecompressVarInt failure: {a0}");
  313. }
  314. // zigzag decoding https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba
  315. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  316. public static long DecompressVarInt(NetworkReader reader)
  317. {
  318. ulong data = DecompressVarUInt(reader);
  319. return ((long)(data >> 1)) ^ -((long)data & 1);
  320. }
  321. }
  322. }