Files
dungeons/Assets/ThirdParty/UltimateXR/Runtime/Scripts/Manipulation/UxrGrabbableObject.cs

2662 lines
115 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableObject.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Core.Math;
using UltimateXR.Core.Settings;
using UltimateXR.Core.StateSync;
using UltimateXR.Devices.Visualization;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.Unity;
using UltimateXR.Extensions.Unity.Math;
using UltimateXR.Networking;
using UnityEngine;
#pragma warning disable 0414
namespace UltimateXR.Manipulation
{
/// <summary>
/// Component that, added to a <see cref="GameObject" />, will enable the object to be grabbed by the
/// <see cref="UxrGrabber" /> components found in the hands of an <see cref="UxrAvatar" />.
/// Some of the main features of grabbable objects are:
/// <list type="bullet">
/// <item>
/// Manipulation is handled automatically by the <see cref="UxrGrabManager" />. There is no special
/// requirement to enable it in a scene, the grab manager will be available as soon as it is invoked.
/// </item>
/// <item>
/// Grabbable objects can be grabbed, released and placed. Releasing an object means dropping it mid-air,
/// while placing it is releasing an object close enough to a compatible
/// <see cref="UxrGrabbableObjectAnchor" />.
/// </item>
/// <item>Objects can be grabbed from different grab points.</item>
/// <item>
/// Additionally, grab points can be expanded using <see cref="UxrGrabPointShape" /> components opening up
/// more complex manipulation by describing grab points as composite shapes.
/// </item>
/// <item>
/// Although all avatars that have <see cref="UxrGrabber" /> components are able to interact with
/// <see cref="UxrGrabbableObject" /> objects, it is possible to register the way specific avatars will
/// interact with it. This allows to specify snap points and poses for different avatars and make sure
/// that all have precise and realistic manipulation.
/// </item>
/// <item>
/// The Hand Pose Editor can create poses that are used by <see cref="UxrGrabbableObject" /> in order to tell
/// how objects are grabbed. The inspector window will preview grab poses and enable editing them.
/// </item>
/// <item>
/// Events such as <see cref="Grabbed" />, <see cref="Released" /> and <see cref="Placed" /> allow to write
/// logic when a user interacts with the object. Each has pre and post events.
/// </item>
/// <item>
/// <see cref="ConstraintsApplying" />, <see cref="ConstraintsApplied" /> and
/// <see cref="ConstraintsFinished" /> allow to program more complex logic when grabbing objects.
/// </item>
/// </list>
/// </summary>
[DisallowMultipleComponent]
public partial class UxrGrabbableObject : UxrComponent<UxrGrabbableObject>, IUxrGrabbable
{
#region Inspector Properties/Serialized Fields
// Grabbable dependency
[SerializeField] private bool _isDummyGrabbableParent;
[SerializeField] private bool _controlParentDirection = true;
[SerializeField] private bool _ignoreGrabbableParentDependency;
// General parameters
[SerializeField] private int _priority;
[SerializeField] private bool _allowMultiGrab = true;
// Constraints
[SerializeField] private UxrTranslationConstraintMode _translationConstraintMode = UxrTranslationConstraintMode.Free;
[SerializeField] private BoxCollider _restrictToBox;
[SerializeField] private SphereCollider _restrictToSphere;
[SerializeField] private Vector3 _translationLimitsMin = Vector3.zero;
[SerializeField] private Vector3 _translationLimitsMax = Vector3.zero;
[SerializeField] private UxrRotationConstraintMode _rotationConstraintMode = UxrRotationConstraintMode.Free;
[SerializeField] private Vector3 _rotationAngleLimitsMin = Vector3.zero;
[SerializeField] private Vector3 _rotationAngleLimitsMax = Vector3.zero;
[SerializeField] private bool _autoRotationProvider = true;
[SerializeField] private UxrRotationProvider _rotationProvider = UxrRotationProvider.HandOrientation;
[SerializeField] private UxrAxis _rotationLongitudinalAxis = UxrAxis.Z;
[SerializeField] private bool _needsTwoHandsToRotate;
[SerializeField] private float _lockedGrabReleaseDistance = 0.4f;
[SerializeField] private float _translationResistance;
[SerializeField] private float _rotationResistance;
// Physics
[SerializeField] private Rigidbody _rigidBodySource;
[SerializeField] private bool _rigidBodyDynamicOnRelease = true;
[SerializeField] private float _verticalReleaseMultiplier = 1.0f;
[SerializeField] private float _horizontalReleaseMultiplier = 1.0f;
// Avatar grips
[SerializeField] private UxrPreviewGrabPoses _previewGrabPosesMode = UxrPreviewGrabPoses.ShowBothHands;
[SerializeField] private int _previewPosesRegenerationType;
[SerializeField] private int _previewPosesRegenerationIndex = -1;
[SerializeField] private GameObject _selectedAvatarForGrips;
// Grab points
[SerializeField] private bool _firstGrabPointIsMain = true;
[SerializeField] private UxrGrabPointInfo _grabPoint = new UxrGrabPointInfo();
[SerializeField] private List<UxrGrabPointInfo> _additionalGrabPoints;
// Placement
[SerializeField] private bool _useParenting = true;
[SerializeField] private bool _autoCreateStartAnchor;
[SerializeField] private float _autoAnchorMaxPlaceDistance = 0.1f;
[SerializeField] private UxrGrabbableObjectAnchor _startAnchor;
[SerializeField] private string _tag = "";
[SerializeField] private bool _dropAlignTransformUseSelf = true;
[SerializeField] private Transform _dropAlignTransform;
[SerializeField] private UxrSnapToAnchorMode _dropSnapMode = UxrSnapToAnchorMode.PositionAndRotation;
[SerializeField] private bool _dropProximityTransformUseSelf = true;
[SerializeField] private Transform _dropProximityTransform;
#endregion
#region Public Types & Data
/// <summary>
/// Event called right before applying the position/rotation constraints to the object.
/// This can be used to get the object position/rotation before any user constraints are
/// applied.
/// </summary>
public event EventHandler<UxrApplyConstraintsEventArgs> ConstraintsApplying;
/// <summary>
/// Event called to apply custom user constraints to the object. Moving the object
/// guarantees that the grips on the object will stay in place even if the object
/// is not parented to the hands.
/// </summary>
public event EventHandler<UxrApplyConstraintsEventArgs> ConstraintsApplied;
/// <summary>
/// Event called right after all <see cref="ConstraintsApplied" /> finished.
/// This should not be used to apply any constraints, only to get the final constrained
/// position/rotation to, for example, apply manipulation haptic feedback.
/// </summary>
public event EventHandler<UxrApplyConstraintsEventArgs> ConstraintsFinished;
/// <summary>
/// <para>
/// Gets whether the grabbable object is a dummy grabbable parent. Dummy grabbable parents are objects
/// that can only be manipulated through the children, but still have their own translation/rotations constraints.
/// </para>
/// <para>
/// An example of a dummy grabbable parent is a door and handle setup. The door is the parent, but the grabbable
/// object is the door handle which can rotate around itself and should also allow rotating the door.
/// Using the door handle only, the door cannot be opened or closed, because only the handle will rotate and the
/// handle is a child of the door.<br />
/// UltimateXR allows grabbable children to control a grabbable parent direction using
/// <see cref="ControlParentDirection" />, but sometimes the parent should not really be grabbable. When the
/// parent is not grabbable but the <see cref="ControlParentDirection" /> is still desired, it is possible to add a
/// <see cref="UxrGrabbableObject" /> component to the parent, set up translation/rotation constraints and enable
/// the <see cref="IsDummyGrabbableParent" /> property.
/// </para>
/// <para>
/// Some other examples where dummy grabbable parents come in handy:
/// <list type="bullet">
/// <item>An aircraft yoke column that moves forward/backward when the child yoke object is being grabbed.</item>
/// </list>
/// </para>
/// </summary>
public bool IsDummyGrabbableParent => _isDummyGrabbableParent;
/// <summary>
/// Gets whether a dependent object can control the grabbable parent's direction when being moved.
/// </summary>
public bool ControlParentDirection => _controlParentDirection;
/// <summary>
/// Gets whether the grabbable parent dependency is ignored. <see cref="UsesGrabbableParentDependency" />.
/// </summary>
public bool IgnoreGrabbableParentDependency => _ignoreGrabbableParentDependency;
/// <summary>
/// Gets whether the object has constraints and at the same time has a grabbable parent. This means that the object
/// can either be considered as another grabbable part of the parent object or a separate grabbable object that is just
/// attached to the parent object but has no control over it. The former are movable parts in a composite object while
/// the latter are independent grabbable objects that happen to be in the hierarchy.
/// </summary>
public bool HasGrabbableParentDependency => IsConstrained && GetGrabbableParent(this) != null;
/// <summary>
/// <para>
/// Gets whether the object has a parent dependency (<see cref="HasGrabbableParentDependency" /> is true) and is
/// using it through <see cref="_ignoreGrabbableParentDependency" /> in the inspector.
/// </para>
/// <para>
/// When a grabbable object that has position/rotation constraints hangs from a hierarchy where another grabbable
/// object is somewhere above, the child grabbable object can either be:
/// <list type="bullet">
/// <item>
/// Dependent (<see cref="_ignoreGrabbableParentDependency" /> is false): The object is considered as
/// another part of the parent grabbable object. It will be constrained by its parent object and can
/// optionally also control the parent's direction when moved.
/// </item>
/// <item>
/// Independent (<see cref="_ignoreGrabbableParentDependency" /> is true): The object is considered as a
/// separate entity where it just happens to be placed under the hierarchy, but it can be moved
/// freely without being constrained by the parent.
/// </item>
/// </list>
/// </para>
/// </summary>
public bool UsesGrabbableParentDependency => HasGrabbableParentDependency && !_ignoreGrabbableParentDependency;
/// <summary>
/// Gets whether the object can be grabbed with more than one hand.
/// </summary>
public bool AllowMultiGrab => _allowMultiGrab;
/// <summary>
/// Gets the first <see cref="UxrGrabbableObject" /> upwards in the hierarchy, and that the object can be dependent on.
/// To check whether the dependency is used (meaning, this object controls the parent direction) use
/// <see cref="UsesGrabbableParentDependency" />.
/// </summary>
public UxrGrabbableObject GrabbableParent => GetGrabbableParent(this);
/// <summary>
/// Gets whether the object has translation/rotation constraints.
/// </summary>
public bool IsConstrained => HasTranslationConstraint || HasRotationConstraint || IsLockedInPlace;
/// <summary>
/// Gets whether the object has a translation constraint.
/// </summary>
public bool HasTranslationConstraint => TranslationConstraint != UxrTranslationConstraintMode.Free;
/// <summary>
/// Gets whether the object has a rotation constraint.
/// </summary>
public bool HasRotationConstraint => RotationConstraint != UxrRotationConstraintMode.Free;
/// <summary>
/// Gets the number of axes that the object can be translated in.
/// </summary>
public int RangeOfMotionTranslationAxisCount
{
get
{
if (TranslationConstraint == UxrTranslationConstraintMode.Free)
{
return 3;
}
if (TranslationConstraint == UxrTranslationConstraintMode.Locked)
{
return 0;
}
return Vector3Ext.DifferentComponentCount(_translationLimitsMin, _translationLimitsMax);
}
}
/// <summary>
/// Gets the number of axes that the object can rotate around.
/// </summary>
public int RangeOfMotionRotationAxisCount
{
get
{
if (RotationConstraint == UxrRotationConstraintMode.Free)
{
return 3;
}
if (RotationConstraint == UxrRotationConstraintMode.Locked)
{
return 0;
}
return Vector3Ext.DifferentComponentCount(_rotationAngleLimitsMin, _rotationAngleLimitsMax);
}
}
/// <summary>
/// Gets the local axes that the object can be translated in.
/// </summary>
public IEnumerable<UxrAxis> RangeOfMotionTranslationAxes
{
get
{
if (TranslationConstraint == UxrTranslationConstraintMode.Free)
{
yield return UxrAxis.X;
yield return UxrAxis.Y;
yield return UxrAxis.Z;
}
if (TranslationConstraint == UxrTranslationConstraintMode.Locked)
{
yield break;
}
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
if (!Mathf.Approximately(_translationLimitsMin[axisIndex], _translationLimitsMax[axisIndex]))
{
yield return axisIndex;
}
}
}
}
/// <summary>
/// Gets the local axes that the object can rotate around.
/// </summary>
public IEnumerable<UxrAxis> RangeOfMotionRotationAxes
{
get
{
if (RotationConstraint == UxrRotationConstraintMode.Free)
{
yield return UxrAxis.X;
yield return UxrAxis.Y;
yield return UxrAxis.Z;
}
if (RotationConstraint == UxrRotationConstraintMode.Locked)
{
yield break;
}
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
if (!Mathf.Approximately(_rotationAngleLimitsMin[axisIndex], _rotationAngleLimitsMax[axisIndex]))
{
yield return axisIndex;
}
}
}
}
/// <summary>
/// Gets the local axes that the object can be translated in with limited range of motion (not freely, nor locked).
/// </summary>
public IEnumerable<UxrAxis> LimitedRangeOfMotionTranslationAxes
{
get
{
if (TranslationConstraint == UxrTranslationConstraintMode.Free || TranslationConstraint == UxrTranslationConstraintMode.Locked)
{
yield break;
}
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
if (!Mathf.Approximately(_translationLimitsMin[axisIndex], _translationLimitsMax[axisIndex]))
{
yield return axisIndex;
}
}
}
}
/// <summary>
/// Gets the local axes that the object can rotate around with limited range of motion (not freely, nor locked).
/// </summary>
public IEnumerable<UxrAxis> LimitedRangeOfMotionRotationAxes
{
get
{
if (RotationConstraint == UxrRotationConstraintMode.Free || RotationConstraint == UxrRotationConstraintMode.Locked)
{
yield break;
}
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
if (!Mathf.Approximately(_rotationAngleLimitsMin[axisIndex], _rotationAngleLimitsMax[axisIndex]))
{
yield return axisIndex;
}
}
}
}
/// <summary>
/// Gets the index of the translation axis if the object can only be translated in that single axis.
/// Will return any of these values: (x = 0, y = 1, z = 2, none or more than one = -1).
/// </summary>
public int SingleTranslationAxisIndex
{
get
{
if (TranslationConstraint == UxrTranslationConstraintMode.Free || TranslationConstraint == UxrTranslationConstraintMode.Locked)
{
return -1;
}
int constrainedAxisIndex = 0;
int constrainedAxisCount = 0;
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
if (!Mathf.Approximately(_translationLimitsMin[axisIndex], _translationLimitsMax[axisIndex]))
{
constrainedAxisIndex = axisIndex;
constrainedAxisCount++;
}
}
return constrainedAxisCount == 1 ? constrainedAxisIndex : -1;
}
}
/// <summary>
/// Gets the index of the rotation axis if the object can only rotate around that single axis.
/// Will return any of these values: (x = 0, y = 1, z = 2, none or more than one = -1).
/// </summary>
public int SingleRotationAxisIndex
{
get
{
if (RotationConstraint == UxrRotationConstraintMode.Free || RotationConstraint == UxrRotationConstraintMode.Locked)
{
return -1;
}
int constrainedAxisIndex = 0;
int constrainedAxisCount = 0;
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
if (!Mathf.Approximately(_rotationAngleLimitsMin[axisIndex], _rotationAngleLimitsMax[axisIndex]))
{
constrainedAxisIndex = axisIndex;
constrainedAxisCount++;
}
}
return constrainedAxisCount == 1 ? constrainedAxisIndex : -1;
}
}
/// <summary>
/// Gets the total number of grab points.
/// </summary>
public int GrabPointCount => IsDummyGrabbableParent ? 0 :
_additionalGrabPoints != null ? _additionalGrabPoints.Count + 1 : 1;
/// <summary>
/// Gets the <see cref="Transform" /> that needs to align with a <see cref="UxrGrabbableObjectAnchor" /> when placing
/// the object on it.
/// </summary>
public Transform DropAlignTransform => _dropAlignTransform == null || _dropAlignTransformUseSelf ? transform : _dropAlignTransform;
/// <summary>
/// Gets the <see cref="Transform" /> that will be used to compute the distance to
/// <see cref="UxrGrabbableObjectAnchor" /> components when looking for the closest available to place it.
/// </summary>
public Transform DropProximityTransform => _dropProximityTransform == null || _dropProximityTransformUseSelf ? transform : _dropProximityTransform;
/// <summary>
/// Gets the distance that the real hand needs to have to the virtual hand in order for the object grip to be released
/// automatically. This happens when a grabbed object has a range of movement and the grip is pulled too far from a
/// valid position.
/// </summary>
public float LockedGrabReleaseDistance => _lockedGrabReleaseDistance;
/// <summary>
/// Gets whether the object's <see cref="RigidBodySource" /> will be made dynamic when the object grip is released.
/// </summary>
public bool RigidBodyDynamicOnRelease => _rigidBodyDynamicOnRelease;
/// <summary>
/// Gets whether the object's <see cref="RigidBodySource" /> can be made dynamic when the object grip is released,
/// checking all conditions.
/// </summary>
public bool CanUseRigidBody => !UsesGrabbableParentDependency;
/// <summary>
/// Gets the vertical velocity factor that will be applied to the object when being thrown.
/// </summary>
public float VerticalReleaseMultiplier => _verticalReleaseMultiplier;
/// <summary>
/// Gets the horizontal velocity factor that will be applied to the object when being thrown.
/// </summary>
public float HorizontalReleaseMultiplier => _horizontalReleaseMultiplier;
/// <summary>
/// Gets whether the object requires both hands grabbing it in order to rotate it.
/// </summary>
public bool NeedsTwoHandsToRotate => _needsTwoHandsToRotate;
/// <summary>
/// Gets the <see cref="UxrGrabbableObjectAnchor" /> the object started on, regardless of the current anchor.
/// </summary>
public UxrGrabbableObjectAnchor StartAnchor => _startAnchor;
/// <summary>
/// Gets the minimum allowed angle in degrees for objects that have a single rotational degree of freedom.
/// </summary>
public float MinSingleRotationDegrees
{
get
{
int singleRotationAxisIndex = SingleRotationAxisIndex;
return singleRotationAxisIndex == -1 ? 0.0f : _rotationAngleLimitsMin[singleRotationAxisIndex];
}
}
/// <summary>
/// Gets the maximum allowed angle in degrees for objects that have a single rotational degree of freedom.
/// </summary>
public float MaxSingleRotationDegrees
{
get
{
int singleRotationAxisIndex = SingleRotationAxisIndex;
return singleRotationAxisIndex == -1 ? 0.0f : _rotationAngleLimitsMax[singleRotationAxisIndex];
}
}
/// <summary>
/// Gets whether the first grab point in the list is the main grab in objects with more than one grab point.
/// When an object is grabbed with both hands, the main grab controls the actual position while the secondary grab
/// controls the direction.
/// Set it to true in objects like a rifle, where the trigger hand should be the first grab in order to keep the object
/// in place, and the front grab will control the aiming direction.
/// If false, the grab point order is irrelevant and the hand that grabbed the object first will be considered as the
/// main grab.
/// </summary>
public bool FirstGrabPointIsMain => _firstGrabPointIsMain;
/// <summary>
/// Gets the rotation provider. The rotation provider is used in objects with constrained position to know
/// which element drives the rotation.
/// </summary>
public UxrRotationProvider RotationProvider => HasTranslationConstraint && LimitedRangeOfMotionRotationAxes.Any() ? _rotationProvider : UxrRotationProvider.HandOrientation;
/// <summary>
/// Gets which axis is the longitudinal axis (x, y or z) in a rotation with constraints on two or more axes.
/// </summary>
public UxrAxis RotationLongitudinalAxis => _rotationLongitudinalAxis;
/// <summary>
/// Gets or sets the rotation angle in degrees for objects that have a single rotational degree of freedom.
/// </summary>
/// <remarks>
/// Internally it calls <see cref="UxrGrabManager.GetObjectSingleRotationAxisDegrees" /> and
/// <see cref="UxrGrabManager.SetObjectSingleRotationAxisDegrees" />.
/// </remarks>
public float SingleRotationAxisDegrees
{
get => UxrGrabManager.Instance.GetObjectSingleRotationAxisDegrees(this);
set => UxrGrabManager.Instance.SetObjectSingleRotationAxisDegrees(this, value);
}
/// <summary>
/// Gets the <see cref="UxrGrabbableObjectAnchor" /> where the object is actually placed or null if it's not placed on
/// any.
/// </summary>
public UxrGrabbableObjectAnchor CurrentAnchor
{
get => _currentAnchor;
internal set => _currentAnchor = value;
}
/// <summary>
/// Gets or sets whether the object can be placed on an <see cref="UxrGrabbableObjectAnchor" />.
/// </summary>
public bool IsPlaceable
{
get => _isPlaceable;
set => _isPlaceable = value;
}
/// <summary>
/// Gets or sets whether the object can be moved/rotated. A locked in place object may be grabbed but cannot be moved.
/// </summary>
public bool IsLockedInPlace
{
get => _isLockedInPlace;
set
{
if (_isLockedInPlace && !value)
{
StartSmoothManipulationTransition();
}
_isLockedInPlace = value;
}
}
/// <summary>
/// Gets or sets the object priority. The priority is used to control which object will be grabbed when multiple
/// objects are in reach and the user performs the grab gesture.
/// The default behaviour is to use the distance and orientation to the objects in reach to select the one with the
/// closest grip. The priority can override this behaviour
/// by selecting the one with the highest priority value. By default all objects have priority 0.
/// </summary>
public int Priority
{
get => _priority;
set => _priority = value;
}
/// <summary>
/// Gets or sets whether to parent the object to the <see cref="UxrGrabbableObjectAnchor" /> being placed. Also whether
/// to set the parent to null when grabbing the object from one.
/// </summary>
public bool UseParenting
{
get => _useParenting;
set => _useParenting = value;
}
/// <summary>
/// Gets or sets the string that identifies which <see cref="UxrGrabbableObjectAnchor" /> components are
/// compatible for placement. A <see cref="UxrGrabbableObject" /> can be placed on an
/// <see cref="UxrGrabbableObjectAnchor" /> only if:
/// <list type="bullet">
/// <item>
/// <see cref="Tag" /> is null or empty and <see cref="UxrGrabbableObjectAnchor" /> has no compatible tags
/// set
/// </item>
/// <item>
/// <see cref="Tag" /> has a value that is in one of the compatible tag entries in
/// <see cref="UxrGrabbableObjectAnchor" />
/// </item>
/// </list>
/// </summary>
public string Tag
{
get => _tag;
set => _tag = value;
}
/// <summary>
/// Gets or sets the translation constraint.
/// </summary>
public UxrTranslationConstraintMode TranslationConstraint
{
get => _translationConstraintMode;
set => _translationConstraintMode = value;
}
/// <summary>
/// Gets or sets the box collider used When <see cref="TranslationConstraint" /> is
/// <see cref="UxrTranslationConstraintMode.RestrictToBox" />.
/// </summary>
public BoxCollider RestrictToBox
{
get => _restrictToBox;
set => _restrictToBox = value;
}
/// <summary>
/// Gets or sets the sphere collider used When <see cref="TranslationConstraint" /> is
/// <see cref="UxrTranslationConstraintMode.RestrictToSphere" />.
/// </summary>
public SphereCollider RestrictToSphere
{
get => _restrictToSphere;
set => _restrictToSphere = value;
}
/// <summary>
/// Gets or sets the translation minimum limits in local space when <see cref="TranslationConstraint" /> is
/// <see cref="UxrTranslationConstraintMode.RestrictLocalOffset" />.
/// </summary>
public Vector3 TranslationLimitsMin
{
get => _translationLimitsMin;
set => _translationLimitsMin = value;
}
/// <summary>
/// Gets or sets the translation maximum limits in local space when <see cref="TranslationConstraint" /> is
/// <see cref="UxrTranslationConstraintMode.RestrictLocalOffset" />.
/// </summary>
public Vector3 TranslationLimitsMax
{
get => _translationLimitsMax;
set => _translationLimitsMax = value;
}
/// <summary>
/// Gets or sets the rotation constraint type.
/// </summary>
public UxrRotationConstraintMode RotationConstraint
{
get => _rotationConstraintMode;
set => _rotationConstraintMode = value;
}
/// <summary>
/// Gets or sets the rotational minimum limits in local space when <see cref="RotationConstraint" /> is
/// <see cref="UxrRotationConstraintMode.RestrictLocalRotation" />.
/// </summary>
public Vector3 RotationAngleLimitsMin
{
get => _rotationAngleLimitsMin;
set => _rotationAngleLimitsMin = value;
}
/// <summary>
/// Gets or sets the rotational maximum limits in local space when <see cref="RotationConstraint" /> is
/// <see cref="UxrRotationConstraintMode.RestrictLocalRotation" />.
/// </summary>
public Vector3 RotationAngleLimitsMax
{
get => _rotationAngleLimitsMax;
set => _rotationAngleLimitsMax = value;
}
/// <summary>
/// Gets or sets the resistance to the object being moved around. This can be used to smooth out the position but also
/// to simulate heavy objects.
/// </summary>
public float TranslationResistance
{
get => _translationResistance;
set => _translationResistance = value;
}
/// <summary>
/// Gets or sets the resistance to the object being rotated. This can be used to smooth out the rotation but also to
/// simulate heavy objects.
/// </summary>
public float RotationResistance
{
get => _rotationResistance;
set => _rotationResistance = value;
}
/// <summary>
/// Gets or sets the rigidbody component that controls the grabbable object when it is in dynamic
/// (physics-enabled) mode.
/// </summary>
public Rigidbody RigidBodySource
{
get => _rigidBodySource;
set => _rigidBodySource = value;
}
/// <summary>
/// Gets or sets how the object will align with a <see cref="UxrGrabbableObjectAnchor" /> when placing it.
/// </summary>
public UxrSnapToAnchorMode DropSnapMode
{
get => _dropSnapMode;
set => _dropSnapMode = value;
}
#endregion
#region Internal Types & Data
/// <summary>
/// Gets the child grabbable objects, grabbed or not, where there is no other grabbable object between the child and
/// this grabbable.
/// </summary>
internal List<UxrGrabbableObject> AllDirectChildren { get; } = new List<UxrGrabbableObject>();
/// <summary>
/// Gets all parent grabbable objects, grabbed or not.
/// </summary>
internal List<UxrGrabbableObject> AllParents { get; private set; } = new List<UxrGrabbableObject>();
/// <summary>
/// Gets the parent grabbable objects, grabbed or not, whose direction is controlled indirectly by this object (
/// <see cref="UsesGrabbableParentDependency" />) and <see cref="ControlParentDirection" />).
/// </summary>
internal List<UxrGrabbableObject> ParentLookAts { get; private set; } = new List<UxrGrabbableObject>();
/// <summary>
/// Gets all child grabbable objects, grabbed or not.
/// </summary>
internal List<UxrGrabbableObject> AllChildren { get; private set; } = new List<UxrGrabbableObject>();
/// <summary>
/// Gets all child grabbable objects, grabbed or not, that have <see cref="UsesGrabbableParentDependency" /> and
/// <see cref="ControlParentDirection" />.
/// </summary>
internal List<UxrGrabbableObject> AllChildrenLookAts { get; private set; } = new List<UxrGrabbableObject>();
/// <summary>
/// Gets the child grabbable objects, grabbed or not, that have <see cref="UsesGrabbableParentDependency" /> and
/// <see cref="ControlParentDirection" />, where there is no other grabbable object between the child that controls
/// this grabbable.
/// </summary>
internal List<UxrGrabbableObject> DirectChildrenLookAts { get; private set; } = new List<UxrGrabbableObject>();
/// <summary>
/// Gets or sets the number of direct children using <see cref="UsesGrabbableParentDependency" /> and (
/// <see cref="ControlParentDirection" />) that are being grabbed the current frame. Used by the
/// <see cref="UxrGrabManager" />.
/// </summary>
internal int DirectLookAtChildGrabbedCount { get; set; }
/// <summary>
/// Gets or sets the number of <see cref="DirectLookAtChildGrabbedCount" /> children processed the current frame. Used
/// by the <see cref="UxrGrabManager" />.
/// </summary>
internal int DirectLookAtChildProcessedCount { get; set; }
/// <summary>
/// Gets or sets the rotation angle in degrees for objects that have a single rotational degree of freedom.
/// </summary>
internal float SingleRotationAngleCumulative
{
get => _singleRotationAngleCumulative;
set => _singleRotationAngleCumulative = value;
}
/// <summary>
/// Gets or sets the initial position of the grabbable object in local grabbable parent space, if a grabbable parent
/// exists.
/// </summary>
internal Vector3 InitialPositionInLocalGrabbableParent { get; private set; }
/// <summary>
/// Gets or sets the object's local position before being updated by the grab manager.
/// This is only updated if the object or a parent/child dependency is being grabbed.
/// </summary>
internal Vector3 LocalPositionBeforeUpdate { get; set; }
/// <summary>
/// Gets or sets the object's local rotation before being updated by the grab manager
/// This is only updated if the object or a parent/child dependency is being grabbed.
/// </summary>
internal Quaternion LocalRotationBeforeUpdate { get; set; }
/// <summary>
/// Gets the placement options used in the last call that placed the object, for example using
/// <see cref="UxrGrabManager.PlaceObject" />.
/// </summary>
internal UxrPlacementOptions PlacementOptions
{
get => _placementOptions;
set => _placementOptions = value;
}
#endregion
#region Implicit IUxrGrabbable
/// <inheritdoc />
public bool IsBeingGrabbed => UxrGrabManager.Instance.IsBeingGrabbed(this);
/// <inheritdoc />
public bool IsGrabbable
{
get => _isGrabbable;
set
{
BeginSync();
_isGrabbable = value;
EndSyncProperty(value);
}
}
/// <inheritdoc />
public bool IsKinematic
{
get => _rigidBodySource == null || _rigidBodySource.isKinematic;
set
{
if (_rigidBodySource != null)
{
_rigidBodySource.isKinematic = value;
}
}
}
/// <inheritdoc />
public event EventHandler<UxrManipulationEventArgs> Grabbing;
/// <inheritdoc />
public event EventHandler<UxrManipulationEventArgs> Grabbed;
/// <inheritdoc />
public event EventHandler<UxrManipulationEventArgs> Releasing;
/// <inheritdoc />
public event EventHandler<UxrManipulationEventArgs> Released;
/// <inheritdoc />
public event EventHandler<UxrManipulationEventArgs> Placing;
/// <inheritdoc />
public event EventHandler<UxrManipulationEventArgs> Placed;
/// <inheritdoc />
public void ResetPositionAndState(bool propagateEvents)
{
transform.localPosition = InitialLocalPosition;
transform.localRotation = InitialLocalRotation;
IsKinematic = _initialIsKinematic;
if (_startAnchor)
{
UxrGrabManager.Instance.PlaceObject(this, _startAnchor, UxrPlacementOptions.None, propagateEvents);
}
}
/// <inheritdoc />
public void ReleaseGrabs(bool propagateEvents)
{
UxrGrabManager.Instance.ReleaseGrabs(this, propagateEvents);
}
#endregion
#region Public Methods
/// <summary>
/// Gets a given grab point information.
/// </summary>
/// <param name="index">Grab point index to get the information of</param>
/// <returns>Grab point information</returns>
public UxrGrabPointInfo GetGrabPoint(int index)
{
if (index == 0)
{
return _grabPoint;
}
if (index >= 1 && index <= _additionalGrabPoints.Count)
{
return _additionalGrabPoints[index - 1];
}
return null;
}
/// <summary>
/// Enables or disables the possibility to use the given grab point.
/// </summary>
/// <param name="grabPoint">Grab point index</param>
/// <param name="grabPointEnabled">Whether to enable or disable interaction</param>
public void SetGrabPointEnabled(int grabPoint, bool grabPointEnabled)
{
if (_grabPointEnabledStates.ContainsKey(grabPoint))
{
if (grabPointEnabled)
{
_grabPointEnabledStates.Remove(grabPoint);
}
else
{
_grabPointEnabledStates[grabPoint] = false; // This is redundant
}
}
else if (!grabPointEnabled && !grabPointEnabled)
{
_grabPointEnabledStates.Add(grabPoint, false);
}
}
/// <summary>
/// Re-enables all disabled grab points by <see cref="SetGrabPointEnabled" />.
/// </summary>
public void EnableAllGrabPoints()
{
_grabPointEnabledStates.Clear();
}
/// <summary>
/// Computes the distance from the object to a <see cref="UxrGrabber" />, which is the component found in
/// <see cref="UxrAvatar" /> hands that are able to grab objects.
/// </summary>
/// <param name="grabber">Grabber component</param>
/// <param name="grabPoint">Grab point index to compute the distance to</param>
/// <param name="distance">
/// Returns the distance, which is not be actual euclidean distance but a value that also takes into
/// account the relative rotation between the grabber and the grab point. This helps favoring grabs that have a more
/// convenient orientation to the grabber and are just a little farther away
/// </param>
/// <param name="distanceWithoutRotation">Returns the euclidean distance, without factoring in any relative rotation</param>
public void GetDistanceFromGrabber(UxrGrabber grabber, int grabPoint, out float distance, out float distanceWithoutRotation)
{
UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint);
Transform grabberProximityTransform = grabber.GetProximityTransform(grabPointInfo.GrabberProximityTransformIndex);
distance = Vector3.Distance(grabberProximityTransform.position, GetGrabPointGrabProximityTransform(grabber, grabPoint).position);
// distanceRotationAdd will store the distance added to count for the rotation and favor those grips closer in orientation to the grabber
float distanceRotationAdd = 0.0f;
// First check if there is an UxrGrabPointShape based component that describes this
UxrGrabPointShape grabPointShape = GetGrabPointShape(grabPoint);
if (grabPointShape != null)
{
distance = grabPointShape.GetDistanceFromGrabber(grabber,
GetGrabPointGrabAlignTransform(grabber.Avatar, grabPoint, grabber.Side),
GetGrabPointGrabProximityTransform(grabber, grabPoint),
grabberProximityTransform);
}
else
{
// If there is no UxrGrabPointShape for this grabPoint just compute the distance normally
if (grabPointInfo.GrabProximityMode == UxrGrabProximityMode.UseProximity)
{
// Offset distance slightly based on relative orientation of grabber and grabbableObject to favor objects whose grab axes are more closely aligned to the grabber.
// This way we can put 2 grabs for different hand orientations in the same position and the one with a closer alignment will be favoured.
if (GetGrabPoint(grabPoint).SnapMode == UxrSnapToHandMode.RotationOnly || GetGrabPoint(grabPoint).SnapMode == UxrSnapToHandMode.PositionAndRotation)
{
float relativeAngleDegrees = Quaternion.Angle(grabber.transform.rotation, GetGrabPointGrabAlignTransform(grabber.Avatar, grabPoint, grabber.Side).rotation);
distanceRotationAdd = Mathf.Abs(relativeAngleDegrees) * UxrConstants.DistanceOffsetByAngle;
}
}
else if (grabPointInfo.GrabProximityMode == UxrGrabProximityMode.BoxConstrained)
{
if (grabPointInfo.GrabProximityBox)
{
distance = grabber.GetProximityTransform(grabPointInfo.GrabberProximityTransformIndex).position.IsInsideBox(grabPointInfo.GrabProximityBox) ? distance : float.MaxValue;
}
}
}
// Do not allow to grab if there is a hand grabbing another grabPoint nearby
for (int otherGrabbedPoint = 0; otherGrabbedPoint < GrabPointCount; ++otherGrabbedPoint)
{
if (otherGrabbedPoint == grabPoint && grabPointShape == null)
{
continue;
}
if (UxrGrabManager.Instance.GetGrabbingHand(this, otherGrabbedPoint, out UxrGrabber otherGrabber))
{
if (GetGrabPoint(grabPoint).SnapMode == UxrSnapToHandMode.PositionAndRotation && GetGrabPoint(otherGrabbedPoint).SnapMode == UxrSnapToHandMode.PositionAndRotation)
{
// Other hand nearby and both have full snap mode? Check if there is room for the new hand
if (grabPointShape && otherGrabbedPoint == grabPoint)
{
// Grabbing same shape. Check if proposed snap point is too close to the other hand or not.
grabPointShape.GetClosestSnap(grabber,
GetGrabPointGrabAlignTransform(grabber.Avatar, grabPoint, grabber.Side),
GetGrabPointGrabProximityTransform(grabber, grabPoint),
grabberProximityTransform,
out Vector3 snapPos,
out Quaternion snapRot);
if (Vector3.Distance(snapPos, otherGrabber.transform.position) <= UxrConstants.MinHandGrabInterDistance)
{
// The other hand is grabbing the same shape and is too close. Avoid this by increasing distance.
distance += 100000.0f;
}
}
else if (Vector3.Distance(GetGrabPointGrabAlignTransform(grabber.Avatar, grabPoint, grabber.Side).position,
GetGrabPointGrabAlignTransform(grabber.Avatar, otherGrabbedPoint, otherGrabber.Side).position) <= UxrConstants.MinHandGrabInterDistance)
{
// Grabbing other point whose snapping point is too close. Avoid this by increasing distance.
distance += 100000.0f;
}
}
}
}
// Factor in the orientation
distanceWithoutRotation = distance;
distance += distanceRotationAdd;
}
/// <summary>
/// Checks whether the object can be grabbed by a <see cref="UxrGrabber" />.
/// </summary>
/// <param name="grabber">Grabber</param>
/// <param name="grabPoint">Grab point index to check</param>
/// <returns>Whether the object can be grabbed by the grabber using the given grab point</returns>
public bool CanBeGrabbedByGrabber(UxrGrabber grabber, int grabPoint)
{
if (_grabPointEnabledStates.ContainsKey(grabPoint))
{
// It always is false when it exists. This has manually been set up by SetGrabPointEnabled()
return false;
}
UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint);
if (grabber == null || IsGrabbable == false || isActiveAndEnabled == false || grabPointInfo == null)
{
return false;
}
if (grabPointInfo.BothHandsCompatible == false && grabPointInfo.HandSide != grabber.Side)
{
// Invalid hand
return false;
}
if (AllChildrenLookAts.Any() && IsDummyGrabbableParent)
{
// Dummy grabbable parents cannot be grabbed
return false;
}
bool isBeingGrabbedByOtherPoint = GrabPointCount > 1 && UxrGrabManager.Instance.IsBeingGrabbed(this) && !UxrGrabManager.Instance.IsBeingGrabbed(this, grabPoint);
bool isBeingGrabbedBySameShape = _grabPointShapes.ContainsKey(grabPoint) && UxrGrabManager.Instance.IsBeingGrabbed(this, grabPoint);
if (!AllowMultiGrab && (isBeingGrabbedByOtherPoint || isBeingGrabbedBySameShape))
{
// Object does not allow to be grabbed with more than one hand
// We skip this check because we want to be able to switch from one hand to the other.
// @TODO: Check if we really need this. Maybe add a flag to check for it or not.
//return false;
}
GetDistanceFromGrabber(grabber, grabPoint, out float distance, out float distanceWithoutRotation);
if (grabPointInfo.GrabProximityMode == UxrGrabProximityMode.BoxConstrained)
{
if (grabPointInfo.GrabProximityBox)
{
return grabber.GetProximityTransform(grabPointInfo.GrabberProximityTransformIndex).position.IsInsideBox(grabPointInfo.GrabProximityBox);
}
}
else if (distanceWithoutRotation <= Mathf.Max(0.0f, GetGrabPoint(grabPoint).MaxDistanceGrab))
{
return true;
}
return false;
}
/// <summary>
/// Computes the position/rotation that a <see cref="UxrGrabber" /> would have to hold the object using the current
/// object position/orientation.
/// </summary>
/// <param name="grabber">Grabber to check</param>
/// <param name="grabPoint">Grab point</param>
/// <param name="grabberPosition">Returns the grabber position</param>
/// <param name="grabberRotation">Returns the grabber orientation</param>
/// <param name="includeAlignToController">
/// Whether to include the rotation required for AlignToController if the grab point has it.
/// </param>
/// <returns>Whether the returned data is meaningful</returns>
public bool ComputeRequiredGrabberTransform(UxrGrabber grabber, int grabPoint, out Vector3 grabberPosition, out Quaternion grabberRotation, bool includeAlignToController = true)
{
grabberPosition = grabber.transform.position;
grabberRotation = grabber.transform.rotation;
UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint);
Transform snapTransform = GetGrabPointGrabAlignTransform(grabber.Avatar, grabPoint, grabber.Side);
if (snapTransform == null || grabPointInfo == null)
{
return false;
}
if (GetSnapModeAffectsPosition(grabPointInfo.SnapMode))
{
grabberPosition = snapTransform.position;
}
if (GetSnapModeAffectsRotation(grabPointInfo.SnapMode))
{
grabberRotation = snapTransform.rotation;
}
UxrGrabPointShape grabPointShape = GetGrabPointShape(grabPoint);
if (grabPointShape != null)
{
Transform grabberProximityTransform = grabber.GetProximityTransform(grabPointInfo.GrabberProximityTransformIndex);
grabPointShape.GetClosestSnap(grabber, snapTransform, GetGrabPointGrabProximityTransform(grabber, grabPoint), grabberProximityTransform, out grabberPosition, out grabberRotation);
}
if (grabPointInfo.AlignToController && includeAlignToController)
{
UxrController3DModel controller3DModel = grabber != null && grabber.Avatar != null ? grabber.Avatar.ControllerInput.GetController3DModel(grabber.Side) : null;
if (controller3DModel != null)
{
Quaternion relativeTrackerRotation = Quaternion.Inverse(grabber.transform.rotation) * controller3DModel.ForwardTrackingRotation;
Quaternion trackerRotation = grabberRotation * relativeTrackerRotation;
Quaternion sourceAlignAxes = grabPointInfo.AlignToControllerAxes != null ? grabPointInfo.AlignToControllerAxes.rotation : transform.rotation;
grabberRotation = sourceAlignAxes * Quaternion.Inverse(trackerRotation) * grabberRotation;
}
}
return true;
}
/// <summary>
/// Checks whether the object is near enough to be placed on the given <see cref="UxrGrabbableObjectAnchor" />.
/// </summary>
/// <param name="anchor">Anchor to check</param>
/// <returns>Whether it is near enough to be placed</returns>
public bool CanBePlacedOnAnchor(UxrGrabbableObjectAnchor anchor)
{
return CanBePlacedOnAnchor(anchor, out float _);
}
/// <summary>
/// Checks whether the object is near enough to be placed on the given <see cref="UxrGrabbableObjectAnchor" />.
/// </summary>
/// <param name="anchor">Anchor to check</param>
/// <param name="distance">Returns the euclidean distance to the anchor</param>
/// <returns>Whether it is near enough to be placed</returns>
public bool CanBePlacedOnAnchor(UxrGrabbableObjectAnchor anchor, out float distance)
{
if (anchor.enabled && anchor.gameObject.activeInHierarchy && anchor.CurrentPlacedObject == null && anchor.IsCompatibleObject(this))
{
distance = Vector3.Distance(DropProximityTransform.position, anchor.DropProximityTransform.position);
if (distance <= anchor.MaxPlaceDistance)
{
return true;
}
}
distance = Mathf.Infinity;
return false;
}
/// <summary>
/// Removes the object from the anchor it is placed on, if any.
/// </summary>
/// <param name="propagateEvents">Whether to propagate events</param>
/// <remarks>
/// Internally it calls <see cref="UxrGrabManager.RemoveObjectFromAnchor" />.
/// </remarks>
public void RemoveFromAnchor(bool propagateEvents)
{
UxrGrabManager.Instance.RemoveObjectFromAnchor(this, propagateEvents);
}
/// <summary>
/// <see cref="UxrGrabManager.KeepGripsInPlace" />.
/// </summary>
public void KeepGripsInPlace()
{
UxrGrabManager.Instance.KeepGripsInPlace(this);
}
/// <summary>
/// Tries to get the longitudinal rotation axis of the grabbable object. If it hasn't been defined by the user (on
/// objects where <see cref="RangeOfMotionRotationAxisCount" /> is less than 2.
/// </summary>
/// <returns>Longitudinal rotation axis</returns>
public UxrAxis GetMostProbableLongitudinalRotationAxis()
{
if (RangeOfMotionRotationAxisCount > 1)
{
// Longitudinal axis is user defined for constrained rotation with more than one axis
return _rotationLongitudinalAxis;
}
// We have an object with a single rotation axis. First compute bounds and see if the rotation pivot is not centered.
Bounds localBounds = gameObject.GetLocalBounds(true);
int maxUncenteredComponent = -1;
float maxUncenteredDistance = 0.0f;
for (int i = 0; i < 3; ++i)
{
float centerOffset = Mathf.Abs(localBounds.center[i]);
if (centerOffset > localBounds.size[i] * 0.25f && centerOffset > maxUncenteredDistance)
{
maxUncenteredComponent = i;
maxUncenteredDistance = centerOffset;
}
}
// Found an axis that is significantly larger than others?
if (maxUncenteredComponent != -1)
{
return maxUncenteredComponent;
}
// At this point the best bet is the single rotation axis
int singleRotationAxisIndex = SingleRotationAxisIndex;
if (singleRotationAxisIndex != -1)
{
return singleRotationAxisIndex;
}
return UxrAxis.Z;
}
/// <summary>
/// Tries to infer the most appropriate <see cref="UxrRotationProvider" /> to rotate the object based on the shape and
/// size of the object, and the grip.
/// </summary>
/// <param name="gripPos">The grip snap position</param>
/// <returns>Most appropriate <see cref="UxrRotationProvider" /></returns>
public UxrRotationProvider GetAutoRotationProvider(Vector3 gripPos)
{
if (IsDummyGrabbableParent)
{
// Dummy grabbable parent objects will 99.99% rotate around pivot
return UxrRotationProvider.HandPositionAroundPivot;
}
if (!(HasTranslationConstraint && LimitedRangeOfMotionRotationAxes.Any()))
{
// No constraint
return UxrRotationProvider.HandOrientation;
}
UxrAxis longitudinalAxis = GetMostProbableLongitudinalRotationAxis();
int singleRotationAxisIndex = SingleRotationAxisIndex;
float leverageDistance = 0.0f;
if (singleRotationAxisIndex != -1)
{
// Object with a single rotation axis
if (longitudinalAxis != singleRotationAxisIndex)
{
// Lever action
return UxrRotationProvider.HandPositionAroundPivot;
}
// Lever action will depend on grabber distance to rotation axis. Smaller than a hand distance will use rotation while larger will use leverage.
leverageDistance = Vector3.ProjectOnPlane(gripPos - transform.position, transform.TransformDirection(longitudinalAxis)).magnitude;
return leverageDistance > UxrConstants.Hand.HandWidth ? UxrRotationProvider.HandPositionAroundPivot : UxrRotationProvider.HandOrientation;
}
// Object with more than one rotation axis
leverageDistance = Mathf.Abs(gripPos.DistanceToPlane(transform.position, transform.TransformDirection(longitudinalAxis)));
return leverageDistance > UxrConstants.Hand.HandWidth ? UxrRotationProvider.HandPositionAroundPivot : UxrRotationProvider.HandOrientation;
}
/// <summary>
/// Stops a smooth manipulation transition.
/// </summary>
public void StopSmoothManipulationTransition()
{
SmoothManipulationTimer = -1.0f;
}
/// <summary>
/// Finishes all smooth transitions (manipulation, placing or constraining).
/// Use this when applying custom translation/rotation to a grabbable object without transitions getting in the way.
/// </summary>
public void FinishSmoothTransitions()
{
StopSmoothManipulationTransition();
StopSmoothConstrain();
StopSmoothAnchorPlacement();
}
#endregion
#region Internal Methods
/// <summary>
/// Checks whether the given snap mode affects the object position. This only references if the object position is
/// going to change in order to snap to the hand, not whether the object itself can be moved while grabbed.
/// </summary>
/// <param name="snapMode">Snap mode</param>
/// <returns>Whether the given snap mode will make the object position move to the snap position</returns>
internal static bool GetSnapModeAffectsPosition(UxrSnapToHandMode snapMode)
{
return snapMode != UxrSnapToHandMode.DontSnap && snapMode != UxrSnapToHandMode.RotationOnly;
}
/// <summary>
/// Checks whether the given snap mode affects the object position. This only references if the object position is
/// going to change in order to snap to the hand, not whether the object itself can be moved while grabbed.
/// </summary>
/// <param name="snapMode">Snap mode</param>
/// <returns>Whether the given snap mode will make the object position move to the snap position</returns>
internal static bool GetSnapModeAffectsPosition(UxrSnapToAnchorMode snapMode)
{
return snapMode != UxrSnapToAnchorMode.DontSnap && snapMode != UxrSnapToAnchorMode.RotationOnly;
}
/// <summary>
/// Checks whether the given snap mode affects the object rotation. This only references if the object rotation is
/// going to change in order to snap to the hand, not whether the object itself can be rotated while grabbed.
/// </summary>
/// <param name="snapMode">Snap mode</param>
/// <returns>Whether the given snap mode will make the object rotate to the snap orientation</returns>
internal static bool GetSnapModeAffectsRotation(UxrSnapToHandMode snapMode)
{
return snapMode != UxrSnapToHandMode.DontSnap && snapMode != UxrSnapToHandMode.PositionOnly;
}
/// <summary>
/// Checks whether the given snap mode affects the object rotation. This only references if the object rotation is
/// going to change in order to snap to the hand, not whether the object itself can be rotated while grabbed.
/// </summary>
/// <param name="snapMode">Snap mode</param>
/// <returns>Whether the given snap mode will make the object rotate to the snap orientation</returns>
internal static bool GetSnapModeAffectsRotation(UxrSnapToAnchorMode snapMode)
{
return snapMode != UxrSnapToAnchorMode.DontSnap && snapMode != UxrSnapToAnchorMode.PositionOnly;
}
/// <summary>
/// Checks whether the given grab point's snap mode affects the object position. This only references if the object
/// position is going to change in order to snap to the hand, not whether the object itself can be moved while grabbed.
/// </summary>
/// <param name="grabPoint">Grab point index</param>
/// <returns>Whether the given grab point's snap mode will make the object position move to the snap position</returns>
internal bool GetGrabPointSnapModeAffectsPosition(int grabPoint)
{
return GetSnapModeAffectsPosition(GetGrabPoint(grabPoint).SnapMode);
}
/// <summary>
/// Same as <see cref="GetGrabPointSnapModeAffectsPosition(int)" /> but also checks if the snap direction is in the
/// direction specified.
/// </summary>
/// <param name="grabPoint">Grab point index</param>
/// <param name="snapDirection">Direction in which the snap should occur</param>
/// <returns>
/// Whether the given grab point's snap mode will make the object position move to the snap position and in the
/// direction specified
/// </returns>
internal bool GetGrabPointSnapModeAffectsPosition(int grabPoint, UxrHandSnapDirection snapDirection)
{
return snapDirection == GetGrabPoint(grabPoint).SnapDirection && GetSnapModeAffectsPosition(GetGrabPoint(grabPoint).SnapMode);
}
/// <summary>
/// Checks whether the given grab point's snap mode affects the object rotation. This only references if the object
/// rotation is going to change in order to snap to the hand, not whether the object itself can be rotated while
/// grabbed.
/// </summary>
/// <param name="grabPoint">Grab point index</param>
/// <returns>Whether the given grab point's snap mode will make the object rotate to the snap orientation</returns>
internal bool GetGrabPointSnapModeAffectsRotation(int grabPoint)
{
return GetSnapModeAffectsRotation(GetGrabPoint(grabPoint).SnapMode);
}
/// <summary>
/// Same as <see cref="GetGrabPointSnapModeAffectsRotation(int)" /> but also checks if the snap direction is in the
/// direction specified.
/// </summary>
/// <param name="grabPoint">Grab point index</param>
/// <param name="snapDirection">Direction in which the snap should occur</param>
/// <returns>
/// Whether the given grab point's snap mode will make the object rotate to the snap orientation and in the
/// direction specified
/// </returns>
internal bool GetGrabPointSnapModeAffectsRotation(int grabPoint, UxrHandSnapDirection snapDirection)
{
return snapDirection == GetGrabPoint(grabPoint).SnapDirection && GetSnapModeAffectsRotation(GetGrabPoint(grabPoint).SnapMode);
}
/// <summary>
/// Updates parent and children information on this grabbable and all other grabbable objects in same the hierarchy, up
/// or down.
/// </summary>
internal void UpdateGrabbableDependencies()
{
static void UpdateGrabbableDependenciesRecursive(UxrGrabbableObject grabbableObject, ref List<UxrGrabbableObject> processed)
{
if (!processed.Contains(grabbableObject))
{
processed.Add(grabbableObject);
grabbableObject.AllParents = GetParents(grabbableObject, false).ToList();
grabbableObject.ParentLookAts = GetParents(grabbableObject, true).ToList();
grabbableObject.AllChildren = GetChildren(grabbableObject, false, false, false).ToList();
grabbableObject.AllChildrenLookAts = GetChildren(grabbableObject, false, true, true).ToList();
grabbableObject.DirectChildrenLookAts = GetChildren(grabbableObject, true, true, true).ToList();
foreach (UxrGrabbableObject parent in grabbableObject.ParentLookAts)
{
UpdateGrabbableDependenciesRecursive(parent, ref processed);
}
foreach (UxrGrabbableObject child in grabbableObject.AllChildren)
{
UpdateGrabbableDependenciesRecursive(child, ref processed);
}
}
}
List<UxrGrabbableObject> processedElements = new List<UxrGrabbableObject>();
UpdateGrabbableDependenciesRecursive(this, ref processedElements);
}
/// <summary>
/// Gets the <see cref="UxrGrabPointShape" /> component for the given grab point.
/// </summary>
/// <param name="grabPoint">Grab point index to get the shape for</param>
/// <returns><see cref="UxrGrabPointShape" /> component or null if there is none</returns>
internal UxrGrabPointShape GetGrabPointShape(int grabPoint)
{
if (_grabPointShapes.TryGetValue(grabPoint, out UxrGrabPointShape shape))
{
return shape;
}
return null;
}
/// <summary>
/// Gets the <see cref="Transform" /> component that is used to compute the distance to a <see cref="UxrGrabber" /> for
/// a given grab point.
/// </summary>
/// <param name="grabber">Grabber to get the distance from</param>
/// <param name="grabPoint">Grab point to get the distance to</param>
/// <returns><see cref="Transform" /> to compute the distance with</returns>
internal Transform GetGrabPointGrabProximityTransform(UxrGrabber grabber, int grabPoint)
{
Transform proximityTransform = null;
UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint);
if (grabPointInfo.GrabProximityTransformUseSelf)
{
proximityTransform = grabber.Side == UxrHandSide.Left ? grabPointInfo.GetGripPoseInfo(grabber.Avatar).GripAlignTransformHandLeft : grabPointInfo.GetGripPoseInfo(grabber.Avatar).GripAlignTransformHandRight;
}
else
{
proximityTransform = grabPointInfo.GrabProximityTransform;
}
return proximityTransform != null ? proximityTransform : transform;
}
/// <summary>
/// Gets the <see cref="Transform" /> that is used to compute the snap to an <see cref="UxrAvatar" />'s hand (
/// <see cref="UxrGrabber" />) for a given grab point when it is grabbed.
/// </summary>
/// <param name="avatar">Avatar to compute the alignment for</param>
/// <param name="grabPoint">Grab point to get the alignment of</param>
/// <param name="handSide">The hand to get the snap transform for</param>
/// <returns><see cref="Transform" /> that should align to the grabber when it is being grabbed</returns>
internal Transform GetGrabPointGrabAlignTransform(UxrAvatar avatar, int grabPoint, UxrHandSide handSide)
{
UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint);
if (grabPointInfo == null)
{
Debug.LogWarning($"{UxrConstants.ManipulationModule}: Object " + name + $" has no grab point info for index {grabPoint}");
return transform;
}
UxrGripPoseInfo gripPoseInfo = grabPointInfo.GetGripPoseInfo(avatar);
if (gripPoseInfo == null)
{
Debug.LogWarning($"{UxrConstants.ManipulationModule}: Object " + name + $" has no grip pose info for avatar {avatar.name}");
return transform;
}
if (handSide == UxrHandSide.Left)
{
if (gripPoseInfo.GripAlignTransformHandLeft == null || grabPointInfo.SnapReference != UxrSnapReference.UseOtherTransform)
{
return transform;
}
return gripPoseInfo.GripAlignTransformHandLeft;
}
if (gripPoseInfo.GripAlignTransformHandRight == null || grabPointInfo.SnapReference != UxrSnapReference.UseOtherTransform)
{
return transform;
}
return gripPoseInfo.GripAlignTransformHandRight;
}
/// <summary>
/// Gets the additional rotation required to apply to a grabber in order to align it with the controller.
/// </summary>
/// <param name="grabber">Grabber</param>
/// <param name="grabPoint">Grab point</param>
/// <returns>Additional rotation to apply to the object using the current grabber to align it with the controller</returns>
internal Quaternion GetGrabberControllerAlignmentRotation(UxrGrabber grabber, int grabPoint)
{
UxrGrabPointInfo grabPointInfo = GetGrabPoint(grabPoint);
Transform snapTransform = GetGrabPointGrabAlignTransform(grabber.Avatar, grabPoint, grabber.Side);
if (snapTransform == null || grabPointInfo == null || !grabPointInfo.AlignToController)
{
return Quaternion.identity;
}
UxrController3DModel controller3DModel = grabber != null && grabber.Avatar != null ? grabber.Avatar.ControllerInput.GetController3DModel(grabber.Side) : null;
if (controller3DModel != null)
{
Quaternion relativeTrackerRotation = Quaternion.Inverse(grabber.transform.rotation) * controller3DModel.ForwardTrackingRotation;
Quaternion trackerRotation = grabber.transform.rotation * relativeTrackerRotation;
Quaternion sourceAlignAxes = grabPointInfo.AlignToControllerAxes != null ? grabPointInfo.AlignToControllerAxes.rotation : transform.rotation;
Quaternion grabberTargetRotation = sourceAlignAxes * Quaternion.Inverse(trackerRotation) * grabber.transform.rotation;
return Quaternion.Inverse(grabber.transform.rotation) * grabberTargetRotation;
}
return Quaternion.identity;
}
/// <summary>
/// Notifies that the object just started to be grabbed.
/// </summary>
/// <param name="grabber">Grabber responsible for grabbing the object</param>
/// <param name="grabPoint">Point that was grabbed</param>
/// <param name="snapPosition">The grabber snap position to use</param>
/// <param name="snapRotation">The grabber snap rotation to use</param>
internal void NotifyBeginGrab(UxrGrabber grabber, int grabPoint, Vector3 snapPosition, Quaternion snapRotation)
{
}
/// <summary>
/// Notifies that the object just stopped being grabbed.
/// </summary>
/// <param name="grabber">Grabber responsible for grabbing the object</param>
/// <param name="grabPoint">Point that was grabbed</param>
internal void NotifyEndGrab(UxrGrabber grabber, int grabPoint)
{
}
/// <summary>
/// Computes, if the object has the AutoRotationProvider set, the rotation provider based on the grab snap position.
/// </summary>
/// <param name="snapPosition">Grab snap position</param>
internal void CheckComputeAutoRotationProvider(Vector3 snapPosition)
{
if (_autoRotationProvider)
{
_rotationProvider = GetAutoRotationProvider(snapPosition);
}
}
/// <summary>
/// Starts a smooth manipulation transition.
/// </summary>
internal void StartSmoothManipulationTransition()
{
SmoothManipulationTimer = UxrConstants.SmoothManipulationTransitionSeconds;
SmoothManipulationLocalPositionStart = transform.localPosition;
SmoothManipulationLocalRotationStart = transform.localRotation;
SmoothManipulationReference = transform.parent;
}
/// <summary>
/// Updates the smooth manipulation transitions if they exist.
/// </summary>
internal void UpdateSmoothManipulationTransition(float deltaTime)
{
if (SmoothManipulationTimer < 0.0f)
{
return;
}
SmoothManipulationTimer -= deltaTime;
if (SmoothManipulationTimer > 0.0f)
{
float t = SmoothManipulationTimer <= 0.0f ? 1.0f : 1.0f - Mathf.Clamp01(SmoothManipulationTimer / UxrConstants.SmoothManipulationTransitionSeconds);
if (transform.parent == SmoothManipulationReference || SmoothManipulationReference == null)
{
transform.localPosition = Vector3.Lerp(SmoothManipulationLocalPositionStart, transform.localPosition, t);
transform.localRotation = Quaternion.Slerp(SmoothManipulationLocalRotationStart, transform.localRotation, t);
}
else
{
Vector3 posStart = TransformExt.GetWorldPosition(SmoothManipulationReference, SmoothManipulationLocalPositionStart);
Vector3 posEnd = transform.position;
Quaternion rotStart = TransformExt.GetWorldRotation(SmoothManipulationReference, SmoothManipulationLocalRotationStart);
Quaternion rotEnd = transform.rotation;
transform.position = Vector3.Lerp(posStart, posEnd, t);
transform.rotation = Quaternion.Slerp(rotStart, rotEnd, t);
}
}
}
/// <summary>
/// Starts a smooth constraining transition.
/// </summary>
internal void StartSmoothConstrain()
{
SmoothConstrainTimer = UxrConstants.SmoothManipulationTransitionSeconds;
SmoothConstrainLocalPositionStart = transform.localPosition;
SmoothConstrainLocalRotationStart = transform.localRotation;
SmoothConstrainReference = transform.parent;
}
/// <summary>
/// Stops a smooth constraining transition if there is one.
/// </summary>
internal void StopSmoothConstrain()
{
SmoothConstrainTimer = -1.0f;
}
/// <summary>
/// Updates the smooth constraining transition if there is one.
/// </summary>
internal void UpdateSmoothConstrainTransition(float deltaTime)
{
if (SmoothConstrainTimer < 0.0f)
{
return;
}
SmoothConstrainTimer -= deltaTime;
float t = 1.0f - Mathf.Clamp01(SmoothConstrainTimer / UxrConstants.SmoothManipulationTransitionSeconds);
if (transform.parent == SmoothConstrainReference || SmoothConstrainReference == null)
{
transform.localPosition = Vector3.Lerp(SmoothConstrainLocalPositionStart, transform.localPosition, t);
transform.localRotation = Quaternion.Slerp(SmoothConstrainLocalRotationStart, transform.localRotation, t);
}
else
{
Vector3 posStart = TransformExt.GetWorldPosition(SmoothConstrainReference, SmoothConstrainLocalPositionStart);
Vector3 posEnd = transform.position;
Quaternion rotStart = TransformExt.GetWorldRotation(SmoothConstrainReference, SmoothConstrainLocalRotationStart);
Quaternion rotEnd = transform.rotation;
transform.position = Vector3.Lerp(posStart, posEnd, t);
transform.rotation = Quaternion.Slerp(rotStart, rotEnd, t);
}
}
/// <summary>
/// Starts a smooth transition to the object placement.
/// </summary>
internal void StartSmoothAnchorPlacement()
{
if (_dropSnapMode != UxrSnapToAnchorMode.DontSnap && CurrentAnchor != null)
{
SmoothPlacementTimer = UxrConstants.SmoothManipulationTransitionSeconds;
SmoothPlacementLocalPositionStart = transform.localPosition;
SmoothPlacementLocalRotationStart = transform.localRotation;
SmoothPlacementReference = transform.parent;
}
}
/// <summary>
/// Stops the smooth anchor placement transition if there is one.
/// </summary>
internal void StopSmoothAnchorPlacement()
{
SmoothPlacementTimer = -1.0f;
}
/// <summary>
/// Updates the smooth anchor placement transition if there is one.
/// </summary>
internal void UpdateSmoothAnchorPlacement(float deltaTime)
{
if (SmoothPlacementTimer < 0.0f || CurrentAnchor == null)
{
return;
}
// Smooth placement transitions
SmoothPlacementTimer -= deltaTime;
float t = 1.0f - Mathf.Clamp01(SmoothPlacementTimer / UxrConstants.SmoothManipulationTransitionSeconds);
Vector3 targetPosition = transform.position;
Quaternion targetRotation = transform.rotation;
TransformExt.ApplyAlignment(ref targetPosition, ref targetRotation, DropAlignTransform.position, DropAlignTransform.rotation, CurrentAnchor.AlignTransform.position, CurrentAnchor.AlignTransform.rotation);
if (GetSnapModeAffectsPosition(DropSnapMode) || PlacementOptions.HasFlag(UxrPlacementOptions.ForceSnapPosition))
{
if (transform.parent == CurrentAnchor.transform)
{
Vector3 targetLocalPosition = TransformExt.GetLocalPosition(transform.parent, targetPosition);
transform.localPosition = Vector3.Lerp(SmoothPlacementLocalPositionStart, targetLocalPosition, t);
}
else
{
Vector3 posStart = TransformExt.GetWorldPosition(SmoothPlacementReference, SmoothPlacementLocalPositionStart);
transform.position = Vector3.Lerp(posStart, targetPosition, t);
}
}
if (GetSnapModeAffectsRotation(DropSnapMode) || PlacementOptions.HasFlag(UxrPlacementOptions.ForceSnapRotation))
{
if (transform.parent == CurrentAnchor.transform)
{
Quaternion targetLocalRotation = TransformExt.GetLocalRotation(transform.parent, targetRotation);
transform.localRotation = Quaternion.Slerp(SmoothPlacementLocalRotationStart, targetLocalRotation, t);
}
else
{
Quaternion rotStart = TransformExt.GetWorldRotation(SmoothPlacementReference, SmoothPlacementLocalRotationStart);
transform.rotation = Quaternion.Slerp(rotStart, targetRotation, t);
}
}
if (SmoothPlacementTimer <= 0.0f)
{
CurrentAnchor.RaiseSmoothTransitionPlaceEnded();
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the object and creates the <see cref="UxrGrabbableObjectAnchor" /> the object is placed on initially if
/// <see cref="_autoCreateStartAnchor" /> is set.
/// </summary>
protected override void Awake()
{
base.Awake();
// Make sure singleton is created so that grabbable objects get registered
UxrGrabManager.Instance.Poke();
// Fix some common mistakes just in case
Vector3 fixedMin = Vector3.Min(_translationLimitsMin, _translationLimitsMax);
Vector3 fixedMax = Vector3.Max(_translationLimitsMin, _translationLimitsMax);
_translationLimitsMin = fixedMin;
_translationLimitsMax = fixedMax;
fixedMin = Vector3.Min(_rotationAngleLimitsMin, _rotationAngleLimitsMax);
fixedMax = Vector3.Max(_rotationAngleLimitsMin, _rotationAngleLimitsMax);
_rotationAngleLimitsMin = fixedMin;
_rotationAngleLimitsMax = fixedMax;
if (_autoCreateStartAnchor)
{
UxrGrabbableObjectAnchor newAnchor = new GameObject($"{name} Auto Anchor", typeof(UxrGrabbableObjectAnchor)).GetComponent<UxrGrabbableObjectAnchor>();
// Make sure to generate same unique ID for the new anchor to support multiplayer, state save, etc.
newAnchor.ChangeUniqueId(GuidExt.Combine(UniqueId, newAnchor.name.GetGuid()));
newAnchor.MaxPlaceDistance = _autoAnchorMaxPlaceDistance;
newAnchor.transform.SetPositionAndRotation(DropAlignTransform);
newAnchor.transform.SetParent(transform.parent);
transform.SetParent(newAnchor.transform);
if (!string.IsNullOrEmpty(Tag))
{
newAnchor.AddCompatibleTags(Tag);
}
_startAnchor = newAnchor;
}
CurrentAnchor = _startAnchor;
if (CurrentAnchor != null)
{
CurrentAnchor.CurrentPlacedObject = this;
if (_rigidBodySource)
{
_rigidBodySource.isKinematic = true;
}
}
_initialIsKinematic = IsKinematic;
_grabPointShapes = new Dictionary<int, UxrGrabPointShape>();
UxrGrabPointShape[] grabPointShapes = GetComponents<UxrGrabPointShape>();
foreach (UxrGrabPointShape grabPointShape in grabPointShapes)
{
if (_grabPointShapes.ContainsKey(grabPointShape.GrabPoint))
{
Debug.LogWarning($"{UxrConstants.ManipulationModule}: Object " + name + " has duplicated GrabPointShape for " + UxrGrabPointIndex.GetIndexDisplayName(this, grabPointShape.GrabPoint));
}
else
{
_grabPointShapes.Add(grabPointShape.GrabPoint, grabPointShape);
}
}
if (GrabbableParent != null)
{
InitialPositionInLocalGrabbableParent = GrabbableParent.transform.InverseTransformPoint(transform.position);
}
UpdateGrabbableDependencies();
}
/// <summary>
/// Called when the object is destroyed. Checks whether it is being grabbed to release it.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
if (IsBeingGrabbed)
{
UxrGrabManager.Instance.ReleaseGrabs(this, true);
}
}
/// <summary>
/// Called when the component is disabled. Stops all transitions.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
SmoothManipulationTimer = -1.0f;
SmoothPlacementTimer = -1.0f;
SmoothConstrainTimer = -1.0f;
}
/// <summary>
/// Resets the component.
/// </summary>
protected override void Reset()
{
base.Reset();
_grabPoint = new UxrGrabPointInfo();
_rotationLongitudinalAxis = GetMostProbableLongitudinalRotationAxis();
}
#endregion
#region Coroutines
/// <summary>
/// Coroutine that will keep manually in sync grabbable object rigidbodies without native network synchronization
/// components.
/// </summary>
/// <param name="grabber">The grabber that released the object</param>
/// <param name="intervalSeconds">Interval in seconds to send sync messages</param>
/// <returns>Coroutine IEnumerator</returns>
private IEnumerator RegularPhysicsSyncCoroutine(UxrGrabber grabber, float intervalSeconds)
{
while (_rigidBodySource != null)
{
bool isSleeping = _rigidBodySource.IsSleeping();
UpdateRigidbody(grabber, isSleeping, transform.position, transform.rotation, _rigidBodySource.velocity, _rigidBodySource.angularVelocity);
if (isSleeping)
{
break;
}
yield return new WaitForSeconds(intervalSeconds);
}
_regularPhysicsSyncCoroutine = null;
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for <see cref="ConstraintsApplying" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseConstraintsApplying(UxrApplyConstraintsEventArgs e)
{
if (UxrGrabManager.Instance.Features.HasFlag(UxrManipulationFeatures.UserConstraints))
{
ConstraintsApplying?.Invoke(this, e);
}
}
/// <summary>
/// Event trigger for <see cref="ConstraintsApplied" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseConstraintsApplied(UxrApplyConstraintsEventArgs e)
{
if (UxrGrabManager.Instance.Features.HasFlag(UxrManipulationFeatures.UserConstraints))
{
ConstraintsApplied?.Invoke(this, e);
}
}
/// <summary>
/// Event trigger for <see cref="ConstraintsFinished" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseConstraintsFinished(UxrApplyConstraintsEventArgs e)
{
if (UxrGrabManager.Instance.Features.HasFlag(UxrManipulationFeatures.UserConstraints))
{
ConstraintsFinished?.Invoke(this, e);
}
}
/// <summary>
/// Event trigger for <see cref="Grabbing" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseGrabbingEvent(UxrManipulationEventArgs e)
{
if (_regularPhysicsSyncCoroutine != null)
{
StopCoroutine(_regularPhysicsSyncCoroutine);
_regularPhysicsSyncCoroutine = null;
}
Grabbing?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="Grabbed" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseGrabbedEvent(UxrManipulationEventArgs e)
{
Grabbed?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="Releasing" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseReleasingEvent(UxrManipulationEventArgs e)
{
Releasing?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="Released" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseReleasedEvent(UxrManipulationEventArgs e)
{
// Check whether the object was released, is in a networking environment and requires manually keep physics in sync through messages
if (UxrGlobalSettings.Instance.SyncGrabbablePhysics &&
_rigidBodySource != null &&
_rigidBodyDynamicOnRelease &&
!IsBeingGrabbed &&
e.Grabber != null &&
e.Grabber.Avatar.GetComponent<IUxrNetworkAvatar>() is IUxrNetworkAvatar networkAvatar &&
networkAvatar.IsLocal &&
UxrNetworkManager.HasInstance &&
!UxrNetworkManager.Instance.NetworkImplementation.HasNetworkTransformSyncComponents(gameObject))
{
if (_regularPhysicsSyncCoroutine != null)
{
StopCoroutine(_regularPhysicsSyncCoroutine);
}
_regularPhysicsSyncCoroutine = StartCoroutine(RegularPhysicsSyncCoroutine(e.Grabber, UxrGlobalSettings.Instance.GrabbableSyncIntervalSeconds));
}
Released?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="Placing" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaisePlacingEvent(UxrManipulationEventArgs e)
{
if (_regularPhysicsSyncCoroutine != null)
{
StopCoroutine(_regularPhysicsSyncCoroutine);
_regularPhysicsSyncCoroutine = null;
}
Placing?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="Placed" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaisePlacedEvent(UxrManipulationEventArgs e)
{
Placed?.Invoke(this, e);
}
#endregion
#region Private Methods
/// <summary>
/// Gets a <see cref="UxrGrabbableObject" />'s list of parents whose direction can be controlled
/// indirectly by a grabbable object.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="onlyControlDirection">
/// Whether to return only the chain to the first parent that is not controlled by using
/// <see cref="UsesGrabbableParentDependency" /> and <see cref="ControlParentDirection" />
/// </param>
/// <returns>Bottom-to-top sorted list of parents</returns>
private static IEnumerable<UxrGrabbableObject> GetParents(UxrGrabbableObject grabbableObject, bool onlyControlDirection)
{
if (grabbableObject == null)
{
yield break;
}
UxrGrabbableObject current = grabbableObject;
UxrGrabbableObject parent = current.GrabbableParent;
while (current != null && parent != null)
{
bool valid = !onlyControlDirection || (current.UsesGrabbableParentDependency && current.ControlParentDirection);
if (valid)
{
yield return parent;
}
else
{
yield break;
}
current = parent;
parent = parent.GrabbableParent;
}
}
/// <summary>
/// Gets a <see cref="UxrGrabbableObject" />'s list of children.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="onlyDirect">
/// Whether to filter children that have no other grabbable between the child and the grabbable
/// object
/// </param>
/// <param name="onlyUseDependency">
/// Whether to filter children that have <see cref="UsesGrabbableParentDependency" />
/// </param>
/// <param name="onlyControlDirection">Whether to filter children that have <see cref="ControlParentDirection" /> </param>
/// <returns>List of children that control the parent grabbable direction</returns>
private static IEnumerable<UxrGrabbableObject> GetChildren(UxrGrabbableObject grabbableObject, bool onlyDirect, bool onlyUseDependency, bool onlyControlDirection)
{
if (grabbableObject == null)
{
yield break;
}
UxrGrabbableObject[] children = grabbableObject.GetComponentsInChildren<UxrGrabbableObject>();
foreach (UxrGrabbableObject child in children)
{
bool validControl = !(onlyUseDependency && !child.UsesGrabbableParentDependency);
if (onlyControlDirection && !child.ControlParentDirection)
{
validControl = false;
}
if (child != grabbableObject && validControl)
{
if (onlyDirect)
{
if (child.GrabbableParent == grabbableObject)
{
yield return child;
}
}
else
{
yield return child;
}
}
}
}
/// <summary>
/// Updates the rigidbody making sure the messages are synchronized through the network.
/// </summary>
/// <param name="grabber">The grabber that released the object</param>
/// <param name="isSleeping">Whether the rigidbody is sleeping</param>
/// <param name="transformPosition">The rigidbody position</param>
/// <param name="transformRotation">The rigidbody rotation</param>
/// <param name="velocity">The rigidbody velocity</param>
/// <param name="angularVelocity">The rigidbody angular velocity</param>
private void UpdateRigidbody(UxrGrabber grabber, bool isSleeping, Vector3 transformPosition, Quaternion transformRotation, Vector3 velocity, Vector3 angularVelocity)
{
if (!_rigidBodySource)
{
return;
}
bool isLocal = grabber != null && grabber.Avatar != null && grabber.Avatar.GetComponent<IUxrNetworkAvatar>() is IUxrNetworkAvatar networkAvatar && networkAvatar.IsLocal;
// Only sync in network, we don't use it anywhere else.
BeginSync(UxrStateSyncOptions.Network);
if (!isLocal)
{
_rigidBodySource.transform.position = transformPosition;
_rigidBodySource.transform.rotation = transformRotation;
_rigidBodySource.velocity = velocity;
_rigidBodySource.angularVelocity = angularVelocity;
if (isSleeping)
{
_rigidBodySource.Sleep();
}
}
EndSyncMethod(new object[] { grabber, isSleeping, transformPosition, transformRotation, velocity, angularVelocity });
}
/// <summary>
/// Gets the first <see cref="UxrGrabbableObject" /> upwards in the hierarchy.
/// </summary>
/// <param name="grabbableTransform"><see cref="UxrGrabbableObject" /> to get the grabbable parent of</param>
/// <returns><see cref="UxrGrabbableObject" /> upwards in the hierarchy</returns>
private UxrGrabbableObject GetGrabbableParent(UxrGrabbableObject grabbableObject)
{
if (grabbableObject.transform.parent != null)
{
UxrGrabbableObject parentGrabbableObject = grabbableObject.transform.parent.GetComponentInParent<UxrGrabbableObject>();
if (parentGrabbableObject != null)
{
return parentGrabbableObject;
}
}
return null;
}
#endregion
#region Private Types & Data
/// <summary>
/// Gets or sets the grabbable object's local position at the beginning of a smooth transition.
/// </summary>
private Vector3 SmoothManipulationLocalPositionStart { get; set; }
/// <summary>
/// Gets or sets the grabbable object's local rotation at the beginning of a smooth transition.
/// </summary>
private Quaternion SmoothManipulationLocalRotationStart { get; set; }
/// <summary>
/// Gets or sets the reference for <see cref="SmoothManipulationLocalPositionStart"/> and <see cref="SmoothManipulationLocalRotationStart"/>.
/// </summary>
private Transform SmoothManipulationReference { get; set; }
/// <summary>
/// Local position when the smooth object placement started.
/// </summary>
private Vector3 SmoothPlacementLocalPositionStart { get; set; }
/// <summary>
/// Local rotation when the smooth object placement started.
/// </summary>
private Quaternion SmoothPlacementLocalRotationStart { get; set; }
/// <summary>
/// Gets or sets the reference for <see cref="SmoothPlacementLocalPositionStart"/> and <see cref="SmoothPlacementLocalRotationStart"/>.
/// </summary>
private Transform SmoothPlacementReference { get; set; }
/// <summary>
/// Local position at the beginning of a smooth constrain transition.
/// </summary>
private Vector3 SmoothConstrainLocalPositionStart { get; set; }
/// <summary>
/// Local rotation at the beginning of a smooth constrain transition.
/// </summary>
private Quaternion SmoothConstrainLocalRotationStart { get; set; }
/// <summary>
/// Gets or sets the reference for <see cref="SmoothConstrainLocalPositionStart"/> and <see cref="SmoothConstrainLocalRotationStart"/>.
/// </summary>
private Transform SmoothConstrainReference { get; set; }
/// <summary>
/// Gets or sets the decreasing timer that is initialized at
/// <see cref="UxrConstants.SmoothManipulationTransitionSeconds" />
/// for smooth manipulation transitions.
/// </summary>
private float SmoothManipulationTimer { get; set; } = -1.0f;
/// <summary>
/// Gets or sets the decreasing placement timer for smooth placement transitions.
/// </summary>
private float SmoothPlacementTimer { get; set; } = -1.0f;
/// <summary>
/// Gets or sets the decreasing constrain timer for smooth constraint transitions.
/// </summary>
private float SmoothConstrainTimer { get; set; } = -1.0f;
/// <summary>
/// Gets whether the object is currently in a smooth transition.
/// </summary>
private bool IsInSmoothTransition => SmoothManipulationTimer > 0.0f || SmoothPlacementTimer > 0.0f || SmoothConstrainTimer > 0.0f;
// Backing fields for public properties
private UxrGrabbableObjectAnchor _currentAnchor;
private bool _isPlaceable = true;
private bool _isLockedInPlace;
// Private vars from internal properties
private float _singleRotationAngleCumulative;
private UxrPlacementOptions _placementOptions;
// Backing fields for IUxrGrabbable
private bool _isGrabbable = true;
// Private vars
private Dictionary<int, bool> _grabPointEnabledStates = new Dictionary<int, bool>();
private bool _initialIsKinematic = true;
private Dictionary<int, UxrGrabPointShape> _grabPointShapes = new Dictionary<int, UxrGrabPointShape>();
// Coroutines
private Coroutine _regularPhysicsSyncCoroutine;
#endregion
#if UNITY_EDITOR
/// <summary>
/// Registers a new avatar to have grips on this object. If the avatar is an instance it will register its source
/// prefab, otherwise it will register the avatar prefab itself. The reason to register the prefab is so that child
/// prefabs/instances will be able to use the same poses.
/// </summary>
/// <param name="avatar">Avatar to register</param>
public void Editor_RegisterAvatarForGrips(UxrAvatar avatar)
{
for (int i = 0; i < GrabPointCount; ++i)
{
GetGrabPoint(i).CheckAddGripPoseInfo(avatar.PrefabGuid);
}
}
/// <summary>
/// Removes an avatar that was registered for grip poses. If the avatar is an instance it will unregister its source
/// prefab, otherwise it will unregister the avatar prefab itself.
/// </summary>
/// <param name="avatar">The avatar to remove</param>
public void Editor_RemoveAvatarFromGrips(UxrAvatar avatar)
{
for (int i = 0; i < GrabPointCount; ++i)
{
UxrGrabPointInfo grabPointInfo = GetGrabPoint(i);
grabPointInfo.RemoveGripPoseInfo(avatar.PrefabGuid);
}
}
/// <summary>
/// Gets the editor currently selected avatar prefab GUID whose grip parameters are being edited.
/// </summary>
/// <returns>Avatar prefab GUID or null if there isn't any avatar prefab selected</returns>
public string Editor_GetSelectedAvatarPrefabGuidForGrips()
{
if (_selectedAvatarForGrips)
{
UxrAvatar avatar = _selectedAvatarForGrips.GetComponent<UxrAvatar>();
if (avatar)
{
return avatar.PrefabGuid;
}
}
return null;
}
/// <summary>
/// Gets the editor currently selected avatar prefab whose grip parameters are being edited.
/// </summary>
/// <returns>Avatar prefab</returns>
public GameObject Editor_GetSelectedAvatarPrefabForGrips()
{
return _selectedAvatarForGrips;
}
/// <summary>
/// Sets the editor currently selected avatar prefab whose grip parameters will be edited.
/// </summary>
public void Editor_SetSelectedAvatarForGrips(GameObject avatarPrefab)
{
_selectedAvatarForGrips = avatarPrefab;
}
/// <summary>
/// Gets the avatar prefab GUIDs that have been registered to have dedicated grip parameters to grab the object.
/// </summary>
/// <returns>Registered avatar prefab GUIDs</returns>
public List<string> Editor_GetRegisteredAvatarsGuids()
{
List<string> registeredAvatars = new List<string>();
// All grab points have the same amount of avatar grip entries. Use default grab point.
UxrGrabPointInfo grabPointInfo = GetGrabPoint(0);
foreach (UxrGripPoseInfo poseInfo in grabPointInfo.AvatarGripPoseEntries)
{
if (!registeredAvatars.Contains(poseInfo.AvatarPrefabGuid))
{
registeredAvatars.Add(poseInfo.AvatarPrefabGuid);
}
}
return registeredAvatars;
}
/// <summary>
/// If we add new grab points from the editor, we want to make sure that all avatars are registered in those new
/// entries.
/// This method makes sure that the avatars are registered in all grab points.
/// </summary>
public void Editor_CheckRegisterAvatarsInNewGrabPoints()
{
List<string> registeredAvatars = new List<string>();
// Look for all registered avatars
for (int i = 0; i < GrabPointCount; ++i)
{
UxrGrabPointInfo grabPointInfo = GetGrabPoint(i);
foreach (UxrGripPoseInfo gripPoseInfo in grabPointInfo.AvatarGripPoseEntries)
{
if (!registeredAvatars.Contains(gripPoseInfo.AvatarPrefabGuid))
{
registeredAvatars.Add(gripPoseInfo.AvatarPrefabGuid);
}
}
}
// Fill missing avatars in grab points
for (int i = 0; i < GrabPointCount; ++i)
{
UxrGrabPointInfo grabPointInfo = GetGrabPoint(i);
foreach (string prefabGuid in registeredAvatars)
{
grabPointInfo.CheckAddGripPoseInfo(prefabGuid);
}
}
}
/// <summary>
/// Gets the prefab avatar that is used for the given grip snap transform. If there is more than one the first is
/// returned.
/// </summary>
/// <param name="snapTransform">The snap transform to get the avatar for</param>
/// <returns>Avatar prefab GUID</returns>
public string Editor_GetGrabAlignTransformAvatar(Transform snapTransform)
{
for (int i = 0; i < GrabPointCount; ++i)
{
UxrGrabPointInfo grabPointInfo = GetGrabPoint(i);
if (grabPointInfo.SnapMode != UxrSnapToHandMode.DontSnap)
{
foreach (UxrGripPoseInfo gripPose in grabPointInfo.AvatarGripPoseEntries)
{
if (gripPose.GripAlignTransformHandLeft == snapTransform || gripPose.GripAlignTransformHandRight == snapTransform)
{
return gripPose.AvatarPrefabGuid;
}
}
}
}
return null;
}
/// <summary>
/// Gets all the transforms used for alignment with the registered avatars.
/// </summary>
/// <returns>List of align transforms</returns>
public IEnumerable<Transform> Editor_GetAllRegisteredAvatarGrabAlignTransforms()
{
for (int i = 0; i < GrabPointCount; ++i)
{
UxrGrabPointInfo grabPointInfo = GetGrabPoint(i);
if (grabPointInfo.SnapMode != UxrSnapToHandMode.DontSnap)
{
foreach (UxrGripPoseInfo gripPose in grabPointInfo.AvatarGripPoseEntries)
{
if (gripPose.GripAlignTransformHandLeft != null)
{
yield return gripPose.GripAlignTransformHandLeft;
}
if (gripPose.GripAlignTransformHandRight != null)
{
yield return gripPose.GripAlignTransformHandRight;
}
}
}
}
}
/// <summary>
/// Gets all the align transforms of a given registered avatar and hand.
/// </summary>
/// <param name="avatar">The avatar instance or prefab</param>
/// <param name="includeLeft">Whether to include snap transforms for the left hand</param>
/// <param name="includeRight">Whether to include snap transforms for the right hand</param>
/// <param name="usePrefabInheritance">Whether to get the transforms for </param>
/// <returns>List of align transforms</returns>
public IEnumerable<Transform> Editor_GetAllRegisteredAvatarGrabAlignTransforms(UxrAvatar avatar, bool includeLeft, bool includeRight, bool usePrefabInheritance)
{
for (int i = 0; i < GrabPointCount; ++i)
{
UxrGrabPointInfo grabPointInfo = GetGrabPoint(i);
if (grabPointInfo.SnapMode != UxrSnapToHandMode.DontSnap)
{
foreach (UxrGripPoseInfo gripPoseInfo in grabPointInfo.GetCompatibleGripPoseInfos(avatar, usePrefabInheritance))
{
if (includeLeft && gripPoseInfo.GripAlignTransformHandLeft != null)
{
yield return gripPoseInfo.GripAlignTransformHandLeft;
}
if (includeRight && gripPoseInfo.GripAlignTransformHandRight != null)
{
yield return gripPoseInfo.GripAlignTransformHandRight;
}
}
}
}
}
/// <summary>
/// Gets the grab point indices that use the given <see cref="Transform" /> for alignment when interacting with an
/// avatar.
/// </summary>
/// <param name="avatar">Avatar</param>
/// <param name="snapTransform">Alignment transform</param>
/// <returns>List of align transforms</returns>
public List<int> Editor_GetGrabPointsForGrabAlignTransform(UxrAvatar avatar, Transform snapTransform)
{
List<int> grabPointIndices = new List<int>();
for (int i = 0; i < GrabPointCount; ++i)
{
UxrGrabPointInfo grabPointInfo = GetGrabPoint(i);
UxrGripPoseInfo gripPoseInfo = grabPointInfo.GetGripPoseInfo(avatar);
if (gripPoseInfo.GripAlignTransformHandLeft == snapTransform || gripPoseInfo.GripAlignTransformHandRight == snapTransform)
{
grabPointIndices.Add(i);
}
}
return grabPointIndices;
}
/// <summary>
/// Gets the <see cref="Transform" /> used for alignment when the given avatar grabs a point using a given hand.
/// </summary>
/// <param name="avatar">Avatar</param>
/// <param name="grabPoint">Grab point</param>
/// <param name="handSide">Hand to get the snap transform for</param>
/// <returns>Alignment transform</returns>
public Transform Editor_GetGrabPointGrabAlignTransform(UxrAvatar avatar, int grabPoint, UxrHandSide handSide)
{
UxrGripPoseInfo gripPoseInfo = GetGrabPoint(grabPoint).GetGripPoseInfo(avatar);
if (handSide == UxrHandSide.Left)
{
if (gripPoseInfo.GripAlignTransformHandLeft == null || GetGrabPoint(grabPoint).SnapReference != UxrSnapReference.UseOtherTransform)
{
return transform;
}
return gripPoseInfo.GripAlignTransformHandLeft;
}
if (gripPoseInfo.GripAlignTransformHandRight == null || GetGrabPoint(grabPoint).SnapReference != UxrSnapReference.UseOtherTransform)
{
return transform;
}
return gripPoseInfo.GripAlignTransformHandRight;
}
/// <summary>
/// Checks whether the object has a grab point with the given <see cref="Transform" /> for alignment.
/// </summary>
/// <param name="snapTransform">Transform to check</param>
/// <returns>Whether the given <see cref="Transform" /> is present in any of the grab point alignments</returns>
public bool Editor_HasGrabPointWithGrabAlignTransform(Transform snapTransform)
{
for (int grabPoint = 0; grabPoint < GrabPointCount; ++grabPoint)
{
for (int i = 0; i < GetGrabPoint(grabPoint).GripPoseInfoCount; ++i)
{
UxrGripPoseInfo gripPoseInfo = GetGrabPoint(grabPoint).GetGripPoseInfo(i);
if (gripPoseInfo.GripAlignTransformHandLeft == snapTransform || gripPoseInfo.GripAlignTransformHandRight == snapTransform)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Checks whether the object has a grab point with the given <see cref="Transform" /> for alignment registered using
/// the given prefab.
/// </summary>
/// <param name="snapTransform">Transform to check</param>
/// <param name="avatarPrefab">Avatar prefab</param>
/// <returns>
/// Whether the given <see cref="Transform" /> is present in any of the grab point alignments registered using the
/// given avatar prefab
/// </returns>
public bool Editor_HasGrabPointWithGrabAlignTransform(Transform snapTransform, GameObject avatarPrefab)
{
for (int grabPoint = 0; grabPoint < GrabPointCount; ++grabPoint)
{
UxrGripPoseInfo gripPoseInfo = GetGrabPoint(grabPoint).GetGripPoseInfo(avatarPrefab.GetComponent<UxrAvatar>(), false);
if (gripPoseInfo != null && (gripPoseInfo.GripAlignTransformHandLeft == snapTransform || gripPoseInfo.GripAlignTransformHandRight == snapTransform))
{
return true;
}
}
return false;
}
#endif
}
}
#pragma warning restore 0414