using FishNet.Utility.Extension; using GameKit.Utilities; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; namespace FishNet.Object.Prediction { internal class SetInterpolationSmoother { #if PREDICTION_V2 #region Private. /// /// How quickly to move towards goal values. /// private MoveRates _moveRates; /// /// Local values of the graphical object when this was initialized. /// private TransformProperties _graphicalInitializedLocalValues; /// /// Values of the graphical object during PreTick or PreReplay. /// private TransformProperties _graphicalPreSimulateWorldValues; /// /// SmoothingData to use. /// private SetInterpolationSmootherData _smoothingData; #endregion /// /// Initializes this smoother; should only be completed once. /// /// internal void InitializeOnce(SetInterpolationSmootherData data) { _smoothingData = data; SetGraphicalObject(data.GraphicalObject); _moveRates = new MoveRates(MoveRates.UNSET_VALUE); _graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties(); } /// /// Sets GraphicalObject; can be changed at runtime. /// /// internal void SetGraphicalObject(Transform value) { _smoothingData.GraphicalObject = value; _graphicalInitializedLocalValues.Update(value.localPosition, value.localRotation, value.localScale); } /// /// Sets the interpolation value to use when the owner of this object. /// /// New interpolation value. internal void SetInterpolation(byte value) { if (value < 1) value = 1; _smoothingData.Interpolation = value; } /// /// Called every frame. /// internal void Update() { if (CanSmooth()) MoveToTarget(); } /// /// Called when the TimeManager invokes OnPreTick. /// internal void OnPreTick() { if (CanSmooth()) { /* Only snap to destination if interpolation is 1. * This ensures the graphics will be at the proper location * before the next movement rates are calculated. */ if (_smoothingData.Interpolation == 1) ResetGraphicalToInitializedLocalOffsets(true, true); _graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties(); } } /// /// Called when TimeManager invokes OnPostTick. /// internal void OnPostTick() { if (CanSmooth()) { _smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPreSimulateWorldValues.Position, _graphicalPreSimulateWorldValues.Rotation); SetGraphicalMoveRates(); } } /// /// Returns if prediction can be used on this rigidbody. /// /// private bool CanSmooth() { NetworkObject nob = _smoothingData.NetworkObject; if (nob == null) return false; if (nob.IsServerOnly) return false; if (!nob.IsHost && nob.SpectatorAdaptiveInterpolation && !nob.IsOwner) return false; return true; } /// /// Moves transform to target values. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void MoveToTarget() { //No rates are set. if (!_moveRates.AnySet) return; Vector3 posGoal = GetGraphicalGoalPosition(); Quaternion rotGoal = GetGraphicalGoalRotation(); /* Only try to update properties if they have a valid move rate. * Properties may have 0f move rate if they did not change. */ Transform t = _smoothingData.GraphicalObject; float delta = Time.deltaTime; //Position. if (_smoothingData.SmoothPosition) { if (_moveRates.InstantPosition) ResetGraphicalToInitializedLocalOffsets(true, false); else if (_moveRates.PositionSet) t.localPosition = Vector3.MoveTowards(t.localPosition, posGoal, _moveRates.Position * delta); } //Rotation. if (_smoothingData.SmoothRotation) { if (_moveRates.InstantRotation) ResetGraphicalToInitializedLocalOffsets(false, true); else if (_moveRates.RotationSet) t.localRotation = Quaternion.RotateTowards(t.localRotation, rotGoal, _moveRates.Rotation * delta); } if (GraphicalObjectMatchesLocalValues(posGoal, rotGoal)) _moveRates.Update(MoveRates.UNSET_VALUE); } /// /// Returns if this transform matches arguments. /// /// private bool GraphicalObjectMatchesLocalValues(Vector3 position, Quaternion rotation) { bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.localPosition == position)); bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.localRotation == rotation)); return (positionMatches && rotationMatches); } /// /// Sets Position and Rotation move rates to reach Target datas. /// private void SetGraphicalMoveRates() { uint interval = _smoothingData.Interpolation; float delta = (float)_smoothingData.NetworkObject?.TimeManager.TickDelta; float rate; float distance; Transform t = _smoothingData.GraphicalObject; /* Position. */ rate = t.localPosition.GetRate(_graphicalInitializedLocalValues.Position, delta, out distance, interval); //If qualifies for teleporting. if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold) { _moveRates.Update(MoveRates.INSTANT_VALUE); } //Smoothing. else { float positionRate = rate.SetIfUnderTolerance(0.0001f, MoveRates.INSTANT_VALUE); rate = t.localRotation.GetRate(_graphicalInitializedLocalValues.Rotation, delta, out _, interval); float rotationRate = rate.SetIfUnderTolerance(0.2f, MoveRates.INSTANT_VALUE); _moveRates.Update(positionRate, rotationRate, MoveRates.UNSET_VALUE); } } /// /// Gets a goal position for the graphical object. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private Vector3 GetGraphicalGoalPosition() { if (_smoothingData.SmoothPosition) return _graphicalInitializedLocalValues.Position; else return _smoothingData.GraphicalObject.position; } /// /// Gets a goal rotation for the graphical object. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private Quaternion GetGraphicalGoalRotation() { if (_smoothingData.SmoothRotation) return _graphicalInitializedLocalValues.Rotation; else return _smoothingData.GraphicalObject.rotation; } /// /// Resets the graphical object to it's transform offsets during instantiation. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ResetGraphicalToInitializedLocalOffsets(bool position, bool rotation) { Transform graphical = _smoothingData.GraphicalObject; if (position) graphical.localPosition = GetGraphicalGoalPosition(); if (rotation) graphical.localRotation = GetGraphicalGoalRotation(); } #endif } }