namespace VRTK.Prefabs.Interactions.Interactors { using UnityEngine; using System.Collections.Generic; using Malimbe.MemberChangeMethod; using Malimbe.MemberClearanceMethod; using Malimbe.XmlDocumentationAttribute; using Malimbe.PropertySerializationAttribute; using Zinnia.Action; using Zinnia.Utility; using Zinnia.Extension; using Zinnia.Data.Attribute; using Zinnia.Data.Collection.List; using Zinnia.Tracking.Velocity; using Zinnia.Tracking.Collision; using Zinnia.Tracking.Collision.Active; using VRTK.Prefabs.Interactions.Interactables; /// /// Sets up the Interactor Prefab grab settings based on the provided user settings. /// public class GrabInteractorConfigurator : MonoBehaviour { #region Facade Settings /// /// The public interface facade. /// [Serialized] [field: Header("Facade Settings"), DocumentedByXml, Restricted] public InteractorFacade Facade { get; protected set; } #endregion #region Reference Settings /// /// The point in which to attach a grabbed Interactable to the Interactor. /// [Serialized, Cleared] [field: Header("Reference Settings"), DocumentedByXml] public GameObject AttachPoint { get; set; } /// /// The to measure the interactors current velocity for throwing on release. /// [Serialized, Cleared] [field: DocumentedByXml] public VelocityTrackerProcessor VelocityTracker { get; set; } #endregion #region Grab Settings /// /// The that will initiate the Interactor grab mechanism. /// [Serialized] [field: Header("Grab Settings"), DocumentedByXml, Restricted] public BooleanAction GrabAction { get; protected set; } /// /// The for checking valid start grabbing action. /// [Serialized] [field: DocumentedByXml, Restricted] public ActiveCollisionPublisher StartGrabbingPublisher { get; protected set; } /// /// The for checking valid stop grabbing action. /// [Serialized] [field: DocumentedByXml, Restricted] public ActiveCollisionPublisher StopGrabbingPublisher { get; protected set; } /// /// The processor for initiating an instant grab. /// [Serialized] [field: DocumentedByXml, Restricted] public GameObject InstantGrabProcessor { get; protected set; } /// /// The processor for initiating a precognitive grab. /// [Serialized] [field: DocumentedByXml, Restricted] public GameObject PrecognitionGrabProcessor { get; protected set; } /// /// The to determine grab precognition. /// [Serialized] [field: DocumentedByXml, Restricted] public CountdownTimer PrecognitionTimer { get; protected set; } /// /// The minimum timer value for the grab precognition . /// [Serialized] [field: DocumentedByXml, Restricted] public float MinPrecognitionTimer { get; protected set; } = 0.01f; /// /// The containing the currently grabbed objects. /// [Serialized] [field: DocumentedByXml, Restricted] public GameObjectObservableList GrabbedObjectsCollection { get; protected set; } #endregion /// /// A collection of currently grabbed GameObjects. /// public IReadOnlyList GrabbedObjects => GrabbedObjectsCollection.NonSubscribableElements; /// /// A reusable instance of event data. /// protected readonly ActiveCollisionsContainer.EventData activeCollisionsEventData = new ActiveCollisionsContainer.EventData(); /// /// 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 touching and untouching. /// public virtual void ConfigurePublishers() { if (StartGrabbingPublisher != null) { StartGrabbingPublisher.Payload.SourceContainer = AttachPoint; } if (StopGrabbingPublisher != null) { StopGrabbingPublisher.Payload.SourceContainer = AttachPoint; } } /// /// 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. /// /// The Interactable to attempt to grab. /// Custom collision data. /// Custom collider data. public virtual void Grab(InteractableFacade interactable, Collision collision, Collider collider) { if (interactable == null) { return; } Ungrab(); 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); } protected virtual void OnEnable() { ConfigureGrabAction(); ConfigureVelocityTrackers(); ConfigurePublishers(); ConfigureGrabPrecognition(); } /// /// 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. /// [CalledAfterChangeOf(nameof(AttachPoint))] protected virtual void OnAfterAttachPointChange() { ConfigurePublishers(); } /// /// Called after has been changed. /// [CalledAfterChangeOf(nameof(VelocityTracker))] protected virtual void OnAfterVelocityTrackerChange() { ConfigureVelocityTrackers(); } } }