589 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			589 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | namespace TPSBR | ||
|  | { | ||
|  | 	using System; | ||
|  | 	using System.Collections.Generic; | ||
|  | 	using Unity.Profiling; | ||
|  | 	using UnityEngine; | ||
|  | 	using Fusion; | ||
|  | 	using Fusion.Addons.KCC; | ||
|  | 
 | ||
|  | 	// !!! WARNING !!! | ||
|  | 	// This processor is a 1:1 copy of default PlatformProcessor except it supports only single platform to reduce network state size to a minimum. | ||
|  | 	// !!! WARNING !!! | ||
|  | 
 | ||
|  | 	/// <summary> | ||
|  | 	/// This processor tracks overlapping platforms (KCC processors implementing <c>IPlatform</c>) and propagates their position and rotation changes to <c>KCC</c>. | ||
|  | 	/// Make sure the script that moves with the <c>IPlatform</c> object has lower execution order => it must be executed before <c>PlatformProcessor</c> and <c>PlatformProcessorUpdater</c>. | ||
|  | 	/// When <c>PlatformProcessor</c> propagates all platform changes, it notifies <c>IPlatformListener</c> processors with absolute transform deltas. | ||
|  | 	/// </summary> | ||
|  | 	[DefaultExecutionOrder(BRPlatformProcessor.EXECUTION_ORDER)] | ||
|  | 	[RequireComponent(typeof(NetworkObject))] | ||
|  |     public unsafe sealed class BRPlatformProcessor : NetworkKCCProcessor, IKCCProcessor, IBeginMove, IEndMove | ||
|  |     { | ||
|  | 		// CONSTANTS | ||
|  | 
 | ||
|  | 		public const int EXECUTION_ORDER = -400; | ||
|  | 		public const int MAX_PLATFORMS   = 1; | ||
|  | 
 | ||
|  | 		// PRIVATE MEMBERS | ||
|  | 
 | ||
|  | 		[SerializeField][Tooltip("How long it takes to move the KCC from world space to platform space.")] | ||
|  | 		private float _platformSpaceTransitionDuration = 0.75f; | ||
|  | 		[SerializeField][Tooltip("How long it takes to move the KCC from platform space to world space.")] | ||
|  | 		private float _worldSpaceTransitionDuration = 0.5f; | ||
|  | 
 | ||
|  | 		[Networked] | ||
|  | 		private ref ProcessorState _state => ref MakeRef<ProcessorState>(); | ||
|  | 
 | ||
|  | 		private KCC        _kcc; | ||
|  | 		private Platform[] _renderPlatforms = new Platform[MAX_PLATFORMS]; | ||
|  | 
 | ||
|  | 		private static List<IPlatform> _cachedPlatforms   = new List<IPlatform>(); | ||
|  | 		private static List<NetworkId> _cachedNetworkIds1 = new List<NetworkId>(); | ||
|  | 		private static List<NetworkId> _cachedNetworkIds2 = new List<NetworkId>(); | ||
|  | 		private static ProfilerMarker  _fixedUpdateMarker = new ProfilerMarker(nameof(BRPlatformProcessor)); | ||
|  | 
 | ||
|  | 		// PUBLIC METHODS | ||
|  | 
 | ||
|  | 		/// <summary> | ||
|  | 		/// Returns <c>true</c> if there is at least one platorm tracked. | ||
|  | 		/// </summary> | ||
|  | 		public bool IsActive() | ||
|  | 		{ | ||
|  | 			return _state.IsActive; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		/// <summary> | ||
|  | 		/// Called by <c>PlatformProcessorUpdater</c>. Do not use from user code. | ||
|  | 		/// </summary> | ||
|  | 		public void ProcessFixedUpdate() | ||
|  | 		{ | ||
|  | 			if (ReferenceEquals(_kcc, null) == true) | ||
|  | 				return; | ||
|  | 
 | ||
|  | 			_fixedUpdateMarker.Begin(); | ||
|  | 
 | ||
|  | 			if (Object.IsInSimulation != _kcc.Object.IsInSimulation) | ||
|  | 			{ | ||
|  | 				// Synchronize simulation state of the processor with KCC. | ||
|  | 				Runner.SetIsSimulated(Object, _kcc.Object.IsInSimulation); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if (Object.IsInSimulation == true) | ||
|  | 			{ | ||
|  | 				// Update state of platforms, track new, cleanup old. | ||
|  | 				UpdatePlatforms(_kcc); | ||
|  | 
 | ||
|  | 				if (_state.IsActive == true) | ||
|  | 				{ | ||
|  | 					// For predicted KCC, propagate position and rotation deltas of all platforms since last fixed update. | ||
|  | 					PropagateMovement(_kcc, _kcc.FixedData, true); | ||
|  | 
 | ||
|  | 					// Copy fixed state to render state as a base. | ||
|  | 					SynchronizeRenderPlatforms(); | ||
|  | 				} | ||
|  | 			} | ||
|  | 			else | ||
|  | 			{ | ||
|  | 				// Otherwise snap the KCC to tracked platforms based on interpolated offsets. | ||
|  | 				// Notice we modify only position, this is essential to get correct results from KCC physics queries. Rotation keeps unchanged. | ||
|  | 				if (TrySetInterpolatedPosition(_kcc, _kcc.FixedData) == true) | ||
|  | 				{ | ||
|  | 					_kcc.SynchronizeTransform(true, false, false); | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			_fixedUpdateMarker.End(); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		/// <summary> | ||
|  | 		/// Called by <c>PlatformProcessorUpdater</c>. Do not use from user code. | ||
|  | 		/// </summary> | ||
|  | 		public void ProcessRender() | ||
|  | 		{ | ||
|  | 			if (ReferenceEquals(_kcc, null) == true) | ||
|  | 				return; | ||
|  | 
 | ||
|  | 			if (_kcc.IsPredictingInRenderUpdate == true) | ||
|  | 			{ | ||
|  | 				if (_state.IsActive == true) | ||
|  | 				{ | ||
|  | 					// For render-predicted KCC, propagate position and rotation deltas of all platforms since last fixed or render update. | ||
|  | 					PropagateMovement(_kcc, _kcc.RenderData, false); | ||
|  | 				} | ||
|  | 			} | ||
|  | 			else | ||
|  | 			{ | ||
|  | 				// Otherwise snap the KCC to tracked platforms based on interpolated offsets. | ||
|  | 				// Notice we modify only position, this is essential to get correct results from KCC physics queries. Rotation keeps unchanged. | ||
|  | 				if (TrySetInterpolatedPosition(_kcc, _kcc.RenderData) == true) | ||
|  | 				{ | ||
|  | 					_kcc.SynchronizeTransform(true, false, false); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// NetworkBehaviour INTERFACE | ||
|  | 
 | ||
|  | 		public override sealed void Spawned() | ||
|  | 		{ | ||
|  | 			Runner.GetSingleton<BRPlatformProcessorUpdater>().Register(this); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public override sealed void Despawned(NetworkRunner runner, bool hasState) | ||
|  | 		{ | ||
|  | 			runner.GetSingleton<BRPlatformProcessorUpdater>().Unregister(this); | ||
|  | 
 | ||
|  | 			_kcc = null; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// NetworkKCCProcessor INTERFACE | ||
|  | 
 | ||
|  | 		public override float GetPriority(KCC kcc) => float.MinValue; | ||
|  | 
 | ||
|  | 		public override void OnEnter(KCC kcc, KCCData data) | ||
|  | 		{ | ||
|  | 			_kcc = kcc; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public override void OnExit(KCC kcc, KCCData data) | ||
|  | 		{ | ||
|  | 			_kcc = null; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public override void OnInterpolate(KCC kcc, KCCData data) | ||
|  | 		{ | ||
|  | 			// This code path can be executed for: | ||
|  | 			// 1. Proxy interpolated in fixed update. | ||
|  | 			// 2. Proxy interpolated in render update. | ||
|  | 			// 3. Input/State authority interpolated in render update. | ||
|  | 
 | ||
|  | 			// For KCC proxy, KCCData.TargetPosition equals to snapshot interpolated position at this point. | ||
|  | 			// However platforms are predicted everywhere - on all server and clients. | ||
|  | 			// If a platform is predicted and KCC proxy interpolated, it results in KCC visual being delayed behind the platform visual. | ||
|  | 
 | ||
|  | 			// Following code recalculates KCC position by snapping it to predicted platform space, matching position of the platform visual. | ||
|  | 			// [KCC position] = [local IPlatform position] + [interpolated IPlatform => KCC offset]. | ||
|  | 			TrySetInterpolatedPosition(kcc, data); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// IKCCProcessor INTERFACE | ||
|  | 
 | ||
|  | 		bool IKCCProcessor.IsActive(KCC kcc) => _state.IsActive; | ||
|  | 
 | ||
|  | 		// IBeginMove INTERFACE | ||
|  | 
 | ||
|  | 		float IKCCStage<BeginMove>.GetPriority(KCC kcc) => float.MaxValue; | ||
|  | 
 | ||
|  | 		void IKCCStage<BeginMove>.Execute(BeginMove stage, KCC kcc, KCCData data) | ||
|  | 		{ | ||
|  | 			// Disable prediction correction and anti-jitter if there is at least one platform tracked. | ||
|  | 			// This must be called in both fixed and render update. | ||
|  | 			kcc.SuppressFeature(EKCCFeature.PredictionCorrection); | ||
|  | 			kcc.SuppressFeature(EKCCFeature.AntiJitter); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// IEndMove INTERFACE | ||
|  | 
 | ||
|  | 		float IKCCStage<EndMove>.GetPriority(KCC kcc) => float.MinValue; | ||
|  | 
 | ||
|  | 		void IKCCStage<EndMove>.Execute(EndMove stage, KCC kcc, KCCData data) | ||
|  | 		{ | ||
|  | 			bool isInFixedUpdate = kcc.IsInFixedUpdate; | ||
|  | 
 | ||
|  | 			// Update Platform => KCC offset after KCC moves. | ||
|  | 			for (int i = 0, count = _state.Platforms.Length; i < count; ++i) | ||
|  | 			{ | ||
|  | 				Platform platform = GetPlatform(i, isInFixedUpdate); | ||
|  | 				if (platform.State != EPlatformState.None) | ||
|  | 				{ | ||
|  | 					platform.KCCOffset = Quaternion.Inverse(platform.Rotation) * (data.TargetPosition - platform.Position); | ||
|  | 					SetPlatform(i, platform, isInFixedUpdate); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// PRIVATE METHODS | ||
|  | 
 | ||
|  | 		private void UpdatePlatforms(KCC kcc) | ||
|  | 		{ | ||
|  | 			// 1. Get all platform objects tracked by KCC. | ||
|  | 			kcc.GetProcessors<IPlatform>(_cachedPlatforms); | ||
|  | 
 | ||
|  | 			// Early exit - performance optimziation. | ||
|  | 			if (_cachedPlatforms.Count <= 0 && _state.IsActive == false) | ||
|  | 				return; | ||
|  | 
 | ||
|  | 			_cachedNetworkIds1.Clear(); // Used to store platforms tracked by KCC. | ||
|  | 			_cachedNetworkIds2.Clear(); // Used to store platforms tracked by PlatformProcessor. | ||
|  | 
 | ||
|  | 			foreach (IPlatform platform in _cachedPlatforms) | ||
|  | 			{ | ||
|  | 				_cachedNetworkIds1.Add(platform.Object.Id); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// 2. Mark all platforms in PlatformProcessor state as inactive if they are not tracked by KCC. | ||
|  | 			for (int i = 0, count = _state.Platforms.Length; i < count; ++i) | ||
|  | 			{ | ||
|  | 				Platform platform = _state.Platforms.Get(i); | ||
|  | 				if (platform.State == EPlatformState.Active && _cachedNetworkIds1.Contains(platform.Id) == false) | ||
|  | 				{ | ||
|  | 					platform.State = EPlatformState.Inactive; | ||
|  | 
 | ||
|  | 					_state.Platforms.Set(i, platform); | ||
|  | 				} | ||
|  | 
 | ||
|  | 				if (platform.Id.IsValid == true) | ||
|  | 				{ | ||
|  | 					_cachedNetworkIds2.Add(platform.Id); | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// 3. Register all platforms tracked by KCC that are not tracked by PlatformProcessor. | ||
|  | 			foreach (IPlatform trackedPlatform in _cachedPlatforms) | ||
|  | 			{ | ||
|  | 				NetworkObject platformObject = trackedPlatform.Object; | ||
|  | 				if (_cachedNetworkIds2.Contains(platformObject.Id) == false) | ||
|  | 				{ | ||
|  | 					// The platform is not yet tracked by PlatformProcessor. Let's try adding it. | ||
|  | 					for (int i = 0, count = _state.Platforms.Length; i < count; ++i) | ||
|  | 					{ | ||
|  | 						if (_state.Platforms.Get(i).State == EPlatformState.None) | ||
|  | 						{ | ||
|  | 							_cachedNetworkIds2.Add(platformObject.Id); | ||
|  | 
 | ||
|  | 							platformObject.transform.GetPositionAndRotation(out Vector3 platformPosition, out Quaternion platformRotation); | ||
|  | 
 | ||
|  | 							Platform platform = new Platform(); | ||
|  | 							platform.Id        = platformObject.Id; | ||
|  | 							platform.State     = EPlatformState.Active; | ||
|  | 							platform.Alpha     = default; | ||
|  | 							platform.Position  = platformPosition; | ||
|  | 							platform.Rotation  = platformRotation; | ||
|  | 							platform.KCCOffset = Quaternion.Inverse(platformRotation) * (kcc.Transform.position - platformPosition); | ||
|  | 
 | ||
|  | 							_state.Platforms.Set(i, platform); | ||
|  | 							break; | ||
|  | 						} | ||
|  | 					} | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			bool isActive = false; | ||
|  | 
 | ||
|  | 			// 4. Update platforms alpha values. | ||
|  | 			// The platform alpha defines how much is the KCC position affected by the platform and is used for smooth transition from from world space to platform space. | ||
|  | 			for (int i = 0, count = _state.Platforms.Length; i < count; ++i) | ||
|  | 			{ | ||
|  | 				Platform platform = _state.Platforms.Get(i); | ||
|  | 				if (platform.State == EPlatformState.Active) | ||
|  | 				{ | ||
|  | 					isActive = true; | ||
|  | 
 | ||
|  | 					if (platform.Alpha < 1.0f) | ||
|  | 					{ | ||
|  | 						// The KCC stands within the platform, increasing alpha to 1.0f. | ||
|  | 						platform.Alpha = _platformSpaceTransitionDuration > 0.001f ? Mathf.Min(platform.Alpha + Runner.DeltaTime / _platformSpaceTransitionDuration, 1.0f) : 1.0f; | ||
|  | 						_state.Platforms.Set(i, platform); | ||
|  | 					} | ||
|  | 				} | ||
|  | 				else if (platform.State == EPlatformState.Inactive) | ||
|  | 				{ | ||
|  | 					// The KCC left the the platform, decreasing alpha to 0.0f. | ||
|  | 					platform.Alpha -= _worldSpaceTransitionDuration > 0.001f ? (Runner.DeltaTime / _worldSpaceTransitionDuration) : 1.0f; | ||
|  | 
 | ||
|  | 					if (platform.Alpha <= 0.0f) | ||
|  | 					{ | ||
|  | 						// Once the alpha is 0.0f, we can remove the platform entirely. | ||
|  | 						platform = default; | ||
|  | 					} | ||
|  | 					else | ||
|  | 					{ | ||
|  | 						isActive = true; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					_state.Platforms.Set(i, platform); | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			_state.IsActive = isActive; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		private void PropagateMovement(KCC kcc, KCCData data, bool isInFixedUpdate) | ||
|  | 		{ | ||
|  | 			bool       synchronize  = false; | ||
|  | 			Vector3    basePosition = data.TargetPosition; | ||
|  | 			Quaternion baseRotation = data.TransformRotation; | ||
|  | 
 | ||
|  | 			// 1. Iterate over all tracked platforms, calculate their position and rotation deltas and propagate them to the KCC. | ||
|  | 			for (int i = 0, count = _state.Platforms.Length; i < count; ++i) | ||
|  | 			{ | ||
|  | 				Platform platform = GetPlatform(i, isInFixedUpdate); | ||
|  | 				if (platform.State != EPlatformState.Active || platform.Id.IsValid == false) | ||
|  | 					continue; | ||
|  | 
 | ||
|  | 				NetworkObject platformObject = Runner.FindObject(platform.Id); | ||
|  | 				if (platformObject == null || platformObject.TryGetComponent(out IPlatform synchronizePlatform) == false) | ||
|  | 					continue; | ||
|  | 
 | ||
|  | 				platformObject.transform.GetPositionAndRotation(out Vector3 currentPlatformPosition, out Quaternion currentPlatformRotation); | ||
|  | 
 | ||
|  | 				// Calculate platform position and rotation delta since last update. | ||
|  | 				Vector3    platformPositionDelta = currentPlatformPosition - platform.Position; | ||
|  | 				Quaternion platformRotationDelta = Quaternion.Inverse(platform.Rotation) * currentPlatformRotation; | ||
|  | 
 | ||
|  | 				if (platform.State == EPlatformState.Inactive) | ||
|  | 				{ | ||
|  | 					// With decreasing alpha we are also lowering the impact of platform transform changes. | ||
|  | 					platformRotationDelta = Quaternion.Slerp(Quaternion.identity, platformRotationDelta, platform.Alpha); | ||
|  | 				} | ||
|  | 
 | ||
|  | 				// The platform rotated, we have to rotate stored KCC position offset. | ||
|  | 				Vector3 recalculatedKCCOffset = platformRotationDelta * platform.KCCOffset; | ||
|  | 
 | ||
|  | 				// Calculate delta between old and new KCC position offset. This needs to be added to KCC to stay on a platform spot. | ||
|  | 				Vector3 kccOffsetDelta = recalculatedKCCOffset - platform.KCCOffset; | ||
|  | 
 | ||
|  | 				// Final KCC position delta is calculated as sum of platform delta and KCC offset delta. | ||
|  | 				// Notice the KCC offset is in platform local space so it needs to be rotated. | ||
|  | 				Vector3 kccPositionDelta = platformPositionDelta + currentPlatformRotation * kccOffsetDelta; | ||
|  | 
 | ||
|  | 				if (platform.State == EPlatformState.Inactive) | ||
|  | 				{ | ||
|  | 					// With decreasing alpha we are also lowering the impact of platform transform changes. | ||
|  | 					kccPositionDelta = Vector3.Lerp(Vector3.zero, kccPositionDelta, platform.Alpha); | ||
|  | 				} | ||
|  | 
 | ||
|  | 				// Propagate calculated position delta to the KCC. | ||
|  | 				data.BasePosition    += kccPositionDelta; | ||
|  | 				data.DesiredPosition += kccPositionDelta; | ||
|  | 				data.TargetPosition  += kccPositionDelta; | ||
|  | 
 | ||
|  | 				// Propagate rotation delta to the KCC. | ||
|  | 				data.AddLookRotation(0.0f, platformRotationDelta.eulerAngles.y); | ||
|  | 
 | ||
|  | 				// Update platform properties with new values. | ||
|  | 				platform.Position  = currentPlatformPosition; | ||
|  | 				platform.Rotation  = currentPlatformRotation; | ||
|  | 				platform.KCCOffset = recalculatedKCCOffset; | ||
|  | 
 | ||
|  | 				// Update PlatformProcessor state. | ||
|  | 				SetPlatform(i, platform, isInFixedUpdate); | ||
|  | 
 | ||
|  | 				// Set flag to synchronize Transform and Ridigbody components. | ||
|  | 				synchronize = true; | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// 2. Deltas from all platforms are propagated, now we have to recalculate Platform => KCC offsets. | ||
|  | 			for (int i = 0, count = _state.Platforms.Length; i < count; ++i) | ||
|  | 			{ | ||
|  | 				Platform platform = GetPlatform(i, isInFixedUpdate); | ||
|  | 				if (platform.State != EPlatformState.None) | ||
|  | 				{ | ||
|  | 					// Offset needs to be calculated for both Active and Inactive platforms. | ||
|  | 					platform.KCCOffset = Quaternion.Inverse(platform.Rotation) * (data.TargetPosition - platform.Position); | ||
|  | 
 | ||
|  | 					// Update PlatformProcessor state. | ||
|  | 					SetPlatform(i, platform, isInFixedUpdate); | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if (synchronize == true) | ||
|  | 			{ | ||
|  | 				// There is at least one platform tracked, Transform and Rigidbody should be refreshed before any KCC begins predicted move. | ||
|  | 				kcc.SynchronizeTransform(true, true, false); | ||
|  | 
 | ||
|  | 				Vector3    positionDelta = data.TargetPosition - basePosition; | ||
|  | 				Quaternion rotationDelta = Quaternion.Inverse(baseRotation) * data.TransformRotation; | ||
|  | 
 | ||
|  | 				// Notify all listeners. | ||
|  | 				foreach (IPlatformListener listener in kcc.GetProcessors<IPlatformListener>(true)) | ||
|  | 				{ | ||
|  | 					try | ||
|  | 					{ | ||
|  | 						listener.OnTransform(kcc, data, positionDelta, rotationDelta); | ||
|  | 					} | ||
|  | 					catch (Exception exception) | ||
|  | 					{ | ||
|  | 						Debug.LogException(exception); | ||
|  | 					} | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		private bool TrySetInterpolatedPosition(KCC kcc, KCCData data) | ||
|  | 		{ | ||
|  | 			// At this point all platforms (IPlatform) should have updated their transforms. | ||
|  | 			// This method calculates interpolated position of the KCC by taking local platform positions + interpolated Position => KCC offsets. | ||
|  | 			// Calculations below result in smooth transition between world and multiple platform spaces. | ||
|  | 
 | ||
|  | 			bool buffersValid = TryGetSnapshotsBuffers(out NetworkBehaviourBuffer fromBuffer, out NetworkBehaviourBuffer toBuffer, out float alpha); | ||
|  | 			if (buffersValid == false) | ||
|  | 				return false; | ||
|  | 
 | ||
|  | 			bool isInSimulation = Object.IsInSimulation; | ||
|  | 
 | ||
|  | 			Vector3 averagePosition = default; | ||
|  | 			float   averageAlpha    = default; | ||
|  | 
 | ||
|  | 			ProcessorState fromState = fromBuffer.ReinterpretState<ProcessorState>(); | ||
|  | 			ProcessorState toState   = toBuffer.ReinterpretState<ProcessorState>(); | ||
|  | 
 | ||
|  | 			for (int i = 0; i < toState.Platforms.Length; ++i) | ||
|  | 			{ | ||
|  | 				Platform fromPlatform = fromState.Platforms.Get(i); | ||
|  | 				Platform toPlatform   = toState.Platforms.Get(i); | ||
|  | 
 | ||
|  | 				if (fromPlatform.State == EPlatformState.None) | ||
|  | 				{ | ||
|  | 					if (toPlatform.State == EPlatformState.None) | ||
|  | 						continue; | ||
|  | 
 | ||
|  | 					// Only To is valid => the KCC just jumped on the platform. | ||
|  | 
 | ||
|  | 					// Render interpolated KCC with predicted fixed simulation - for perfect snapping we want to interpolate only if the state is Active (KCC stands within the platform trigger). | ||
|  | 					// Otherwise the KCC could penetrate geometry while keeping Inactive state during platform-space => world-space transition, which is undesired. | ||
|  | 					if (isInSimulation == true && toPlatform.State != EPlatformState.Active) | ||
|  | 						continue; | ||
|  | 
 | ||
|  | 					NetworkObject toPlatformObject = Runner.FindObject(toPlatform.Id); | ||
|  | 					if (toPlatformObject == null) | ||
|  | 						continue; | ||
|  | 
 | ||
|  | 					// In following calculations we're interpolating between [world-space interpolated KCC position] and [platform-space interpolated KCC position]. | ||
|  | 
 | ||
|  | 					toPlatformObject.transform.GetPositionAndRotation(out Vector3 platformPosition, out Quaternion platformRotation); | ||
|  | 
 | ||
|  | 					Vector3 kccPosition = data.TargetPosition; | ||
|  | 					Vector3 toPosition  = platformPosition + platformRotation * toPlatform.KCCOffset; | ||
|  | 
 | ||
|  | 					if (kcc.GetInterpolatedNetworkBufferPosition(out Vector3 interpolatedKCCPosition) == true) | ||
|  | 					{ | ||
|  | 						kccPosition = interpolatedKCCPosition; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					averagePosition += Vector3.Lerp(kccPosition, toPosition, alpha) * toPlatform.Alpha; | ||
|  | 					averageAlpha    += toPlatform.Alpha; | ||
|  | 				} | ||
|  | 				else if (toPlatform.State == EPlatformState.None) | ||
|  | 				{ | ||
|  | 					if (fromPlatform.State == EPlatformState.None) | ||
|  | 						continue; | ||
|  | 
 | ||
|  | 					// Only From is valid => the KCC just left the platform. | ||
|  | 
 | ||
|  | 					// Render interpolated KCC with predicted fixed simulation - for perfect snapping we want to interpolate only if the state is Active (KCC stands within the platform trigger). | ||
|  | 					// Otherwise the KCC could penetrate geometry while keeping Inactive state during platform-space => world-space transition, which is undesired. | ||
|  | 					if (isInSimulation == true && fromPlatform.State != EPlatformState.Active) | ||
|  | 						continue; | ||
|  | 
 | ||
|  | 					NetworkObject fromPlatformObject = Runner.FindObject(fromPlatform.Id); | ||
|  | 					if (fromPlatformObject == null) | ||
|  | 						continue; | ||
|  | 
 | ||
|  | 					// In following calculations we're interpolating between [world-space interpolated KCC position] and [platform-space interpolated KCC position]. | ||
|  | 
 | ||
|  | 					fromPlatformObject.transform.GetPositionAndRotation(out Vector3 platformPosition, out Quaternion platformRotation); | ||
|  | 
 | ||
|  | 					Vector3 fromPosition = platformPosition + platformRotation * fromPlatform.KCCOffset; | ||
|  | 					Vector3 kccPosition  = data.TargetPosition; | ||
|  | 
 | ||
|  | 					if (kcc.GetInterpolatedNetworkBufferPosition(out Vector3 interpolatedKCCPosition) == true) | ||
|  | 					{ | ||
|  | 						kccPosition = interpolatedKCCPosition; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					averagePosition += Vector3.Lerp(fromPosition, kccPosition, alpha) * fromPlatform.Alpha; | ||
|  | 					averageAlpha    += fromPlatform.Alpha; | ||
|  | 				} | ||
|  | 				else | ||
|  | 				{ | ||
|  | 					if (toPlatform.Id != fromPlatform.Id) | ||
|  | 						continue; | ||
|  | 
 | ||
|  | 					// From and To are same platform objects. | ||
|  | 
 | ||
|  | 					// Render interpolated KCC with predicted fixed simulation - for perfect snapping we want to interpolate only if the state is Active (KCC stands within the platform trigger). | ||
|  | 					// Otherwise the KCC could penetrate geometry while keeping Inactive state during platform-space => world-space transition, which is undesired. | ||
|  | 					if (isInSimulation == true && (fromPlatform.State != EPlatformState.Active || toPlatform.State != EPlatformState.Active)) | ||
|  | 						continue; | ||
|  | 
 | ||
|  | 					NetworkObject platformObject = Runner.FindObject(toPlatform.Id); | ||
|  | 					if (platformObject == null) | ||
|  | 						continue; | ||
|  | 
 | ||
|  | 					// In following calculations we're interpolating between two platform-space interpolated KCC positions. | ||
|  | 
 | ||
|  | 					float   platformAlpha          = Mathf.Lerp(fromPlatform.Alpha, toPlatform.Alpha, alpha); | ||
|  | 					Vector3 platformRelativeOffset = Vector3.Lerp(fromPlatform.KCCOffset, toPlatform.KCCOffset, alpha); | ||
|  | 
 | ||
|  | 					platformObject.transform.GetPositionAndRotation(out Vector3 platformPosition, out Quaternion platformRotation); | ||
|  | 
 | ||
|  | 					averagePosition += (platformPosition + platformRotation * platformRelativeOffset) * platformAlpha; | ||
|  | 					averageAlpha    += platformAlpha; | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if (averageAlpha < 0.001f) | ||
|  | 				return false; | ||
|  | 
 | ||
|  | 			// Final position equals to weighted average of snap-interpolated KCC positions. | ||
|  | 			data.TargetPosition = averagePosition / averageAlpha; | ||
|  | 			return true; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// HELPER METHODS | ||
|  | 
 | ||
|  | 		private Platform GetPlatform(int index, bool isInFixedUpdate) | ||
|  | 		{ | ||
|  | 			return isInFixedUpdate == true ? _state.Platforms.Get(index) : _renderPlatforms[index]; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		private void SetPlatform(int index, Platform platform, bool isInFixedUpdate) | ||
|  | 		{ | ||
|  | 			if (isInFixedUpdate == true) | ||
|  | 			{ | ||
|  | 				_state.Platforms.Set(index, platform); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			_renderPlatforms[index] = platform; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		private void SynchronizeRenderPlatforms() | ||
|  | 		{ | ||
|  | 			for (int i = 0, count = _state.Platforms.Length; i < count; ++i) | ||
|  | 			{ | ||
|  | 				_renderPlatforms[i] = _state.Platforms.Get(i); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// DATA STRUCTURES | ||
|  | 
 | ||
|  | 		public enum EPlatformState | ||
|  | 		{ | ||
|  | 			None     = 0, | ||
|  | 			Active   = 1, | ||
|  | 			Inactive = 2, | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public struct Platform : INetworkStruct | ||
|  | 		{ | ||
|  | 			public NetworkId      Id; | ||
|  | 			public EPlatformState State; | ||
|  | 			public float          Alpha; | ||
|  | 			public Vector3        Position; | ||
|  | 			public Quaternion     Rotation; | ||
|  | 			public Vector3        KCCOffset; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		public struct ProcessorState : INetworkStruct | ||
|  | 		{ | ||
|  | 			public int Flags; | ||
|  | 			[Networked][Capacity(MAX_PLATFORMS)] | ||
|  | 			public NetworkArray<Platform> Platforms => default; | ||
|  | 
 | ||
|  | 			public bool IsActive { get => (Flags & 1) == 1; set { if (value) { Flags |= 1; } else { Flags &= ~1; } } } | ||
|  | 		} | ||
|  | 	} | ||
|  | } |