namespace Tilia.Interactions.Interactables.Interactors { using System.Collections; using System.Collections.Generic; using Tilia.Interactions.Interactables.Interactables; using UnityEngine; using Zinnia.Action; using Zinnia.Data.Attribute; using Zinnia.Data.Collection.List; using Zinnia.Extension; using Zinnia.Tracking.Collision; using Zinnia.Tracking.Collision.Active; using Zinnia.Tracking.Velocity; using Zinnia.Utility; /// /// Sets up the Interactor Prefab grab settings based on the provided user settings. /// public class GrabInteractorConfigurator : MonoBehaviour { #region Facade Settings [Header("Facade Settings")] [Tooltip("The public interface facade.")] [SerializeField] [Restricted] private InteractorFacade facade; /// /// The public interface facade. /// public InteractorFacade Facade { get { return facade; } set { facade = value; } } #endregion #region Grab Settings [Header("Grab Settings")] [Tooltip("The BooleanAction that will initiate the Interactor grab mechanism.")] [SerializeField] [Restricted] private BooleanAction grabAction; /// /// The that will initiate the Interactor grab mechanism. /// public BooleanAction GrabAction { get { return grabAction; } set { grabAction = value; } } [Tooltip("The VelocityTrackerProcessor to measure the interactors current velocity for throwing on release.")] [SerializeField] [Restricted] private VelocityTrackerProcessor velocityTracker; /// /// The to measure the interactors current velocity for throwing on release. /// public VelocityTrackerProcessor VelocityTracker { get { return velocityTracker; } set { velocityTracker = value; if (this.IsMemberChangeAllowed()) { OnAfterVelocityTrackerChange(); } } } [Tooltip("The ActiveCollisionPublisher for checking valid start grabbing action.")] [SerializeField] [Restricted] private ActiveCollisionPublisher startGrabbingPublisher; /// /// The for checking valid start grabbing action. /// public ActiveCollisionPublisher StartGrabbingPublisher { get { return startGrabbingPublisher; } set { startGrabbingPublisher = value; } } [Tooltip("The ActiveCollisionPublisher for checking valid stop grabbing action.")] [SerializeField] [Restricted] private ActiveCollisionPublisher stopGrabbingPublisher; /// /// The for checking valid stop grabbing action. /// public ActiveCollisionPublisher StopGrabbingPublisher { get { return stopGrabbingPublisher; } set { stopGrabbingPublisher = value; } } [Tooltip("The processor for initiating an instant grab.")] [SerializeField] [Restricted] private GameObject instantGrabProcessor; /// /// The processor for initiating an instant grab. /// public GameObject InstantGrabProcessor { get { return instantGrabProcessor; } set { instantGrabProcessor = value; } } [Tooltip("The processor for initiating a precognitive grab.")] [SerializeField] [Restricted] private GameObject precognitionGrabProcessor; /// /// The processor for initiating a precognitive grab. /// public GameObject PrecognitionGrabProcessor { get { return precognitionGrabProcessor; } set { precognitionGrabProcessor = value; } } [Tooltip("The CountdownTimer to determine grab precognition.")] [SerializeField] [Restricted] private CountdownTimer precognitionTimer; /// /// The to determine grab precognition. /// public CountdownTimer PrecognitionTimer { get { return precognitionTimer; } set { precognitionTimer = value; } } [Tooltip("The minimum timer value for the grab precognition CountdownTimer.")] [SerializeField] [Restricted] private float minPrecognitionTimer = 0.01f; /// /// The minimum timer value for the grab precognition . /// public float MinPrecognitionTimer { get { return minPrecognitionTimer; } set { minPrecognitionTimer = value; } } [Tooltip("The GameObjectObservableSet containing the currently grabbed objects.")] [SerializeField] [Restricted] private GameObjectObservableList grabbedObjectsCollection; /// /// The containing the currently grabbed objects. /// public GameObjectObservableList GrabbedObjectsCollection { get { return grabbedObjectsCollection; } set { grabbedObjectsCollection = value; } } [Tooltip("A BooleanAction for holding the state of whether the Interactor is grabbing something.")] [SerializeField] [Restricted] private BooleanAction isGrabbingAction; /// /// A for holding the state of whether the Interactor is grabbing something. /// public BooleanAction IsGrabbingAction { get { return isGrabbingAction; } set { isGrabbingAction = value; } } [Tooltip("Whether to simulate a touch before force grabbing.")] [SerializeField] [Restricted] private bool touchBeforeForceGrab = true; /// /// Whether to simulate a touch before force grabbing. /// public bool TouchBeforeForceGrab { get { return touchBeforeForceGrab; } set { touchBeforeForceGrab = value; } } #endregion /// /// A collection of currently grabbed GameObjects. /// public virtual IReadOnlyList GrabbedObjects => GrabbedObjectsCollection.NonSubscribableElements; /// /// Manages the reset simulate touch logic. /// protected Coroutine simulateTouchResetRoutine; /// /// A reusable instance of event data. /// protected readonly ActiveCollisionsContainer.EventData activeCollisionsEventData = new ActiveCollisionsContainer.EventData(); /// /// Clears . /// public virtual void ClearVelocityTracker() { if (!this.IsValidState()) { return; } VelocityTracker = default; } /// /// Configures the action used to control grabbing. /// public virtual void ConfigureGrabAction() { if (GrabAction != null && Facade != null && Facade.GrabAction != null) { GrabAction.RunWhenActiveAndEnabled(() => GrabAction.ClearSources()); GrabAction.RunWhenActiveAndEnabled(() => GrabAction.AddSource(Facade.GrabAction)); } } /// /// Configures the velocity tracker used for grabbing. /// public virtual void ConfigureVelocityTrackers() { if (VelocityTracker != null && Facade != null && Facade.VelocityTracker != null) { VelocityTracker.VelocityTrackers.RunWhenActiveAndEnabled(() => VelocityTracker.VelocityTrackers.Clear()); VelocityTracker.VelocityTrackers.RunWhenActiveAndEnabled(() => VelocityTracker.VelocityTrackers.Add(Facade.VelocityTracker)); } } /// /// Configures the components for grab precognition. /// public virtual void ConfigureGrabPrecognition() { if (Facade.GrabPrecognition < MinPrecognitionTimer && !Facade.GrabPrecognition.ApproxEquals(0f)) { Facade.GrabPrecognition = MinPrecognitionTimer; } PrecognitionTimer.StartTime = Facade.GrabPrecognition; ChooseGrabProcessor(); } /// /// Attempt to grab an Interactable to the current Interactor utilizing custom collision data and ungrabs any existing grab. /// /// The Interactable to attempt to grab. /// Custom collision data. /// Custom collider data. public virtual void Grab(InteractableFacade interactable, Collision collision, Collider collider) { Grab(interactable, collision, collider, true); } /// /// Attempt to grab an Interactable to the current Interactor utilizing custom collision data and does not ungrab any existing grab.. /// /// The Interactable to attempt to grab. /// Custom collision data. /// Custom collider data. public virtual void GrabIgnoreUngrab(InteractableFacade interactable, Collision collision, Collider collider) { Grab(interactable, collision, collider, false); } /// /// Attempt to grab an Interactable to the current Interactor utilizing custom collision data. /// /// The Interactable to attempt to grab. /// Custom collision data. /// Custom collider data. /// Whether to ungrab any existing grab. public virtual void Grab(InteractableFacade interactable, Collision collision, Collider collider, bool ungrabExistingGrab) { if (interactable == null || !this.IsValidState()) { return; } if (ungrabExistingGrab) { Ungrab(); } if (TouchBeforeForceGrab) { Facade.SimulateTouch(interactable); CancelSimulateTouchResetRoutine(); simulateTouchResetRoutine = StartCoroutine( ResetSimulateTouchState(interactable, interactable.Configuration.CollisionNotifier.enabled, interactable.Configuration.CollisionNotifier.ProcessCollisionsWhenDisabled)); interactable.Configuration.CollisionNotifier.ProcessCollisionsWhenDisabled = false; interactable.Configuration.CollisionNotifier.enabled = false; } StartGrabbingPublisher.SetActiveCollisions(CreateActiveCollisionsEventData(interactable.gameObject, collision, collider)); ProcessGrabAction(StartGrabbingPublisher, true); if (interactable.IsGrabTypeToggle) { ProcessGrabAction(StartGrabbingPublisher, false); } } /// /// Attempt to ungrab currently grabbed Interactables to the current Interactor. /// public virtual void Ungrab() { if (GrabbedObjects.Count == 0) { return; } InteractableFacade interactable = GrabbedObjects[0].TryGetComponent(true, true); if (interactable.IsGrabTypeToggle) { if (StartGrabbingPublisher.Payload.ActiveCollisions.Count == 0) { StartGrabbingPublisher.SetActiveCollisions(CreateActiveCollisionsEventData(interactable.gameObject, null, null)); } ProcessGrabAction(StartGrabbingPublisher, true); } ProcessGrabAction(StopGrabbingPublisher, false); } /// /// Attempts to automatically emit precognition grab if there are registered consumers. /// public virtual void PrecognitionGrabForRegisteredConsumers() { if (StartGrabbingPublisher.RegisteredConsumerContainer != null && StartGrabbingPublisher.RegisteredConsumerContainer.RegisteredConsumers.Count > 0) { PrecognitionTimer.EmitStatus(); } } protected virtual void OnEnable() { ConfigureGrabAction(); ConfigureVelocityTrackers(); ConfigureGrabPrecognition(); } protected virtual void OnDisable() { CancelSimulateTouchResetRoutine(); } /// /// Cancels the coroutine. /// protected virtual void CancelSimulateTouchResetRoutine() { if (simulateTouchResetRoutine != null) { StopCoroutine(simulateTouchResetRoutine); simulateTouchResetRoutine = null; } } /// /// Resets the state set by the simulate touch logic. /// /// The interactable to reset on. /// The value to set the value to. /// The value to set the value to. /// An enumerator for controlling the coroutine. protected virtual IEnumerator ResetSimulateTouchState(InteractableFacade interactable, bool collisionNotifierEnabled, bool processCollisionsWhenDisabled) { yield return new WaitForFixedUpdate(); if (interactable != null) { interactable.Configuration.CollisionNotifier.enabled = collisionNotifierEnabled; interactable.Configuration.CollisionNotifier.ProcessCollisionsWhenDisabled = processCollisionsWhenDisabled; } simulateTouchResetRoutine = null; } /// /// Chooses which grab processing to perform on the grab action. /// protected virtual void ChooseGrabProcessor() { bool disablePrecognition = PrecognitionTimer.StartTime.ApproxEquals(0f); InstantGrabProcessor.SetActive(disablePrecognition); PrecognitionGrabProcessor.SetActive(!disablePrecognition); } /// /// Processes the given collision data into a grab action based on the given state. /// /// The collision data to process. /// The grab state to check against. protected virtual void ProcessGrabAction(ActiveCollisionPublisher publisher, bool actionState) { InstantGrabProcessor.SetActive(false); PrecognitionGrabProcessor.SetActive(false); if (GrabAction.Value != actionState) { GrabAction.Receive(actionState); } if (GrabAction.Value) { publisher.Publish(); } ChooseGrabProcessor(); } /// /// Creates Active Collision data based on the given parameters. /// /// The source of the forwarding the collision data. /// The data on the point of the collision for precision grabbing. /// The collider that has been collided with. /// protected virtual ActiveCollisionsContainer.EventData CreateActiveCollisionsEventData(GameObject forwardSource, Collision collision = null, Collider collider = null) { collider = collider == null ? forwardSource.GetComponentInChildren() : collider; if (activeCollisionsEventData.ActiveCollisions.Count == 0) { activeCollisionsEventData.ActiveCollisions.Add(new CollisionNotifier.EventData()); } activeCollisionsEventData.ActiveCollisions[0].Set(forwardSource.TryGetComponent(), collider.isTrigger, collision, collider); return activeCollisionsEventData; } /// /// Called after has been changed. /// protected virtual void OnAfterVelocityTrackerChange() { ConfigureVelocityTrackers(); } } }