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