namespace Zinnia.Tracking.Follow.Modifier.Property.Rotation
{
using UnityEngine;
using Zinnia.Data.Type;
using Zinnia.Extension;
///
/// Updates the transform rotation of the target to match the difference in position of the source.
///
public class TransformPositionDifferenceRotation : PropertyModifier
{
[Tooltip("The maximum distance the Source GameObject can be from the Offset GameObject to allow the rotation to apply.")]
[SerializeField]
private Vector3 sourceToOffsetMaximumDistance = Vector3.one * float.PositiveInfinity;
///
/// The maximum distance the Source can be from the Offset to allow the rotation to apply.
///
public Vector3 SourceToOffsetMaximumDistance
{
get
{
return sourceToOffsetMaximumDistance;
}
set
{
sourceToOffsetMaximumDistance = value;
}
}
[Tooltip("The drag applied to the rotation to slow it down.")]
[SerializeField]
private float angularDrag = 1f;
///
/// The drag applied to the rotation to slow it down.
///
public float AngularDrag
{
get
{
return angularDrag;
}
set
{
angularDrag = value;
}
}
[Tooltip("Determines which axes to rotate.")]
[SerializeField]
private Vector3State followOnAxis = Vector3State.True;
///
/// Determines which axes to rotate.
///
public Vector3State FollowOnAxis
{
get
{
return followOnAxis;
}
set
{
followOnAxis = value;
}
}
[Tooltip("An optional GameObject that is negated from the calculation if both the source and target are descendants of it.")]
[SerializeField]
private GameObject ancestor;
///
/// An optional that is negated from the calculation if both the source and target are descendants of it.
///
public GameObject Ancestor
{
get
{
return ancestor;
}
set
{
ancestor = value;
}
}
///
/// The current angular velocity the rotation is applying to the target.
///
public Vector3 AngularVelocity { get; protected set; }
///
/// The previous source world position.
///
protected Vector3? previousSourcePosition;
///
/// Clears .
///
public virtual void ClearAncestor()
{
if (!this.IsValidState())
{
return;
}
Ancestor = default;
}
///
/// Sets the x value.
///
/// The value to set to.
public virtual void SetFollowOnAxisX(bool value)
{
FollowOnAxis = new Vector3State(value, FollowOnAxis.yState, FollowOnAxis.zState);
}
///
/// Sets the y value.
///
/// The value to set to.
public virtual void SetFollowOnAxisY(bool value)
{
FollowOnAxis = new Vector3State(FollowOnAxis.xState, value, FollowOnAxis.zState);
}
///
/// Sets the z value.
///
/// The value to set to.
public virtual void SetFollowOnAxisZ(bool value)
{
FollowOnAxis = new Vector3State(FollowOnAxis.xState, FollowOnAxis.yState, value);
}
///
/// Resets the state of the source previous position.
///
public virtual void ResetPreviousState()
{
previousSourcePosition = null;
}
///
/// Rotates the target based on the position difference of the 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)
{
AngularVelocity = CalculateAngularVelocity(source, target, offset);
target.transform.localRotation *= Quaternion.Euler(AngularVelocity);
}
protected virtual void OnDisable()
{
ResetPreviousState();
}
///
/// Calculates the angular velocity based on the differing source position in relation to the target position.
///
/// The source to utilize in the modification.
/// The target to modify.
/// The offset of the target against the source when modifying.
/// The angular velocity to project onto the target.
protected virtual Vector3 CalculateAngularVelocity(GameObject source, GameObject target, GameObject offset)
{
Vector3 negatePosition = Ancestor != null ? Ancestor.transform.position : Vector3.zero;
Vector3 sourcePosition = source.transform.position - negatePosition;
Vector3 targetPosition = target.transform.position - negatePosition;
Vector3 offsetPosition = offset != null ? offset.transform.position - negatePosition : Vector3.zero;
if (previousSourcePosition == null)
{
previousSourcePosition = sourcePosition;
}
if (offset != null && !sourcePosition.WithinDistance(offsetPosition, SourceToOffsetMaximumDistance))
{
return Vector3.zero;
}
float xDegree = FollowOnAxis.xState ? CalculateAngle(target.transform.right, targetPosition, (Vector3)previousSourcePosition, sourcePosition) : 0f;
float yDegree = FollowOnAxis.yState ? CalculateAngle(target.transform.up, targetPosition, (Vector3)previousSourcePosition, sourcePosition) : 0f;
float zDegree = FollowOnAxis.zState ? CalculateAngle(target.transform.forward, targetPosition, (Vector3)previousSourcePosition, sourcePosition) : 0f;
previousSourcePosition = sourcePosition;
return ApplyDrag(new Vector3(xDegree, yDegree, zDegree));
}
///
/// Calculates the rotational angle for an axis based on the difference between two points around the origin.
///
/// The direction representing the axis.
/// The angle centre.
/// The first point to calculate the angle from.
/// The second point to calculate the angle to.
/// The angle in degrees between the two points.
protected virtual float CalculateAngle(Vector3 originDirection, Vector3 originPoint, Vector3 pointA, Vector3 pointB)
{
Vector3 heading = pointB - originPoint;
float headingMagnitude = heading.magnitude;
Vector3 sideA = pointA - originPoint;
if (headingMagnitude.ApproxEquals(0f))
{
return 0f;
}
Vector3 sideB = heading * (1f / headingMagnitude);
return Mathf.Atan2(Vector3.Dot(originDirection, Vector3.Cross(sideA, sideB)), Vector3.Dot(sideA, sideB)) * Mathf.Rad2Deg;
}
///
/// Applies an opposing drag force to the current rotational velocity.
///
/// The current rotational velocity being applied.
/// The rotational velocity with the opposing drag applied to slow it down.
protected virtual Vector3 ApplyDrag(Vector3 angularVelocity)
{
return angularVelocity * (1f / AngularDrag);
}
}
}