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