// --------------------------------------------------------------------------------------------------------------------
//
// 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
}
}