Compression.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  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. // divide by precision (functions backported from Mirror II)
  11. // for example, 0.1 cm precision converts '5.0f' float to '50' long.
  12. //
  13. // 'long' instead of 'int' to allow for large enough worlds.
  14. // value / precision exceeds int.max range too easily.
  15. // Convert.ToInt32/64 would throw.
  16. // https://github.com/vis2k/DOTSNET/issues/59
  17. //
  18. // 'long' and 'int' will result in the same bandwidth though.
  19. // for example, ScaleToLong(10.5, 0.1) = 105.
  20. // int: 0x00000069
  21. // long: 0x0000000000000069
  22. // delta compression will reduce both to 1 byte.
  23. //
  24. // returns
  25. // 'true' if scaling was possible within 'long' bounds.
  26. // 'false' if clamping was necessary.
  27. // never throws. checking result is optional.
  28. public static bool ScaleToLong(float value, float precision, out long result)
  29. {
  30. // user might try to pass precision = 0 to disable rounding.
  31. // this is not supported.
  32. // throw to make the user fix this immediately.
  33. // otherwise we would have to reinterpret-cast if ==0 etc.
  34. // this function should be kept simple.
  35. // if rounding isn't wanted, this function shouldn't be called.
  36. if (precision == 0) throw new DivideByZeroException($"ScaleToLong: precision=0 would cause null division. If rounding isn't wanted, don't call this function.");
  37. // catch OverflowException if value/precision > long.max.
  38. // attackers should never be able to throw exceptions.
  39. try
  40. {
  41. result = Convert.ToInt64(value / precision);
  42. return true;
  43. }
  44. // clamp to .max/.min.
  45. // returning '0' would make far away entities reset to origin.
  46. // returning 'max' would keep them stuck at the end of the world.
  47. // the latter is much easier to debug.
  48. catch (OverflowException)
  49. {
  50. result = value > 0 ? long.MaxValue : long.MinValue;
  51. return false;
  52. }
  53. }
  54. // returns
  55. // 'true' if scaling was possible within 'long' bounds.
  56. // 'false' if clamping was necessary.
  57. // never throws. checking result is optional.
  58. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  59. public static bool ScaleToLong(Vector3 value, float precision, out long x, out long y, out long z)
  60. {
  61. // attempt to convert every component.
  62. // do not return early if one conversion returned 'false'.
  63. // the return value is optional. always attempt to convert all.
  64. bool result = true;
  65. result &= ScaleToLong(value.x, precision, out x);
  66. result &= ScaleToLong(value.y, precision, out y);
  67. result &= ScaleToLong(value.z, precision, out z);
  68. return result;
  69. }
  70. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  71. public static bool ScaleToLong(Vector3 value, float precision, out Vector3Long quantized)
  72. {
  73. quantized = Vector3Long.zero;
  74. return ScaleToLong(value, precision, out quantized.x, out quantized.y, out quantized.z);
  75. }
  76. // multiple by precision.
  77. // for example, 0.1 cm precision converts '50' long to '5.0f' float.
  78. public static float ScaleToFloat(long value, float precision)
  79. {
  80. // user might try to pass precision = 0 to disable rounding.
  81. // this is not supported.
  82. // throw to make the user fix this immediately.
  83. // otherwise we would have to reinterpret-cast if ==0 etc.
  84. // this function should be kept simple.
  85. // if rounding isn't wanted, this function shouldn't be called.
  86. if (precision == 0) throw new DivideByZeroException($"ScaleToLong: precision=0 would cause null division. If rounding isn't wanted, don't call this function.");
  87. return value * precision;
  88. }
  89. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  90. public static Vector3 ScaleToFloat(long x, long y, long z, float precision)
  91. {
  92. Vector3 v;
  93. v.x = ScaleToFloat(x, precision);
  94. v.y = ScaleToFloat(y, precision);
  95. v.z = ScaleToFloat(z, precision);
  96. return v;
  97. }
  98. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  99. public static Vector3 ScaleToFloat(Vector3Long value, float precision) =>
  100. ScaleToFloat(value.x, value.y, value.z, precision);
  101. // scale a float within min/max range to an ushort between min/max range
  102. // note: can also use this for byte range from byte.MinValue to byte.MaxValue
  103. public static ushort ScaleFloatToUShort(float value, float minValue, float maxValue, ushort minTarget, ushort maxTarget)
  104. {
  105. // note: C# ushort - ushort => int, hence so many casts
  106. // max ushort - min ushort only fits into something bigger
  107. int targetRange = maxTarget - minTarget;
  108. float valueRange = maxValue - minValue;
  109. float valueRelative = value - minValue;
  110. return (ushort)(minTarget + (ushort)(valueRelative / valueRange * targetRange));
  111. }
  112. // scale an ushort within min/max range to a float between min/max range
  113. // note: can also use this for byte range from byte.MinValue to byte.MaxValue
  114. public static float ScaleUShortToFloat(ushort value, ushort minValue, ushort maxValue, float minTarget, float maxTarget)
  115. {
  116. // note: C# ushort - ushort => int, hence so many casts
  117. float targetRange = maxTarget - minTarget;
  118. ushort valueRange = (ushort)(maxValue - minValue);
  119. ushort valueRelative = (ushort)(value - minValue);
  120. return minTarget + (valueRelative / (float)valueRange * targetRange);
  121. }
  122. // quaternion compression //////////////////////////////////////////////
  123. // smallest three: https://gafferongames.com/post/snapshot_compression/
  124. // compresses 16 bytes quaternion into 4 bytes
  125. // helper function to find largest absolute element
  126. // returns the index of the largest one
  127. public static int LargestAbsoluteComponentIndex(Vector4 value, out float largestAbs, out Vector3 withoutLargest)
  128. {
  129. // convert to abs
  130. Vector4 abs = new Vector4(Mathf.Abs(value.x), Mathf.Abs(value.y), Mathf.Abs(value.z), Mathf.Abs(value.w));
  131. // set largest to first abs (x)
  132. largestAbs = abs.x;
  133. withoutLargest = new Vector3(value.y, value.z, value.w);
  134. int largestIndex = 0;
  135. // compare to the others, starting at second value
  136. // performance for 100k calls
  137. // for-loop: 25ms
  138. // manual checks: 22ms
  139. if (abs.y > largestAbs)
  140. {
  141. largestIndex = 1;
  142. largestAbs = abs.y;
  143. withoutLargest = new Vector3(value.x, value.z, value.w);
  144. }
  145. if (abs.z > largestAbs)
  146. {
  147. largestIndex = 2;
  148. largestAbs = abs.z;
  149. withoutLargest = new Vector3(value.x, value.y, value.w);
  150. }
  151. if (abs.w > largestAbs)
  152. {
  153. largestIndex = 3;
  154. largestAbs = abs.w;
  155. withoutLargest = new Vector3(value.x, value.y, value.z);
  156. }
  157. return largestIndex;
  158. }
  159. const float QuaternionMinRange = -0.707107f;
  160. const float QuaternionMaxRange = 0.707107f;
  161. const ushort TenBitsMax = 0b11_1111_1111;
  162. // note: assumes normalized quaternions
  163. public static uint CompressQuaternion(Quaternion q)
  164. {
  165. // note: assuming normalized quaternions is enough. no need to force
  166. // normalize here. we already normalize when decompressing.
  167. // find the largest component index [0,3] + value
  168. int largestIndex = LargestAbsoluteComponentIndex(new Vector4(q.x, q.y, q.z, q.w), out float _, out Vector3 withoutLargest);
  169. // from here on, we work with the 3 components without largest!
  170. // "You might think you need to send a sign bit for [largest] in
  171. // case it is negative, but you don’t, because you can make
  172. // [largest] always positive by negating the entire quaternion if
  173. // [largest] is negative. in quaternion space (x,y,z,w) and
  174. // (-x,-y,-z,-w) represent the same rotation."
  175. if (q[largestIndex] < 0)
  176. withoutLargest = -withoutLargest;
  177. // put index & three floats into one integer.
  178. // => index is 2 bits (4 values require 2 bits to store them)
  179. // => the three floats are between [-0.707107,+0.707107] because:
  180. // "If v is the absolute value of the largest quaternion
  181. // component, the next largest possible component value occurs
  182. // when two components have the same absolute value and the
  183. // other two components are zero. The length of that quaternion
  184. // (v,v,0,0) is 1, therefore v^2 + v^2 = 1, 2v^2 = 1,
  185. // v = 1/sqrt(2). This means you can encode the smallest three
  186. // components in [-0.707107,+0.707107] instead of [-1,+1] giving
  187. // you more precision with the same number of bits."
  188. // => the article recommends storing each float in 9 bits
  189. // => our uint has 32 bits, so we might as well store in (32-2)/3=10
  190. // 10 bits max value: 1023=0x3FF (use OSX calc to flip 10 bits)
  191. ushort aScaled = ScaleFloatToUShort(withoutLargest.x, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
  192. ushort bScaled = ScaleFloatToUShort(withoutLargest.y, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
  193. ushort cScaled = ScaleFloatToUShort(withoutLargest.z, QuaternionMinRange, QuaternionMaxRange, 0, TenBitsMax);
  194. // now we just need to pack them into one integer
  195. // -> index is 2 bit and needs to be shifted to 31..32
  196. // -> a is 10 bit and needs to be shifted 20..30
  197. // -> b is 10 bit and needs to be shifted 10..20
  198. // -> c is 10 bit and needs to be at 0..10
  199. return (uint)(largestIndex << 30 | aScaled << 20 | bScaled << 10 | cScaled);
  200. }
  201. // Quaternion normalizeSAFE from ECS math.normalizesafe()
  202. // => useful to produce valid quaternions even if client sends invalid
  203. // data
  204. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  205. static Quaternion QuaternionNormalizeSafe(Quaternion value)
  206. {
  207. // The smallest positive normal number representable in a float.
  208. const float FLT_MIN_NORMAL = 1.175494351e-38F;
  209. Vector4 v = new Vector4(value.x, value.y, value.z, value.w);
  210. float length = Vector4.Dot(v, v);
  211. return length > FLT_MIN_NORMAL
  212. ? value.normalized
  213. : Quaternion.identity;
  214. }
  215. // note: gives normalized quaternions
  216. public static Quaternion DecompressQuaternion(uint data)
  217. {
  218. // get cScaled which is at 0..10 and ignore the rest
  219. ushort cScaled = (ushort)(data & TenBitsMax);
  220. // get bScaled which is at 10..20 and ignore the rest
  221. ushort bScaled = (ushort)((data >> 10) & TenBitsMax);
  222. // get aScaled which is at 20..30 and ignore the rest
  223. ushort aScaled = (ushort)((data >> 20) & TenBitsMax);
  224. // get 2 bit largest index, which is at 31..32
  225. int largestIndex = (int)(data >> 30);
  226. // scale back to floats
  227. float a = ScaleUShortToFloat(aScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
  228. float b = ScaleUShortToFloat(bScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
  229. float c = ScaleUShortToFloat(cScaled, 0, TenBitsMax, QuaternionMinRange, QuaternionMaxRange);
  230. // calculate the omitted component based on a²+b²+c²+d²=1
  231. float d = Mathf.Sqrt(1 - a*a - b*b - c*c);
  232. // reconstruct based on largest index
  233. Vector4 value;
  234. switch (largestIndex)
  235. {
  236. case 0: value = new Vector4(d, a, b, c); break;
  237. case 1: value = new Vector4(a, d, b, c); break;
  238. case 2: value = new Vector4(a, b, d, c); break;
  239. default: value = new Vector4(a, b, c, d); break;
  240. }
  241. // ECS Rotation only works with normalized quaternions.
  242. // make sure that's always the case here to avoid ECS bugs where
  243. // everything stops moving if the quaternion isn't normalized.
  244. // => NormalizeSafe returns a normalized quaternion even if we pass
  245. // in NaN from deserializing invalid values!
  246. return QuaternionNormalizeSafe(new Quaternion(value.x, value.y, value.z, value.w));
  247. }
  248. // varint compression //////////////////////////////////////////////////
  249. // helper function to predict varint size for a given number.
  250. // useful when checking if a message + size header will fit, etc.
  251. public static int VarUIntSize(ulong value)
  252. {
  253. if (value <= 240)
  254. return 1;
  255. if (value <= 2287)
  256. return 2;
  257. if (value <= 67823)
  258. return 3;
  259. if (value <= 16777215)
  260. return 4;
  261. if (value <= 4294967295)
  262. return 5;
  263. if (value <= 1099511627775)
  264. return 6;
  265. if (value <= 281474976710655)
  266. return 7;
  267. if (value <= 72057594037927935)
  268. return 8;
  269. return 9;
  270. }
  271. // helper function to predict varint size for a given number.
  272. // useful when checking if a message + size header will fit, etc.
  273. public static int VarIntSize(long value)
  274. {
  275. // CompressVarInt zigzags it first
  276. ulong zigzagged = (ulong)((value >> 63) ^ (value << 1));
  277. return VarUIntSize(zigzagged);
  278. }
  279. // compress ulong varint.
  280. // same result for ulong, uint, ushort and byte. only need one function.
  281. // NOT an extension. otherwise weaver might accidentally use it.
  282. public static void CompressVarUInt(NetworkWriter writer, ulong value)
  283. {
  284. // straight forward implementation:
  285. // keep this for understanding & debugging.
  286. /*
  287. if (value <= 240)
  288. {
  289. writer.WriteByte((byte)value);
  290. return;
  291. }
  292. if (value <= 2287)
  293. {
  294. writer.WriteByte((byte)(((value - 240) >> 8) + 241));
  295. writer.WriteByte((byte)((value - 240) & 0xFF));
  296. return;
  297. }
  298. if (value <= 67823)
  299. {
  300. writer.WriteByte((byte)249);
  301. writer.WriteByte((byte)((value - 2288) >> 8));
  302. writer.WriteByte((byte)((value - 2288) & 0xFF));
  303. return;
  304. }
  305. if (value <= 16777215)
  306. {
  307. writer.WriteByte((byte)250);
  308. writer.WriteByte((byte)(value & 0xFF));
  309. writer.WriteByte((byte)((value >> 8) & 0xFF));
  310. writer.WriteByte((byte)((value >> 16) & 0xFF));
  311. return;
  312. }
  313. if (value <= 4294967295)
  314. {
  315. writer.WriteByte((byte)251);
  316. writer.WriteByte((byte)(value & 0xFF));
  317. writer.WriteByte((byte)((value >> 8) & 0xFF));
  318. writer.WriteByte((byte)((value >> 16) & 0xFF));
  319. writer.WriteByte((byte)((value >> 24) & 0xFF));
  320. return;
  321. }
  322. if (value <= 1099511627775)
  323. {
  324. writer.WriteByte((byte)252);
  325. writer.WriteByte((byte)(value & 0xFF));
  326. writer.WriteByte((byte)((value >> 8) & 0xFF));
  327. writer.WriteByte((byte)((value >> 16) & 0xFF));
  328. writer.WriteByte((byte)((value >> 24) & 0xFF));
  329. writer.WriteByte((byte)((value >> 32) & 0xFF));
  330. return;
  331. }
  332. if (value <= 281474976710655)
  333. {
  334. writer.WriteByte((byte)253);
  335. writer.WriteByte((byte)(value & 0xFF));
  336. writer.WriteByte((byte)((value >> 8) & 0xFF));
  337. writer.WriteByte((byte)((value >> 16) & 0xFF));
  338. writer.WriteByte((byte)((value >> 24) & 0xFF));
  339. writer.WriteByte((byte)((value >> 32) & 0xFF));
  340. writer.WriteByte((byte)((value >> 40) & 0xFF));
  341. return;
  342. }
  343. if (value <= 72057594037927935)
  344. {
  345. writer.WriteByte((byte)254);
  346. writer.WriteByte((byte)(value & 0xFF));
  347. writer.WriteByte((byte)((value >> 8) & 0xFF));
  348. writer.WriteByte((byte)((value >> 16) & 0xFF));
  349. writer.WriteByte((byte)((value >> 24) & 0xFF));
  350. writer.WriteByte((byte)((value >> 32) & 0xFF));
  351. writer.WriteByte((byte)((value >> 40) & 0xFF));
  352. writer.WriteByte((byte)((value >> 48) & 0xFF));
  353. return;
  354. }
  355. // all others
  356. {
  357. writer.WriteByte((byte)255);
  358. writer.WriteByte((byte)(value & 0xFF));
  359. writer.WriteByte((byte)((value >> 8) & 0xFF));
  360. writer.WriteByte((byte)((value >> 16) & 0xFF));
  361. writer.WriteByte((byte)((value >> 24) & 0xFF));
  362. writer.WriteByte((byte)((value >> 32) & 0xFF));
  363. writer.WriteByte((byte)((value >> 40) & 0xFF));
  364. writer.WriteByte((byte)((value >> 48) & 0xFF));
  365. writer.WriteByte((byte)((value >> 56) & 0xFF));
  366. }
  367. */
  368. // faster implementation writes multiple bytes at once.
  369. // avoids extra Space, WriteBlittable overhead.
  370. // VarInt is in hot path, performance matters here.
  371. if (value <= 240)
  372. {
  373. byte a = (byte)value;
  374. writer.WriteByte(a);
  375. return;
  376. }
  377. if (value <= 2287)
  378. {
  379. byte a = (byte)(((value - 240) >> 8) + 241);
  380. byte b = (byte)((value - 240) & 0xFF);
  381. writer.WriteUShort((ushort)(b << 8 | a));
  382. return;
  383. }
  384. if (value <= 67823)
  385. {
  386. byte a = (byte)249;
  387. byte b = (byte)((value - 2288) >> 8);
  388. byte c = (byte)((value - 2288) & 0xFF);
  389. writer.WriteByte(a);
  390. writer.WriteUShort((ushort)(c << 8 | b));
  391. return;
  392. }
  393. if (value <= 16777215)
  394. {
  395. byte a = (byte)250;
  396. uint b = (uint)(value << 8);
  397. writer.WriteUInt(b | a);
  398. return;
  399. }
  400. if (value <= 4294967295)
  401. {
  402. byte a = (byte)251;
  403. uint b = (uint)value;
  404. writer.WriteByte(a);
  405. writer.WriteUInt(b);
  406. return;
  407. }
  408. if (value <= 1099511627775)
  409. {
  410. byte a = (byte)252;
  411. byte b = (byte)(value & 0xFF);
  412. uint c = (uint)(value >> 8);
  413. writer.WriteUShort((ushort)(b << 8 | a));
  414. writer.WriteUInt(c);
  415. return;
  416. }
  417. if (value <= 281474976710655)
  418. {
  419. byte a = (byte)253;
  420. byte b = (byte)(value & 0xFF);
  421. byte c = (byte)((value >> 8) & 0xFF);
  422. uint d = (uint)(value >> 16);
  423. writer.WriteByte(a);
  424. writer.WriteUShort((ushort)(c << 8 | b));
  425. writer.WriteUInt(d);
  426. return;
  427. }
  428. if (value <= 72057594037927935)
  429. {
  430. byte a = 254;
  431. ulong b = value << 8;
  432. writer.WriteULong(b | a);
  433. return;
  434. }
  435. // all others
  436. {
  437. writer.WriteByte(255);
  438. writer.WriteULong(value);
  439. }
  440. }
  441. // zigzag encoding https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba
  442. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  443. public static void CompressVarInt(NetworkWriter writer, long i)
  444. {
  445. ulong zigzagged = (ulong)((i >> 63) ^ (i << 1));
  446. CompressVarUInt(writer, zigzagged);
  447. }
  448. // NOT an extension. otherwise weaver might accidentally use it.
  449. public static ulong DecompressVarUInt(NetworkReader reader)
  450. {
  451. byte a0 = reader.ReadByte();
  452. if (a0 < 241)
  453. {
  454. return a0;
  455. }
  456. byte a1 = reader.ReadByte();
  457. if (a0 <= 248)
  458. {
  459. return 240 + ((a0 - (ulong)241) << 8) + a1;
  460. }
  461. byte a2 = reader.ReadByte();
  462. if (a0 == 249)
  463. {
  464. return 2288 + ((ulong)a1 << 8) + a2;
  465. }
  466. byte a3 = reader.ReadByte();
  467. if (a0 == 250)
  468. {
  469. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16);
  470. }
  471. byte a4 = reader.ReadByte();
  472. if (a0 == 251)
  473. {
  474. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24);
  475. }
  476. byte a5 = reader.ReadByte();
  477. if (a0 == 252)
  478. {
  479. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32);
  480. }
  481. byte a6 = reader.ReadByte();
  482. if (a0 == 253)
  483. {
  484. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40);
  485. }
  486. byte a7 = reader.ReadByte();
  487. if (a0 == 254)
  488. {
  489. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40) + (((ulong)a7) << 48);
  490. }
  491. byte a8 = reader.ReadByte();
  492. if (a0 == 255)
  493. {
  494. return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40) + (((ulong)a7) << 48) + (((ulong)a8) << 56);
  495. }
  496. throw new IndexOutOfRangeException($"DecompressVarInt failure: {a0}");
  497. }
  498. // zigzag decoding https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba
  499. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  500. public static long DecompressVarInt(NetworkReader reader)
  501. {
  502. ulong data = DecompressVarUInt(reader);
  503. return ((long)(data >> 1)) ^ -((long)data & 1);
  504. }
  505. }
  506. }