// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- 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 { /// /// Component that, added to a , will enable the object to be grabbed by the /// components found in the hands of an . /// Some of the main features of grabbable objects are: /// /// /// Manipulation is handled automatically by the . There is no special /// requirement to enable it in a scene, the grab manager will be available as soon as it is invoked. /// /// /// 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 /// . /// /// Objects can be grabbed from different grab points. /// /// Additionally, grab points can be expanded using components opening up /// more complex manipulation by describing grab points as composite shapes. /// /// /// Although all avatars that have components are able to interact with /// 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. /// /// /// The Hand Pose Editor can create poses that are used by in order to tell /// how objects are grabbed. The inspector window will preview grab poses and enable editing them. /// /// /// Events such as , and allow to write /// logic when a user interacts with the object. Each has pre and post events. /// /// /// , and /// allow to program more complex logic when grabbing objects. /// /// /// [DisallowMultipleComponent] public partial class UxrGrabbableObject : UxrComponent, 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 _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 /// /// 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. /// public event EventHandler ConstraintsApplying; /// /// 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. /// public event EventHandler ConstraintsApplied; /// /// Event called right after all 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. /// public event EventHandler ConstraintsFinished; /// /// /// 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. /// /// /// 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.
/// UltimateXR allows grabbable children to control a grabbable parent direction using /// , but sometimes the parent should not really be grabbable. When the /// parent is not grabbable but the is still desired, it is possible to add a /// component to the parent, set up translation/rotation constraints and enable /// the property. ///
/// /// Some other examples where dummy grabbable parents come in handy: /// /// An aircraft yoke column that moves forward/backward when the child yoke object is being grabbed. /// /// ///
public bool IsDummyGrabbableParent => _isDummyGrabbableParent; /// /// Gets whether a dependent object can control the grabbable parent's direction when being moved. /// public bool ControlParentDirection => _controlParentDirection; /// /// Gets whether the grabbable parent dependency is ignored. . /// public bool IgnoreGrabbableParentDependency => _ignoreGrabbableParentDependency; /// /// 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. /// public bool HasGrabbableParentDependency => IsConstrained && GetGrabbableParent(this) != null; /// /// /// Gets whether the object has a parent dependency ( is true) and is /// using it through in the inspector. /// /// /// 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: /// /// /// Dependent ( 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. /// /// /// Independent ( 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. /// /// /// /// public bool UsesGrabbableParentDependency => HasGrabbableParentDependency && !_ignoreGrabbableParentDependency; /// /// Gets whether the object can be grabbed with more than one hand. /// public bool AllowMultiGrab => _allowMultiGrab; /// /// Gets the first 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 /// . /// public UxrGrabbableObject GrabbableParent => GetGrabbableParent(this); /// /// Gets whether the object has translation/rotation constraints. /// public bool IsConstrained => HasTranslationConstraint || HasRotationConstraint || IsLockedInPlace; /// /// Gets whether the object has a translation constraint. /// public bool HasTranslationConstraint => TranslationConstraint != UxrTranslationConstraintMode.Free; /// /// Gets whether the object has a rotation constraint. /// public bool HasRotationConstraint => RotationConstraint != UxrRotationConstraintMode.Free; /// /// Gets the number of axes that the object can be translated in. /// public int RangeOfMotionTranslationAxisCount { get { if (TranslationConstraint == UxrTranslationConstraintMode.Free) { return 3; } if (TranslationConstraint == UxrTranslationConstraintMode.Locked) { return 0; } return Vector3Ext.DifferentComponentCount(_translationLimitsMin, _translationLimitsMax); } } /// /// Gets the number of axes that the object can rotate around. /// public int RangeOfMotionRotationAxisCount { get { if (RotationConstraint == UxrRotationConstraintMode.Free) { return 3; } if (RotationConstraint == UxrRotationConstraintMode.Locked) { return 0; } return Vector3Ext.DifferentComponentCount(_rotationAngleLimitsMin, _rotationAngleLimitsMax); } } /// /// Gets the local axes that the object can be translated in. /// public IEnumerable 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; } } } } /// /// Gets the local axes that the object can rotate around. /// public IEnumerable 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; } } } } /// /// Gets the local axes that the object can be translated in with limited range of motion (not freely, nor locked). /// public IEnumerable 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; } } } } /// /// Gets the local axes that the object can rotate around with limited range of motion (not freely, nor locked). /// public IEnumerable 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; } } } } /// /// 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). /// 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; } } /// /// 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). /// 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; } } /// /// Gets the total number of grab points. /// public int GrabPointCount => IsDummyGrabbableParent ? 0 : _additionalGrabPoints != null ? _additionalGrabPoints.Count + 1 : 1; /// /// Gets the that needs to align with a when placing /// the object on it. /// public Transform DropAlignTransform => _dropAlignTransform == null || _dropAlignTransformUseSelf ? transform : _dropAlignTransform; /// /// Gets the that will be used to compute the distance to /// components when looking for the closest available to place it. /// public Transform DropProximityTransform => _dropProximityTransform == null || _dropProximityTransformUseSelf ? transform : _dropProximityTransform; /// /// 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. /// public float LockedGrabReleaseDistance => _lockedGrabReleaseDistance; /// /// Gets whether the object's will be made dynamic when the object grip is released. /// public bool RigidBodyDynamicOnRelease => _rigidBodyDynamicOnRelease; /// /// Gets whether the object's can be made dynamic when the object grip is released, /// checking all conditions. /// public bool CanUseRigidBody => !UsesGrabbableParentDependency; /// /// Gets the vertical velocity factor that will be applied to the object when being thrown. /// public float VerticalReleaseMultiplier => _verticalReleaseMultiplier; /// /// Gets the horizontal velocity factor that will be applied to the object when being thrown. /// public float HorizontalReleaseMultiplier => _horizontalReleaseMultiplier; /// /// Gets whether the object requires both hands grabbing it in order to rotate it. /// public bool NeedsTwoHandsToRotate => _needsTwoHandsToRotate; /// /// Gets the the object started on, regardless of the current anchor. /// public UxrGrabbableObjectAnchor StartAnchor => _startAnchor; /// /// Gets the minimum allowed angle in degrees for objects that have a single rotational degree of freedom. /// public float MinSingleRotationDegrees { get { int singleRotationAxisIndex = SingleRotationAxisIndex; return singleRotationAxisIndex == -1 ? 0.0f : _rotationAngleLimitsMin[singleRotationAxisIndex]; } } /// /// Gets the maximum allowed angle in degrees for objects that have a single rotational degree of freedom. /// public float MaxSingleRotationDegrees { get { int singleRotationAxisIndex = SingleRotationAxisIndex; return singleRotationAxisIndex == -1 ? 0.0f : _rotationAngleLimitsMax[singleRotationAxisIndex]; } } /// /// 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. /// public bool FirstGrabPointIsMain => _firstGrabPointIsMain; /// /// Gets the rotation provider. The rotation provider is used in objects with constrained position to know /// which element drives the rotation. /// public UxrRotationProvider RotationProvider => HasTranslationConstraint && LimitedRangeOfMotionRotationAxes.Any() ? _rotationProvider : UxrRotationProvider.HandOrientation; /// /// Gets which axis is the longitudinal axis (x, y or z) in a rotation with constraints on two or more axes. /// public UxrAxis RotationLongitudinalAxis => _rotationLongitudinalAxis; /// /// Gets or sets the rotation angle in degrees for objects that have a single rotational degree of freedom. /// /// /// Internally it calls and /// . /// public float SingleRotationAxisDegrees { get => UxrGrabManager.Instance.GetObjectSingleRotationAxisDegrees(this); set => UxrGrabManager.Instance.SetObjectSingleRotationAxisDegrees(this, value); } /// /// Gets the where the object is actually placed or null if it's not placed on /// any. /// public UxrGrabbableObjectAnchor CurrentAnchor { get => _currentAnchor; internal set => _currentAnchor = value; } /// /// Gets or sets whether the object can be placed on an . /// public bool IsPlaceable { get => _isPlaceable; set => _isPlaceable = value; } /// /// Gets or sets whether the object can be moved/rotated. A locked in place object may be grabbed but cannot be moved. /// public bool IsLockedInPlace { get => _isLockedInPlace; set { if (_isLockedInPlace && !value) { StartSmoothManipulationTransition(); } _isLockedInPlace = value; } } /// /// 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. /// public int Priority { get => _priority; set => _priority = value; } /// /// Gets or sets whether to parent the object to the being placed. Also whether /// to set the parent to null when grabbing the object from one. /// public bool UseParenting { get => _useParenting; set => _useParenting = value; } /// /// Gets or sets the string that identifies which components are /// compatible for placement. A can be placed on an /// only if: /// /// /// is null or empty and has no compatible tags /// set /// /// /// has a value that is in one of the compatible tag entries in /// /// /// /// public string Tag { get => _tag; set => _tag = value; } /// /// Gets or sets the translation constraint. /// public UxrTranslationConstraintMode TranslationConstraint { get => _translationConstraintMode; set => _translationConstraintMode = value; } /// /// Gets or sets the box collider used When is /// . /// public BoxCollider RestrictToBox { get => _restrictToBox; set => _restrictToBox = value; } /// /// Gets or sets the sphere collider used When is /// . /// public SphereCollider RestrictToSphere { get => _restrictToSphere; set => _restrictToSphere = value; } /// /// Gets or sets the translation minimum limits in local space when is /// . /// public Vector3 TranslationLimitsMin { get => _translationLimitsMin; set => _translationLimitsMin = value; } /// /// Gets or sets the translation maximum limits in local space when is /// . /// public Vector3 TranslationLimitsMax { get => _translationLimitsMax; set => _translationLimitsMax = value; } /// /// Gets or sets the rotation constraint type. /// public UxrRotationConstraintMode RotationConstraint { get => _rotationConstraintMode; set => _rotationConstraintMode = value; } /// /// Gets or sets the rotational minimum limits in local space when is /// . /// public Vector3 RotationAngleLimitsMin { get => _rotationAngleLimitsMin; set => _rotationAngleLimitsMin = value; } /// /// Gets or sets the rotational maximum limits in local space when is /// . /// public Vector3 RotationAngleLimitsMax { get => _rotationAngleLimitsMax; set => _rotationAngleLimitsMax = value; } /// /// 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. /// public float TranslationResistance { get => _translationResistance; set => _translationResistance = value; } /// /// 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. /// public float RotationResistance { get => _rotationResistance; set => _rotationResistance = value; } /// /// Gets or sets the rigidbody component that controls the grabbable object when it is in dynamic /// (physics-enabled) mode. /// public Rigidbody RigidBodySource { get => _rigidBodySource; set => _rigidBodySource = value; } /// /// Gets or sets how the object will align with a when placing it. /// public UxrSnapToAnchorMode DropSnapMode { get => _dropSnapMode; set => _dropSnapMode = value; } #endregion #region Internal Types & Data /// /// Gets the child grabbable objects, grabbed or not, where there is no other grabbable object between the child and /// this grabbable. /// internal List AllDirectChildren { get; } = new List(); /// /// Gets all parent grabbable objects, grabbed or not. /// internal List AllParents { get; private set; } = new List(); /// /// Gets the parent grabbable objects, grabbed or not, whose direction is controlled indirectly by this object ( /// ) and ). /// internal List ParentLookAts { get; private set; } = new List(); /// /// Gets all child grabbable objects, grabbed or not. /// internal List AllChildren { get; private set; } = new List(); /// /// Gets all child grabbable objects, grabbed or not, that have and /// . /// internal List AllChildrenLookAts { get; private set; } = new List(); /// /// Gets the child grabbable objects, grabbed or not, that have and /// , where there is no other grabbable object between the child that controls /// this grabbable. /// internal List DirectChildrenLookAts { get; private set; } = new List(); /// /// Gets or sets the number of direct children using and ( /// ) that are being grabbed the current frame. Used by the /// . /// internal int DirectLookAtChildGrabbedCount { get; set; } /// /// Gets or sets the number of children processed the current frame. Used /// by the . /// internal int DirectLookAtChildProcessedCount { get; set; } /// /// Gets or sets the rotation angle in degrees for objects that have a single rotational degree of freedom. /// internal float SingleRotationAngleCumulative { get => _singleRotationAngleCumulative; set => _singleRotationAngleCumulative = value; } /// /// Gets or sets the initial position of the grabbable object in local grabbable parent space, if a grabbable parent /// exists. /// internal Vector3 InitialPositionInLocalGrabbableParent { get; private set; } /// /// 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. /// internal Vector3 LocalPositionBeforeUpdate { get; set; } /// /// 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. /// internal Quaternion LocalRotationBeforeUpdate { get; set; } /// /// Gets the placement options used in the last call that placed the object, for example using /// . /// internal UxrPlacementOptions PlacementOptions { get => _placementOptions; set => _placementOptions = value; } #endregion #region Implicit IUxrGrabbable /// public bool IsBeingGrabbed => UxrGrabManager.Instance.IsBeingGrabbed(this); /// public bool IsGrabbable { get => _isGrabbable; set { BeginSync(); _isGrabbable = value; EndSyncProperty(value); } } /// public bool IsKinematic { get => _rigidBodySource == null || _rigidBodySource.isKinematic; set { if (_rigidBodySource != null) { _rigidBodySource.isKinematic = value; } } } /// public event EventHandler Grabbing; /// public event EventHandler Grabbed; /// public event EventHandler Releasing; /// public event EventHandler Released; /// public event EventHandler Placing; /// public event EventHandler Placed; /// public void ResetPositionAndState(bool propagateEvents) { transform.localPosition = InitialLocalPosition; transform.localRotation = InitialLocalRotation; IsKinematic = _initialIsKinematic; if (_startAnchor) { UxrGrabManager.Instance.PlaceObject(this, _startAnchor, UxrPlacementOptions.None, propagateEvents); } } /// public void ReleaseGrabs(bool propagateEvents) { UxrGrabManager.Instance.ReleaseGrabs(this, propagateEvents); } #endregion #region Public Methods /// /// Gets a given grab point information. /// /// Grab point index to get the information of /// Grab point information public UxrGrabPointInfo GetGrabPoint(int index) { if (index == 0) { return _grabPoint; } if (index >= 1 && index <= _additionalGrabPoints.Count) { return _additionalGrabPoints[index - 1]; } return null; } /// /// Enables or disables the possibility to use the given grab point. /// /// Grab point index /// Whether to enable or disable interaction 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); } } /// /// Re-enables all disabled grab points by . /// public void EnableAllGrabPoints() { _grabPointEnabledStates.Clear(); } /// /// Computes the distance from the object to a , which is the component found in /// hands that are able to grab objects. /// /// Grabber component /// Grab point index to compute the distance to /// /// 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 /// /// Returns the euclidean distance, without factoring in any relative rotation 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; } /// /// Checks whether the object can be grabbed by a . /// /// Grabber /// Grab point index to check /// Whether the object can be grabbed by the grabber using the given grab point 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; } /// /// Computes the position/rotation that a would have to hold the object using the current /// object position/orientation. /// /// Grabber to check /// Grab point /// Returns the grabber position /// Returns the grabber orientation /// /// Whether to include the rotation required for AlignToController if the grab point has it. /// /// Whether the returned data is meaningful 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; } /// /// Checks whether the object is near enough to be placed on the given . /// /// Anchor to check /// Whether it is near enough to be placed public bool CanBePlacedOnAnchor(UxrGrabbableObjectAnchor anchor) { return CanBePlacedOnAnchor(anchor, out float _); } /// /// Checks whether the object is near enough to be placed on the given . /// /// Anchor to check /// Returns the euclidean distance to the anchor /// Whether it is near enough to be placed 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; } /// /// Removes the object from the anchor it is placed on, if any. /// /// Whether to propagate events /// /// Internally it calls . /// public void RemoveFromAnchor(bool propagateEvents) { UxrGrabManager.Instance.RemoveObjectFromAnchor(this, propagateEvents); } /// /// . /// public void KeepGripsInPlace() { UxrGrabManager.Instance.KeepGripsInPlace(this); } /// /// Tries to get the longitudinal rotation axis of the grabbable object. If it hasn't been defined by the user (on /// objects where is less than 2. /// /// Longitudinal rotation axis 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; } /// /// Tries to infer the most appropriate to rotate the object based on the shape and /// size of the object, and the grip. /// /// The grip snap position /// Most appropriate 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; } /// /// Stops a smooth manipulation transition. /// public void StopSmoothManipulationTransition() { SmoothManipulationTimer = -1.0f; } /// /// 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. /// public void FinishSmoothTransitions() { StopSmoothManipulationTransition(); StopSmoothConstrain(); StopSmoothAnchorPlacement(); } #endregion #region Internal Methods /// /// 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. /// /// Snap mode /// Whether the given snap mode will make the object position move to the snap position internal static bool GetSnapModeAffectsPosition(UxrSnapToHandMode snapMode) { return snapMode != UxrSnapToHandMode.DontSnap && snapMode != UxrSnapToHandMode.RotationOnly; } /// /// 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. /// /// Snap mode /// Whether the given snap mode will make the object position move to the snap position internal static bool GetSnapModeAffectsPosition(UxrSnapToAnchorMode snapMode) { return snapMode != UxrSnapToAnchorMode.DontSnap && snapMode != UxrSnapToAnchorMode.RotationOnly; } /// /// 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. /// /// Snap mode /// Whether the given snap mode will make the object rotate to the snap orientation internal static bool GetSnapModeAffectsRotation(UxrSnapToHandMode snapMode) { return snapMode != UxrSnapToHandMode.DontSnap && snapMode != UxrSnapToHandMode.PositionOnly; } /// /// 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. /// /// Snap mode /// Whether the given snap mode will make the object rotate to the snap orientation internal static bool GetSnapModeAffectsRotation(UxrSnapToAnchorMode snapMode) { return snapMode != UxrSnapToAnchorMode.DontSnap && snapMode != UxrSnapToAnchorMode.PositionOnly; } /// /// 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. /// /// Grab point index /// Whether the given grab point's snap mode will make the object position move to the snap position internal bool GetGrabPointSnapModeAffectsPosition(int grabPoint) { return GetSnapModeAffectsPosition(GetGrabPoint(grabPoint).SnapMode); } /// /// Same as but also checks if the snap direction is in the /// direction specified. /// /// Grab point index /// Direction in which the snap should occur /// /// Whether the given grab point's snap mode will make the object position move to the snap position and in the /// direction specified /// internal bool GetGrabPointSnapModeAffectsPosition(int grabPoint, UxrHandSnapDirection snapDirection) { return snapDirection == GetGrabPoint(grabPoint).SnapDirection && GetSnapModeAffectsPosition(GetGrabPoint(grabPoint).SnapMode); } /// /// 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. /// /// Grab point index /// Whether the given grab point's snap mode will make the object rotate to the snap orientation internal bool GetGrabPointSnapModeAffectsRotation(int grabPoint) { return GetSnapModeAffectsRotation(GetGrabPoint(grabPoint).SnapMode); } /// /// Same as but also checks if the snap direction is in the /// direction specified. /// /// Grab point index /// Direction in which the snap should occur /// /// Whether the given grab point's snap mode will make the object rotate to the snap orientation and in the /// direction specified /// internal bool GetGrabPointSnapModeAffectsRotation(int grabPoint, UxrHandSnapDirection snapDirection) { return snapDirection == GetGrabPoint(grabPoint).SnapDirection && GetSnapModeAffectsRotation(GetGrabPoint(grabPoint).SnapMode); } /// /// Updates parent and children information on this grabbable and all other grabbable objects in same the hierarchy, up /// or down. /// internal void UpdateGrabbableDependencies() { static void UpdateGrabbableDependenciesRecursive(UxrGrabbableObject grabbableObject, ref List 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 processedElements = new List(); UpdateGrabbableDependenciesRecursive(this, ref processedElements); } /// /// Gets the component for the given grab point. /// /// Grab point index to get the shape for /// component or null if there is none internal UxrGrabPointShape GetGrabPointShape(int grabPoint) { if (_grabPointShapes.TryGetValue(grabPoint, out UxrGrabPointShape shape)) { return shape; } return null; } /// /// Gets the component that is used to compute the distance to a for /// a given grab point. /// /// Grabber to get the distance from /// Grab point to get the distance to /// to compute the distance with 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; } /// /// Gets the that is used to compute the snap to an 's hand ( /// ) for a given grab point when it is grabbed. /// /// Avatar to compute the alignment for /// Grab point to get the alignment of /// The hand to get the snap transform for /// that should align to the grabber when it is being grabbed 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; } /// /// Gets the additional rotation required to apply to a grabber in order to align it with the controller. /// /// Grabber /// Grab point /// Additional rotation to apply to the object using the current grabber to align it with the controller 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; } /// /// Notifies that the object just started to be grabbed. /// /// Grabber responsible for grabbing the object /// Point that was grabbed /// The grabber snap position to use /// The grabber snap rotation to use internal void NotifyBeginGrab(UxrGrabber grabber, int grabPoint, Vector3 snapPosition, Quaternion snapRotation) { } /// /// Notifies that the object just stopped being grabbed. /// /// Grabber responsible for grabbing the object /// Point that was grabbed internal void NotifyEndGrab(UxrGrabber grabber, int grabPoint) { } /// /// Computes, if the object has the AutoRotationProvider set, the rotation provider based on the grab snap position. /// /// Grab snap position internal void CheckComputeAutoRotationProvider(Vector3 snapPosition) { if (_autoRotationProvider) { _rotationProvider = GetAutoRotationProvider(snapPosition); } } /// /// Starts a smooth manipulation transition. /// internal void StartSmoothManipulationTransition() { SmoothManipulationTimer = UxrConstants.SmoothManipulationTransitionSeconds; SmoothManipulationLocalPositionStart = transform.localPosition; SmoothManipulationLocalRotationStart = transform.localRotation; SmoothManipulationReference = transform.parent; } /// /// Updates the smooth manipulation transitions if they exist. /// 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); } } } /// /// Starts a smooth constraining transition. /// internal void StartSmoothConstrain() { SmoothConstrainTimer = UxrConstants.SmoothManipulationTransitionSeconds; SmoothConstrainLocalPositionStart = transform.localPosition; SmoothConstrainLocalRotationStart = transform.localRotation; SmoothConstrainReference = transform.parent; } /// /// Stops a smooth constraining transition if there is one. /// internal void StopSmoothConstrain() { SmoothConstrainTimer = -1.0f; } /// /// Updates the smooth constraining transition if there is one. /// 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); } } /// /// Starts a smooth transition to the object placement. /// internal void StartSmoothAnchorPlacement() { if (_dropSnapMode != UxrSnapToAnchorMode.DontSnap && CurrentAnchor != null) { SmoothPlacementTimer = UxrConstants.SmoothManipulationTransitionSeconds; SmoothPlacementLocalPositionStart = transform.localPosition; SmoothPlacementLocalRotationStart = transform.localRotation; SmoothPlacementReference = transform.parent; } } /// /// Stops the smooth anchor placement transition if there is one. /// internal void StopSmoothAnchorPlacement() { SmoothPlacementTimer = -1.0f; } /// /// Updates the smooth anchor placement transition if there is one. /// 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 /// /// Initializes the object and creates the the object is placed on initially if /// is set. /// 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(); // 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(); UxrGrabPointShape[] grabPointShapes = GetComponents(); 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(); } /// /// Called when the object is destroyed. Checks whether it is being grabbed to release it. /// protected override void OnDestroy() { base.OnDestroy(); if (IsBeingGrabbed) { UxrGrabManager.Instance.ReleaseGrabs(this, true); } } /// /// Called when the component is disabled. Stops all transitions. /// protected override void OnDisable() { base.OnDisable(); SmoothManipulationTimer = -1.0f; SmoothPlacementTimer = -1.0f; SmoothConstrainTimer = -1.0f; } /// /// Resets the component. /// protected override void Reset() { base.Reset(); _grabPoint = new UxrGrabPointInfo(); _rotationLongitudinalAxis = GetMostProbableLongitudinalRotationAxis(); } #endregion #region Coroutines /// /// Coroutine that will keep manually in sync grabbable object rigidbodies without native network synchronization /// components. /// /// The grabber that released the object /// Interval in seconds to send sync messages /// Coroutine IEnumerator 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 /// /// Event trigger for . /// /// Event parameters internal void RaiseConstraintsApplying(UxrApplyConstraintsEventArgs e) { if (UxrGrabManager.Instance.Features.HasFlag(UxrManipulationFeatures.UserConstraints)) { ConstraintsApplying?.Invoke(this, e); } } /// /// Event trigger for . /// /// Event parameters internal void RaiseConstraintsApplied(UxrApplyConstraintsEventArgs e) { if (UxrGrabManager.Instance.Features.HasFlag(UxrManipulationFeatures.UserConstraints)) { ConstraintsApplied?.Invoke(this, e); } } /// /// Event trigger for . /// /// Event parameters internal void RaiseConstraintsFinished(UxrApplyConstraintsEventArgs e) { if (UxrGrabManager.Instance.Features.HasFlag(UxrManipulationFeatures.UserConstraints)) { ConstraintsFinished?.Invoke(this, e); } } /// /// Event trigger for . /// /// Event parameters internal void RaiseGrabbingEvent(UxrManipulationEventArgs e) { if (_regularPhysicsSyncCoroutine != null) { StopCoroutine(_regularPhysicsSyncCoroutine); _regularPhysicsSyncCoroutine = null; } Grabbing?.Invoke(this, e); } /// /// Event trigger for . /// /// Event parameters internal void RaiseGrabbedEvent(UxrManipulationEventArgs e) { Grabbed?.Invoke(this, e); } /// /// Event trigger for . /// /// Event parameters internal void RaiseReleasingEvent(UxrManipulationEventArgs e) { Releasing?.Invoke(this, e); } /// /// Event trigger for . /// /// Event parameters 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() 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); } /// /// Event trigger for . /// /// Event parameters internal void RaisePlacingEvent(UxrManipulationEventArgs e) { if (_regularPhysicsSyncCoroutine != null) { StopCoroutine(_regularPhysicsSyncCoroutine); _regularPhysicsSyncCoroutine = null; } Placing?.Invoke(this, e); } /// /// Event trigger for . /// /// Event parameters internal void RaisePlacedEvent(UxrManipulationEventArgs e) { Placed?.Invoke(this, e); } #endregion #region Private Methods /// /// Gets a 's list of parents whose direction can be controlled /// indirectly by a grabbable object. /// /// Grabbable object /// /// Whether to return only the chain to the first parent that is not controlled by using /// and /// /// Bottom-to-top sorted list of parents private static IEnumerable 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; } } /// /// Gets a 's list of children. /// /// Grabbable object /// /// Whether to filter children that have no other grabbable between the child and the grabbable /// object /// /// /// Whether to filter children that have /// /// Whether to filter children that have /// List of children that control the parent grabbable direction private static IEnumerable GetChildren(UxrGrabbableObject grabbableObject, bool onlyDirect, bool onlyUseDependency, bool onlyControlDirection) { if (grabbableObject == null) { yield break; } UxrGrabbableObject[] children = grabbableObject.GetComponentsInChildren(); 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; } } } } /// /// Updates the rigidbody making sure the messages are synchronized through the network. /// /// The grabber that released the object /// Whether the rigidbody is sleeping /// The rigidbody position /// The rigidbody rotation /// The rigidbody velocity /// The rigidbody angular velocity 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() 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 }); } /// /// Gets the first upwards in the hierarchy. /// /// to get the grabbable parent of /// upwards in the hierarchy private UxrGrabbableObject GetGrabbableParent(UxrGrabbableObject grabbableObject) { if (grabbableObject.transform.parent != null) { UxrGrabbableObject parentGrabbableObject = grabbableObject.transform.parent.GetComponentInParent(); if (parentGrabbableObject != null) { return parentGrabbableObject; } } return null; } #endregion #region Private Types & Data /// /// Gets or sets the grabbable object's local position at the beginning of a smooth transition. /// private Vector3 SmoothManipulationLocalPositionStart { get; set; } /// /// Gets or sets the grabbable object's local rotation at the beginning of a smooth transition. /// private Quaternion SmoothManipulationLocalRotationStart { get; set; } /// /// Gets or sets the reference for and . /// private Transform SmoothManipulationReference { get; set; } /// /// Local position when the smooth object placement started. /// private Vector3 SmoothPlacementLocalPositionStart { get; set; } /// /// Local rotation when the smooth object placement started. /// private Quaternion SmoothPlacementLocalRotationStart { get; set; } /// /// Gets or sets the reference for and . /// private Transform SmoothPlacementReference { get; set; } /// /// Local position at the beginning of a smooth constrain transition. /// private Vector3 SmoothConstrainLocalPositionStart { get; set; } /// /// Local rotation at the beginning of a smooth constrain transition. /// private Quaternion SmoothConstrainLocalRotationStart { get; set; } /// /// Gets or sets the reference for and . /// private Transform SmoothConstrainReference { get; set; } /// /// Gets or sets the decreasing timer that is initialized at /// /// for smooth manipulation transitions. /// private float SmoothManipulationTimer { get; set; } = -1.0f; /// /// Gets or sets the decreasing placement timer for smooth placement transitions. /// private float SmoothPlacementTimer { get; set; } = -1.0f; /// /// Gets or sets the decreasing constrain timer for smooth constraint transitions. /// private float SmoothConstrainTimer { get; set; } = -1.0f; /// /// Gets whether the object is currently in a smooth transition. /// 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 _grabPointEnabledStates = new Dictionary(); private bool _initialIsKinematic = true; private Dictionary _grabPointShapes = new Dictionary(); // Coroutines private Coroutine _regularPhysicsSyncCoroutine; #endregion #if UNITY_EDITOR /// /// 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. /// /// Avatar to register public void Editor_RegisterAvatarForGrips(UxrAvatar avatar) { for (int i = 0; i < GrabPointCount; ++i) { GetGrabPoint(i).CheckAddGripPoseInfo(avatar.PrefabGuid); } } /// /// 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. /// /// The avatar to remove public void Editor_RemoveAvatarFromGrips(UxrAvatar avatar) { for (int i = 0; i < GrabPointCount; ++i) { UxrGrabPointInfo grabPointInfo = GetGrabPoint(i); grabPointInfo.RemoveGripPoseInfo(avatar.PrefabGuid); } } /// /// Gets the editor currently selected avatar prefab GUID whose grip parameters are being edited. /// /// Avatar prefab GUID or null if there isn't any avatar prefab selected public string Editor_GetSelectedAvatarPrefabGuidForGrips() { if (_selectedAvatarForGrips) { UxrAvatar avatar = _selectedAvatarForGrips.GetComponent(); if (avatar) { return avatar.PrefabGuid; } } return null; } /// /// Gets the editor currently selected avatar prefab whose grip parameters are being edited. /// /// Avatar prefab public GameObject Editor_GetSelectedAvatarPrefabForGrips() { return _selectedAvatarForGrips; } /// /// Sets the editor currently selected avatar prefab whose grip parameters will be edited. /// public void Editor_SetSelectedAvatarForGrips(GameObject avatarPrefab) { _selectedAvatarForGrips = avatarPrefab; } /// /// Gets the avatar prefab GUIDs that have been registered to have dedicated grip parameters to grab the object. /// /// Registered avatar prefab GUIDs public List Editor_GetRegisteredAvatarsGuids() { List registeredAvatars = new List(); // 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; } /// /// 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. /// public void Editor_CheckRegisterAvatarsInNewGrabPoints() { List registeredAvatars = new List(); // 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); } } } /// /// Gets the prefab avatar that is used for the given grip snap transform. If there is more than one the first is /// returned. /// /// The snap transform to get the avatar for /// Avatar prefab GUID 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; } /// /// Gets all the transforms used for alignment with the registered avatars. /// /// List of align transforms public IEnumerable 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; } } } } } /// /// Gets all the align transforms of a given registered avatar and hand. /// /// The avatar instance or prefab /// Whether to include snap transforms for the left hand /// Whether to include snap transforms for the right hand /// Whether to get the transforms for /// List of align transforms public IEnumerable 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; } } } } } /// /// Gets the grab point indices that use the given for alignment when interacting with an /// avatar. /// /// Avatar /// Alignment transform /// List of align transforms public List Editor_GetGrabPointsForGrabAlignTransform(UxrAvatar avatar, Transform snapTransform) { List grabPointIndices = new List(); 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; } /// /// Gets the used for alignment when the given avatar grabs a point using a given hand. /// /// Avatar /// Grab point /// Hand to get the snap transform for /// Alignment transform 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; } /// /// Checks whether the object has a grab point with the given for alignment. /// /// Transform to check /// Whether the given is present in any of the grab point alignments 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; } /// /// Checks whether the object has a grab point with the given for alignment registered using /// the given prefab. /// /// Transform to check /// Avatar prefab /// /// Whether the given is present in any of the grab point alignments registered using the /// given avatar prefab /// public bool Editor_HasGrabPointWithGrabAlignTransform(Transform snapTransform, GameObject avatarPrefab) { for (int grabPoint = 0; grabPoint < GrabPointCount; ++grabPoint) { UxrGripPoseInfo gripPoseInfo = GetGrabPoint(grabPoint).GetGripPoseInfo(avatarPrefab.GetComponent(), false); if (gripPoseInfo != null && (gripPoseInfo.GripAlignTransformHandLeft == snapTransform || gripPoseInfo.GripAlignTransformHandRight == snapTransform)) { return true; } } return false; } #endif } } #pragma warning restore 0414