namespace Zinnia.Tracking.Modification
{
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using Zinnia.Data.Attribute;
using Zinnia.Data.Enum;
using Zinnia.Data.Type;
using Zinnia.Extension;
///
/// Applies the transform properties from a given source onto the given target .
///
public class TransformPropertyApplier : MonoBehaviour
{
///
/// Holds data about a event.
///
[Serializable]
public class EventData
{
[Tooltip("The source TransformData to obtain the transformation properties from.")]
[SerializeField]
private TransformData eventSource;
///
/// The source to obtain the transformation properties from.
///
public TransformData EventSource
{
get
{
return eventSource;
}
set
{
eventSource = value;
}
}
[Tooltip("The target TransformData to apply transformations to.")]
[SerializeField]
private TransformData eventTarget;
///
/// The target to apply transformations to.
///
public TransformData EventTarget
{
get
{
return eventTarget;
}
set
{
eventTarget = value;
}
}
///
/// Clears .
///
public virtual void ClearEventSource()
{
EventSource = default;
}
///
/// Clears .
///
public virtual void ClearEventTarget()
{
EventTarget = default;
}
public EventData Set(EventData source)
{
return Set(source.EventSource, source.EventTarget);
}
public EventData Set(TransformData source, TransformData target)
{
EventSource = source;
EventTarget = target;
return this;
}
public void Clear()
{
Set(default, default);
}
}
///
/// Defines the event with the .
///
[Serializable]
public class UnityEvent : UnityEvent { }
///
/// A reusable instance of .
///
protected static readonly WaitForEndOfFrame DelayInstruction = new WaitForEndOfFrame();
#region Reference Settings
[Header("Reference Settings")]
[Tooltip("The source to obtain the transformation properties from.")]
[SerializeField]
private TransformData source;
///
/// The source to obtain the transformation properties from.
///
public TransformData Source
{
get
{
return source;
}
set
{
source = value;
}
}
[Tooltip("The target to apply the transformations to.")]
[SerializeField]
private GameObject target;
///
/// The target to apply the transformations to.
///
public GameObject Target
{
get
{
return target;
}
set
{
target = value;
}
}
[Tooltip("The offset/pivot when applying the transformations.")]
[SerializeField]
private GameObject offset;
///
/// The offset/pivot when applying the transformations.
///
public GameObject Offset
{
get
{
return offset;
}
set
{
offset = value;
}
}
#endregion
#region Apply Settings
[Header("Apply Settings")]
[Tooltip("Determines whether to allow any mutation to take place.")]
[SerializeField]
private bool allowMutate = true;
///
/// Determines whether to allow any mutation to take place.
///
public bool AllowMutate
{
get
{
return allowMutate;
}
set
{
allowMutate = value;
}
}
[Tooltip("Determines which axes to apply on when utilizing the position offset.")]
[SerializeField]
private Vector3State applyPositionOffsetOnAxis = Vector3State.True;
///
/// Determines which axes to apply on when utilizing the position offset.
///
public Vector3State ApplyPositionOffsetOnAxis
{
get
{
return applyPositionOffsetOnAxis;
}
set
{
applyPositionOffsetOnAxis = value;
}
}
[Tooltip("Determines which axes to apply on when utilizing the rotation offset.")]
[SerializeField]
private Vector3State applyRotationOffsetOnAxis = Vector3State.True;
///
/// Determines which axes to apply on when utilizing the rotation offset.
///
public Vector3State ApplyRotationOffsetOnAxis
{
get
{
return applyRotationOffsetOnAxis;
}
set
{
applyRotationOffsetOnAxis = value;
}
}
[Tooltip("The Transform properties to apply the transformations on.")]
[SerializeField]
[UnityFlags]
private TransformProperties applyTransformations = (TransformProperties)(-1);
///
/// The properties to apply the transformations on.
///
public TransformProperties ApplyTransformations
{
get
{
return applyTransformations;
}
set
{
applyTransformations = value;
}
}
#endregion
#region Transition Settings
[Header("Transition Settings")]
[Tooltip("The amount of time to take when transitioning from the current Transform state to the modified Transform state.")]
[SerializeField]
private float transitionDuration;
///
/// The amount of time to take when transitioning from the current state to the modified state.
///
public float TransitionDuration
{
get
{
return transitionDuration;
}
set
{
transitionDuration = value;
}
}
[Tooltip("Whether to still apply the transformation properties even if the new properties are equal to the existing properties.")]
[SerializeField]
private bool shouldApplyToEqualProperties;
///
/// Whether to still apply the transformation properties even if the new properties are equal to the existing properties.
///
public bool ShouldApplyToEqualProperties
{
get
{
return shouldApplyToEqualProperties;
}
set
{
shouldApplyToEqualProperties = value;
}
}
[Tooltip("The threshold the current Transform properties can be within of the destination properties to be considered equal.")]
[SerializeField]
private float transitionDestinationThreshold = 0.01f;
///
/// The threshold the current properties can be within of the destination properties to be considered equal.
///
public float TransitionDestinationThreshold
{
get
{
return transitionDestinationThreshold;
}
set
{
transitionDestinationThreshold = value;
}
}
[Tooltip("Whether to treat the transformation destination properties as dynamic when transitioning the Target.")]
[SerializeField]
private bool isTransitionDestinationDynamic;
///
/// Whether to treat the transformation destination properties as dynamic when transitioning the .
///
public bool IsTransitionDestinationDynamic
{
get
{
return isTransitionDestinationDynamic;
}
set
{
isTransitionDestinationDynamic = value;
}
}
#endregion
#region Applier Events
///
/// Emitted before the transformation process occurs.
///
[Header("Applier Events")]
public UnityEvent BeforeTransformUpdated = new UnityEvent();
///
/// Emitted after the transformation process has occured.
///
public UnityEvent AfterTransformUpdated = new UnityEvent();
///
/// Emitted if the mutation is skipped due to being false.
///
public UnityEvent TransformUpdateSkipped = new UnityEvent();
#endregion
///
/// The routine for managing the transition of the transform.
///
protected Coroutine transitionRoutine;
///
/// The cached event data payload.
///
protected readonly EventData eventData = new EventData();
///
/// A reused data instance for .
///
protected readonly TransformData sourceTransformData = new TransformData();
///
/// A reused data instance for .
///
protected readonly TransformData targetTransformData = new TransformData();
///
/// Clears .
///
public virtual void ClearSource()
{
if (!this.IsValidState())
{
return;
}
Source = default;
}
///
/// Clears .
///
public virtual void ClearTarget()
{
if (!this.IsValidState())
{
return;
}
Target = default;
}
///
/// Clears .
///
public virtual void ClearOffset()
{
if (!this.IsValidState())
{
return;
}
Offset = default;
}
///
/// Sets the parameter from data.
///
/// The data to build the new source from.
public virtual void SetSource(GameObject source)
{
sourceTransformData.Clear();
if (source == null)
{
return;
}
sourceTransformData.Transform = source.transform;
Source = sourceTransformData;
}
///
/// Sets the parameter from .
///
/// The data to build the new target from.
public virtual void SetTarget(TransformData target)
{
Target = target.TryGetGameObject();
}
///
/// Sets the parameter from .
///
/// The data to build the new offset from.
public virtual void SetOffset(TransformData offset)
{
Offset = offset.TryGetGameObject();
}
///
/// Sets the x value.
///
/// The value to set to.
public virtual void SetApplyPositionOffsetOnAxisX(bool value)
{
ApplyPositionOffsetOnAxis = new Vector3State(value, ApplyPositionOffsetOnAxis.yState, ApplyPositionOffsetOnAxis.zState);
}
///
/// Sets the y value.
///
/// The value to set to.
public virtual void SetApplyPositionOffsetOnAxisY(bool value)
{
ApplyPositionOffsetOnAxis = new Vector3State(ApplyPositionOffsetOnAxis.xState, value, ApplyPositionOffsetOnAxis.zState);
}
///
/// Sets the z value.
///
/// The value to set to.
public virtual void SetApplyPositionOffsetOnAxisZ(bool value)
{
ApplyPositionOffsetOnAxis = new Vector3State(ApplyPositionOffsetOnAxis.xState, ApplyPositionOffsetOnAxis.yState, value);
}
///
/// Sets the x value.
///
/// The value to set to.
public virtual void SetApplyRotationOffsetOnAxisX(bool value)
{
ApplyRotationOffsetOnAxis = new Vector3State(value, ApplyRotationOffsetOnAxis.yState, ApplyRotationOffsetOnAxis.zState);
}
///
/// Sets the y value.
///
/// The value to set to.
public virtual void SetApplyRotationOffsetOnAxisY(bool value)
{
ApplyRotationOffsetOnAxis = new Vector3State(ApplyRotationOffsetOnAxis.xState, value, ApplyRotationOffsetOnAxis.zState);
}
///
/// Sets the z value.
///
/// The value to set to.
public virtual void SetApplyRotationOffsetOnAxisZ(bool value)
{
ApplyRotationOffsetOnAxis = new Vector3State(ApplyRotationOffsetOnAxis.xState, ApplyRotationOffsetOnAxis.yState, value);
}
///
/// Applies the properties of the parameter to the target.
///
public virtual void Apply()
{
if (!this.IsValidState() || Target == null || Source == null || Source.Transform == null)
{
return;
}
targetTransformData.Clear();
targetTransformData.Transform = Target.transform;
targetTransformData.UseLocalValues = Source.UseLocalValues;
Vector3 destinationScale = CalculateScale(Source, targetTransformData);
Quaternion destinationRotation = CalculateRotation(Source, targetTransformData);
Vector3 destinationPosition = CalculatePosition(Source, targetTransformData, destinationScale, destinationRotation);
ProcessTransform(Source, targetTransformData, destinationScale, destinationRotation, destinationPosition);
}
///
/// Cancels the transition of the transformation.
///
public virtual void CancelTransition()
{
if (transitionRoutine != null)
{
StopCoroutine(transitionRoutine);
}
}
protected virtual void OnDisable()
{
CancelTransition();
}
///
/// Calculates the final scale to apply based on the given target .
///
/// The source that will be used to determine the scale transformation that is to be applied.
/// The target that will have the scale transformations applied to.
/// The calculated scale.
protected virtual Vector3 CalculateScale(TransformData source, TransformData target)
{
if ((ApplyTransformations & TransformProperties.Scale) == 0)
{
return target.Scale;
}
return source.Scale;
}
///
/// Calculates the final rotation to apply based on the given target .
///
/// The source that will be used to determine the rotation transformation that is to be applied.
/// The target that will have the rotation transformations applied to.
/// The calculated rotation.
protected virtual Quaternion CalculateRotation(TransformData source, TransformData target)
{
if ((ApplyTransformations & TransformProperties.Rotation) == 0)
{
return target.Rotation;
}
if (Offset == null)
{
return source.Rotation;
}
Quaternion rotationAdjustedByOffset = source.Rotation * (target.Rotation * Quaternion.Inverse(Offset.transform.rotation));
Vector3 axisAdjustedRotation = GetOffsetData(ApplyRotationOffsetOnAxis, rotationAdjustedByOffset.eulerAngles, source.Rotation.eulerAngles);
return Quaternion.Euler(axisAdjustedRotation);
}
///
/// Calculates the final position to apply based on the given target .
///
/// The source that will be used to determine the position transformation that is to be applied.
/// The target that will have the position transformations applied to.
/// The current scale to apply to the target.
/// The current rotation to apply to the target.
/// The calculated position.
protected virtual Vector3 CalculatePosition(TransformData source, TransformData target, Vector3 currentScale, Quaternion currentRotation)
{
Vector3 currentPosition = source.Position;
if ((ApplyTransformations & TransformProperties.Position) == 0)
{
if (Offset == null)
{
return target.Position;
}
currentPosition = GetOffsetData(ApplyPositionOffsetOnAxis, Offset.transform.position, target.Position);
}
if (Offset == null)
{
return currentPosition;
}
if ((ApplyTransformations & TransformProperties.Rotation) == 0)
{
return CalculatePositionWithOffset(currentPosition, target.Position, Offset.transform.position);
}
Vector3 calculatedOffset = CalculatePositionWithOffset(Vector3.zero, target.Position, Offset.transform.position) * -1f;
Quaternion relativeRotation = Quaternion.Inverse(target.Rotation) * currentRotation;
Vector3 adjustedOffset = relativeRotation * calculatedOffset;
Vector3 scaleFactor = currentScale.Divide(target.Scale);
Vector3 scaledOffset = Vector3.Scale(adjustedOffset, scaleFactor);
return currentPosition - scaledOffset;
}
///
/// Calculates the position with the appropriate offset.
///
/// The source current position.
/// The target current position.
/// The offset current position.
/// Position with the applied offset.
protected virtual Vector3 CalculatePositionWithOffset(Vector3 sourcePosition, Vector3 targetPosition, Vector3 offsetPosition)
{
Vector3 positionAdjustedByOffset = sourcePosition - (offsetPosition - targetPosition);
return GetOffsetData(ApplyPositionOffsetOnAxis, positionAdjustedByOffset, sourcePosition);
}
///
/// Applies final transformations to the given .
///
/// The source to obtain the transformation properties from.
/// The target to apply transformations to.
/// The scale to apply to the target.
/// The rotation to apply to the target.
/// The position to apply to the target.
protected virtual void ProcessTransform(TransformData source, TransformData target, Vector3 destinationScale, Quaternion destinationRotation, Vector3 destinationPosition)
{
if (ArePropertiesEqual(destinationPosition, target.Position, destinationRotation, target.Rotation, destinationScale, target.Scale))
{
return;
}
BeforeTransformUpdated?.Invoke(eventData.Set(source, target));
if (!AllowMutate)
{
TransformUpdateSkipped?.Invoke(eventData.Set(source, target));
return;
}
if (TransitionDuration.ApproxEquals(0f))
{
UpdateTransformProperties(target.Transform, destinationScale, destinationRotation, destinationPosition, target.UseLocalValues);
AfterTransformUpdated?.Invoke(eventData.Set(source, target));
}
else
{
CancelTransition();
transitionRoutine = StartCoroutine(TransitionTransform(source, target, IsTransitionDestinationDynamic, destinationScale, destinationRotation, destinationPosition));
}
}
///
/// Creates the return data based on whether to use the offset values or the original value depending on the given state.
///
/// The state to determine which value to use on each coordinate.
/// The offset values.
/// The original values.
/// The combined result containing the relevant offset and original values based on the given state.
protected virtual Vector3 GetOffsetData(Vector3State applyStates, Vector3 offsetValue, Vector3 originalValue)
{
return new Vector3(
applyStates.xState ? offsetValue.x : originalValue.x,
applyStates.yState ? offsetValue.y : originalValue.y,
applyStates.zState ? offsetValue.z : originalValue.z
);
}
///
/// Applies the transformation to the over the .
///
/// The to obtain the transformation properties from.
/// The to apply the transformations to.
/// Whether the transformation destination is statically set to the initial properties at the start of the routine or whether the destination is dynamically updated to match the current properties.
/// The final scale of the .
/// The final rotation of the .
/// The final position for the .
/// Coroutine enumerator.
protected virtual IEnumerator TransitionTransform(TransformData source, TransformData target, bool dynamicDestination, Vector3 destinationScale, Quaternion destinationRotation, Vector3 destinationPosition)
{
Vector3 initialScale = target.Scale;
Quaternion initialRotation = target.Rotation;
Vector3 initalPosition = target.Position;
float elapsedTime = 0f;
while (elapsedTime < TransitionDuration)
{
bool equalityCheck = dynamicDestination ?
ArePropertiesEqual(source.Position, target.Position, source.Rotation, target.Rotation, source.Scale, target.Scale) :
ArePropertiesEqual(initalPosition, destinationPosition, initialRotation, destinationRotation, initialScale, destinationScale);
if (equalityCheck)
{
break;
}
if (dynamicDestination)
{
destinationScale = CalculateScale(source, target);
destinationRotation = CalculateRotation(source, target);
destinationPosition = CalculatePosition(source, target, destinationScale, destinationRotation);
}
float lerpFrame = elapsedTime / TransitionDuration;
UpdateTransformProperties(target.Transform,
Vector3.Lerp(initialScale, destinationScale, lerpFrame),
Quaternion.Lerp(initialRotation, destinationRotation, lerpFrame),
Vector3.Lerp(initalPosition, destinationPosition, lerpFrame),
target.UseLocalValues);
elapsedTime += Time.deltaTime;
yield return DelayInstruction;
}
UpdateTransformProperties(target.Transform, destinationScale, destinationRotation, destinationPosition, target.UseLocalValues);
AfterTransformUpdated?.Invoke(eventData.Set(source, target));
}
///
/// Determines whether the start properties equal the destination properties.
///
/// The initial position of the .
/// The final position for the .
/// The initial rotation of the .
/// The final rotation of the .
/// The initial scale of the .
/// The final scale of the .
/// Whether the start properties equal the destination properties.
protected virtual bool ArePropertiesEqual(Vector3 startPosition, Vector3 destinationPosition, Quaternion startRotation, Quaternion destinationRotation, Vector3 startScale, Vector3 destinationScale)
{
return !ShouldApplyToEqualProperties
&& startPosition.ApproxEquals(destinationPosition, TransitionDestinationThreshold)
&& startRotation.eulerAngles.ApproxEquals(destinationRotation.eulerAngles, TransitionDestinationThreshold)
&& startScale.ApproxEquals(destinationScale, TransitionDestinationThreshold);
}
///
/// Updates the properties on the given .
///
/// The to update the properties on.
/// The scale to set to.
/// The rotation to set to.
/// The position to set to.
/// Whether to set the local or world properties.
protected virtual void UpdateTransformProperties(Transform target, Vector3 scale, Quaternion rotation, Vector3 position, bool setLocalValues)
{
if (setLocalValues)
{
target.localScale = scale;
target.localRotation = rotation;
target.localPosition = position;
}
else
{
target.SetGlobalScale(scale);
target.rotation = rotation;
target.position = position;
}
}
}
}