namespace Zinnia.Tracking.Modification { using System.Collections; using UnityEngine; using UnityEngine.Events; using Zinnia.Extension; using Zinnia.Process; /// /// Modifies the given target direction by rotating it to look at a point in space whilst pivoting on another point in space. /// public class DirectionModifier : MonoBehaviour, IProcessable { /// /// The target to use for the rotational up. /// public enum RotationTargetType { /// /// Do not use any target for rotational up. /// UseNoTarget, /// /// Use the for rotational up. /// UsePivotAsTarget, /// /// Use the for rotational up. /// UseLookAtAsTarget } #region Reference Settings [Header("Reference Settings")] [Tooltip("The target to rotate.")] [SerializeField] private GameObject target; /// /// The target to rotate. /// public GameObject Target { get { return target; } set { target = value; } } [Tooltip("The object to look at when affecting rotation.")] [SerializeField] private GameObject lookAt; /// /// The object to look at when affecting rotation. /// public GameObject LookAt { get { return lookAt; } set { lookAt = value; if (this.IsMemberChangeAllowed()) { OnAfterLookAtChange(); } } } [Tooltip("The object to be used as the pivot point for rotation.")] [SerializeField] private GameObject pivot; /// /// The object to be used as the pivot point for rotation. /// public GameObject Pivot { get { return pivot; } set { pivot = value; } } [Tooltip("The object providing a rotational offset for the Target.")] [SerializeField] private GameObject targetOffset; /// /// The object providing a rotational offset for the . /// public GameObject TargetOffset { get { return targetOffset; } set { targetOffset = value; } } [Tooltip("The object providing a rotational offset for the Pivot.")] [SerializeField] private GameObject pivotOffset; /// /// The object providing a rotational offset for the . /// public GameObject PivotOffset { get { return pivotOffset; } set { pivotOffset = value; } } [Tooltip("An optional GameObject that will mirror the rotation of the Pivot.")] [SerializeField] private GameObject pivotRotationMirror; /// /// An optional that will mirror the rotation of the . /// public GameObject PivotRotationMirror { get { return pivotRotationMirror; } set { pivotRotationMirror = value; } } #endregion #region Control Settings [Header("Control Settings")] [Tooltip("The target object to use for setting the world up during the rotation process.")] [SerializeField] private RotationTargetType rotationUpTarget = RotationTargetType.UsePivotAsTarget; /// /// The target object to use for setting the world up during the rotation process. /// public RotationTargetType RotationUpTarget { get { return rotationUpTarget; } set { rotationUpTarget = value; } } [Tooltip("Whether to snap the Target origin to the LookAt origin.")] [SerializeField] private bool snapToLookAt = true; /// /// Whether to snap the origin to the origin. /// public bool SnapToLookAt { get { return snapToLookAt; } set { snapToLookAt = value; } } [Tooltip("The speed in which the rotation is reset to the original speed when the orientation is reset. The higher the value the slower the speed.\n\n* `0` resets the rotation immediately.\n* `infinity` ensures the rotation is never reset.")] [SerializeField] private float resetOrientationSpeed = 0.1f; /// /// The speed in which the rotation is reset to the original speed when the orientation is reset. The higher the value the slower the speed. /// /// /// * `0` resets the rotation immediately. /// * `infinity` ensures the rotation is never reset. /// public float ResetOrientationSpeed { get { return resetOrientationSpeed; } set { resetOrientationSpeed = value; } } #endregion #region Orientation Events /// /// Emitted when the orientation is reset. /// [Header("Orientation Events")] public UnityEvent OrientationReset = new UnityEvent(); /// /// Emitted when the orientation reset action is canceled. /// public UnityEvent OrientationResetCancelled = new UnityEvent(); #endregion /// /// The initial rotation of the . /// protected Quaternion targetInitialRotation; /// /// The rotation of the when released. /// protected Quaternion targetReleaseRotation; /// /// The initial rotation of the . /// protected Quaternion pivotInitialRotation; /// /// The rotation of the when released. /// protected Quaternion pivotReleaseRotation; /// /// The initial rotation offset. /// protected Quaternion offsetInitialRotation; /// /// A reference to the started routine. /// protected Coroutine resetOrientationRoutine; /// /// Determines whether the is in front of the within the local space. /// protected virtual bool IsLookAtInFrontOfPivot => Target != null && Pivot != null && LookAt != null ? Target.transform.InverseTransformPoint(LookAt.transform.position).z > Target.transform.InverseTransformPoint(Pivot.transform.position).z : false; /// /// Clears . /// public virtual void ClearTarget() { if (!this.IsValidState()) { return; } Target = default; } /// /// Clears . /// public virtual void ClearLookAt() { if (!this.IsValidState()) { return; } LookAt = default; } /// /// Clears . /// public virtual void ClearPivotProperty() { if (!this.IsValidState()) { return; } Pivot = default; } /// /// Clears . /// public virtual void ClearTargetOffset() { if (!this.IsValidState()) { return; } TargetOffset = default; } /// /// Clears . /// public virtual void ClearPivotOffset() { if (!this.IsValidState()) { return; } PivotOffset = default; } /// /// Clears . /// public virtual void ClearPivotRotationMirror() { if (!this.IsValidState()) { return; } PivotRotationMirror = default; } /// /// Sets the . /// /// The index of the . public virtual void SetRotationUpTarget(int index) { RotationUpTarget = EnumExtensions.GetByIndex(index); } /// /// Processes the current direction modification. /// public virtual void Process() { if (!this.IsValidState()) { return; } SetTargetRotation(); } /// /// Clears the existing created pivot point. /// public virtual void ClearPivot() { pivotReleaseRotation = Pivot != null ? Pivot.transform.rotation : Quaternion.identity; } /// /// Saves the existing orientation of the target. /// /// Determines whether to cancel any existing orientation reset process. public virtual void SaveOrientation(bool cancelResetOrientation = true) { if (!this.IsValidState()) { return; } targetInitialRotation = Target != null ? Target.transform.rotation : Quaternion.identity; pivotInitialRotation = Pivot != null ? Pivot.transform.rotation : Quaternion.identity; if (cancelResetOrientation) { CancelResetOrientation(); } } /// /// Resets the orientation of the target to it's initial rotation. /// public virtual void ResetOrientation() { if (!this.IsValidState()) { return; } pivotReleaseRotation = Pivot != null ? Pivot.transform.rotation : pivotReleaseRotation; if (ResetOrientationSpeed < float.MaxValue && ResetOrientationSpeed > 0f) { resetOrientationRoutine = StartCoroutine(ResetOrientationRoutine()); } else if (ResetOrientationSpeed.ApproxEquals(0f)) { SetOrientationToSaved(); } else { OrientationReset?.Invoke(); } } /// /// Cancels any existing reset orientation process. /// public virtual void CancelResetOrientation() { if (resetOrientationRoutine != null) { StopCoroutine(resetOrientationRoutine); } resetOrientationRoutine = null; OrientationResetCancelled?.Invoke(); } protected virtual void OnDisable() { CancelResetOrientation(); } /// /// Resets the target rotation to the initial rotation. /// protected virtual void SetOrientationToSaved() { if (Target == null) { return; } Target.transform.rotation = GetActualInitialRotation(); OrientationReset?.Invoke(); } /// /// Sets the target rotation to look at the specific point in space. /// protected virtual void SetTargetRotation() { if (Target == null || LookAt == null || Pivot == null) { return; } Target.transform.rotation = Quaternion.LookRotation(GetRotation(), GetUpwards()) * (PivotOffset != null && PivotOffset.activeInHierarchy ? Quaternion.Inverse(PivotOffset.transform.localRotation) : Quaternion.identity); if (!SnapToLookAt) { Target.transform.rotation *= offsetInitialRotation; } if (PivotRotationMirror != null) { PivotRotationMirror.transform.rotation = Pivot.transform.rotation; } } /// /// Gets the rotation Vector based on the position of the and . /// /// protected virtual Vector3 GetRotation() { return IsLookAtInFrontOfPivot ? LookAt.transform.position - Pivot.transform.position : Pivot.transform.position - LookAt.transform.position; } /// /// Gets the rotational up Vector based on the value. /// /// The rotational up to use. protected virtual Vector3 GetUpwards() { switch (RotationUpTarget) { case RotationTargetType.UseNoTarget: return Vector3.up; case RotationTargetType.UsePivotAsTarget: return Pivot != null ? Pivot.transform.up : Vector3.zero; case RotationTargetType.UseLookAtAsTarget: return LookAt != null ? LookAt.transform.up : Vector3.zero; } return Vector3.zero; } /// /// Rotates the target back to the original rotation over a given period of time. /// /// The enumerator. protected virtual IEnumerator ResetOrientationRoutine() { if (Target == null) { yield break; } float elapsedTime = 0f; targetReleaseRotation = Target.transform.rotation; while (elapsedTime < ResetOrientationSpeed) { Target.transform.rotation = Quaternion.Lerp(targetReleaseRotation, GetActualInitialRotation(), elapsedTime / ResetOrientationSpeed); elapsedTime += Time.deltaTime; yield return null; } SetOrientationToSaved(); } /// /// Gets the actual initial rotation of the target based on any changes to the pivot rotation. /// /// The actual initial rotation. protected virtual Quaternion GetActualInitialRotation() { return targetInitialRotation * (pivotReleaseRotation * Quaternion.Inverse(pivotInitialRotation)); } /// /// Sets the value based on the initial rotations and positions of the reference objects. /// protected virtual void SetOffsetRotation() { offsetInitialRotation = (LookAt != null && Pivot != null ? Quaternion.Inverse(Quaternion.LookRotation(GetRotation(), GetUpwards())) * Pivot.transform.rotation : Quaternion.identity) * (TargetOffset != null ? Quaternion.Inverse(TargetOffset.transform.localRotation) : Quaternion.identity); } /// /// Called after has been changed. /// protected virtual void OnAfterLookAtChange() { SetOffsetRotation(); } } }