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