// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components.Singleton;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Manipulation
{
///
/// Manager that takes care of updating all the manipulation mechanics. The manipulation system handles three main
/// types of entities:
///
/// -
/// : Components usually assigned to each hand of an and
/// that are able to grab objects
///
/// - : Objects that can be grabbed
/// - : Anchors where grabbable objects can be placed
///
///
public partial class UxrGrabManager : UxrSingleton
{
#region Public Types & Data
///
/// Event called whenever a component is about to try to grab something (a hand is beginning
/// to close). If it ends up grabbing something will depend on whether there is a in
/// reach.
/// Properties available:
///
/// -
/// : Grabber that tried to grab.
///
///
///
public event EventHandler GrabTrying;
///
/// Event called whenever a component is about to grab a .
/// The following properties from will contain meaningful data:
///
/// -
/// : Object that is about to be grabbed.
///
/// -
/// : Anchor where the object is currently placed. Null
/// if it isn't placed.
///
/// -
/// : Grabber that is about to grab the object.
///
/// -
/// : Grab point index of the object that is about to be
/// grabbed.
///
/// -
/// : true if it is already being grabbed and
/// will be grabbed with one more hand after. False if no hand is currently grabbing it.
///
///
///
public event EventHandler ObjectGrabbing;
///
/// Same as but called right after the object was grabbed.
///
public event EventHandler ObjectGrabbed;
///
/// Event called whenever a component is about to release the
/// that it is holding and there is no nearby
/// to place it on.
/// The following properties from will contain meaningful data:
///
/// -
/// : Object that is about to be released.
///
/// -
/// : Anchor where the object was originally grabbed
/// from. Null if it wasn't grabbed from an anchor.
///
/// -
/// : Grabber that is about to release the object.
///
/// -
/// : Grab point index of the object that is being
/// grabbed by the .
///
/// -
/// : true if it is already being grabbed with another hand
/// that will keep holding it. False if no other hand is currently grabbing it.
///
/// -
/// : True if it was released because another
/// grabbed it, false otherwise. if
/// is
/// true then will tell if it was released by all hands
/// (false) or if it was just released by one hand and the other(s) still keep the grab (true).
///
/// -
/// : Velocity the object is being released with.
///
/// -
/// : Angular velocity the object is being
/// released with.
///
///
///
///
/// If the object is being released on a that can hold it, it will
/// generate a event instead. Whenever an object is released it will either generate
/// either a Place or Release event, but not both.
///
public event EventHandler ObjectReleasing;
///
/// Same as but called right after the object was released.
///
public event EventHandler ObjectReleased;
///
/// Event called whenever a is about to be placed on an
/// .
/// The following properties from will contain meaningful data:
///
/// -
/// : Object that is about to be placed.
///
/// -
/// : Anchor where the object is about to be placed on.
///
/// -
/// : Grabber that is placing the object.
///
/// -
/// : Grab point index of the object that is being
/// grabbed by the .
///
///
///
///
/// If the object is being placed it will not generate a event. Whenever an object is
/// released it will either generate either a Place or Release event, but not both.
///
public event EventHandler ObjectPlacing;
///
/// Same as but called right after the object was placed.
///
public event EventHandler ObjectPlaced;
///
/// Event called whenever a is about to be removed from an
/// .
/// The following properties from will contain meaningful data:
///
/// -
/// : Object that is about to be removed.
///
/// -
/// : Anchor where the object is currently placed.
///
/// -
/// : Grabber that is about to remove the object by grabbing it.
/// This can be null if the object is removed through code using ,
/// or >
///
/// -
/// : Only if the object is being removed by grabbing it:
/// Grab point index of the object that is about to be grabbed by the .
///
///
///
public event EventHandler ObjectRemoving;
///
/// Same as but called right after the object was removed.
///
public event EventHandler ObjectRemoved;
///
/// Event called whenever an being grabbed by a entered the
/// valid placement range (distance) of a compatible .
/// The following properties from will contain meaningful data:
///
/// -
/// : Object that entered the valid placement range.
///
/// -
/// : Anchor where the object can potentially be placed.
///
/// -
/// : Grabber that is holding the object. If more than one
/// grabber is holding it, it will indicate the first one to grab it.
///
///
///
///
/// Only enter/leave events will be generated. To check if an object can be placed on an anchor use
/// .
///
///
public event EventHandler AnchorRangeEntered;
///
/// Same as but when leaving the valid range.
///
///
/// Only enter/leave events will be generated. To check if an object can be placed on an anchor use
/// .
///
///
public event EventHandler AnchorRangeLeft;
///
/// Event called whenever a enters the valid grab range (distance) of a
/// placed on an .
/// The following properties from will contain meaningful data:
///
/// -
/// : Object that is within reach.
///
/// -
/// : Anchor where the object is placed.
///
/// -
/// : Grabber that entered the valid grab range.
///
/// -
/// : Grab point index that is within reach.
///
///
///
///
/// Only enter/leave events will be generated. To check if an object can be grabbed use
/// .
///
///
public event EventHandler PlacedObjectRangeEntered;
///
/// Same as but when leaving the valid range.
///
///
/// Only enter/leave events will be generated. To check if an object can be grabbed use
/// .
///
///
public event EventHandler PlacedObjectRangeLeft;
///
/// Gets or sets the manipulation features that are used when the manager is updated.
///
public UxrManipulationFeatures Features { get; set; } = UxrManipulationFeatures.All;
#endregion
#region Internal Methods
///
/// Updates the grab manager to the current frame.
///
internal void UpdateManager()
{
// Initializes the variables for a manipulation frame update computation.
InitializeManipulationFrame();
// Updates the grabbable objects based on manipulation logic
UpdateManipulation();
if (Features.HasFlag(UxrManipulationFeatures.Affordances))
{
// Updates visual feedback states (objects that can be grabbed, anchors where a grabbed object can be placed on, etc.)
UpdateAffordances();
}
// Perform operations that need to be done at the end of the updating process.
FinalizeManipulationFrame();
}
#endregion
#region Unity
///
/// Initializes the manager and subscribes to global events.
///
protected override void Awake()
{
base.Awake();
UxrGrabbableObjectAnchor.GlobalEnabled += GrabbableObjectAnchor_Enabled;
UxrGrabbableObjectAnchor.GlobalDisabled += GrabbableObjectAnchor_Disabled;
UxrGrabbableObject.GlobalDisabled += GrabbableObject_Disabled;
}
///
/// Unsubscribes from global events.
///
protected override void OnDestroy()
{
base.OnDestroy();
UxrGrabbableObjectAnchor.GlobalEnabled -= GrabbableObjectAnchor_Enabled;
UxrGrabbableObjectAnchor.GlobalDisabled -= GrabbableObjectAnchor_Disabled;
UxrGrabbableObject.GlobalDisabled += GrabbableObject_Disabled;
}
///
/// Subscribes to events.
///
protected override void OnEnable()
{
base.OnEnable();
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
}
///
/// Unsubscribes from events.
///
protected override void OnDisable()
{
base.OnDisable();
UxrAvatar.GlobalAvatarMoved -= UxrAvatar_GlobalAvatarMoved;
}
#endregion
#region Event Handling Methods
///
/// Called when a grabbable object anchor was enabled. Adds it to the internal list.
///
/// Anchor that was enabled
private void GrabbableObjectAnchor_Enabled(UxrGrabbableObjectAnchor anchor)
{
_grabbableObjectAnchors.Add(anchor, new GrabbableObjectAnchorInfo());
}
///
/// Called when a grabbable object anchor was disabled. Removes it from the internal list.
///
/// Anchor that was disabled
private void GrabbableObjectAnchor_Disabled(UxrGrabbableObjectAnchor anchor)
{
_grabbableObjectAnchors.Remove(anchor);
}
///
/// Called when a grabbable object was disabled. Removes it from current grabs if present.
///
/// Grabbable object that was disabled
private void GrabbableObject_Disabled(UxrGrabbableObject grabbableObject)
{
if (_currentManipulations.ContainsKey(grabbableObject))
{
_currentManipulations.Remove(grabbableObject);
}
}
///
/// Called when an avatar was moved due to regular movement or teleportation. It is used to process the objects that
/// are being grabbed to the avatar to keep it in the same relative position/orientation.
///
/// Event sender
/// Event parameters
private void UxrAvatar_GlobalAvatarMoved(object sender, UxrAvatarMoveEventArgs e)
{
UxrAvatar avatar = sender as UxrAvatar;
if (avatar == null || avatar.AvatarMode == UxrAvatarMode.UpdateExternally)
{
return;
}
// Create anonymous pairs of grabbable objects and their grabs that are affected by the avatar position change
var dependencies = _currentManipulations.Where(pair => pair.Value.Grabbers.Any(g => g.Avatar == avatar)).Select(pair => new { GrabbableObject = pair.Key, Grabs = pair.Value.Grabs.Where(g => g.Grabber.Avatar == avatar) });
foreach (var dependency in dependencies)
{
UxrGrabbableObject grabbableObject = dependency.GrabbableObject;
// Move grabbed objects without being parented to avatar to new position/orientation to avoid rubber-band effects
if (!grabbableObject.transform.HasParent(avatar.transform))
{
UxrGrabbableObject grabbableRoot = grabbableObject.AllParents.LastOrDefault() ?? grabbableObject;
// Use this handy method to make the grabbable object keep the relative positioning to the avatar
e.ReorientRelativeToAvatar(grabbableRoot.transform);
grabbableRoot.LocalPositionBeforeUpdate = grabbableRoot.transform.localPosition;
grabbableRoot.LocalRotationBeforeUpdate = grabbableRoot.transform.localRotation;
ConstrainTransform(grabbableRoot);
KeepGripsInPlace(grabbableRoot);
foreach (UxrGrabbableObject grabbableChild in grabbableRoot.AllChildren)
{
grabbableChild.LocalPositionBeforeUpdate = grabbableChild.transform.localPosition;
grabbableChild.LocalRotationBeforeUpdate = grabbableChild.transform.localRotation;
ConstrainTransform(grabbableChild);
KeepGripsInPlace(grabbableChild);
}
}
}
}
#endregion
#region Event Trigger Methods
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnGrabTrying(UxrManipulationEventArgs e, bool propagateEvent)
{
if (UxrGlobalSettings.Instance.LogLevelManipulation >= UxrLogLevel.Verbose)
{
Debug.Log($"{UxrConstants.ManipulationModule} Trying to grab using {e.Grabber}.");
}
if (propagateEvent)
{
GrabTrying?.Invoke(this, e);
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnObjectGrabbing(UxrManipulationEventArgs e, bool propagateEvent)
{
if (UxrGlobalSettings.Instance.LogLevelManipulation >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.ManipulationModule} {e.ToString(UxrGlobalSettings.Instance.LogLevelManipulation == UxrLogLevel.Verbose)}");
}
if (propagateEvent)
{
ObjectGrabbing?.Invoke(this, e);
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnObjectGrabbed(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
ObjectGrabbed?.Invoke(this, e);
}
if (e.GrabbableObject)
{
e.GrabbableObject.UpdateGrabbableDependencies();
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnObjectReleasing(UxrManipulationEventArgs e, bool propagateEvent)
{
if (UxrGlobalSettings.Instance.LogLevelManipulation >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.ManipulationModule} {e.ToString(UxrGlobalSettings.Instance.LogLevelManipulation == UxrLogLevel.Verbose)}");
}
if (propagateEvent)
{
ObjectReleasing?.Invoke(this, e);
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnObjectReleased(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
ObjectReleased?.Invoke(this, e);
}
if (e.GrabbableObject)
{
e.GrabbableObject.UpdateGrabbableDependencies();
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnObjectPlacing(UxrManipulationEventArgs e, bool propagateEvent)
{
if (UxrGlobalSettings.Instance.LogLevelManipulation >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.ManipulationModule} {e.ToString(UxrGlobalSettings.Instance.LogLevelManipulation == UxrLogLevel.Verbose)}");
}
if (propagateEvent)
{
ObjectPlacing?.Invoke(this, e);
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnObjectPlaced(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
ObjectPlaced?.Invoke(this, e);
}
if (e.GrabbableObject)
{
e.GrabbableObject.UpdateGrabbableDependencies();
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnObjectRemoving(UxrManipulationEventArgs e, bool propagateEvent)
{
if (UxrGlobalSettings.Instance.LogLevelManipulation >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.ManipulationModule} {e.ToString(UxrGlobalSettings.Instance.LogLevelManipulation == UxrLogLevel.Verbose)}");
}
if (propagateEvent)
{
ObjectRemoving?.Invoke(this, e);
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnObjectRemoved(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
ObjectRemoved?.Invoke(this, e);
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnAnchorRangeEntered(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
AnchorRangeEntered?.Invoke(this, e);
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnAnchorRangeLeft(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
AnchorRangeLeft?.Invoke(this, e);
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnPlacedObjectRangeEntered(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
PlacedObjectRangeEntered?.Invoke(this, e);
}
}
///
/// Event trigger for .
///
/// Event parameters
/// Whether to propagate the event
private void OnPlacedObjectRangeLeft(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
PlacedObjectRangeLeft?.Invoke(this, e);
}
}
#endregion
#region Private Methods
///
/// Initializes the variables for a manipulation frame update computation.
///
private void InitializeManipulationFrame()
{
// Store the unprocessed grabber positions for this update.
foreach (UxrGrabber grabber in UxrGrabber.AllComponents)
{
grabber.UnprocessedGrabberPosition = grabber.transform.position;
grabber.UnprocessedGrabberRotation = grabber.transform.rotation;
}
// Update grabbable object information
void InitializeGrabbableData(UxrGrabbableObject grabbableObject)
{
grabbableObject.DirectLookAtChildProcessedCount = 0;
grabbableObject.DirectLookAtChildGrabbedCount = grabbableObject.DirectChildrenLookAts.Count(IsBeingGrabbed);
grabbableObject.LocalPositionBeforeUpdate = grabbableObject.transform.localPosition;
grabbableObject.LocalRotationBeforeUpdate = grabbableObject.transform.localRotation;
}
foreach (KeyValuePair manipulationInfoPair in _currentManipulations)
{
if (manipulationInfoPair.Key == null)
{
continue;
}
UxrGrabbableObject grabbableParent = manipulationInfoPair.Key.GrabbableParent;
InitializeGrabbableData(manipulationInfoPair.Key);
if (grabbableParent != null)
{
InitializeGrabbableData(grabbableParent);
}
foreach (UxrGrabbableObject child in manipulationInfoPair.Key.DirectChildrenLookAts)
{
InitializeGrabbableData(child);
}
manipulationInfoPair.Value.LocalManipulationRotationPivot = Vector3.zero;
}
// Initialize some anchor variables for later
foreach (KeyValuePair anchorPair in _grabbableObjectAnchors)
{
if (anchorPair.Key.CurrentPlacedObject == null)
{
anchorPair.Value.HadCompatibleObjectNearLastFrame = anchorPair.Value.HasCompatibleObjectNear;
anchorPair.Value.HasCompatibleObjectNear = false;
}
else
{
anchorPair.Value.GrabberNear = null;
}
anchorPair.Value.FullGrabberNear = null;
anchorPair.Value.GrabPointNear = -1;
}
}
///
/// Performs operations that require to be done at the end of the manipulation update pipeline.
///
private void FinalizeManipulationFrame()
{
// Update grabbers
foreach (UxrGrabber grabber in UxrGrabber.EnabledComponents)
{
grabber.UpdateThrowPhysicsInfo();
grabber.UpdateHandGrabberRenderer();
}
}
///
/// Updates visual feedback states (objects that can be grabbed, anchors where a grabbed object can be placed on,
/// etc.).
///
private void UpdateAffordances()
{
// Look for grabbed objects that can be placed on anchors
foreach (KeyValuePair manipulationInfoPair in _currentManipulations)
{
UxrGrabbableObjectAnchor anchorTargetCandidate = null;
float minDistance = float.MaxValue;
if (manipulationInfoPair.Key.UsesGrabbableParentDependency == false && manipulationInfoPair.Key.IsPlaceable)
{
foreach (KeyValuePair anchorPair in _grabbableObjectAnchors)
{
if (manipulationInfoPair.Key.CanBePlacedOnAnchor(anchorPair.Key, out float distance) && distance < minDistance)
{
anchorTargetCandidate = anchorPair.Key;
minDistance = distance;
}
}
}
// Is there a compatible anchor if we would release it? store the grabber for later
if (anchorTargetCandidate != null)
{
_grabbableObjectAnchors[anchorTargetCandidate].HasCompatibleObjectNear = true;
_grabbableObjectAnchors[anchorTargetCandidate].FullGrabberNear = manipulationInfoPair.Value.Grabs[0].Grabber;
_grabbableObjectAnchors[anchorTargetCandidate].LastFullGrabberNear = manipulationInfoPair.Value.Grabs[0].Grabber;
}
}
// Look for objects that can be grabbed to update feedback objects (blinks, labels...).
// First pass: get closest candidate for each grabber.
Dictionary> possibleGrabs = null;
foreach (UxrGrabber grabber in UxrGrabber.EnabledComponents)
{
if (grabber.GrabbedObject == null)
{
if (GetClosestGrabbableObject(grabber, out UxrGrabbableObject grabbableCandidate, out int grabPointCandidate) && !IsBeingGrabbed(grabbableCandidate, grabPointCandidate))
{
if (possibleGrabs == null)
{
possibleGrabs = new Dictionary>();
}
if (possibleGrabs.ContainsKey(grabbableCandidate))
{
possibleGrabs[grabbableCandidate].Add(grabPointCandidate);
}
else
{
possibleGrabs.Add(grabbableCandidate, new List { grabPointCandidate });
}
}
}
}
// Second pass: update visual feedback objects for grabbable objects.
foreach (UxrGrabbableObject grabbable in UxrGrabbableObject.EnabledComponents)
{
// First disable all needed, then enable them in another pass because some points may share the same object
for (int point = 0; point < grabbable.GrabPointCount; ++point)
{
GameObject enableOnHandNear = grabbable.GetGrabPoint(point).EnableOnHandNear;
if (enableOnHandNear)
{
bool enableObject = false;
List grabPoints = null;
if (possibleGrabs != null && possibleGrabs.TryGetValue(grabbable, out grabPoints))
{
enableObject = grabPoints.Contains(point);
}
if (!enableObject && enableOnHandNear.activeSelf)
{
// Try to find first if other point needs to enable it
bool foundEnable = false;
for (int pointOther = 0; pointOther < grabbable.GrabPointCount; ++pointOther)
{
GameObject enableOnHandNearOther = grabbable.GetGrabPoint(pointOther).EnableOnHandNear;
if (enableOnHandNear == enableOnHandNearOther)
{
if (possibleGrabs != null && possibleGrabs.TryGetValue(grabbable, out List grabPointsOther))
{
foundEnable = grabPoints.Contains(pointOther);
if (foundEnable)
{
break;
}
}
}
}
if (!foundEnable)
{
enableOnHandNear.SetActive(false);
break;
}
}
}
}
for (int point = 0; point < grabbable.GrabPointCount; ++point)
{
GameObject enableOnHandNear = grabbable.GetGrabPoint(point).EnableOnHandNear;
if (enableOnHandNear)
{
bool enableObject = false;
if (possibleGrabs != null && possibleGrabs.TryGetValue(grabbable, out List grabPoints))
{
enableObject = grabPoints.Contains(point);
}
if (enableObject && !enableOnHandNear.activeSelf)
{
enableOnHandNear.SetActive(true);
break;
}
}
}
}
// Look for empty hand being able to grab something from an anchor to update anchor visual feedback objects later and also raise events. First pass: gather info.
foreach (UxrGrabber grabber in UxrGrabber.EnabledComponents)
{
if (grabber.GrabbedObject == null)
{
UxrGrabbableObjectAnchor anchorCandidate = null;
int grabPointCandidate = 0;
int maxPriority = int.MinValue;
float minDistanceWithoutRotation = float.MaxValue; // Between different objects we don't take orientations into account
foreach (KeyValuePair anchorPair in _grabbableObjectAnchors)
{
UxrGrabbableObjectAnchor grabbableAnchor = anchorPair.Key;
if (grabbableAnchor.CurrentPlacedObject != null)
{
// For the same object we will not just consider the distance but also how close the grabber is to the grip orientation
float minDistance = float.MaxValue;
for (int point = 0; point < grabbableAnchor.CurrentPlacedObject.GrabPointCount; ++point)
{
if (grabbableAnchor.CurrentPlacedObject.CanBeGrabbedByGrabber(grabber, point))
{
grabbableAnchor.CurrentPlacedObject.GetDistanceFromGrabber(grabber, point, out float distance, out float distanceWithoutRotation);
if (grabbableAnchor.CurrentPlacedObject.Priority > maxPriority)
{
anchorCandidate = grabbableAnchor;
grabPointCandidate = point;
minDistance = distance;
minDistanceWithoutRotation = distanceWithoutRotation;
maxPriority = grabbableAnchor.CurrentPlacedObject.Priority;
}
else
{
if ((anchorCandidate == grabbableAnchor && distance < minDistance) || (anchorCandidate != grabbableAnchor && distanceWithoutRotation < minDistanceWithoutRotation))
{
anchorCandidate = grabbableAnchor;
grabPointCandidate = point;
minDistance = distance;
minDistanceWithoutRotation = distanceWithoutRotation;
}
}
}
}
}
}
if (anchorCandidate != null)
{
_grabbableObjectAnchors[anchorCandidate].GrabberNear = null;
_grabbableObjectAnchors[anchorCandidate].GrabPointNear = grabPointCandidate;
}
}
}
// Second pass: update object states and raise events.
foreach (KeyValuePair anchorPair in _grabbableObjectAnchors)
{
if (anchorPair.Key.CurrentPlacedObject == null)
{
if (anchorPair.Value.LastValidGrabberNear != null)
{
OnPlacedObjectRangeEntered(UxrManipulationEventArgs.FromOther(UxrManipulationEventType.PlacedObjectRangeEntered, anchorPair.Key.CurrentPlacedObject, anchorPair.Key, anchorPair.Value.LastValidGrabberNear, anchorPair.Value.LastValidGrabPointNear), true);
anchorPair.Value.LastValidGrabberNear = null;
anchorPair.Value.LastValidGrabPointNear = -1;
}
if (anchorPair.Value.HasCompatibleObjectNear && !anchorPair.Value.HadCompatibleObjectNearLastFrame)
{
OnAnchorRangeEntered(UxrManipulationEventArgs.FromOther(UxrManipulationEventType.AnchorRangeEntered, anchorPair.Value.FullGrabberNear.GrabbedObject, anchorPair.Key, anchorPair.Value.FullGrabberNear), true);
}
if (!anchorPair.Value.HasCompatibleObjectNear && anchorPair.Value.HadCompatibleObjectNearLastFrame)
{
OnAnchorRangeLeft(UxrManipulationEventArgs.FromOther(UxrManipulationEventType.AnchorRangeLeft, anchorPair.Value.LastFullGrabberNear.GrabbedObject, anchorPair.Key, anchorPair.Value.LastFullGrabberNear), true);
}
if (anchorPair.Key.ActivateOnCompatibleNear)
{
anchorPair.Key.ActivateOnCompatibleNear.SetActive(anchorPair.Value.HasCompatibleObjectNear);
}
if (anchorPair.Key.ActivateOnCompatibleNotNear)
{
anchorPair.Key.ActivateOnCompatibleNotNear.SetActive(!anchorPair.Value.HasCompatibleObjectNear);
}
if (anchorPair.Key.ActivateOnHandNearAndGrabbable)
{
anchorPair.Key.ActivateOnHandNearAndGrabbable.SetActive(false);
}
}
else
{
if (anchorPair.Value.GrabberNear != anchorPair.Value.LastValidGrabberNear)
{
if (anchorPair.Value.GrabberNear != null)
{
OnPlacedObjectRangeEntered(UxrManipulationEventArgs.FromOther(UxrManipulationEventType.PlacedObjectRangeEntered, anchorPair.Key.CurrentPlacedObject, anchorPair.Key, anchorPair.Value.GrabberNear, anchorPair.Value.GrabPointNear), true);
}
else if (anchorPair.Value.LastValidGrabberNear != null)
{
OnPlacedObjectRangeLeft(UxrManipulationEventArgs.FromOther(UxrManipulationEventType.PlacedObjectRangeLeft, anchorPair.Key.CurrentPlacedObject, anchorPair.Key, anchorPair.Value.LastValidGrabberNear, anchorPair.Value.GrabPointNear), true);
}
anchorPair.Value.LastValidGrabberNear = anchorPair.Value.GrabberNear;
anchorPair.Value.LastValidGrabPointNear = anchorPair.Value.GrabPointNear;
}
if (anchorPair.Key.ActivateOnHandNearAndGrabbable)
{
anchorPair.Key.ActivateOnHandNearAndGrabbable.SetActive(anchorPair.Value.GrabberNear != null);
}
if (anchorPair.Key.ActivateOnPlaced)
{
anchorPair.Key.ActivateOnPlaced.SetActive(true);
}
if (anchorPair.Key.ActivateOnEmpty)
{
anchorPair.Key.ActivateOnEmpty.SetActive(false);
}
}
}
}
#endregion
#region Private Types & Data
private Dictionary _currentManipulations = new Dictionary();
private readonly Dictionary _grabbableObjectAnchors = new Dictionary();
#endregion
}
}