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