namespace Tilia.Interactions.Interactables.Interactables.Grab.Action
{
using Tilia.Interactions.Interactables.Interactors;
using UnityEngine;
using Zinnia.Data.Attribute;
using Zinnia.Data.Collection.List;
using Zinnia.Extension;
using Zinnia.Tracking.Collision.Active;
using Zinnia.Tracking.Follow;
using Zinnia.Tracking.Follow.Modifier;
using Zinnia.Tracking.Velocity;
///
/// Describes an action that allows the Interactable to follow an Interactor's position, rotation and scale.
///
public class GrabInteractableFollowAction : GrabInteractableAction
{
///
/// The way in which the object is moved.
///
public enum TrackingType
{
///
/// Updates the transform data directly, outside of the physics system.
///
FollowTransform,
///
/// Updates the using velocity to stay within the bounds of the physics system.
///
FollowRigidbody,
///
/// Updates the rotation by using a force at position.
///
FollowRigidbodyForceRotate,
///
/// Updates the transform rotation based on the position difference of the source.
///
FollowTransformPositionDifferenceRotate,
///
/// Updates the transform rotation based on the rotation of the Interactor's angular velocity around a given axis.
///
FollowRotateAroundAngularVelocity
}
///
/// The offset to apply on grab.
///
public enum OffsetType
{
///
/// No offset is applied.
///
None,
///
/// An offset of a specified is applied to orientate the Interactable on grab.
///
OrientationHandle,
///
/// An offset of where the collision between the Interactor and Interactable is applied for precision grabbing.
///
PrecisionPoint,
///
/// The precision point offset will always be re-created based on the latest Interactor grabbing the Interactable.
///
ForcePrecisionPoint
}
///
/// The type of processing for the orientation handle.
///
public enum OrientationProcessorType
{
///
/// Uses the GameObjectRelations logic.
///
GameObjectRelation,
///
/// Uses the Rules Matcherr logic.
///
RuleBased
}
#region Interactable Settings
[Header("Follow Settings")]
[Tooltip("Determines how to track the movement of Interactable to the Interactor.")]
[SerializeField]
private TrackingType followTracking;
///
/// Determines how to track the movement of Interactable to the Interactor.
///
public TrackingType FollowTracking
{
get
{
return followTracking;
}
set
{
followTracking = value;
if (this.IsMemberChangeAllowed())
{
OnAfterFollowTrackingChange();
}
}
}
[Tooltip("The offset to apply when grabbing the Interactable.")]
[SerializeField]
private OffsetType grabOffset;
///
/// The offset to apply when grabbing the Interactable.
///
public OffsetType GrabOffset
{
get
{
return grabOffset;
}
set
{
grabOffset = value;
if (this.IsMemberChangeAllowed())
{
OnAfterGrabOffsetChange();
}
}
}
[Tooltip("The type of orientation handle logic to use.")]
[SerializeField]
private OrientationProcessorType orientationHandleLogic;
///
/// The type of orientation handle logic to use.
///
public OrientationProcessorType OrientationHandleLogic
{
get
{
return orientationHandleLogic;
}
set
{
orientationHandleLogic = value;
if (this.IsMemberChangeAllowed())
{
OnAfterOrientationHandleLogicChange();
}
}
}
[Tooltip("Whether the Rigidbody of the Interactable should be in a kinematic state when the follow action is active.")]
[SerializeField]
private bool isKinematicWhenActive = true;
///
/// Whether the of the Interactable should be in a kinematic state when the follow action is active.
///
public bool IsKinematicWhenActive
{
get
{
return isKinematicWhenActive;
}
set
{
isKinematicWhenActive = value;
}
}
[Tooltip("Whether the Rigidbody of the Interactable should be in a kinematic state when the follow action is inactive.")]
[SerializeField]
private bool isKinematicWhenInactive;
///
/// Whether the of the Interactable should be in a kinematic state when the follow action is inactive.
///
public bool IsKinematicWhenInactive
{
get
{
return isKinematicWhenInactive;
}
set
{
isKinematicWhenInactive = value;
}
}
[Tooltip("Whether the IsKinematicWhenInactive property inherits the kinematic state from GrabSetup.Facade.ConsumerRigidbody.isKinematic.")]
[SerializeField]
private bool willInheritIsKinematicWhenInactiveFromConsumerRigidbody = true;
///
/// Whether the property inherits the kinematic state from .
///
public bool WillInheritIsKinematicWhenInactiveFromConsumerRigidbody
{
get
{
return willInheritIsKinematicWhenInactiveFromConsumerRigidbody;
}
set
{
willInheritIsKinematicWhenInactiveFromConsumerRigidbody = value;
}
}
#endregion
#region Tracking Settings
[Header("Interactable Settings")]
[Tooltip("The Zinnia.Tracking.Follow.ObjectFollower for tracking movement.")]
[SerializeField]
[Restricted]
private ObjectFollower objectFollower;
///
/// The for tracking movement.
///
public ObjectFollower ObjectFollower
{
get
{
return objectFollower;
}
set
{
objectFollower = value;
}
}
[Tooltip("A GameObjectObservableList collection of all position modifiers used within the follow modifiers.")]
[SerializeField]
[Restricted]
private GameObjectObservableList positionModifiers;
///
/// A collection of all position modifiers used within the follow modifiers.
///
public GameObjectObservableList PositionModifiers
{
get
{
return positionModifiers;
}
set
{
positionModifiers = value;
}
}
[Tooltip("A GameObjectObservableList collection of all rotation modifiers used within the follow modifiers.")]
[SerializeField]
[Restricted]
private GameObjectObservableList rotationModifiers;
///
/// A collection of all rotation modifiers used within the follow modifiers.
///
public GameObjectObservableList RotationModifiers
{
get
{
return rotationModifiers;
}
set
{
rotationModifiers = value;
}
}
[Tooltip("A GameObjectObservableList collection of all scale modifiers used within the follow modifiers.")]
[SerializeField]
[Restricted]
private GameObjectObservableList scaleModifiers;
///
/// A collection of all scale modifiers used within the follow modifiers.
///
public GameObjectObservableList ScaleModifiers
{
get
{
return scaleModifiers;
}
set
{
scaleModifiers = value;
}
}
[Tooltip("The FollowModifier to move by following the transform.")]
[SerializeField]
[Restricted]
private FollowModifier followTransformModifier;
///
/// The to move by following the transform.
///
public FollowModifier FollowTransformModifier
{
get
{
return followTransformModifier;
}
set
{
followTransformModifier = value;
}
}
[Tooltip("The FollowModifier to move by applying velocities to the Rigidbody.")]
[SerializeField]
[Restricted]
private FollowModifier followRigidbodyModifier;
///
/// The to move by applying velocities to the .
///
public FollowModifier FollowRigidbodyModifier
{
get
{
return followRigidbodyModifier;
}
set
{
followRigidbodyModifier = value;
}
}
[Tooltip("The FollowModifier to rotate by applying a force to the Rigidbody.")]
[SerializeField]
[Restricted]
private FollowModifier followRigidbodyForceRotateModifier;
///
/// The to rotate by applying a force to the .
///
public FollowModifier FollowRigidbodyForceRotateModifier
{
get
{
return followRigidbodyForceRotateModifier;
}
set
{
followRigidbodyForceRotateModifier = value;
}
}
[Tooltip("The FollowModifier to rotate by the angle difference between the source positions.")]
[SerializeField]
[Restricted]
private FollowModifier followTransformRotateOnPositionDifferenceModifier;
///
/// The to rotate by the angle difference between the source positions.
///
public FollowModifier FollowTransformRotateOnPositionDifferenceModifier
{
get
{
return followTransformRotateOnPositionDifferenceModifier;
}
set
{
followTransformRotateOnPositionDifferenceModifier = value;
}
}
[Tooltip("The FollowModifier to rotate by the angular velocity of the source Interactor.")]
[SerializeField]
[Restricted]
private FollowModifier followRotateAroundAngularVelocityModifier;
///
/// The to rotate by the angular velocity of the source Interactor.
///
public FollowModifier FollowRotateAroundAngularVelocityModifier
{
get
{
return followRotateAroundAngularVelocityModifier;
}
set
{
followRotateAroundAngularVelocityModifier = value;
}
}
[Tooltip("The ObjectFollower to force snap the Interactable to the Interactor.")]
[SerializeField]
[Restricted]
private ObjectFollower forceSnapFollower;
///
/// The to force snap the Interactable to the Interactor.
///
public ObjectFollower ForceSnapFollower
{
get
{
return forceSnapFollower;
}
set
{
forceSnapFollower = value;
}
}
[Tooltip("The Zinnia.Tracking.Velocity.VelocityApplier to apply velocity on ungrab.")]
[SerializeField]
[Restricted]
private VelocityApplier velocityApplier;
///
/// The to apply velocity on ungrab.
///
public VelocityApplier VelocityApplier
{
get
{
return velocityApplier;
}
set
{
velocityApplier = value;
}
}
[Tooltip("The Zinnia.Tracking.Velocity.VelocityMultiplier to multiply the applied velocity on ungrab.")]
[SerializeField]
[Restricted]
private VelocityMultiplier velocityMultiplier;
///
/// The to multiply the applied velocity on ungrab.
///
public VelocityMultiplier VelocityMultiplier
{
get
{
return velocityMultiplier;
}
set
{
velocityMultiplier = value;
}
}
#endregion
#region Grab Offset Settings
[Header("Grab Offset Settings")]
[Tooltip("The container for the precision point logic.")]
[SerializeField]
[Restricted]
private GameObject precisionLogicContainer;
///
/// The container for the precision point logic.
///
public GameObject PrecisionLogicContainer
{
get
{
return precisionLogicContainer;
}
set
{
precisionLogicContainer = value;
}
}
[Tooltip("The Collision Point Container component for the precision point logic.")]
[SerializeField]
[Restricted]
private CollisionPointContainer collisionPointContainerComponent;
///
/// The component for the precision point logic.
///
public CollisionPointContainer CollisionPointContainerComponent
{
get
{
return collisionPointContainerComponent;
}
set
{
collisionPointContainerComponent = value;
}
}
[Tooltip("The container for the precision point creation logic.")]
[SerializeField]
[Restricted]
private GameObject precisionCreateContainer;
///
/// The container for the precision point creation logic.
///
public GameObject PrecisionCreateContainer
{
get
{
return precisionCreateContainer;
}
set
{
precisionCreateContainer = value;
}
}
[Tooltip("The container for the precision point force creation logic.")]
[SerializeField]
[Restricted]
private GameObject precisionForceCreateContainer;
///
/// The container for the precision point force creation logic.
///
public GameObject PrecisionForceCreateContainer
{
get
{
return precisionForceCreateContainer;
}
set
{
precisionForceCreateContainer = value;
}
}
[Tooltip("The container for the orientation handles.")]
[SerializeField]
[Restricted]
private GameObject orientationHandleContainer;
///
/// The container for the orientation handles.
///
public GameObject OrientationHandleContainer
{
get
{
return orientationHandleContainer;
}
set
{
orientationHandleContainer = value;
}
}
[Tooltip("The container for the orientation handle logic.")]
[SerializeField]
[Restricted]
private GameObject orientationLogicContainer;
///
/// The container for the orientation handle logic.
///
public GameObject OrientationLogicContainer
{
get
{
return orientationLogicContainer;
}
set
{
orientationLogicContainer = value;
}
}
[Tooltip("The container for the orientation GameObject Relations logic.")]
[SerializeField]
[Restricted]
private GameObject orientationRelationsLogicContainer;
///
/// The container for the orientation GameObject Relations logic.
///
public GameObject OrientationRelationsLogicContainer
{
get
{
return orientationRelationsLogicContainer;
}
set
{
orientationRelationsLogicContainer = value;
}
}
[Tooltip("The container for the orientation Rules Matcher logic.")]
[SerializeField]
[Restricted]
private GameObject orientationRulesMatcherLogicContainer;
///
/// The container for the orientation Rules Matcher logic.
///
public GameObject OrientationRulesMatcherLogicContainer
{
get
{
return orientationRulesMatcherLogicContainer;
}
set
{
orientationRulesMatcherLogicContainer = value;
}
}
#endregion
///
/// The collsiion point that is created upon a precision grab occurring.
///
public virtual GameObject PrecisionCollisionPoint => ObjectFollower != null && ObjectFollower.TargetOffsets != null && ObjectFollower.TargetOffsets.NonSubscribableElements.Count > 0 ? ObjectFollower.TargetOffsets.NonSubscribableElements[0] : null;
///
/// The linked to the interactable.
///
public virtual Rigidbody InteractableRigidbody => GrabSetup != null && GrabSetup.Facade != null && GrabSetup.Facade.Configuration != null ? GrabSetup.Facade.Configuration.ConsumerRigidbody : null;
///
/// Sets the .
///
/// The index of the .
public virtual void SetFollowTracking(int index)
{
FollowTracking = EnumExtensions.GetByIndex(index);
}
///
/// Sets the .
///
/// The index of the .
public virtual void SetGrabOffset(int index)
{
GrabOffset = EnumExtensions.GetByIndex(index);
}
///
/// Sets the .
///
/// The index of the .
public virtual void SetOrientationHandleLogic(int index)
{
OrientationHandleLogic = EnumExtensions.GetByIndex(index);
}
///
/// Applies the active kinematic state to the of the Interactable.
///
/// The initiator that is causing this state change.
public virtual void ApplyActiveKinematicState(GameObject initiator)
{
if (InteractableRigidbody == null)
{
return;
}
PrepareColliderForKinematicChange(initiator);
InteractableRigidbody.isKinematic = IsKinematicWhenActive;
}
///
/// Applies the inactive kinematic state to the of the Interactable.
///
/// The initiator that is causing this state change.
public virtual void ApplyInactiveKinematicState(GameObject initiator)
{
if (InteractableRigidbody == null)
{
return;
}
PrepareColliderForKinematicChange(initiator);
InteractableRigidbody.isKinematic = IsKinematicWhenInactive;
}
///
/// Force snaps the Interactable to the following Interactor.
///
public virtual void ForceSnapOrientation()
{
ForceSnapFollower.Process();
}
///
/// Enables the GameObject Relations Orientation Handle logic.
///
public virtual void UseGameObjectRelationsOrientationHandleLogic()
{
orientationRelationsLogicContainer.SetActive(true);
orientationRulesMatcherLogicContainer.SetActive(false);
}
///
/// Enables the Rules Matcher Orientation Handle logic.
///
public virtual void UseRulesMatcherOrientationHandleLogic()
{
orientationRelationsLogicContainer.SetActive(false);
orientationRulesMatcherLogicContainer.SetActive(true);
}
protected virtual void OnEnable()
{
ConfigureFollowTracking();
ConfigureGrabOffset();
OnAfterOrientationHandleLogicChange();
}
///
/// Prepares the initiator for a kinematic change if the initiator is an Interactor.
///
/// The potential Interactor causing this state change.
protected virtual void PrepareColliderForKinematicChange(GameObject initiator)
{
InteractorFacade interactor = initiator.GetComponent();
if (interactor == null || InteractableRigidbody == null)
{
return;
}
interactor.TouchConfiguration.TouchTracker.PrepareKinematicStateChange(InteractableRigidbody);
GrabSetup.KinematicStateToChange?.Invoke(InteractableRigidbody);
}
///
/// Configures the appropriate processes to be used for follow tracking based on the setting.
///
protected virtual void ConfigureFollowTracking()
{
if (WillInheritIsKinematicWhenInactiveFromConsumerRigidbody)
{
IsKinematicWhenInactive = GrabSetup != null && InteractableRigidbody != null ? InteractableRigidbody.isKinematic : false;
}
switch (FollowTracking)
{
case TrackingType.FollowTransform:
FollowTransformModifier.gameObject.SetActive(true);
FollowRigidbodyModifier.gameObject.SetActive(false);
FollowRigidbodyForceRotateModifier.gameObject.SetActive(false);
FollowTransformRotateOnPositionDifferenceModifier.gameObject.SetActive(false);
FollowRotateAroundAngularVelocityModifier.gameObject.SetActive(false);
ObjectFollower.FollowModifier = FollowTransformModifier;
IsKinematicWhenActive = true;
break;
case TrackingType.FollowRigidbody:
FollowTransformModifier.gameObject.SetActive(false);
FollowRigidbodyModifier.gameObject.SetActive(true);
FollowRigidbodyForceRotateModifier.gameObject.SetActive(false);
FollowTransformRotateOnPositionDifferenceModifier.gameObject.SetActive(false);
FollowRotateAroundAngularVelocityModifier.gameObject.SetActive(false);
ObjectFollower.FollowModifier = FollowRigidbodyModifier;
IsKinematicWhenActive = false;
break;
case TrackingType.FollowRigidbodyForceRotate:
FollowTransformModifier.gameObject.SetActive(false);
FollowRigidbodyModifier.gameObject.SetActive(false);
FollowRigidbodyForceRotateModifier.gameObject.SetActive(true);
FollowTransformRotateOnPositionDifferenceModifier.gameObject.SetActive(false);
FollowRotateAroundAngularVelocityModifier.gameObject.SetActive(false);
ObjectFollower.FollowModifier = FollowRigidbodyForceRotateModifier;
IsKinematicWhenActive = false;
break;
case TrackingType.FollowTransformPositionDifferenceRotate:
FollowTransformModifier.gameObject.SetActive(false);
FollowRigidbodyModifier.gameObject.SetActive(false);
FollowRigidbodyForceRotateModifier.gameObject.SetActive(false);
FollowTransformRotateOnPositionDifferenceModifier.gameObject.SetActive(true);
FollowRotateAroundAngularVelocityModifier.gameObject.SetActive(false);
ObjectFollower.FollowModifier = FollowTransformRotateOnPositionDifferenceModifier;
IsKinematicWhenActive = true;
break;
case TrackingType.FollowRotateAroundAngularVelocity:
FollowTransformModifier.gameObject.SetActive(false);
FollowRigidbodyModifier.gameObject.SetActive(false);
FollowRigidbodyForceRotateModifier.gameObject.SetActive(false);
FollowTransformRotateOnPositionDifferenceModifier.gameObject.SetActive(false);
FollowRotateAroundAngularVelocityModifier.gameObject.SetActive(true);
ObjectFollower.FollowModifier = FollowRotateAroundAngularVelocityModifier;
IsKinematicWhenActive = true;
break;
}
}
///
/// Configures the appropriate processes to be used for grab offset based on the setting.
///
protected virtual void ConfigureGrabOffset()
{
switch (GrabOffset)
{
case OffsetType.None:
PrecisionLogicContainer.TrySetActive(false);
OrientationLogicContainer.TrySetActive(false);
break;
case OffsetType.PrecisionPoint:
PrecisionLogicContainer.TrySetActive(true);
PrecisionCreateContainer.TrySetActive(true);
PrecisionForceCreateContainer.TrySetActive(false);
OrientationLogicContainer.TrySetActive(false);
break;
case OffsetType.ForcePrecisionPoint:
PrecisionLogicContainer.TrySetActive(true);
PrecisionForceCreateContainer.TrySetActive(true);
PrecisionCreateContainer.TrySetActive(false);
OrientationLogicContainer.TrySetActive(false);
break;
case OffsetType.OrientationHandle:
PrecisionLogicContainer.TrySetActive(false);
OrientationLogicContainer.TrySetActive(true);
break;
}
}
///
protected override void OnAfterGrabSetupChange()
{
ObjectFollower.Targets.RunWhenActiveAndEnabled(() => ObjectFollower.Targets.Clear());
ObjectFollower.Targets.RunWhenActiveAndEnabled(() => ObjectFollower.Targets.Add(GrabSetup.Facade.Configuration.ConsumerContainer));
VelocityApplier.Target = InteractableRigidbody != null ? InteractableRigidbody : null;
ConfigureFollowTracking();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterFollowTrackingChange()
{
ConfigureFollowTracking();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterGrabOffsetChange()
{
ConfigureGrabOffset();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterOrientationHandleLogicChange()
{
switch (orientationHandleLogic)
{
case OrientationProcessorType.GameObjectRelation:
UseGameObjectRelationsOrientationHandleLogic();
break;
case OrientationProcessorType.RuleBased:
UseRulesMatcherOrientationHandleLogic();
break;
}
}
}
}