using System; using UnityEngine; namespace FishNet.Serializing.Helping { /// /// Credit to https://github.com/viliwonka /// https://github.com/FirstGearGames/FishNet/pull/23 /// public static class Quaternion64Compression { // 64 bit quaternion compression // [4 bits] largest component // [21 bits] higher res // [21 bits] higher res // [20 bits] higher res // sum is 64 bits private const float Maximum = +1.0f / 1.414214f; private const int BitsPerAxis_H = 21; // higher res, 21 bits private const int BitsPerAxis_L = 20; // lower res, 20 bits private const int LargestComponentShift = BitsPerAxis_H * 2 + BitsPerAxis_L * 1; private const int AShift = BitsPerAxis_H + BitsPerAxis_L; private const int BShift = BitsPerAxis_L; private const int IntScale_H = (1 << (BitsPerAxis_H - 1)) - 1; private const int IntMask_H = (1 << BitsPerAxis_H) - 1; private const int IntScale_L = (1 << (BitsPerAxis_L - 1)) - 1; private const int IntMask_L = (1 << BitsPerAxis_L) - 1; public static ulong Compress(Quaternion quaternion) { float absX = Mathf.Abs(quaternion.x); float absY = Mathf.Abs(quaternion.y); float absZ = Mathf.Abs(quaternion.z); float absW = Mathf.Abs(quaternion.w); ComponentType largestComponent = ComponentType.X; float largestAbs = absX; float largest = quaternion.x; if (absY > largestAbs) { largestAbs = absY; largestComponent = ComponentType.Y; largest = quaternion.y; } if (absZ > largestAbs) { largestAbs = absZ; largestComponent = ComponentType.Z; largest = quaternion.z; } if (absW > largestAbs) { largestComponent = ComponentType.W; largest = quaternion.w; } float a = 0; float b = 0; float c = 0; switch (largestComponent) { case ComponentType.X: a = quaternion.y; b = quaternion.z; c = quaternion.w; break; case ComponentType.Y: a = quaternion.x; b = quaternion.z; c = quaternion.w; break; case ComponentType.Z: a = quaternion.x; b = quaternion.y; c = quaternion.w; break; case ComponentType.W: a = quaternion.x; b = quaternion.y; c = quaternion.z; break; } if (largest < 0) { a = -a; b = -b; c = -c; } ulong integerA = ScaleToUint_H(a); ulong integerB = ScaleToUint_H(b); ulong integerC = ScaleToUint_L(c); return (((ulong)largestComponent) << LargestComponentShift) | (integerA << AShift) | (integerB << BShift) | integerC; } private static ulong ScaleToUint_H(float v) { float normalized = v / Maximum; return (ulong)Mathf.RoundToInt(normalized * IntScale_H) & IntMask_H; } private static ulong ScaleToUint_L(float v) { float normalized = v / Maximum; return (ulong)Mathf.RoundToInt(normalized * IntScale_L) & IntMask_L; } private static float ScaleToFloat_H(ulong v) { float unscaled = v * Maximum / IntScale_H; if (unscaled > Maximum) unscaled -= Maximum * 2; return unscaled; } private static float ScaleToFloat_L(ulong v) { float unscaled = v * Maximum / IntScale_L; if (unscaled > Maximum) unscaled -= Maximum * 2; return unscaled; } public static Quaternion Decompress(ulong compressed) { var largestComponentType = (ComponentType)(compressed >> LargestComponentShift); ulong integerA = (compressed >> AShift) & IntMask_H; ulong integerB = (compressed >> BShift) & IntMask_H; ulong integerC = compressed & IntMask_L; float a = ScaleToFloat_H(integerA); float b = ScaleToFloat_H(integerB); float c = ScaleToFloat_L(integerC); Quaternion rotation; switch (largestComponentType) { case ComponentType.X: // (?) y z w rotation.y = a; rotation.z = b; rotation.w = c; rotation.x = Mathf.Sqrt(1 - rotation.y * rotation.y - rotation.z * rotation.z - rotation.w * rotation.w); break; case ComponentType.Y: // x (?) z w rotation.x = a; rotation.z = b; rotation.w = c; rotation.y = Mathf.Sqrt(1 - rotation.x * rotation.x - rotation.z * rotation.z - rotation.w * rotation.w); break; case ComponentType.Z: // x y (?) w rotation.x = a; rotation.y = b; rotation.w = c; rotation.z = Mathf.Sqrt(1 - rotation.x * rotation.x - rotation.y * rotation.y - rotation.w * rotation.w); break; case ComponentType.W: // x y z (?) rotation.x = a; rotation.y = b; rotation.z = c; rotation.w = Mathf.Sqrt(1 - rotation.x * rotation.x - rotation.y * rotation.y - rotation.z * rotation.z); break; default: // Should never happen! throw new ArgumentOutOfRangeException("Unknown rotation component type: " + largestComponentType); } return rotation; } } }