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();
}
}
}
}