namespace Zinnia.Tracking.Follow.Modifier.Property.Rotation { using UnityEngine; using Zinnia.Extension; /// /// Updates the angular velocity by rotating towards a given source. /// public class RigidbodyAngularVelocity : DivergablePropertyModifier { #region Velocity Settings [Header("Velocity Settings")] [Tooltip("The maximum squared magnitude of angular velocity that can be applied to the source Transform.")] [SerializeField] private float angularVelocityLimit = float.PositiveInfinity; /// /// The maximum squared magnitude of angular velocity that can be applied to the source . /// public float AngularVelocityLimit { get { return angularVelocityLimit; } set { angularVelocityLimit = value; } } [Tooltip("The maximum difference in distance to the tracked position.")] [SerializeField] private float maxDistanceDelta = 10f; /// /// The maximum difference in distance to the tracked position. /// public float MaxDistanceDelta { get { return maxDistanceDelta; } set { maxDistanceDelta = value; } } #endregion #region Velocity Settings [Header("Calculation Settings")] [Tooltip("Whether to use the optional offset to set the target Rigidbody.centerOfMass;")] [SerializeField] private bool useOffsetAsCentreOfMass; /// /// Whether to use the optional offset to set the target ; /// public bool UseOffsetAsCentreOfMass { get { return useOffsetAsCentreOfMass; } set { useOffsetAsCentreOfMass = value; } } [Tooltip("Whether calculate the rotational angle in degrees;")] [SerializeField] private bool calculateAngleInDegrees = true; /// /// Whether calculate the rotational angle in degrees; /// public bool CalculateAngleInDegrees { get { return calculateAngleInDegrees; } set { calculateAngleInDegrees = value; } } #endregion /// /// A cached version of the target . /// protected Rigidbody cachedTargetRigidbody; /// /// A cached version of the target. /// protected GameObject cachedTarget; /// /// A cached version of the offset. /// protected GameObject cachedOffset; /// /// Sets the on the based on the position. /// If is not set then the will be reset. /// public virtual void SetCentreOfMass() { ResetCentreOfMass(); if (!UseOffsetAsCentreOfMass || cachedTargetRigidbody == null || cachedOffset == null) { return; } cachedTargetRigidbody.centerOfMass = cachedOffset.transform.position; } /// /// Resets the on the . /// public virtual void ResetCentreOfMass() { if (cachedTargetRigidbody == null) { return; } cachedTargetRigidbody.ResetCenterOfMass(); } /// /// Modifies the target angular velocity to rotate towards the given source. /// /// The source to utilize in the modification. /// The target to modify. /// The offset of the target against the source when modifying. protected override void DoModify(GameObject source, GameObject target, GameObject offset = null) { cachedTargetRigidbody = cachedTargetRigidbody == null || target != cachedTarget ? target.TryGetComponent(true) : cachedTargetRigidbody; cachedOffset = offset; if (cachedTarget == null || cachedTarget != target) { SetCentreOfMass(); } cachedTarget = target; Quaternion rotationDelta = source.transform.rotation * Quaternion.Inverse(offset != null ? offset.transform.rotation : target.transform.rotation); rotationDelta.ToAngleAxis(out float angle, out Vector3 axis); float deltaTime = CalculateAngleInDegrees ? 1f : Time.inFixedTimeStep ? Time.fixedDeltaTime : Time.deltaTime; angle = angle.GetSignedDegree() * (CalculateAngleInDegrees ? 1f : Mathf.Deg2Rad) / deltaTime; if (!angle.ApproxEquals(0)) { Vector3 angularTarget = angle * axis; Vector3 calculatedAngularVelocity = Vector3.MoveTowards(cachedTargetRigidbody.angularVelocity, angularTarget, MaxDistanceDelta / deltaTime); if (float.IsPositiveInfinity(AngularVelocityLimit) || calculatedAngularVelocity.sqrMagnitude < AngularVelocityLimit) { cachedTargetRigidbody.angularVelocity = calculatedAngularVelocity; } } base.DoModify(source, target, offset); } /// /// Gets the source and target Euler rotations to check divergence against. /// /// The source to check against. /// The target to check with. /// Any offset applied to the target. /// The source position. /// The target position. protected override void GetCheckPoints(GameObject source, GameObject target, GameObject offset, out Vector3 a, out Vector3 b) { a = source.transform.SignedEulerAngles(); b = target.transform.SignedEulerAngles(); if (offset != null) { a = (source.transform.rotation * Quaternion.Inverse(offset.transform.localRotation)).eulerAngles.UnsignedEulerToSignedEuler(); } } } }