namespace Tilia.Interactions.Interactables.Interactables
{
using System;
using System.Collections.Generic;
using Tilia.Interactions.Interactables.Interactables.ComponentTags;
using Tilia.Interactions.Interactables.Interactables.Grab;
using Tilia.Interactions.Interactables.Interactables.Grab.Action;
using Tilia.Interactions.Interactables.Interactables.Touch;
using Tilia.Interactions.Interactables.Interactables.Utility;
using Tilia.Interactions.Interactables.Interactors;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using Zinnia.Data.Attribute;
using Zinnia.Data.Collection.List;
using Zinnia.Extension;
using Zinnia.Rule;
using Zinnia.Tracking.Collision;
using Zinnia.Tracking.Collision.Active.Operation.Extraction;
///
/// Sets up the Interactable Prefab based on the provided user settings.
///
public class InteractableConfigurator : MonoBehaviour
{
#region Facade Settings
[Header("Facade Settings")]
[Tooltip("The public interface facade.")]
[SerializeField]
[Restricted]
private InteractableFacade facade;
///
/// The public interface facade.
///
public InteractableFacade Facade
{
get
{
return facade;
}
set
{
facade = value;
}
}
#endregion
#region Restriction Settings
[Header("Restriction Settings")]
[Tooltip("The rule to determine what is not allowed to touch this interactable.")]
[SerializeField]
private RuleContainer disallowedTouchInteractors;
///
/// The rule to determine what is not allowed to touch this interactable.
///
public RuleContainer DisallowedTouchInteractors
{
get
{
return disallowedTouchInteractors;
}
set
{
disallowedTouchInteractors = value;
if (this.IsMemberChangeAllowed())
{
OnAfterDisallowedTouchInteractorsChange();
}
}
}
[Tooltip("The rule to determine what is not allowed to grab this interactable.")]
[SerializeField]
private RuleContainer disallowedGrabInteractors;
///
/// The rule to determine what is not allowed to grab this interactable.
///
public RuleContainer DisallowedGrabInteractors
{
get
{
return disallowedGrabInteractors;
}
set
{
disallowedGrabInteractors = value;
if (this.IsMemberChangeAllowed())
{
OnAfterDisallowedGrabInteractorsChange();
}
}
}
#endregion
#region Container Settings
[Header("Container Settings")]
[Tooltip("The overall container for the interactable consumers.")]
[SerializeField]
private GameObject consumerContainer;
///
/// The overall container for the interactable consumers.
///
public GameObject ConsumerContainer
{
get
{
return consumerContainer;
}
set
{
consumerContainer = value;
if (this.IsMemberChangeAllowed())
{
OnAfterConsumerContainerChange();
}
}
}
[Tooltip("The Rigidbody for the overall collisions.")]
[SerializeField]
private Rigidbody consumerRigidbody;
///
/// The for the overall collisions.
///
public Rigidbody ConsumerRigidbody
{
get
{
return consumerRigidbody;
}
set
{
consumerRigidbody = value;
if (this.IsMemberChangeAllowed())
{
OnAfterConsumerRigidbodyChange();
}
}
}
#endregion
#region Reference Settings
[Header("Reference Settings")]
[Tooltip("The linked CollisionNotifier.")]
[SerializeField]
[Restricted]
private CollisionNotifier collisionNotifier;
///
/// The linked .
///
public CollisionNotifier CollisionNotifier
{
get
{
return collisionNotifier;
}
set
{
collisionNotifier = value;
}
}
[Tooltip("The GameObject that contains the mesh for the Interactable.")]
[SerializeField]
[Restricted]
private GameObject meshContainer;
///
/// The that contains the mesh for the Interactable.
///
public GameObject MeshContainer
{
get
{
return meshContainer;
}
set
{
meshContainer = value;
}
}
[Tooltip("The linked NotifierContainerExtractor for adding collisions.")]
[SerializeField]
[Restricted]
private NotifierContainerExtractor addCollisionExtractor;
///
/// The linked for adding collisions.
///
public NotifierContainerExtractor AddCollisionExtractor
{
get
{
return addCollisionExtractor;
}
set
{
addCollisionExtractor = value;
}
}
[Tooltip("The linked NotifierContainerExtractor for removing collisions.")]
[SerializeField]
[Restricted]
private NotifierContainerExtractor removeCollisionExtractor;
///
/// The linked for removing collisions.
///
public NotifierContainerExtractor RemoveCollisionExtractor
{
get
{
return removeCollisionExtractor;
}
set
{
removeCollisionExtractor = value;
}
}
[Tooltip("The linked GameObjectObservableList.")]
[SerializeField]
[Restricted]
private GameObjectObservableList activeCollisions;
///
/// The linked .
///
public GameObjectObservableList ActiveCollisions
{
get
{
return activeCollisions;
}
set
{
activeCollisions = value;
}
}
[Tooltip("The linked Touch Internal Setup.")]
[SerializeField]
[Restricted]
private TouchInteractableConfigurator touchConfiguration;
///
/// The linked Touch Internal Setup.
///
public TouchInteractableConfigurator TouchConfiguration
{
get
{
return touchConfiguration;
}
set
{
touchConfiguration = value;
}
}
[Tooltip("The linked Grab Internal Setup.")]
[SerializeField]
[Restricted]
private GrabInteractableConfigurator grabConfiguration;
///
/// The linked Grab Internal Setup.
///
public GrabInteractableConfigurator GrabConfiguration
{
get
{
return grabConfiguration;
}
set
{
grabConfiguration = value;
}
}
[Tooltip("The linked Interactable Visibility Status Tag.")]
[SerializeField]
[Restricted]
private InteractableVisibilityStatusTag interactableVisibilityStatusTagContainer;
///
/// The linked Interactable Visibility Status Tag.
///
public InteractableVisibilityStatusTag InteractableVisibilityStatusTagContainer
{
get
{
return interactableVisibilityStatusTagContainer;
}
set
{
interactableVisibilityStatusTagContainer = value;
}
}
#endregion
///
/// The total number of available grab action types.
///
public virtual int GrabActionTypesCount => GrabConfiguration.ActionTypes.NonSubscribableElements.Count;
///
/// Whether the Interactable is visible or not.
///
public virtual bool IsVisible
{
get
{
return meshColliderTriggerStates.Count == 0;
}
set
{
if (value)
{
RestoreGrabStateFromCache();
RestoreConsumerRigidbodyKinematicStateFromCache();
RestoreCollidersAndRenderers();
if (InteractableVisibilityStatusTagContainer != null)
{
InteractableVisibilityStatusTagContainer.enabled = true;
}
}
else
{
CacheAndSetConsumerRigidbodyKinematicState();
CacheAndDisableGrabState();
DisableCollidersAndRenderers();
if (InteractableVisibilityStatusTagContainer != null)
{
InteractableVisibilityStatusTagContainer.enabled = false;
}
}
}
}
///
/// A collection of trigger states for the colliders held within the .
///
protected Dictionary meshColliderTriggerStates = new Dictionary();
///
/// A collection of enabled states for the renderers held within the .
///
protected Dictionary meshRendererEnabledStates = new Dictionary();
///
/// The cached for when the follow/control direction pairing is required to be set up.
///
protected GrabInteractableFollowAction followCachedAction;
///
/// The cached for when the follow/control direction pairing is required to be set up.
///
protected GrabInteractableControlDirectionAction controlDirectionCachedAction;
///
/// The cached kinematic state of the when changing the visibility state.
///
protected bool cachedKinematicState;
///
/// The cached active state of the when changing the visibility state.
///
protected bool cachedGrabAllowanceState;
///
/// Clears .
///
public virtual void ClearDisallowedTouchInteractors()
{
if (!this.IsValidState())
{
return;
}
DisallowedTouchInteractors = default;
}
///
/// Clears .
///
public virtual void ClearDisallowedGrabInteractors()
{
if (!this.IsValidState())
{
return;
}
DisallowedGrabInteractors = default;
}
///
/// Clears .
///
public virtual void ClearConsumerContainer()
{
if (!this.IsValidState())
{
return;
}
ConsumerContainer = default;
}
///
/// Clears .
///
public virtual void ClearConsumerRigidbody()
{
if (!this.IsValidState())
{
return;
}
ConsumerRigidbody = default;
}
///
/// Determines whether the grab configuration is set.
///
/// Whether the grab configuration is set.
public virtual bool IsGrabConfigurationSet()
{
return GrabConfiguration != null;
}
///
/// Gets the for the given Grab .
///
/// The action type to attempt to get.
/// The GameObject for the given action type.
public virtual GameObject GetGrabActionTypeObject(InteractableFactory.ActionType type)
{
return GetGrabActionTypeObject((int)type);
}
///
/// Gets the for the given Grab Action Type index.
///
/// The index to attempt to get.
/// The found GameObject for the given index type.
public virtual GameObject GetGrabActionTypeObject(int index)
{
return GrabConfiguration.ActionTypes.NonSubscribableElements[index];
}
///
/// Gets the current grab configuration primary action.
///
/// The current primary action.
public virtual GrabInteractableAction GetPrimaryAction()
{
return IsGrabConfigurationSet() && GrabConfiguration.PrimaryAction != null ? GrabConfiguration.PrimaryAction : null;
}
///
/// Gets the current grab configuration secondary action.
///
/// The current secondary action.
public virtual GrabInteractableAction GetSecondaryAction()
{
return IsGrabConfigurationSet() && GrabConfiguration.SecondaryAction != null ? GrabConfiguration.SecondaryAction : null;
}
///
/// Updates the primary grab action to the given type.
///
/// The grab action type.
/// The new grab action.
public virtual GrabInteractableAction UpdatePrimaryAction(InteractableFactory.ActionType type)
{
return UpdatePrimaryAction((int)type);
}
///
/// Updates the primary grab action to the given type index.
///
/// The grab action type index.
/// The new grab action.
public virtual GrabInteractableAction UpdatePrimaryAction(int index)
{
GrabInteractableAction action = CreateGrabAction(GetPrimaryAction(), index, 0);
GrabConfiguration.PrimaryAction = action;
SetFollowAndControlDirectionPair();
return action;
}
///
/// Updates the secondary grab action to the given type.
///
/// The grab action type.
/// The new grab action.
public virtual GrabInteractableAction UpdateSecondaryAction(InteractableFactory.ActionType type)
{
return UpdateSecondaryAction((int)type);
}
///
/// Updates the secondary grab action to the given type index.
///
/// The grab action type index.
/// The new grab action.
public virtual GrabInteractableAction UpdateSecondaryAction(int index)
{
GrabInteractableAction action = CreateGrabAction(GetSecondaryAction(), index, 1);
GrabConfiguration.SecondaryAction = action;
SetFollowAndControlDirectionPair();
return action;
}
///
/// Sets the Maximum Angular Velocity of the in radians per seconds.
///
/// The maximum angular velocity in radians per seconds.
public virtual void SetRigidbodyMaxAngularVelocity(float angularVelocity)
{
if (consumerRigidbody == null)
{
return;
}
consumerRigidbody.maxAngularVelocity = angularVelocity;
}
protected virtual void OnEnable()
{
SetFollowAndControlDirectionPair();
}
///
/// Sets up the relevant linkages if the follow and control direction actions are selected in a pairing.
///
protected virtual void SetFollowAndControlDirectionPair()
{
GrabInteractableAction primaryAction = GetPrimaryAction();
GrabInteractableAction secondaryAction = GetSecondaryAction();
if (secondaryAction != null && typeof(GrabInteractableControlDirectionAction).IsAssignableFrom(secondaryAction.GetType()))
{
controlDirectionCachedAction = (GrabInteractableControlDirectionAction)secondaryAction;
controlDirectionCachedAction.LinkedObjects = null;
}
if (primaryAction == null ||
secondaryAction == null ||
!typeof(GrabInteractableFollowAction).IsAssignableFrom(primaryAction.GetType()) ||
!typeof(GrabInteractableControlDirectionAction).IsAssignableFrom(secondaryAction.GetType()))
{
if (followCachedAction != null)
{
followCachedAction.CollisionPointContainerComponent.PointSet.RemoveListener(SetFollowPrecisionPointToDirectionModifierPivot);
followCachedAction.CollisionPointContainerComponent.PointUnset.RemoveListener(UnsetFollowPrecisionPointToDirectionModifierPivot);
followCachedAction = null;
controlDirectionCachedAction = null;
}
return;
}
followCachedAction = (GrabInteractableFollowAction)primaryAction;
controlDirectionCachedAction = (GrabInteractableControlDirectionAction)secondaryAction;
controlDirectionCachedAction.LinkedObjects = followCachedAction.RotationModifiers;
followCachedAction.CollisionPointContainerComponent.PointSet.AddListener(SetFollowPrecisionPointToDirectionModifierPivot);
followCachedAction.CollisionPointContainerComponent.PointUnset.AddListener(UnsetFollowPrecisionPointToDirectionModifierPivot);
}
///
/// Sets up the link between the primary and the secondary to mirror the follow precision point to the control direction pivot.
///
/// The precision point container.
protected virtual void SetFollowPrecisionPointToDirectionModifierPivot(GameObject precisionPointContainer)
{
if (controlDirectionCachedAction.DirectionModifier.ResetOrientationSpeed < float.MaxValue)
{
return;
}
controlDirectionCachedAction.DirectionModifier.PivotRotationMirror = precisionPointContainer;
}
///
/// Unsets the set up done in the method.
///
/// The precision point container.
protected virtual void UnsetFollowPrecisionPointToDirectionModifierPivot(GameObject precisionPointContainer)
{
controlDirectionCachedAction.DirectionModifier.ClearPivotRotationMirror();
}
///
/// Creates a grab action for the given type.
///
/// The current action.
/// The new action type to create.
/// The position the action should take in the hierarcy.
/// The created Grab Action.
protected virtual GrabInteractableAction CreateGrabAction(GrabInteractableAction currentAction, int newActionType, int siblingPosition)
{
if (!IsGrabConfigurationSet())
{
return null;
}
if (currentAction != null)
{
try
{
//If the Unity version supports destroying the nested prefab then do that.
DestroyImmediate(currentAction.gameObject);
}
catch (InvalidOperationException)
{
//Otherwise just disable that GameObject.
currentAction.gameObject.SetActive(false);
}
}
GameObject toInstantiate = GrabConfiguration.ActionTypes.NonSubscribableElements[newActionType];
GameObject actionObject = null;
#if UNITY_EDITOR
actionObject = Application.isPlaying ? Instantiate(toInstantiate) : (GameObject)PrefabUtility.InstantiatePrefab(toInstantiate, facade.Configuration.GrabConfiguration.ActionTypes.transform);
#else
actionObject = Instantiate(toInstantiate);
#endif
actionObject.transform.position = facade.transform.position;
actionObject.transform.rotation = facade.transform.rotation;
actionObject.transform.SetGlobalScale(facade.transform.lossyScale);
actionObject.transform.SetParent(facade.Configuration.GrabConfiguration.ActionTypes.transform);
actionObject.name = actionObject.name.Replace("(Clone)", "(Generated)");
actionObject.transform.SetSiblingIndex(siblingPosition);
return actionObject.GetComponent();
}
///
/// Configures any container data to the sub setup components.
///
protected virtual void ConfigureContainer()
{
TouchConfiguration.ConfigureContainer();
GrabConfiguration.ConfigureContainer();
}
///
/// Caches the existing kinematic state and sets it to kinematic.
///
protected virtual void CacheAndSetConsumerRigidbodyKinematicState()
{
if (consumerRigidbody != null)
{
cachedKinematicState = consumerRigidbody.isKinematic;
consumerRigidbody.isKinematic = true;
if (facade.IsGrabbed)
{
facade.LastUngrabbed.AddListener(HandleHiddenLastUngrab);
}
}
}
///
/// Deals with when a hidden object is last ungrabbed to ensure the kinematic state is correct.
///
/// unused.
protected virtual void HandleHiddenLastUngrab(InteractorFacade _)
{
cachedKinematicState = consumerRigidbody.isKinematic;
consumerRigidbody.isKinematic = true;
}
///
/// Restores the kinematic state of the to the cached state.
///
protected virtual void RestoreConsumerRigidbodyKinematicStateFromCache()
{
if (consumerRigidbody != null)
{
facade.LastUngrabbed.RemoveListener(HandleHiddenLastUngrab);
consumerRigidbody.isKinematic = cachedKinematicState;
if (facade.IsGrabbed)
{
facade.SnapFollowOrientation();
}
}
}
///
/// Caches the existing grab state of the and hides the to prevent grab logic.
///
protected virtual void CacheAndDisableGrabState()
{
if (GrabConfiguration != null && GrabConfiguration.GrabReceiver != null && GrabConfiguration.GrabReceiver.GrabConsumer != null)
{
cachedGrabAllowanceState = GrabConfiguration.GrabReceiver.GrabConsumer.gameObject.activeInHierarchy;
GrabConfiguration.GrabReceiver.GrabConsumer.gameObject.SetActive(false);
}
}
///
/// Restores the grab state of the and applies the active state to the from the cached state.
///
protected virtual void RestoreGrabStateFromCache()
{
if (GrabConfiguration != null && GrabConfiguration.GrabReceiver != null && GrabConfiguration.GrabReceiver.GrabConsumer != null)
{
GrabConfiguration.GrabReceiver.GrabConsumer.gameObject.SetActive(cachedGrabAllowanceState);
}
}
///
/// Hides all of the found and components in the and saves their current state for later restore.
///
protected virtual void DisableCollidersAndRenderers()
{
if (MeshContainer == null)
{
return;
}
meshColliderTriggerStates.Clear();
foreach (Collider current in MeshContainer.GetComponentsInChildren())
{
meshColliderTriggerStates.Add(current, current.isTrigger);
current.isTrigger = true;
}
meshRendererEnabledStates.Clear();
foreach (Renderer current in MeshContainer.GetComponentsInChildren())
{
meshRendererEnabledStates.Add(current, current.enabled);
current.enabled = false;
}
}
///
/// Restores all of the current saved states for the found and components found in the .
///
protected virtual void RestoreCollidersAndRenderers()
{
if (MeshContainer == null)
{
return;
}
foreach (Collider current in MeshContainer.GetComponentsInChildren())
{
if (meshColliderTriggerStates.TryGetValue(current, out bool state))
{
current.isTrigger = state;
}
}
meshColliderTriggerStates.Clear();
foreach (Renderer current in MeshContainer.GetComponentsInChildren())
{
if (meshRendererEnabledStates.TryGetValue(current, out bool state))
{
current.enabled = state;
}
}
meshRendererEnabledStates.Clear();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterDisallowedTouchInteractorsChange()
{
TouchConfiguration.TouchValidity.ReceiveValidity = DisallowedTouchInteractors;
}
///
/// Called after has been changed.
///
protected virtual void OnAfterDisallowedGrabInteractorsChange()
{
GrabConfiguration.GrabReceiver.GrabValidity.ReceiveValidity = DisallowedGrabInteractors;
}
///
/// Called after has been changed.
///
protected virtual void OnAfterConsumerContainerChange()
{
ConfigureContainer();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterConsumerRigidbodyChange()
{
ConfigureContainer();
}
}
}