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