Files
dungeons/Assets/UltimateXR/Runtime/Scripts/Manipulation/UxrGrabManager.cs
2024-08-06 21:58:35 +02:00

989 lines
45 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabManager.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
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
{
/// <summary>
/// Manager that takes care of updating all the manipulation mechanics. The manipulation system handles three main
/// types of entities:
/// <list type="bullet">
/// <item>
/// <see cref="UxrGrabber" />: Components usually assigned to each hand of an <see cref="UxrAvatar" /> and
/// that are able to grab objects
/// </item>
/// <item><see cref="UxrGrabbableObject" />: Objects that can be grabbed</item>
/// <item><see cref="UxrGrabbableObjectAnchor" />: Anchors where grabbable objects can be placed</item>
/// </list>
/// </summary>
public partial class UxrGrabManager : UxrSingleton<UxrGrabManager>
{
#region Public Types & Data
/// <summary>
/// Event called whenever a <see cref="UxrGrabber" /> 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 <see cref="UxrGrabbableObject" /> in
/// reach.
/// Properties available:
/// <list type="bullet">
/// <item>
/// <see cref="UxrManipulationEventArgs.Grabber" />: Grabber that tried to grab.
/// </item>
/// </list>
/// </summary>
public event EventHandler<UxrManipulationEventArgs> GrabTrying;
/// <summary>
/// Event called whenever a <see cref="UxrGrabber" /> component is about to grab a <see cref="UxrGrabbableObject" />.
/// The following properties from <see cref="UxrManipulationEventArgs" /> will contain meaningful data:
/// <list type="bullet">
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableObject" />: Object that is about to be grabbed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableAnchor" />: Anchor where the object is currently placed. Null
/// if it isn't placed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.Grabber" />: Grabber that is about to grab the object.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabPointIndex" />: Grab point index of the object that is about to be
/// grabbed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.IsMultiHands" />: true if it is already being grabbed and
/// will be grabbed with one more hand after. False if no hand is currently grabbing it.
/// </item>
/// </list>
/// </summary>
public event EventHandler<UxrManipulationEventArgs> ObjectGrabbing;
/// <summary>
/// Same as <see cref="ObjectGrabbing" /> but called right after the object was grabbed.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> ObjectGrabbed;
/// <summary>
/// Event called whenever a <see cref="UxrGrabber" /> component is about to release the
/// <see cref="UxrGrabbableObject" /> that it is holding and there is no <see cref="UxrGrabbableObjectAnchor" /> nearby
/// to place it on.
/// The following properties from <see cref="UxrManipulationEventArgs" /> will contain meaningful data:
/// <list type="bullet">
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableObject" />: Object that is about to be released.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableAnchor" />: Anchor where the object was originally grabbed
/// from. Null if it wasn't grabbed from an anchor.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.Grabber" />: Grabber that is about to release the object.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabPointIndex" />: Grab point index of the object that is being
/// grabbed by the <see cref="UxrGrabber" />.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.IsMultiHands" />: true if it is already being grabbed with another hand
/// that will keep holding it. False if no other hand is currently grabbing it.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.IsSwitchHands" />: True if it was released because another
/// <see cref="UxrGrabber" /> grabbed it, false otherwise. if
/// <see cref="UxrManipulationEventArgs.IsMultiHands" /> is
/// true then <see cref="UxrManipulationEventArgs.IsSwitchHands" /> 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).
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.ReleaseVelocity" />: Velocity the object is being released with.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.ReleaseAngularVelocity" />: Angular velocity the object is being
/// released with.
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// If the object is being released on a <see cref="UxrGrabbableObjectAnchor" /> that can hold it, it will
/// generate a <see cref="ObjectPlacing" /> event instead. Whenever an object is released it will either generate
/// either a Place or Release event, but not both.
/// </remarks>
public event EventHandler<UxrManipulationEventArgs> ObjectReleasing;
/// <summary>
/// Same as <see cref="ObjectReleasing" /> but called right after the object was released.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> ObjectReleased;
/// <summary>
/// Event called whenever a <see cref="UxrGrabbableObject" /> is about to be placed on an
/// <see cref="UxrGrabbableObjectAnchor" />.
/// The following properties from <see cref="UxrManipulationEventArgs" /> will contain meaningful data:
/// <list type="bullet">
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableObject" />: Object that is about to be placed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableAnchor" />: Anchor where the object is about to be placed on.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.Grabber" />: Grabber that is placing the object.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabPointIndex" />: Grab point index of the object that is being
/// grabbed by the <see cref="UxrGrabber" />.
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// If the object is being placed it will not generate a <see cref="ObjectReleasing" /> event. Whenever an object is
/// released it will either generate either a Place or Release event, but not both.
/// </remarks>
public event EventHandler<UxrManipulationEventArgs> ObjectPlacing;
/// <summary>
/// Same as <see cref="ObjectPlacing" /> but called right after the object was placed.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> ObjectPlaced;
/// <summary>
/// Event called whenever a <see cref="UxrGrabbableObject" /> is about to be removed from an
/// <see cref="UxrGrabbableObjectAnchor" />.
/// The following properties from <see cref="UxrManipulationEventArgs" /> will contain meaningful data:
/// <list type="bullet">
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableObject" />: Object that is about to be removed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableAnchor" />: Anchor where the object is currently placed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.Grabber" />: Grabber that is about to remove the object by grabbing it.
/// This can be null if the object is removed through code using <see cref="RemoveObjectFromAnchor" />,
/// <see cref="UxrGrabbableObject.RemoveFromAnchor" /> or <see cref="UxrGrabbableObjectAnchor.RemoveObject" />>
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabPointIndex" />: Only if the object is being removed by grabbing it:
/// Grab point index of the object that is about to be grabbed by the <see cref="UxrGrabber" />.
/// </item>
/// </list>
/// </summary>
public event EventHandler<UxrManipulationEventArgs> ObjectRemoving;
/// <summary>
/// Same as <see cref="ObjectRemoving" /> but called right after the object was removed.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> ObjectRemoved;
/// <summary>
/// Event called whenever an <see cref="UxrGrabbableObject" /> being grabbed by a <see cref="UxrGrabber" /> entered the
/// valid placement range (distance) of a compatible <see cref="UxrGrabbableObjectAnchor" />.
/// The following properties from <see cref="UxrManipulationEventArgs" /> will contain meaningful data:
/// <list type="bullet">
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableObject" />: Object that entered the valid placement range.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableAnchor" />: Anchor where the object can potentially be placed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.Grabber" />: Grabber that is holding the object. If more than one
/// grabber is holding it, it will indicate the first one to grab it.
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// Only enter/leave events will be generated. To check if an object can be placed on an anchor use
/// <see cref="UxrGrabbableObject.CanBePlacedOnAnchor" />.
/// </remarks>
/// <seealso cref="AnchorRangeLeft" />
public event EventHandler<UxrManipulationEventArgs> AnchorRangeEntered;
/// <summary>
/// Same as <see cref="AnchorRangeEntered" /> but when leaving the valid range.
/// </summary>
/// <remarks>
/// Only enter/leave events will be generated. To check if an object can be placed on an anchor use
/// <see cref="UxrGrabbableObject.CanBePlacedOnAnchor" />.
/// </remarks>
/// <seealso cref="AnchorRangeEntered" />
public event EventHandler<UxrManipulationEventArgs> AnchorRangeLeft;
/// <summary>
/// Event called whenever a <see cref="UxrGrabber" /> enters the valid grab range (distance) of a
/// <see cref="UxrGrabbableObject" /> placed on an <see cref="UxrGrabbableObjectAnchor" />.
/// The following properties from <see cref="UxrManipulationEventArgs" /> will contain meaningful data:
/// <list type="bullet">
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableObject" />: Object that is within reach.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableAnchor" />: Anchor where the object is placed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.Grabber" />: Grabber that entered the valid grab range.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabPointIndex" />: Grab point index that is within reach.
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// Only enter/leave events will be generated. To check if an object can be grabbed use
/// <see cref="UxrGrabbableObject.CanBeGrabbedByGrabber" />.
/// </remarks>
/// <seealso cref="PlacedObjectRangeLeft" />
public event EventHandler<UxrManipulationEventArgs> PlacedObjectRangeEntered;
/// <summary>
/// Same as <see cref="PlacedObjectRangeEntered" /> but when leaving the valid range.
/// </summary>
/// <remarks>
/// Only enter/leave events will be generated. To check if an object can be grabbed use
/// <see cref="UxrGrabbableObject.CanBeGrabbedByGrabber" />.
/// </remarks>
/// <seealso cref="PlacedObjectRangeEntered" />
public event EventHandler<UxrManipulationEventArgs> PlacedObjectRangeLeft;
/// <summary>
/// Gets or sets the manipulation features that are used when the manager is updated.
/// </summary>
public UxrManipulationFeatures Features { get; set; } = UxrManipulationFeatures.All;
#endregion
#region Internal Methods
/// <summary>
/// Updates the grab manager to the current frame.
/// </summary>
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
/// <summary>
/// Initializes the manager and subscribes to global events.
/// </summary>
protected override void Awake()
{
base.Awake();
UxrGrabbableObjectAnchor.GlobalEnabled += GrabbableObjectAnchor_Enabled;
UxrGrabbableObjectAnchor.GlobalDisabled += GrabbableObjectAnchor_Disabled;
UxrGrabbableObject.GlobalDisabled += GrabbableObject_Disabled;
}
/// <summary>
/// Unsubscribes from global events.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
UxrGrabbableObjectAnchor.GlobalEnabled -= GrabbableObjectAnchor_Enabled;
UxrGrabbableObjectAnchor.GlobalDisabled -= GrabbableObjectAnchor_Disabled;
UxrGrabbableObject.GlobalDisabled += GrabbableObject_Disabled;
}
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrAvatar.GlobalAvatarMoved -= UxrAvatar_GlobalAvatarMoved;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when a grabbable object anchor was enabled. Adds it to the internal list.
/// </summary>
/// <param name="anchor">Anchor that was enabled</param>
private void GrabbableObjectAnchor_Enabled(UxrGrabbableObjectAnchor anchor)
{
_grabbableObjectAnchors.Add(anchor, new GrabbableObjectAnchorInfo());
}
/// <summary>
/// Called when a grabbable object anchor was disabled. Removes it from the internal list.
/// </summary>
/// <param name="anchor">Anchor that was disabled</param>
private void GrabbableObjectAnchor_Disabled(UxrGrabbableObjectAnchor anchor)
{
_grabbableObjectAnchors.Remove(anchor);
}
/// <summary>
/// Called when a grabbable object was disabled. Removes it from current grabs if present.
/// </summary>
/// <param name="grabbableObject">Grabbable object that was disabled</param>
private void GrabbableObject_Disabled(UxrGrabbableObject grabbableObject)
{
if (_currentManipulations.ContainsKey(grabbableObject))
{
_currentManipulations.Remove(grabbableObject);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
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
/// <summary>
/// Event trigger for <see cref="GrabTrying" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
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);
}
}
/// <summary>
/// Event trigger for <see cref="ObjectGrabbing" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
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);
}
}
/// <summary>
/// Event trigger for <see cref="ObjectGrabbed" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
private void OnObjectGrabbed(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
ObjectGrabbed?.Invoke(this, e);
}
if (e.GrabbableObject)
{
e.GrabbableObject.UpdateGrabbableDependencies();
}
}
/// <summary>
/// Event trigger for <see cref="ObjectReleasing" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
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);
}
}
/// <summary>
/// Event trigger for <see cref="ObjectReleased" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
private void OnObjectReleased(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
ObjectReleased?.Invoke(this, e);
}
if (e.GrabbableObject)
{
e.GrabbableObject.UpdateGrabbableDependencies();
}
}
/// <summary>
/// Event trigger for <see cref="ObjectPlacing" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
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);
}
}
/// <summary>
/// Event trigger for <see cref="ObjectPlaced" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
private void OnObjectPlaced(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
ObjectPlaced?.Invoke(this, e);
}
if (e.GrabbableObject)
{
e.GrabbableObject.UpdateGrabbableDependencies();
}
}
/// <summary>
/// Event trigger for <see cref="ObjectRemoving" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
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);
}
}
/// <summary>
/// Event trigger for <see cref="ObjectRemoved" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
private void OnObjectRemoved(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
ObjectRemoved?.Invoke(this, e);
}
}
/// <summary>
/// Event trigger for <see cref="AnchorRangeEntered" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
private void OnAnchorRangeEntered(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
AnchorRangeEntered?.Invoke(this, e);
}
}
/// <summary>
/// Event trigger for <see cref="AnchorRangeLeft" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
private void OnAnchorRangeLeft(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
AnchorRangeLeft?.Invoke(this, e);
}
}
/// <summary>
/// Event trigger for <see cref="PlacedObjectRangeEntered" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
private void OnPlacedObjectRangeEntered(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
PlacedObjectRangeEntered?.Invoke(this, e);
}
}
/// <summary>
/// Event trigger for <see cref="PlacedObjectRangeLeft" />.
/// </summary>
/// <param name="e">Event parameters</param>
/// <param name="propagateEvent">Whether to propagate the event</param>
private void OnPlacedObjectRangeLeft(UxrManipulationEventArgs e, bool propagateEvent)
{
if (propagateEvent)
{
PlacedObjectRangeLeft?.Invoke(this, e);
}
}
#endregion
#region Private Methods
/// <summary>
/// Initializes the variables for a manipulation frame update computation.
/// </summary>
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<UxrGrabbableObject, RuntimeManipulationInfo> 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<UxrGrabbableObjectAnchor, GrabbableObjectAnchorInfo> 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;
}
}
/// <summary>
/// Performs operations that require to be done at the end of the manipulation update pipeline.
/// </summary>
private void FinalizeManipulationFrame()
{
// Update grabbers
foreach (UxrGrabber grabber in UxrGrabber.EnabledComponents)
{
grabber.UpdateThrowPhysicsInfo();
grabber.UpdateHandGrabberRenderer();
}
}
/// <summary>
/// Updates visual feedback states (objects that can be grabbed, anchors where a grabbed object can be placed on,
/// etc.).
/// </summary>
private void UpdateAffordances()
{
// Look for grabbed objects that can be placed on anchors
foreach (KeyValuePair<UxrGrabbableObject, RuntimeManipulationInfo> manipulationInfoPair in _currentManipulations)
{
UxrGrabbableObjectAnchor anchorTargetCandidate = null;
float minDistance = float.MaxValue;
if (manipulationInfoPair.Key.UsesGrabbableParentDependency == false && manipulationInfoPair.Key.IsPlaceable)
{
foreach (KeyValuePair<UxrGrabbableObjectAnchor, GrabbableObjectAnchorInfo> 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<UxrGrabbableObject, List<int>> 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<UxrGrabbableObject, List<int>>();
}
if (possibleGrabs.ContainsKey(grabbableCandidate))
{
possibleGrabs[grabbableCandidate].Add(grabPointCandidate);
}
else
{
possibleGrabs.Add(grabbableCandidate, new List<int> { 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<int> 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<int> 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<int> 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<UxrGrabbableObjectAnchor, GrabbableObjectAnchorInfo> 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<UxrGrabbableObjectAnchor, GrabbableObjectAnchorInfo> 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<UxrGrabbableObject, RuntimeManipulationInfo> _currentManipulations = new Dictionary<UxrGrabbableObject, RuntimeManipulationInfo>();
private readonly Dictionary<UxrGrabbableObjectAnchor, GrabbableObjectAnchorInfo> _grabbableObjectAnchors = new Dictionary<UxrGrabbableObjectAnchor, GrabbableObjectAnchorInfo>();
#endregion
}
}