namespace Fusion.Addons.KCC { using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; public sealed unsafe class KCCNetworkProperties : KCCNetworkProperty { // CONSTANTS private const int TRSP_POSITION_ACCURACY = 1 << 10; private const int PROPERTIES_WORD_COUNT = 11; private const int INTERACTIONS_BITS_SHIFT = 8; private const int MAX_INTERACTIONS_SINGLE = 1 << INTERACTIONS_BITS_SHIFT; private const int INTERACTIONS_MASK_SINGLE = MAX_INTERACTIONS_SINGLE - 1; private int _maxTotalInteractions; private int _interactionsWordCount; // CONSTRUCTORS public KCCNetworkProperties(KCCNetworkContext context) : base(context, GetTotalWordCount(context)) { GetInteractionsWordCount(context, out _maxTotalInteractions, out _interactionsWordCount); } // PUBLIC METHODS public static void ReadPositions(NetworkBehaviourBuffer fromBuffer, NetworkBehaviourBuffer toBuffer, out Vector3 fromTargetPosition, out Vector3 toTargetPosition) { fromTargetPosition = fromBuffer.ReinterpretState().Position; toTargetPosition = toBuffer.ReinterpretState().Position; KCCInterpolationInfo interpolationInfo = new KCCInterpolationInfo(); interpolationInfo.FromBuffer = fromBuffer; interpolationInfo.ToBuffer = toBuffer; interpolationInfo.Offset = NetworkTRSPData.WORDS; ReadVector3s(ref interpolationInfo, out Vector3 fromPositionExtension, out Vector3 toPositionExtension); fromTargetPosition += fromPositionExtension; toTargetPosition += toPositionExtension; } public static void ReadTransforms(NetworkBehaviourBuffer fromBuffer, NetworkBehaviourBuffer toBuffer, out Vector3 fromTargetPosition, out Vector3 toTargetPosition, out float fromLookPitch, out float toLookPitch, out float fromLookYaw, out float toLookYaw) { fromTargetPosition = fromBuffer.ReinterpretState().Position; toTargetPosition = toBuffer.ReinterpretState().Position; int offset = NetworkTRSPData.WORDS + 3; // NetworkTRSPData + Position Extension (3) fromLookPitch = fromBuffer.ReinterpretState(offset); toLookPitch = toBuffer.ReinterpretState(offset); offset += 1; fromLookYaw = fromBuffer.ReinterpretState(offset); toLookYaw = toBuffer.ReinterpretState(offset); } // KCCNetworkProperty INTERFACE public override void Read(int* ptr) { KCCData data = Context.Data; KCCSettings settings = Context.Settings; NetworkRunner runner = Context.KCC.Runner; Vector3 basePosition = ((NetworkTRSPData*)ptr)->Position; ptr += NetworkTRSPData.WORDS; data.TargetPosition = basePosition + ReadVector3(ref ptr); data.LookPitch = ReadFloat(ref ptr); data.LookYaw = ReadFloat(ref ptr); int combinedData = ReadInt(ref ptr); // Bits 0 - 7 => teleport counter // Bits 8 - 15 => jump counter data.IsActive = ((combinedData >> 16) & 0b1) == 1; data.IsGrounded = ((combinedData >> 17) & 0b1) == 1; data.WasGrounded = ((combinedData >> 18) & 0b1) == 1; data.IsSteppingUp = ((combinedData >> 19) & 0b1) == 1; data.WasSteppingUp = ((combinedData >> 20) & 0b1) == 1; data.IsSnappingToGround = ((combinedData >> 21) & 0b1) == 1; data.WasSnappingToGround = ((combinedData >> 22) & 0b1) == 1; data.HasTeleported = ((combinedData >> 23) & 0b1) == 1; data.JumpFrames = ((combinedData >> 24) & 0b1); int combinedSettings = ReadInt(ref ptr); settings.IsTrigger = ((combinedSettings >> 0) & 0b1) == 1; settings.ForcePredictedLookRotation = ((combinedSettings >> 1) & 0b1) == 1; settings.AllowClientTeleports = ((combinedSettings >> 2) & 0b1) == 1; settings.Shape = (EKCCShape)((combinedSettings >> 3) & 0b11); settings.InputAuthorityBehavior = (EKCCAuthorityBehavior)((combinedSettings >> 5) & 0b11); settings.StateAuthorityBehavior = (EKCCAuthorityBehavior)((combinedSettings >> 7) & 0b11); settings.ProxyInterpolationMode = (EKCCInterpolationMode)((combinedSettings >> 9) & 0b11); settings.ColliderLayer = ((combinedSettings >> 11) & 0b11111); settings.Features = (EKCCFeatures)((combinedSettings >> 16) & 0b11111); settings.CollisionLayerMask = ReadInt(ref ptr); settings.Radius = ReadFloat(ref ptr); settings.Height = ReadFloat(ref ptr); settings.Extent = ReadFloat(ref ptr); ReadInteractions(runner, data, _maxTotalInteractions, ptr); ptr += _interactionsWordCount; } public override void Write(int* ptr) { KCCData data = Context.Data; KCCSettings settings = Context.Settings; NetworkRunner runner = Context.KCC.Runner; Vector3 fullPrecisionPosition = data.TargetPosition; NetworkTRSPData* networkTRSPData = (NetworkTRSPData*)ptr; networkTRSPData->Parent = NetworkBehaviourId.None; networkTRSPData->Position = fullPrecisionPosition; ptr += NetworkTRSPData.WORDS; Vector3 positionExtension = default; if (settings.CompressNetworkPosition == false) { Vector3 networkBufferPosition; networkBufferPosition.x = FloatUtils.Decompress(FloatUtils.Compress(fullPrecisionPosition.x, TRSP_POSITION_ACCURACY), TRSP_POSITION_ACCURACY); networkBufferPosition.y = FloatUtils.Decompress(FloatUtils.Compress(fullPrecisionPosition.y, TRSP_POSITION_ACCURACY), TRSP_POSITION_ACCURACY); networkBufferPosition.z = FloatUtils.Decompress(FloatUtils.Compress(fullPrecisionPosition.z, TRSP_POSITION_ACCURACY), TRSP_POSITION_ACCURACY); positionExtension = fullPrecisionPosition - networkBufferPosition; } WriteVector3(positionExtension, ref ptr); WriteFloat(data.LookPitch, ref ptr); WriteFloat(data.LookYaw, ref ptr); int combinedData = 0; byte currentTeleportCounter = (byte)(((*ptr) >> 0) & 0b11111111); byte currentJumpCounter = (byte)(((*ptr) >> 8) & 0b11111111); if (data.HasTeleported == true) { ++currentTeleportCounter; } if (data.HasJumped == true) { ++currentJumpCounter; } combinedData |= (currentTeleportCounter & 0b11111111) << 0; combinedData |= (currentJumpCounter & 0b11111111) << 8; if (data.IsActive != default) { combinedData |= 1 << 16; } if (data.IsGrounded != default) { combinedData |= 1 << 17; } if (data.WasGrounded != default) { combinedData |= 1 << 18; } if (data.IsSteppingUp != default) { combinedData |= 1 << 19; } if (data.WasSteppingUp != default) { combinedData |= 1 << 20; } if (data.IsSnappingToGround != default) { combinedData |= 1 << 21; } if (data.WasSnappingToGround != default) { combinedData |= 1 << 22; } if (data.HasTeleported != default) { combinedData |= 1 << 23; } if (data.JumpFrames != default) { combinedData |= 1 << 24; } WriteInt(combinedData, ref ptr); int combinedSettings = 0; if (settings.IsTrigger != default) { combinedSettings |= 1 << 0; } if (settings.ForcePredictedLookRotation != default) { combinedSettings |= 1 << 1; } if (settings.AllowClientTeleports != default) { combinedSettings |= 1 << 2; } combinedSettings |= ((int)settings.Shape & 0b11) << 3; combinedSettings |= ((int)settings.InputAuthorityBehavior & 0b11) << 5; combinedSettings |= ((int)settings.StateAuthorityBehavior & 0b11) << 7; combinedSettings |= ((int)settings.ProxyInterpolationMode & 0b11) << 9; combinedSettings |= (settings.ColliderLayer & 0b11111) << 11; combinedSettings |= ((int)settings.Features & 0b11111) << 16; WriteInt(combinedSettings, ref ptr); WriteInt(settings.CollisionLayerMask, ref ptr); WriteFloat(settings.Radius, ref ptr); WriteFloat(settings.Height, ref ptr); WriteFloat(settings.Extent, ref ptr); WriteInteractions(runner, data, _maxTotalInteractions, ptr); ptr += _interactionsWordCount; } public override void Interpolate(KCCInterpolationInfo interpolationInfo) { KCCData data = Context.Data; KCCSettings settings = Context.Settings; NetworkRunner runner = Context.KCC.Runner; Vector3 fromTargetPosition = interpolationInfo.FromBuffer.ReinterpretState().Position; Vector3 toTargetPosition = interpolationInfo.ToBuffer.ReinterpretState().Position; interpolationInfo.Offset += NetworkTRSPData.WORDS; ReadVector3s(ref interpolationInfo, out Vector3 fromPositionExtension, out Vector3 toPositionExtension); fromTargetPosition += fromPositionExtension; toTargetPosition += toPositionExtension; ReadFloats(ref interpolationInfo, out float fromLookPitch, out float toLookPitch); ReadFloats(ref interpolationInfo, out float fromLookYaw, out float toLookYaw); ReadInts(ref interpolationInfo, out int fromCombinedData, out int toCombinedData); byte fromTeleportCounter = (byte)((fromCombinedData >> 0) & 0b11111111); byte toTeleportCounter = (byte)((toCombinedData >> 0) & 0b11111111); data.RealVelocity = Vector3.zero; data.RealSpeed = 0.0f; if (toTeleportCounter != fromTeleportCounter) { data.BasePosition = toTargetPosition; data.DesiredPosition = toTargetPosition; data.TargetPosition = toTargetPosition; data.LookPitch = toLookPitch; data.LookYaw = toLookYaw; } else { data.BasePosition = fromTargetPosition; data.DesiredPosition = toTargetPosition; data.TargetPosition = Vector3.Lerp(fromTargetPosition, toTargetPosition, interpolationInfo.Alpha); data.LookPitch = Mathf.Lerp(fromLookPitch, toLookPitch, interpolationInfo.Alpha); data.LookYaw = KCCUtility.InterpolateRange(fromLookYaw, toLookYaw, -180.0f, 180.0f, interpolationInfo.Alpha); int ticks = interpolationInfo.ToBuffer.Tick - interpolationInfo.FromBuffer.Tick; if (ticks > 0) { data.RealVelocity = (toTargetPosition - fromTargetPosition) / (runner.DeltaTime * ticks); data.RealSpeed = data.RealVelocity.magnitude; } } data.HasTeleported = false; if (Context.LastInterpolationTeleportCounter != toTeleportCounter) { if (Context.LastInterpolationTeleportCounter >= 0) { data.HasTeleported = true; } Context.LastInterpolationTeleportCounter = toTeleportCounter; } byte fromJumpCounter = (byte)((fromCombinedData >> 8) & 0b11111111); byte toJumpCounter = (byte)((toCombinedData >> 8) & 0b11111111); data.JumpFrames = 0; if (Context.LastInterpolationJumpCounter != toJumpCounter) { if (Context.LastInterpolationJumpCounter >= 0) { data.JumpFrames = 1; } Context.LastInterpolationJumpCounter = toJumpCounter; } // Following properties are not interpolated, they are set from Read() method. // Combined Settings // KCCSettings.CollisionLayerMask // KCCSettings.Radius // KCCSettings.Height // KCCSettings.Extent interpolationInfo.Offset += 5; interpolationInfo.Offset += _interactionsWordCount; } // PRIVATE METHODS private static void ReadInteractions(NetworkRunner runner, KCCData data, int maxTotalInteractions, int* ptr) { if (maxTotalInteractions <= 0) return; int interactionCount = *ptr; int* interactionPtr = ptr + 1; int collisionCount = (interactionCount >> (INTERACTIONS_BITS_SHIFT * 0)) & INTERACTIONS_MASK_SINGLE; int modifierCount = (interactionCount >> (INTERACTIONS_BITS_SHIFT * 1)) & INTERACTIONS_MASK_SINGLE; int ignoreCount = (interactionCount >> (INTERACTIONS_BITS_SHIFT * 2)) & INTERACTIONS_MASK_SINGLE; data.Collisions.Clear(); for (int i = 0; i < collisionCount; ++i) { KCCNetworkID networkID = KCCNetworkUtility.ReadNetworkID(interactionPtr); interactionPtr += KCCNetworkID.WORD_COUNT; if (networkID.IsValid == true) { data.Collisions.Add(KCCNetworkID.GetNetworkObject(runner, networkID), networkID); } } data.Modifiers.Clear(); for (int i = 0; i < modifierCount; ++i) { KCCNetworkID networkID = KCCNetworkUtility.ReadNetworkID(interactionPtr); interactionPtr += KCCNetworkID.WORD_COUNT; if (networkID.IsValid == true) { data.Modifiers.Add(KCCNetworkID.GetNetworkObject(runner, networkID), networkID); } } data.Ignores.Clear(); for (int i = 0; i < ignoreCount; ++i) { KCCNetworkID networkID = KCCNetworkUtility.ReadNetworkID(interactionPtr); interactionPtr += KCCNetworkID.WORD_COUNT; if (networkID.IsValid == true) { data.Ignores.Add(KCCNetworkID.GetNetworkObject(runner, networkID), networkID); } } } private static void WriteInteractions(NetworkRunner runner, KCCData data, int maxTotalInteractions, int* ptr) { if (maxTotalInteractions <= 0) return; int* interactionPtr = ptr + 1; int interactionCount = 0; int collisionCount = 0; int modifierCount = 0; int ignoreCount = 0; if (interactionCount < maxTotalInteractions) { List collisions = data.Collisions.All; for (int i = 0, count = collisions.Count; i < count; ++i) { KCCCollision collision = collisions[i]; if (collision.NetworkID.IsValid == false) continue; KCCNetworkUtility.WriteNetworkID(interactionPtr, collision.NetworkID); interactionPtr += KCCNetworkID.WORD_COUNT; ++interactionCount; ++collisionCount; if (collisionCount >= MAX_INTERACTIONS_SINGLE || interactionCount >= maxTotalInteractions) break; } } if (interactionCount < maxTotalInteractions) { List modifiers = data.Modifiers.All; for (int i = 0, count = modifiers.Count; i < count; ++i) { KCCModifier modifier = modifiers[i]; if (modifier.NetworkID.IsValid == false) continue; KCCNetworkUtility.WriteNetworkID(interactionPtr, modifier.NetworkID); interactionPtr += KCCNetworkID.WORD_COUNT; ++interactionCount; ++modifierCount; if (modifierCount >= MAX_INTERACTIONS_SINGLE || interactionCount >= maxTotalInteractions) break; } } if (interactionCount < maxTotalInteractions) { List ignores = data.Ignores.All; for (int i = 0, count = ignores.Count; i < count; ++i) { KCCIgnore ignore = ignores[i]; if (ignore.NetworkID.IsValid == false) continue; KCCNetworkUtility.WriteNetworkID(interactionPtr, ignore.NetworkID); interactionPtr += KCCNetworkID.WORD_COUNT; ++interactionCount; ++ignoreCount; if (ignoreCount >= MAX_INTERACTIONS_SINGLE || interactionCount >= maxTotalInteractions) break; } } interactionCount = default; interactionCount |= collisionCount << (INTERACTIONS_BITS_SHIFT * 0); interactionCount |= modifierCount << (INTERACTIONS_BITS_SHIFT * 1); interactionCount |= ignoreCount << (INTERACTIONS_BITS_SHIFT * 2); *ptr = interactionCount; } private static void GetInteractionsWordCount(KCCNetworkContext context, out int maxInteractions, out int interactionsWordCount) { if (context.Settings.NetworkedInteractions > 0) { maxInteractions = context.Settings.NetworkedInteractions; interactionsWordCount = 1 + KCCNetworkID.WORD_COUNT * maxInteractions; } else { maxInteractions = 0; interactionsWordCount = 0; } } private static int GetTotalWordCount(KCCNetworkContext context) { int wordCount = NetworkTRSPData.WORDS + PROPERTIES_WORD_COUNT; GetInteractionsWordCount(context, out int maxInteractions, out int interactionsWordCount); wordCount += interactionsWordCount; return wordCount; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int ReadInt(ref int* ptr) { int value = *ptr; ++ptr; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int ReadInt(ref int* ptrFrom, ref int* ptrTo, float alpha) { int value = alpha < 0.5f ? *ptrFrom : *ptrTo; ++ptrFrom; ++ptrTo; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void WriteInt(int value, ref int* ptr) { *ptr = value; ++ptr; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float ReadFloat(ref int* ptr) { float value = *(float*)ptr; ++ptr; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float ReadFloat(ref int* ptrFrom, ref int* ptrTo, float alpha) { float value = alpha < 0.5f ? *(float*)ptrFrom : *(float*)ptrTo; ++ptrFrom; ++ptrTo; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void WriteFloat(float value, ref int* ptr) { *(float*)ptr = value; ++ptr; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool CompareAndWriteFloat(float value, ref int* ptr) { bool result = true; if (*(float*)ptr != value) { *(float*)ptr = value; result = false; } ++ptr; return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector3 ReadVector3(ref int* ptr) { Vector3 value; value.x = *(float*)ptr; ++ptr; value.y = *(float*)ptr; ++ptr; value.z = *(float*)ptr; ++ptr; return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void WriteVector3(Vector3 value, ref int* ptr) { *(float*)ptr = value.x; ++ptr; *(float*)ptr = value.y; ++ptr; *(float*)ptr = value.z; ++ptr; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool CompareAndWriteVector3(Vector3 value, ref int* ptr) { bool result = true; if (*(float*)ptr != value.x) { *(float*)ptr = value.x; result = false; } ++ptr; if (*(float*)ptr != value.y) { *(float*)ptr = value.y; result = false; } ++ptr; if (*(float*)ptr != value.z) { *(float*)ptr = value.z; result = false; } ++ptr; return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int InterpolateInt(ref KCCInterpolationInfo interpolationInfo) { int fromValue = interpolationInfo.FromBuffer.ReinterpretState(interpolationInfo.Offset); int toValue = interpolationInfo.ToBuffer.ReinterpretState(interpolationInfo.Offset); ++interpolationInfo.Offset; return interpolationInfo.Alpha < 0.5f ? fromValue : toValue; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float InterpolateFloat(ref KCCInterpolationInfo interpolationInfo) { float fromValue = interpolationInfo.FromBuffer.ReinterpretState(interpolationInfo.Offset); float toValue = interpolationInfo.ToBuffer.ReinterpretState(interpolationInfo.Offset); ++interpolationInfo.Offset; return interpolationInfo.Alpha < 0.5f ? fromValue : toValue; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ReadInts(ref KCCInterpolationInfo interpolationInfo, out int fromValue, out int toValue) { fromValue = interpolationInfo.FromBuffer.ReinterpretState(interpolationInfo.Offset); toValue = interpolationInfo.ToBuffer.ReinterpretState(interpolationInfo.Offset); ++interpolationInfo.Offset; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ReadFloats(ref KCCInterpolationInfo interpolationInfo, out float fromValue, out float toValue) { fromValue = interpolationInfo.FromBuffer.ReinterpretState(interpolationInfo.Offset); toValue = interpolationInfo.ToBuffer.ReinterpretState(interpolationInfo.Offset); ++interpolationInfo.Offset; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ReadVector3s(ref KCCInterpolationInfo interpolationInfo, out Vector3 fromValue, out Vector3 toValue) { int offset = interpolationInfo.Offset; fromValue.x = interpolationInfo.FromBuffer.ReinterpretState(offset + 0); fromValue.y = interpolationInfo.FromBuffer.ReinterpretState(offset + 1); fromValue.z = interpolationInfo.FromBuffer.ReinterpretState(offset + 2); toValue.x = interpolationInfo.ToBuffer.ReinterpretState(offset + 0); toValue.y = interpolationInfo.ToBuffer.ReinterpretState(offset + 1); toValue.z = interpolationInfo.ToBuffer.ReinterpretState(offset + 2); interpolationInfo.Offset += 3; } } }