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