// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using UltimateXR.Core.Components; using UnityEngine; namespace UltimateXR.Manipulation { /// /// Component that, added to a , will enable objects to be /// placed on it. /// Some of the main features of grabbable object anchors are: /// /// /// Placement mechanics are handled automatically by the . There is no special /// requirement to set it up in a scene, the grab manager will be available as soon as it is required. /// /// /// Compatible tags allow to model which objects can be placed on the anchor. If the list is empty, the /// anchor is compatible with all other that do not have a tag. /// /// /// Events such as and allow to write logic when a user interacts /// with the anchor. Each one has pre and post events. /// /// /// , , /// , and /// can be used to activate/deactivate objects on manipulation events. They can /// be assigned during edit-time using the inspector and also at runtime. /// /// /// and can be used to model complex /// compatibility behaviour that changes at runtime. /// /// /// public partial class UxrGrabbableObjectAnchor : UxrComponent { #region Inspector Properties/Serialized Fields [SerializeField] private List _compatibleTags = new List(); [SerializeField] private float _maxPlaceDistance = 0.1f; [SerializeField] private bool _alignTransformUseSelf = true; [SerializeField] private Transform _alignTransform; [SerializeField] private bool _dropProximityTransformUseSelf = true; [SerializeField] private Transform _dropProximityTransform; [SerializeField] private GameObject _activateOnCompatibleNear; [SerializeField] private GameObject _activateOnCompatibleNotNear; [SerializeField] private GameObject _activateOnHandNearAndGrabbable; [SerializeField] private GameObject _activateOnPlaced; [SerializeField] private GameObject _activateOnEmpty; #endregion #region Public Types & Data /// /// Event called right before an object is placed on the anchor. /// public event EventHandler Placing; /// /// Event called right after an object was placed on the anchor. /// public event EventHandler Placed; /// /// Event called right before the currently placed object is removed from the anchor. /// public event EventHandler Removing; /// /// Event called right after the currently placed object is removed from the anchor. /// public event EventHandler Removed; /// /// Event called right after an object that was placed on the anchor ended its smooth placing transition. /// public event EventHandler SmoothPlaceTransitionEnded; /// /// Gets the that will be used to snap the placed on it. /// public Transform AlignTransform { get { if (_alignTransform == null || _alignTransformUseSelf) { return transform; } return _alignTransform; } } /// /// Gets the that will be used to compute the distance to /// that can be placed on it, in order to determine if they are close enough. /// public Transform DropProximityTransform { get { if (_dropProximityTransform == null || _dropProximityTransformUseSelf) { return transform; } return _dropProximityTransform; } } /// /// Gets the that is currently placed on the anchor. /// public UxrGrabbableObject CurrentPlacedObject { get => _currentPlacedObject; internal set => _currentPlacedObject = value; } /// /// Gets or sets the maximum distance from which an object that is released will be placed on the anchor. /// public float MaxPlaceDistance { get => _maxPlaceDistance; set => _maxPlaceDistance = value; } /// /// Gets or sets the object that will be enabled or disabled depending on if there is a grabbed compatible /// close enough to be placed on it. /// public GameObject ActivateOnCompatibleNear { get => _activateOnCompatibleNear; set => _activateOnCompatibleNear = value; } /// /// Gets or sets the object that will be enabled or disabled depending on if there isn't a grabbed compatible /// close enough to be placed on it. /// public GameObject ActivateOnCompatibleNotNear { get => _activateOnCompatibleNotNear; set => _activateOnCompatibleNotNear = value; } /// /// Gets or sets the object that will be enabled or disabled depending on if there is a /// currently placed on the anchor and a close enough to /// grab it. /// public GameObject ActivateOnHandNearAndGrabbable { get => _activateOnHandNearAndGrabbable; set => _activateOnHandNearAndGrabbable = value; } /// /// Gets or sets the object that will be enabled or disabled depending on if there is a /// currently placed on the anchor. /// public GameObject ActivateOnPlaced { get => _activateOnPlaced; set => _activateOnPlaced = value; } /// /// Gets or sets the object that will be enabled or disabled depending on if there isn't a /// currently placed on the anchor. /// public GameObject ActivateOnEmpty { get => _activateOnEmpty; set => _activateOnEmpty = value; } #endregion #region Public Methods /// /// Adds compatible tags to the list of compatible tags that control which objects can be placed on the anchor. /// /// Tags to add /// tags is null public void AddCompatibleTags(params string[] tags) { if (tags == null) { throw new ArgumentNullException(nameof(tags)); } _compatibleTags.AddRange(tags); } /// /// Removes compatible tags from the list of compatible tags that control which objects can be placed on the anchor. /// /// Tags to remove /// tags is null public void RemoveCompatibleTags(params string[] tags) { if (tags == null) { throw new ArgumentNullException(nameof(tags)); } foreach (string tag in tags) { _compatibleTags.Remove(tag); } } /// /// Removes the currently placed object, if there is any, from the anchor. /// /// Whether the call should generate any events or not public void RemoveObject(bool propagateEvents) { if (CurrentPlacedObject != null) { UxrGrabManager.Instance.RemoveObjectFromAnchor(CurrentPlacedObject, propagateEvents); } } /// /// Adds a placing validator to the internal list of validators. Placing validators are functions that are used in /// addition to compatibility tags in order to determine if a can be placed on the /// anchor. /// An object can be placed on an anchor if the tag is compatible and if it is allowed by all of the placing /// validators. /// /// /// New placing validator function to add. It takes an as input /// and returns a boolean telling whether it can be placed or not /// /// The validator function is null public void AddPlacingValidator(Func validator) { if (validator == null) { throw new ArgumentNullException(nameof(validator)); } _placingValidators.Add(validator); } /// /// Removes a placing validator added using . /// /// Validator to remove /// the validator function is null public void RemovePlacingValidator(Func validator) { if (validator == null) { throw new ArgumentNullException(nameof(validator)); } _placingValidators.Remove(validator); } /// /// Checks whether the given is compatible with the anchor, which means that it can /// potentially be placed on it if there is no other object placed. /// /// Object to check /// Whether the object is compatible with the anchor public bool IsCompatibleObject(UxrGrabbableObject grabbableObject) { return grabbableObject != null && _placingValidators.All(v => v(grabbableObject)) && IsCompatibleObjectTag(grabbableObject.Tag); } #endregion #region Unity /// /// Initializes the component. /// protected override void Awake() { base.Awake(); UxrGrabManager.Instance.Poke(); if (_activateOnCompatibleNear != null) { _activateOnCompatibleNear.SetActive(false); } if (_activateOnCompatibleNotNear != null) { _activateOnCompatibleNotNear.SetActive(false); } if (_activateOnHandNearAndGrabbable != null) { _activateOnHandNearAndGrabbable.SetActive(false); } } /// /// Removes the validators. /// protected override void OnDestroy() { base.OnDestroy(); _placingValidators.Clear(); } /// /// Performs additional initialization. /// protected override void Start() { base.Start(); if (_activateOnPlaced != null) { _activateOnPlaced.SetActive(CurrentPlacedObject != null); } if (_activateOnEmpty != null) { _activateOnEmpty.SetActive(CurrentPlacedObject == null); } } #endregion #region Event Trigger Methods /// /// Event trigger for . /// /// Event parameters internal void RaisePlacingEvent(UxrManipulationEventArgs e) { Placing?.Invoke(this, e); } /// /// Event trigger for . /// /// Event parameters internal void RaisePlacedEvent(UxrManipulationEventArgs e) { Placed?.Invoke(this, e); _smoothPlaceEventArgs = e; } /// /// Event trigger for . /// /// Event parameters internal void RaiseRemovingEvent(UxrManipulationEventArgs e) { Removing?.Invoke(this, e); } /// /// Event trigger for . /// /// Event parameters internal void RaiseRemovedEvent(UxrManipulationEventArgs e) { Removed?.Invoke(this, e); } /// /// Event trigger for . /// internal void RaiseSmoothTransitionPlaceEnded() { SmoothPlaceTransitionEnded?.Invoke(this, _smoothPlaceEventArgs); } #endregion #region Private Methods /// /// Checking whether the given tag is compatible with the anchor. /// /// Tag to check whether it is compatible /// Whether the tag is compatible private bool IsCompatibleObjectTag(string otherTag) { if (_compatibleTags == null || _compatibleTags.Count == 0) { return string.IsNullOrEmpty(otherTag); } return _compatibleTags.Contains(otherTag); } #endregion #region Private Types & Data private readonly List> _placingValidators = new List>(); private UxrManipulationEventArgs _smoothPlaceEventArgs; private UxrGrabbableObject _currentPlacedObject; #endregion } }