Add ultimate xr

This commit is contained in:
2024-08-06 21:58:35 +02:00
parent 864033bf10
commit 7165bacd9d
3952 changed files with 2162037 additions and 35 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e89d565d8ac0453ab05ff9bf2cb3676e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrBlendPoseType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Manipulation.HandPoses
{
/// <summary>
/// Enumerates the different poses in a blend pose.
/// </summary>
public enum UxrBlendPoseType
{
/// <summary>
/// Not a blend pose.
/// </summary>
None,
/// <summary>
/// Pose with the open hand.
/// </summary>
OpenGrip,
/// <summary>
/// Pose with the closed hand.
/// </summary>
ClosedGrip
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7be4a9e3a7f4cdbadc26754550fd0ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,167 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFingerDescriptor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Avatar.Rig;
using UltimateXR.Core.Math;
using UnityEditor;
using UnityEngine;
namespace UltimateXR.Manipulation.HandPoses
{
/// <summary>
/// Stores base-independent node orientations for a finger.
/// </summary>
[Serializable]
public struct UxrFingerDescriptor
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _hasMetacarpalInfo;
[SerializeField] private UxrFingerNodeDescriptor _metacarpal;
[SerializeField] private UxrFingerNodeDescriptor _proximal;
[SerializeField] private UxrFingerNodeDescriptor _proximalNoMetacarpal;
[SerializeField] private UxrFingerNodeDescriptor _intermediate;
[SerializeField] private UxrFingerNodeDescriptor _distal;
#endregion
#region Public Types & Data
/// <summary>
/// Gets whether metacarpal bone information is present. Metacarpal information is optional.
/// </summary>
public bool HasMetacarpalInfo => _hasMetacarpalInfo;
/// <summary>
/// Gets the metacarpal bone transform information.
/// </summary>
public UxrFingerNodeDescriptor Metacarpal => _metacarpal;
/// <summary>
/// Gets the proximal bone transform information.
/// </summary>
public UxrFingerNodeDescriptor Proximal => _proximal;
/// <summary>
/// Gets the proximal bone transform information with respect to the wrist even if there is metacarpal information. It
/// is used in case a pose including metacarpal information wants to be mapped to a hand that has no metacarpal bones.
/// </summary>
public UxrFingerNodeDescriptor ProximalNoMetacarpal => _proximalNoMetacarpal;
/// <summary>
/// Gets the intermediate bone transform information.
/// </summary>
public UxrFingerNodeDescriptor Intermediate => _intermediate;
/// <summary>
/// Gets the distal bone transform information.
/// </summary>
public UxrFingerNodeDescriptor Distal => _distal;
#endregion
#region Public Methods
/// <summary>
/// Computes well-known axes systems for all finger bones, to handle transforms independently of the coordinate system
/// being used by a hand rig.
/// </summary>
/// <param name="wrist">Wrist transform</param>
/// <param name="finger">Finger rig information</param>
/// <param name="handLocalAxes">Well-known axes system for the hand</param>
/// <param name="fingerLocalAxes">Well-known axes system for the finger elements</param>
/// <param name="computeRelativeMatrixOnly">Whether to compute only the relative transform to the hand</param>
public void Compute(Transform wrist, UxrAvatarFinger finger, UxrUniversalLocalAxes handLocalAxes, UxrUniversalLocalAxes fingerLocalAxes, bool computeRelativeMatrixOnly)
{
if (finger.Metacarpal)
{
_hasMetacarpalInfo = true;
_metacarpal.Compute(wrist, wrist, finger.Metacarpal, handLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
_proximal.Compute(wrist, finger.Metacarpal, finger.Proximal, fingerLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
_proximalNoMetacarpal.Compute(wrist, wrist, finger.Proximal, handLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
}
else
{
_hasMetacarpalInfo = false;
_proximal.Compute(wrist, wrist, finger.Proximal, handLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
_proximalNoMetacarpal.Compute(wrist, wrist, finger.Proximal, handLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
}
_intermediate.Compute(wrist, finger.Proximal, finger.Intermediate, fingerLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
_distal.Compute(wrist, finger.Intermediate, finger.Distal, fingerLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
}
/// <summary>
/// Mirrors the bone information, so that it can be used for the opposite hand.
/// </summary>
public void Mirror()
{
if (_hasMetacarpalInfo)
{
_metacarpal.Mirror();
}
_proximal.Mirror();
_proximalNoMetacarpal.Mirror();
_intermediate.Mirror();
_distal.Mirror();
}
/// <summary>
/// Interpolates the data towards another descriptor.
/// </summary>
/// <param name="to">Descriptor to interpolate the data to</param>
/// <param name="t">Interpolation factor [0.0, 1.0]</param>
public void InterpolateTo(UxrFingerDescriptor to, float t)
{
if (_hasMetacarpalInfo)
{
_metacarpal.InterpolateTo(to._metacarpal, t);
}
_proximal.InterpolateTo(to._proximal, t);
_proximalNoMetacarpal.InterpolateTo(to._proximalNoMetacarpal, t);
_intermediate.InterpolateTo(to._intermediate, t);
_distal.InterpolateTo(to._distal, t);
}
#if UNITY_EDITOR
/// <summary>
/// Outputs transform information to the editor window.
/// </summary>
/// <param name="prefix">String to prefix the information with</param>
public void DrawEditorDebugLabels(string prefix)
{
EditorGUILayout.LabelField(prefix + _proximal.Right);
EditorGUILayout.LabelField(prefix + _proximal.Up);
EditorGUILayout.LabelField(prefix + _proximal.Forward);
}
#endif
/// <summary>
/// Compares the transform information with another finger.
/// </summary>
/// <param name="other">Finger information to compare it to</param>
/// <returns>Whether both fingers describe the same transform information</returns>
public bool Equals(UxrFingerDescriptor other)
{
if (_hasMetacarpalInfo != other._hasMetacarpalInfo)
{
return false;
}
if (_hasMetacarpalInfo)
{
return _metacarpal.Equals(other._metacarpal) && _proximal.Equals(other._proximal) && _intermediate.Equals(other._intermediate) && _distal.Equals(other._distal);
}
return _proximal.Equals(other._proximal) && _intermediate.Equals(other._intermediate) && _distal.Equals(other._distal);
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 25fe587ebcf04b76afc04cfd1a84d413
timeCreated: 1643749392

View File

@@ -0,0 +1,229 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFingerNodeDescriptor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core.Math;
using UnityEngine;
namespace UltimateXR.Manipulation.HandPoses
{
/// <summary>
/// Stores a bone's right, up and forward vectors in local coordinates of its parent. Right, up and forward
/// vectors will always point to this directions independently of how the transforms have been set up in
/// order to guarantee poses can be reused by other hands that use a different coordinate system.
/// </summary>
[Serializable]
public struct UxrFingerNodeDescriptor
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Matrix4x4 _transformRelativeToHand;
[SerializeField] private Vector3 _right;
[SerializeField] private Vector3 _up;
[SerializeField] private Vector3 _forward;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the original relative transform to the hand bone. We use it mainly to compute
/// <see cref="UxrGrabbableObject" /> preview meshes more conveniently.
/// </summary>
public Matrix4x4 TransformRelativeToHand => _transformRelativeToHand;
/// <summary>
/// Gets the universal right vector. The vector that points in our well-known right direction, in the coordinate system
/// of the finger.
/// </summary>
public Vector3 Right => _right;
/// <summary>
/// Gets the universal up vector. The vector that points in our well-known up direction, in the coordinate system of
/// the finger.
/// </summary>
public Vector3 Up => _up;
/// <summary>
/// Gets the universal forward vector. The vector that points in our well-known forward direction, in the coordinate
/// system of the finger.
/// </summary>
public Vector3 Forward => _forward;
#endregion
#region Constructors & Finalizer
/// <summary>
/// Creates a well-known axes system for a node, to handle transforms independently of the coordinate system being used
/// by a hand rig.
/// </summary>
/// <param name="hand">Hand node</param>
/// <param name="parent">Parent node</param>
/// <param name="node">Current node being created</param>
/// <param name="parentLocalAxes">
/// In local coordinates, which parent axes point to the well-known right, up and forward directions
/// </param>
/// <param name="nodeLocalAxes">
/// In local coordinates, which node axes point to the well-known right, up and forward directions
/// </param>
public UxrFingerNodeDescriptor(Transform hand, Transform parent, Transform node, UxrUniversalLocalAxes parentLocalAxes, UxrUniversalLocalAxes nodeLocalAxes)
{
_right = Vector3.right;
_up = Vector3.up;
_forward = Vector3.forward;
_transformRelativeToHand = Matrix4x4.identity;
if (hand == null || parent == null || node != null)
{
return;
}
Compute(hand, parent, node, parentLocalAxes, nodeLocalAxes, false);
}
#endregion
#region Public Methods
/// <summary>
/// Creates a well-known axes system for a node, to handle transforms independently of the coordinate system being used
/// by a hand rig.
/// </summary>
/// <param name="hand">Hand node</param>
/// <param name="parent">Parent node</param>
/// <param name="node">Current node being created</param>
/// <param name="parentLocalAxes">
/// In local coordinates, which parent axes point to the well-known right, up and forward
/// directions
/// </param>
/// <param name="nodeLocalAxes">
/// In local coordinates, which node axes point to the well-known right, up and forward
/// directions
/// </param>
/// <param name="computeRelativeMatrixOnly">Whether to compute only the <see cref="TransformRelativeToHand" /> value</param>
public void Compute(Transform hand, Transform parent, Transform node, UxrUniversalLocalAxes parentLocalAxes, UxrUniversalLocalAxes nodeLocalAxes, bool computeRelativeMatrixOnly)
{
_transformRelativeToHand = hand.worldToLocalMatrix * node.localToWorldMatrix;
if (!computeRelativeMatrixOnly)
{
Matrix4x4 matrixParent = new Matrix4x4();
matrixParent.SetColumn(0, parent.TransformVector(parentLocalAxes.LocalRight));
matrixParent.SetColumn(1, parent.TransformVector(parentLocalAxes.LocalUp));
matrixParent.SetColumn(2, parent.TransformVector(parentLocalAxes.LocalForward));
matrixParent.SetColumn(3, new Vector4(parent.position.x, parent.position.y, parent.position.z, 1));
_right = matrixParent.inverse.MultiplyVector(node.TransformVector(nodeLocalAxes.LocalRight));
_up = matrixParent.inverse.MultiplyVector(node.TransformVector(nodeLocalAxes.LocalUp));
_forward = matrixParent.inverse.MultiplyVector(node.TransformVector(nodeLocalAxes.LocalForward));
}
}
/// <summary>
/// Mirrors the descriptor. Useful to switch between left and right hand data.
/// </summary>
public void Mirror()
{
// We do not need to mirror position and rotation because we don't use them for mirroring
_right.x = -_right.x;
_right = -_right;
_up.x = -_up.x;
_forward.x = -_forward.x;
}
/// <summary>
/// Interpolates the axes data towards another descriptor.
/// </summary>
/// <param name="to">Descriptor to interpolate the data to</param>
/// <param name="t">Interpolation factor [0.0, 1.0]</param>
public void InterpolateTo(UxrFingerNodeDescriptor to, float t)
{
Quaternion quatSlerp = Quaternion.Slerp(Quaternion.LookRotation(_forward, _up), Quaternion.LookRotation(to._forward, to._up), t);
_right = quatSlerp * Vector3.right;
_up = quatSlerp * Vector3.up;
_forward = quatSlerp * Vector3.forward;
// For performance reasons, _transformRelativeToHand isn't interpolated because it is only used for grab preview poses. Interpolation is used for runtime pose blending.
// If at any point it becomes necessary, uncomment the line below:
// _transformRelativeToHand = Matrix4x4Ext.Interpolate(_transformRelativeToHand, to._transformRelativeToHand, t);
}
/// <summary>
/// Checks if the content of two FingerNodeDescriptors is equal (they describe the same axes).
/// </summary>
/// <param name="other">UxrFingerNodeDescriptor to compare it to</param>
/// <returns>Boolean telling if the two FingerNodeDescriptors describe the same axes</returns>
public bool Equals(UxrFingerNodeDescriptor other)
{
float epsilon = 0.00001f;
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
if (Mathf.Abs(_transformRelativeToHand[i, j] - other._transformRelativeToHand[i, j]) > epsilon)
{
return false;
}
}
}
bool equal = _right == other._right && _up == other._up && _forward == other._forward;
/*
double inequalityThreshold = 9.99999943962493E-11;
if (Right != other.Right)
{
double inequalityValue = GetInequalityValue(Right, other.Right);
double inequalityMargin = inequalityValue - inequalityThreshold;
double inequalityFactor = inequalityValue / inequalityThreshold;
Debug.Log($"right != other.right Inequality value = {inequalityValue}, margin = {inequalityMargin}, factor {inequalityFactor}");
}
if (Up != other.Up)
{
double inequalityValue = GetInequalityValue(Up, other.Up);
double inequalityMargin = inequalityValue - inequalityThreshold;
double inequalityFactor = inequalityValue / inequalityThreshold;
Debug.Log($"up != other.up Inequality value = {inequalityValue}, margin = {inequalityMargin}, factor {inequalityFactor}");
}
if (Forward != other.Forward)
{
double inequalityValue = GetInequalityValue(Forward, other.Forward);
double inequalityMargin = inequalityValue - inequalityThreshold;
double inequalityFactor = inequalityValue / inequalityThreshold;
Debug.Log($"forward != other.forward Inequality value = {inequalityValue}, margin = {inequalityMargin}, factor {inequalityFactor}");
}*/
return equal;
}
#endregion
#region Private Methods
/// <summary>
/// Gets an inequality value that measures how different two vectors are. It is used to provide a way to compare
/// vectors considering floating point errors.
/// </summary>
/// <param name="lhs">Vector A</param>
/// <param name="rhs">Vector B</param>
/// <returns>Inequality value</returns>
private double GetInequalityValue(Vector3 lhs, Vector3 rhs)
{
float num1 = lhs.x - rhs.x;
float num2 = lhs.y - rhs.y;
float num3 = lhs.z - rhs.z;
return num1 * (double)num1 + num2 * (double)num2 + num3 * (double)num3;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8f9dc40c111844888f796a51ac2e7e82
timeCreated: 1643749470

View File

@@ -0,0 +1,234 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHandDescriptor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Avatar;
using UltimateXR.Avatar.Rig;
using UltimateXR.Core;
using UltimateXR.Core.Math;
using UnityEngine;
namespace UltimateXR.Manipulation.HandPoses
{
/// <summary>
/// Stores base-independent node orientations for all fingers of a hand.
/// </summary>
[Serializable]
public class UxrHandDescriptor
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrFingerDescriptor _index;
[SerializeField] private UxrFingerDescriptor _middle;
[SerializeField] private UxrFingerDescriptor _ring;
[SerializeField] private UxrFingerDescriptor _little;
[SerializeField] private UxrFingerDescriptor _thumb;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the index finger information.
/// </summary>
public UxrFingerDescriptor Index => _index;
/// <summary>
/// Gets the middle finger information.
/// </summary>
public UxrFingerDescriptor Middle => _middle;
/// <summary>
/// Gets the ring finger information.
/// </summary>
public UxrFingerDescriptor Ring => _ring;
/// <summary>
/// Gets the little finger information.
/// </summary>
public UxrFingerDescriptor Little => _little;
/// <summary>
/// Gets the thumb finger information.
/// </summary>
public UxrFingerDescriptor Thumb => _thumb;
#endregion
#region Constructors & Finalizer
/// <summary>
/// Default constructor.
/// </summary>
public UxrHandDescriptor()
{
_index = new UxrFingerDescriptor();
_middle = new UxrFingerDescriptor();
_ring = new UxrFingerDescriptor();
_little = new UxrFingerDescriptor();
_thumb = new UxrFingerDescriptor();
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="avatar">Avatar whose hand to compute the descriptor for</param>
/// <param name="handSide">Which hand to process</param>
public UxrHandDescriptor(UxrAvatar avatar, UxrHandSide handSide)
{
_index = new UxrFingerDescriptor();
_middle = new UxrFingerDescriptor();
_ring = new UxrFingerDescriptor();
_little = new UxrFingerDescriptor();
_thumb = new UxrFingerDescriptor();
Compute(avatar, handSide);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="arm">Arm whose hand to compute the descriptor for</param>
/// <param name="handLocalAxes">Hand axes system</param>
/// <param name="fingerLocalAxes">Finger axes system</param>
public UxrHandDescriptor(UxrAvatarArm arm, UxrUniversalLocalAxes handLocalAxes, UxrUniversalLocalAxes fingerLocalAxes)
{
_index = new UxrFingerDescriptor();
_middle = new UxrFingerDescriptor();
_ring = new UxrFingerDescriptor();
_little = new UxrFingerDescriptor();
_thumb = new UxrFingerDescriptor();
Compute(arm, handLocalAxes, fingerLocalAxes);
}
#endregion
#region Public Methods
/// <summary>
/// Gets the given finger.
/// </summary>
/// <param name="fingerType">Which finger to get</param>
/// <returns>Finger information</returns>
public UxrFingerDescriptor GetFinger(UxrFingerType fingerType)
{
switch (fingerType)
{
case UxrFingerType.Thumb: return Thumb;
case UxrFingerType.Index: return Index;
case UxrFingerType.Middle: return Middle;
case UxrFingerType.Ring: return Ring;
case UxrFingerType.Little: return Little;
default: throw new ArgumentOutOfRangeException(nameof(fingerType), fingerType, null);
}
}
/// <summary>
/// Computes the hand data.
/// </summary>
/// <param name="avatar">Avatar to compute the hand data of</param>
/// <param name="handSide">Which hand to compute the hand data of</param>
/// <param name="computeRelativeMatrixOnly">Whether to compute the relative transform to the hand only</param>
public void Compute(UxrAvatar avatar, UxrHandSide handSide, bool computeRelativeMatrixOnly = false)
{
Compute(handSide == UxrHandSide.Left ? avatar.AvatarRig.LeftArm : avatar.AvatarRig.RightArm, avatar.AvatarRigInfo.GetArmInfo(handSide).HandUniversalLocalAxes, avatar.AvatarRigInfo.GetArmInfo(handSide).FingerUniversalLocalAxes, computeRelativeMatrixOnly);
}
/// <summary>
/// Computes the hand data.
/// </summary>
/// <param name="arm">Arm where the hand is</param>
/// <param name="handLocalAxes">Hand axes system</param>
/// <param name="fingerLocalAxes">Finger axes system</param>
/// <param name="computeRelativeMatrixOnly">Whether to compute the relative transform to the hand only</param>
public void Compute(UxrAvatarArm arm, UxrUniversalLocalAxes handLocalAxes, UxrUniversalLocalAxes fingerLocalAxes, bool computeRelativeMatrixOnly = false)
{
_index.Compute(arm.Hand.Wrist, arm.Hand.Index, handLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
_middle.Compute(arm.Hand.Wrist, arm.Hand.Middle, handLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
_ring.Compute(arm.Hand.Wrist, arm.Hand.Ring, handLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
_little.Compute(arm.Hand.Wrist, arm.Hand.Little, handLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
_thumb.Compute(arm.Hand.Wrist, arm.Hand.Thumb, handLocalAxes, fingerLocalAxes, computeRelativeMatrixOnly);
}
/// <summary>
/// Copies the data from another descriptor.
/// </summary>
/// <param name="src">Source data</param>
public void CopyFrom(UxrHandDescriptor src)
{
_index = src._index;
_middle = src._middle;
_ring = src._ring;
_little = src._little;
_thumb = src._thumb;
}
/// <summary>
/// Interpolates the data towards another descriptor.
/// </summary>
/// <param name="to">Descriptor to interpolate the data to</param>
/// <param name="t">Interpolation factor [0.0, 1.0]</param>
public void InterpolateTo(UxrHandDescriptor to, float t)
{
_index.InterpolateTo(to._index, t);
_middle.InterpolateTo(to._middle, t);
_ring.InterpolateTo(to._ring, t);
_little.InterpolateTo(to._little, t);
_thumb.InterpolateTo(to._thumb, t);
}
#if UNITY_EDITOR
/// <summary>
/// Outputs transform data in the editor window.
/// </summary>
public void DrawEditorDebugLabels()
{
_index.DrawEditorDebugLabels("index: ");
_middle.DrawEditorDebugLabels("middle: ");
_ring.DrawEditorDebugLabels("ring: ");
_little.DrawEditorDebugLabels("little: ");
_thumb.DrawEditorDebugLabels("thumb: ");
}
#endif
/// <summary>
/// Returns a hand descriptor with mirrored transforms, so that the data can be used for the opposite hand.
/// </summary>
/// <returns>Mirrored hand descriptor</returns>
public UxrHandDescriptor Mirrored()
{
UxrHandDescriptor mirroredHandDescriptor = new UxrHandDescriptor();
mirroredHandDescriptor.CopyFrom(this);
mirroredHandDescriptor._index.Mirror();
mirroredHandDescriptor._middle.Mirror();
mirroredHandDescriptor._ring.Mirror();
mirroredHandDescriptor._little.Mirror();
mirroredHandDescriptor._thumb.Mirror();
return mirroredHandDescriptor;
}
/// <summary>
/// Checks whether a hand descriptor contains the same transform data.
/// </summary>
/// <param name="other">Hand descriptor to compare it to</param>
/// <returns>Whether the hand descriptor contains the same transform data</returns>
public bool Equals(UxrHandDescriptor other)
{
if (other != null)
{
return _index.Equals(other._index) && _middle.Equals(other._middle) && _ring.Equals(other._ring) && _little.Equals(other._little) && _thumb.Equals(other._thumb);
}
return false;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a99921d3e6b94b62bb8cd0b7a227be0a
timeCreated: 1643749379

View File

@@ -0,0 +1,183 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHandPoseAsset.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core;
using UnityEngine;
namespace UltimateXR.Manipulation.HandPoses
{
/// <summary>
/// ScriptableObject that stores custom hand poses. Data is stored in a well-known axes system so that poses can be
/// exchanged between different avatars.
/// </summary>
[Serializable]
public class UxrHandPoseAsset : ScriptableObject
{
#region Inspector Properties/Serialized Fields
[SerializeField] private int _handPoseAssetVersion;
[SerializeField] private UxrHandPoseType _poseType;
[SerializeField] private UxrHandDescriptor _handDescriptorLeft;
[SerializeField] private UxrHandDescriptor _handDescriptorRight;
[SerializeField] private UxrHandDescriptor _handDescriptorOpenLeft;
[SerializeField] private UxrHandDescriptor _handDescriptorOpenRight;
[SerializeField] private UxrHandDescriptor _handDescriptorClosedLeft;
[SerializeField] private UxrHandDescriptor _handDescriptorClosedRight;
#endregion
#region Public Types & Data
/// <summary>
/// Current data version.
/// </summary>
public const int CurrentVersion = 1;
/// <summary>
/// Gets the version the pose was stored in.
/// </summary>
public int Version
{
get => _handPoseAssetVersion;
set => _handPoseAssetVersion = value;
}
/// <summary>
/// Gets the pose type.
/// </summary>
public UxrHandPoseType PoseType
{
get => _poseType;
set => _poseType = value;
}
/// <summary>
/// Gets the left fixed pose hand descriptor.
/// </summary>
public UxrHandDescriptor HandDescriptorLeft
{
get => _handDescriptorLeft;
set => _handDescriptorLeft = value;
}
/// <summary>
/// Gets the right fixed pose hand descriptor.
/// </summary>
public UxrHandDescriptor HandDescriptorRight
{
get => _handDescriptorRight;
set => _handDescriptorRight = value;
}
/// <summary>
/// Gets the left blend pose hand descriptor for the open state.
/// </summary>
public UxrHandDescriptor HandDescriptorOpenLeft
{
get => _handDescriptorOpenLeft;
set => _handDescriptorOpenLeft = value;
}
/// <summary>
/// Gets the right blend pose hand descriptor for the open state.
/// </summary>
public UxrHandDescriptor HandDescriptorOpenRight
{
get => _handDescriptorOpenRight;
set => _handDescriptorOpenRight = value;
}
/// <summary>
/// Gets the left blend pose hand descriptor for the closed state.
/// </summary>
public UxrHandDescriptor HandDescriptorClosedLeft
{
get => _handDescriptorClosedLeft;
set => _handDescriptorClosedLeft = value;
}
/// <summary>
/// Gets the right blend pose hand descriptor for the closed state.
/// </summary>
public UxrHandDescriptor HandDescriptorClosedRight
{
get => _handDescriptorClosedRight;
set => _handDescriptorClosedRight = value;
}
#endregion
#region Public Methods
/// <summary>
/// Gets the hand descriptor for the given hand, based on the <see cref="PoseType" />.
/// </summary>
/// <param name="handSide">Hand to get the descriptor for</param>
/// <param name="blendPoseType">
/// If <see cref="PoseType" /> is <see cref="UxrHandPoseType.Blend" />, whether to get the open or
/// closed pose descriptor.
/// </param>
/// <returns>Hand descriptor</returns>
public UxrHandDescriptor GetHandDescriptor(UxrHandSide handSide, UxrBlendPoseType blendPoseType = UxrBlendPoseType.None)
{
return PoseType switch
{
UxrHandPoseType.Fixed => handSide == UxrHandSide.Left ? _handDescriptorLeft : _handDescriptorRight,
UxrHandPoseType.Blend when blendPoseType == UxrBlendPoseType.OpenGrip => handSide == UxrHandSide.Left ? _handDescriptorOpenLeft : _handDescriptorOpenRight,
UxrHandPoseType.Blend when blendPoseType == UxrBlendPoseType.ClosedGrip => handSide == UxrHandSide.Left ? _handDescriptorClosedLeft : _handDescriptorClosedRight,
_ => null
};
}
/// <summary>
/// Gets the hand descriptor for the given hand, based on an external <see cref="UxrHandPoseType" /> parameter.
/// </summary>
/// <param name="handSide">Hand to get the descriptor for</param>
/// <param name="poseType">The pose type to get the descriptor for</param>
/// <param name="blendPoseType">
/// If <see cref="PoseType" /> is <see cref="UxrHandPoseType.Blend" />, whether to get the open or
/// closed pose descriptor.
/// </param>
/// <returns>Hand descriptor</returns>
public UxrHandDescriptor GetHandDescriptor(UxrHandSide handSide, UxrHandPoseType poseType, UxrBlendPoseType blendPoseType = UxrBlendPoseType.None)
{
return poseType switch
{
UxrHandPoseType.Fixed => handSide == UxrHandSide.Left ? _handDescriptorLeft : _handDescriptorRight,
UxrHandPoseType.Blend when blendPoseType == UxrBlendPoseType.OpenGrip => handSide == UxrHandSide.Left ? _handDescriptorOpenLeft : _handDescriptorOpenRight,
UxrHandPoseType.Blend when blendPoseType == UxrBlendPoseType.ClosedGrip => handSide == UxrHandSide.Left ? _handDescriptorClosedLeft : _handDescriptorClosedRight,
_ => null
};
}
#if UNITY_EDITOR
/// <summary>
/// Outputs transform debug data to the editor window.
/// </summary>
/// <param name="handSide">Hand to output the data for</param>
/// <param name="blendPoseType">The blend pose type or <see cref="UxrBlendPoseType.None" /> if it is a fixed pose</param>
public void DrawEditorDebugLabels(UxrHandSide handSide, UxrBlendPoseType blendPoseType = UxrBlendPoseType.None)
{
UxrHandDescriptor handDescriptor = handSide == UxrHandSide.Left ? HandDescriptorLeft : HandDescriptorRight;
if (blendPoseType == UxrBlendPoseType.OpenGrip)
{
handDescriptor = handSide == UxrHandSide.Left ? HandDescriptorOpenLeft : HandDescriptorOpenRight;
}
else if (blendPoseType == UxrBlendPoseType.ClosedGrip)
{
handDescriptor = handSide == UxrHandSide.Left ? HandDescriptorClosedLeft : HandDescriptorClosedRight;
}
handDescriptor?.DrawEditorDebugLabels();
}
#endif
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: a3e4dbcd197921446835b52ba65d418e
timeCreated: 1543332642
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHandPoseType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Manipulation.HandPoses
{
/// <summary>
/// Enumerates the different pose types.
/// </summary>
public enum UxrHandPoseType
{
/// <summary>
/// Not initialized.
/// </summary>
None,
/// <summary>
/// Fixed pose (pose with a single state).
/// </summary>
Fixed,
/// <summary>
/// Blend pose. A blend pose has two states, open and closed, and allows to blend between them. In the grabbing system
/// it allows using a single blend grab pose for multiple objects with different sizes.
/// </summary>
Blend
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1657dc842d6e4de4a6c3182e6f4efa04
timeCreated: 1643744329

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f893437d37f64f978cd75aed2aa14320
timeCreated: 1643993603

View File

@@ -0,0 +1,94 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAlignOnRelease.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Animation.Interpolation;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Manipulation.Helpers
{
/// <summary>
/// Aligns an object smoothly whenever it is released to keep it leveled. Should be used on non physics-driven
/// grabbable objects, which remain floating in the air when being released.
/// </summary>
public class UxrAlignOnRelease : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _onlyLevel = true;
[SerializeField] [Range(0.0f, 1.0f)] private float _smoothFactor = 0.2f;
[SerializeField] private List<UxrGrabbableObject> _grabbableObjects;
#endregion
#region Unity
/// <summary>
/// Caches the transform component.
/// </summary>
protected override void Awake()
{
base.Awake();
_selfTransform = transform;
}
/// <summary>
/// Updates the transform while the object is not being grabbed.
/// </summary>
private void Update()
{
if (!IsBeingGrabbed)
{
// Smoothly rotate towards horizontal orientation when not being grabbed
if (_onlyLevel == false)
{
_selfTransform.rotation = UxrInterpolator.SmoothDampRotation(_selfTransform.rotation, Quaternion.FromToRotation(_selfTransform.up, Vector3.up) * _selfTransform.rotation, _smoothFactor);
}
else
{
Vector3 projectedRight = Vector3.ProjectOnPlane(transform.right, Vector3.up);
Quaternion targetRotation = Quaternion.FromToRotation(_selfTransform.right, projectedRight) * _selfTransform.rotation;
if ((targetRotation * Vector3.up).y < 0.0f)
{
targetRotation = targetRotation * Quaternion.AngleAxis(180.0f, Vector3.forward);
}
_selfTransform.rotation = UxrInterpolator.SmoothDampRotation(_selfTransform.rotation, targetRotation, _smoothFactor);
}
}
}
#endregion
#region Private Types & Data
/// <summary>
/// Gets whether the object is being grabbed using any of the registered grabbable objects.
/// </summary>
private bool IsBeingGrabbed
{
get
{
foreach (UxrGrabbableObject grabbableObject in _grabbableObjects)
{
if (UxrGrabManager.Instance.IsBeingGrabbed(grabbableObject))
{
return true;
}
}
return false;
}
}
private Transform _selfTransform;
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1dd4099f01318e34680aafb12edd7b4d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,61 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAutoSlideInAnchor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Manipulation.Helpers
{
/// <summary>
/// Anchor component for <see cref="UxrAutoSlideInAnchor" />. Grabbable objects with the
/// <see cref="UxrAutoSlideInObject" /> component will automatically attach/detach from this anchor.
/// </summary>
[RequireComponent(typeof(UxrGrabbableObjectAnchor))]
public class UxrAutoSlideInAnchor : UxrComponent<UxrAutoSlideInAnchor>
{
#region Public Types & Data
/// <summary>
/// Gets the anchor.
/// </summary>
public UxrGrabbableObjectAnchor Anchor
{
get
{
if (_anchor == null)
{
_anchor = GetComponent<UxrGrabbableObjectAnchor>();
}
return _anchor;
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
if (_anchor == null)
{
_anchor = GetComponent<UxrGrabbableObjectAnchor>();
}
}
#endregion
#region Private Types & Data
private UxrGrabbableObjectAnchor _anchor;
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0e85a05d37194014d8a60f3f2f72936a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAutoSlideInObject.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
namespace UltimateXR.Manipulation.Helpers
{
public partial class UxrAutoSlideInObject
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
// Manipulations are already handled through events, we don't serialize them in incremental changes
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
SerializeStateValue(level, options, nameof(_insertAxis), ref _insertAxis);
SerializeStateValue(level, options, nameof(_insertOffset), ref _insertOffset);
SerializeStateValue(level, options, nameof(_insertOffsetSign), ref _insertOffsetSign);
SerializeStateValue(level, options, nameof(_objectLocalSize), ref _objectLocalSize);
SerializeStateValue(level, options, nameof(_slideInTimer), ref _slideInTimer);
SerializeStateValue(level, options, nameof(_placedAfterSlidingIn), ref _placedAfterSlidingIn);
SerializeStateValue(level, options, nameof(_manipulationHapticFeedback), ref _manipulationHapticFeedback);
SerializeStateValue(level, options, nameof(_minHapticAmplitude), ref _minHapticAmplitude);
SerializeStateValue(level, options, nameof(_maxHapticAmplitude), ref _maxHapticAmplitude);
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: be3cda6fb2a64942a98f487a1e810b67
timeCreated: 1706041007

View File

@@ -0,0 +1,369 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAutoSlideInObject.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Linq;
using UltimateXR.Animation.Interpolation;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Core.Math;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.Unity;
using UltimateXR.Haptics.Helpers;
using UnityEngine;
namespace UltimateXR.Manipulation.Helpers
{
/// <summary>
/// Component that, together with <see cref="UxrAutoSlideInAnchor" /> will add the following behaviour to a
/// <see cref="UxrGrabbableObject" />:
/// <list type="bullet">
/// <item>
/// It will slide along the axis given by the grabbable object translation constraint. The constraint should
/// be pre-configured along a single axis.
/// </item>
/// <item>
/// It will be smoothly removed from the anchor and made free if dragged beyond the upper translation
/// constraint.
/// </item>
/// <item>It will be smoothly placed automatically on the anchor when moved back close enough.</item>
/// <item>It will fall back by itself when released while sliding along the axis.</item>
/// </list>
/// </summary>
public partial class UxrAutoSlideInObject : UxrGrabbableObjectComponent<UxrAutoSlideInObject>
{
[SerializeField] private Vector3 _translationConstraintMin = Vector3.zero;
[SerializeField] private Vector3 _translationConstraintMax = Vector3.forward * 0.1f;
#region Public Types & Data
/// <summary>
/// Event called right after the object hit the end after sliding in after it was released.
/// </summary>
public event Action PlacedAfterSlidingIn;
#endregion
#region Unity
/// <summary>
/// Subscribes to the avatars updated event so that the manipulation logic is done after all manipulation
/// logic has been updated.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
}
/// <summary>
/// Unsubscribes from the avatars updated event.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
}
/// <summary>
/// Initialize component.
/// </summary>
protected override void Start()
{
base.Start();
_placedAfterSlidingIn = GrabbableObject.CurrentAnchor != null;
// Get slide axis
int insertAxis = GrabbableObject.SingleTranslationAxisIndex;
if (insertAxis == -1 || GrabbableObject.TranslationConstraint != UxrTranslationConstraintMode.RestrictLocalOffset)
{
if (UxrGlobalSettings.Instance.LogLevelManipulation >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.ManipulationModule} {this}: {nameof(UxrGrabbableObject)} component needs to have a local offset translation constraint along a single axis to work properly");
}
_insertAxis = UxrAxis.Z;
}
else
{
_insertAxis = insertAxis;
}
// Store haptic feedback component in case it exists, to disable it while the object is out of the sliding zone
_manipulationHapticFeedback = GetComponent<UxrManipulationHapticFeedback>();
// Compute the slide length
_insertOffset = _translationConstraintMax[_insertAxis] > -_translationConstraintMin[_insertAxis] ? _translationConstraintMax[_insertAxis] : _translationConstraintMin[_insertAxis];
_insertOffsetSign = Mathf.Sign(_insertOffset);
_insertOffset = Mathf.Abs(_insertOffset);
// Fix some object parameters if we need to
if (GrabbableObject.DropSnapMode != UxrSnapToAnchorMode.DontSnap)
{
if (UxrGlobalSettings.Instance.LogLevelManipulation >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.ManipulationModule} {this.GetPathUnderScene()}: GrabbableObject needs DropSnapMode to be DontSnap in order to work properly. Overriding.");
}
GrabbableObject.DropSnapMode = UxrSnapToAnchorMode.DontSnap;
}
GrabbableObject.IsPlaceable = false; // We will handle placement ourselves
// Compute the object size in local coordinates
_objectLocalSize = gameObject.GetLocalBounds(true).size;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called after UltimateXR has done all the frame updating. Does the manipulation logic.
/// </summary>
private void UxrManager_AvatarsUpdated()
{
bool grabbedByLocalAvatar = UxrGrabManager.Instance.IsBeingGrabbed(GrabbableObject) && UxrGrabManager.Instance.GetGrabbingHands(GrabbableObject).First().Avatar.AvatarMode == UxrAvatarMode.Local;
if (GrabbableObject.CurrentAnchor == null && grabbedByLocalAvatar)
{
// The object is being grabbed and is detached. Check if we need to place it on an anchor again by proximity.
foreach (UxrAutoSlideInAnchor anchor in UxrAutoSlideInAnchor.EnabledComponents.Where(a => a.Anchor.enabled))
{
// If it is inside the valid release "volume", place it in the anchor again and let it slide by re-assigning the constraints
if (anchor.Anchor.CurrentPlacedObject == null && anchor.Anchor.IsCompatibleObject(GrabbableObject) && IsObjectNearPlacement(anchor.Anchor))
{
AttachObject(anchor);
return;
}
}
}
if (GrabbableObject.CurrentAnchor != null && _insertAxis != null)
{
// Object can only move in a specific axis but if it is grabbed past this distance it becomes free
if (transform.parent != null && grabbedByLocalAvatar && Mathf.Abs(GrabbableObject.InitialLocalPosition[_insertAxis] - transform.localPosition[_insertAxis]) > _insertOffset * 0.99f)
{
DetachObject();
return;
}
// If it is not being grabbed it will slide in
if (!GrabbableObject.IsBeingGrabbed)
{
// Use simple gravity to slide in. Gravity will be mapped to z axis to slide in our local coordinate system.
Vector3 speed = Physics.gravity * _slideInTimer;
Vector3 pos = GrabbableObject.transform.localPosition;
pos[_insertAxis] += Time.deltaTime * speed.y * _insertOffsetSign;
if ((_insertOffsetSign > 0.0f && pos[_insertAxis] < GrabbableObject.InitialLocalPosition[_insertAxis]) ||
(_insertOffsetSign < 0.0f && pos[_insertAxis] > GrabbableObject.InitialLocalPosition[_insertAxis]))
{
pos[_insertAxis] = GrabbableObject.InitialLocalPosition[_insertAxis];
if (_placedAfterSlidingIn == false)
{
_placedAfterSlidingIn = true;
OnPlacedAfterSlidingIn();
}
}
// Interpolate other rotation/translation in case the object was released before the transition
float smooth = 0.1f;
pos[_insertAxis.Perpendicular] = UxrInterpolator.SmoothDamp(pos[_insertAxis.Perpendicular], GrabbableObject.InitialLocalPosition[_insertAxis.Perpendicular], smooth);
pos[_insertAxis.OtherPerpendicular] = UxrInterpolator.SmoothDamp(pos[_insertAxis.OtherPerpendicular], GrabbableObject.InitialLocalPosition[_insertAxis.OtherPerpendicular], smooth);
GrabbableObject.transform.localRotation = UxrInterpolator.SmoothDampRotation(GrabbableObject.transform.localRotation, GrabbableObject.InitialLocalRotation, smooth);
// Update
GrabbableObject.transform.localPosition = pos;
_slideInTimer += Time.deltaTime;
}
else
{
_slideInTimer = 0.0f;
}
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Called when the object was placed at the end sliding in after it was released.
/// Use in child classes to
/// </summary>
protected virtual void OnPlacedAfterSlidingIn()
{
PlacedAfterSlidingIn?.Invoke();
}
/// <summary>
/// Called right after the object was grabbed.
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
{
if (!GrabbableObject.IsLockedInPlace)
{
_placedAfterSlidingIn = false;
}
}
/// <summary>
/// Called right after the object was released.
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnObjectReleased(UxrManipulationEventArgs e)
{
if (e.GrabbableObject.CurrentAnchor != null && e.GrabbableObject.RigidBodySource)
{
// Force kinematic while released, so that we update the position/rotation.
e.GrabbableObject.RigidBodySource.isKinematic = true;
}
}
#endregion
#region Protected Overrides UxrGrabbableObjectComponent<UxrAutoSlideInObject>
/// <inheritdoc />
protected override bool IsGrabbableObjectRequired => true;
#endregion
#region Protected Methods
/// <summary>
/// Attaches the object to the anchor and assigns constraints to let it slide.
/// </summary>
protected void AttachObject(UxrAutoSlideInAnchor anchor)
{
// This method will be synchronized through network
BeginSync();
// Set up constraints and place
GrabbableObject.TranslationConstraint = UxrTranslationConstraintMode.RestrictLocalOffset;
GrabbableObject.RotationConstraint = UxrRotationConstraintMode.Locked;
GrabbableObject.TranslationLimitsMin = _translationConstraintMin;
GrabbableObject.TranslationLimitsMax = _translationConstraintMax;
UxrGrabManager.Instance.PlaceObject(GrabbableObject, anchor.Anchor, UxrPlacementOptions.Smooth | UxrPlacementOptions.DontRelease, true);
if (_manipulationHapticFeedback)
{
_manipulationHapticFeedback.MinAmplitude = _minHapticAmplitude;
_manipulationHapticFeedback.MaxAmplitude = _maxHapticAmplitude;
}
EndSyncMethod(new object[] { anchor });
}
/// <summary>
/// Detaches the object from the anchor so that it becomes free.
/// </summary>
protected void DetachObject()
{
// This method will be synchronized through network
BeginSync();
if (_manipulationHapticFeedback)
{
_minHapticAmplitude = _manipulationHapticFeedback.MinAmplitude;
_maxHapticAmplitude = _manipulationHapticFeedback.MaxAmplitude;
_manipulationHapticFeedback.MinAmplitude = 0.0f;
_manipulationHapticFeedback.MaxAmplitude = 0.0f;
}
UxrGrabManager.Instance.RemoveObjectFromAnchor(GrabbableObject, true);
GrabbableObject.TranslationConstraint = UxrTranslationConstraintMode.Free;
GrabbableObject.RotationConstraint = UxrRotationConstraintMode.Free;
EndSyncMethod();
}
#endregion
#region Private Methods
/// <summary>
/// Checks whether the object is close enough to the given anchor to be placed.
/// </summary>
/// <param name="anchor">Object anchor</param>
/// <returns>Whether the object is close enough</returns>
private bool IsObjectNearPlacement(UxrGrabbableObjectAnchor anchor)
{
if (anchor.enabled == false)
{
return false;
}
// Is it near enough in the longitudinal axis?
float threshold = Mathf.Min(0.03f, Mathf.Abs(_insertOffset * 0.1f));
Vector3 localOffset = anchor.AlignTransform.InverseTransformPoint(GrabbableObject.DropAlignTransform.position) - GrabbableObject.InitialLocalPosition;
bool isInLongitudinalAxisRange = (_insertOffsetSign > 0.0f && localOffset[_insertAxis] < +_insertOffset - threshold && localOffset[_insertAxis] > 0.0f) ||
(_insertOffsetSign < 0.0f && localOffset[_insertAxis] > -_insertOffset + threshold && localOffset[_insertAxis] < 0.0f);
// Is it near enough in both other axes?
float minGrabDistance = float.MaxValue;
foreach (UxrGrabber grabber in UxrGrabManager.Instance.GetGrabbingHands(GrabbableObject))
{
UxrGrabPointInfo grabPointInfo = GrabbableObject.GetGrabPoint(UxrGrabManager.Instance.GetGrabbedPoint(grabber));
if (grabPointInfo.MaxDistanceGrab < minGrabDistance)
{
minGrabDistance = grabPointInfo.MaxDistanceGrab;
}
}
// We use some calculations for the other axes so that it feels good.
float sizeOneAxis = Mathf.Min(Mathf.Max(_objectLocalSize[_insertAxis.Perpendicular], 0.1f), minGrabDistance);
float sizeOtherAxis = Mathf.Min(Mathf.Max(_objectLocalSize[_insertAxis.OtherPerpendicular], 0.1f), minGrabDistance);
// Return conditions
return isInLongitudinalAxisRange && Mathf.Abs(localOffset[_insertAxis.Perpendicular]) < sizeOneAxis && Mathf.Abs(localOffset[_insertAxis.OtherPerpendicular]) < sizeOtherAxis;
}
#endregion
#region Private Types & Data
private UxrAxis _insertAxis;
private float _insertOffset;
private float _insertOffsetSign;
private Vector3 _objectLocalSize;
private float _slideInTimer;
private bool _placedAfterSlidingIn;
private UxrManipulationHapticFeedback _manipulationHapticFeedback;
private float _minHapticAmplitude;
private float _maxHapticAmplitude;
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d7264c3b787447c4596209ea4b950a39
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,100 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrDependentGrabbable.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components.Composite;
using UnityEngine;
namespace UltimateXR.Manipulation.Helpers
{
/// <summary>
/// Component that allows an object be grabbed only if another object is being grabbed. For instance, it can
/// be added to a grenade pin to make sure the pin is never grabbed unless the grenade is being grabbed too.
/// Otherwise the pin could be removed by mistake when trying to grab the grenade.
/// </summary>
public class UxrDependentGrabbable : UxrGrabbableObjectComponent<UxrDependentGrabbable>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrGrabbableObject _dependentOn;
[SerializeField] private bool _onlyOnce;
#endregion
#region Public Types & Data
/// <summary>
/// Gets or sets the grabbable object the component depends on.
/// </summary>
public UxrGrabbableObject DependentFrom
{
get => _dependentOn;
set => _dependentOn = value;
}
/// <summary>
/// Whether to stop toggling the enabled state once the dependent object was grabbed. For instance, a grenade pin
/// should remain grabbable once it has been removed from the grenade, no matter if the grenade is being grabbed or
/// not at that point.
/// </summary>
public bool OnlyOnce
{
get => _onlyOnce;
set => _onlyOnce = value;
}
#endregion
#region Unity
/// <summary>
/// Initializes the grabbable object state.
/// </summary>
protected override void Start()
{
base.Start();
GrabbableObject.enabled = false;
}
/// <summary>
/// Updates the grabbable object state.
/// </summary>
private void Update()
{
if (GrabbableObject && DependentFrom && _check)
{
GrabbableObject.enabled = UxrGrabManager.Instance.IsBeingGrabbed(DependentFrom);
if (GrabbableObject.enabled && OnlyOnce)
{
_check = false;
}
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Called whenever the object was grabbed.
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
{
base.OnObjectGrabbed(e);
_check = false;
}
#endregion
#region Private Types & Data
private bool _check = true;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 873afa6bf9451c3479e294d279b19ed9
timeCreated: 1492963941
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableResizable.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
namespace UltimateXR.Manipulation.Helpers
{
public sealed partial class UxrGrabbableResizable
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
// Manipulations are already handled through events, we don't serialize them in incremental changes
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
SerializeStateValue(level, options, nameof(_grabbingCount), ref _grabbingCount);
SerializeStateValue(level, options, nameof(_grabbedCount), ref _grabbedCount);
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 75671c44324f466baec19cfc69507c7b
timeCreated: 1706004361

View File

@@ -0,0 +1,421 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableResizable.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.Unity;
using UnityEngine;
#pragma warning disable 67 // Disable warnings due to unused events
namespace UltimateXR.Manipulation.Helpers
{
/// <summary>
/// <para>
/// Component that allows an object to be scaled by grabbing it by both sides and moving them closer or apart.
/// The hierarchy should be as follows:
/// </para>
/// <code>
/// -Root GameObject: With UxrGrabbableResizable and UxrGrabbableObject component.
/// | The UxrGrabbableObject is a dummy grabbable parent that enables moving
/// | this root by grabbing the child extensions. It can also have its own
/// | grab points but they are not required.
/// |---Root resizable: Object that will be scaled when the two extensions are moved.
/// |---Grabbable left: Left grabbable extension with locked rotation and translation
/// | constrained to sliding it left-right.
/// |---Grabbable right: Right grabbable extension with locked rotation and translation
/// constrained to sliding it left-right.
/// </code>
/// All objects should use an axis system with x right, y up and z forward.
/// </summary>
public sealed partial class UxrGrabbableResizable : UxrComponent, IUxrGrabbable
{
#region Inspector Properties/Serialized Fields
[Header("General")] [SerializeField] private Transform _resizableRoot;
[SerializeField] private float _startScale = 1.0f;
[Header("Grabbing")] [SerializeField] private UxrGrabbableObject _grabbableRoot;
[SerializeField] private UxrGrabbableObject _grabbableExtendLeft;
[SerializeField] private UxrGrabbableObject _grabbableExtendRight;
[Header("Haptics")] [SerializeField] [Range(0.0f, 1.0f)] private float _hapticsIntensity = 0.1f;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the <see cref="Transform" /> that is going to be scaled when the two grabbable objects are moved apart.
/// </summary>
public Transform ResizableRoot => _resizableRoot;
/// <summary>
/// Gets the root grabbable object.
/// </summary>
public UxrGrabbableObject GrabbableRoot => _grabbableRoot;
/// <summary>
/// Gets the left grabbable extension.
/// </summary>
public UxrGrabbableObject GrabbableExtendLeft => _grabbableExtendLeft;
/// <summary>
/// Gets the right grabbable extension.
/// </summary>
public UxrGrabbableObject GrabbableExtendRight => _grabbableExtendRight;
#endregion
#region Implicit IUxrGrabbable
/// <inheritdoc />
public bool IsBeingGrabbed => GrabbableRoot.IsBeingGrabbed || GrabbableExtendLeft.IsBeingGrabbed || GrabbableExtendRight.IsBeingGrabbed;
/// <inheritdoc />
public bool IsGrabbable
{
get => GrabbableRoot.IsGrabbable || GrabbableExtendLeft.IsGrabbable || GrabbableExtendRight.IsGrabbable;
set
{
BeginSync();
GrabbableRoot.IsGrabbable = value;
GrabbableExtendLeft.IsGrabbable = value;
GrabbableExtendRight.IsGrabbable = value;
EndSyncProperty(value);
}
}
/// <inheritdoc />
public bool IsKinematic
{
get => GrabbableRoot.IsKinematic || GrabbableExtendLeft.IsKinematic || GrabbableExtendRight.IsKinematic;
set
{
BeginSync();
GrabbableRoot.IsKinematic = value;
GrabbableExtendLeft.IsKinematic = value;
GrabbableExtendRight.IsKinematic = value;
EndSyncProperty(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)
{
// This method will be synchronized through network
BeginSync();
ReleaseGrabs(true);
GrabbableRoot.ResetPositionAndState(propagateEvents);
GrabbableExtendLeft.ResetPositionAndState(propagateEvents);
GrabbableExtendRight.ResetPositionAndState(propagateEvents);
UpdateResizableScale();
EndSyncMethod(new object[] { propagateEvents });
}
/// <inheritdoc />
public void ReleaseGrabs(bool propagateEvents)
{
// This method will be synchronized through network
BeginSync();
GrabbableRoot.ReleaseGrabs(propagateEvents);
GrabbableExtendLeft.ReleaseGrabs(propagateEvents);
GrabbableExtendRight.ReleaseGrabs(propagateEvents);
_grabbingCount = 0;
_grabbedCount = 0;
EndSyncMethod(new object[] { propagateEvents });
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_initialGrabsSeparation = Vector3.Distance(GrabbableExtendLeft.transform.position, GrabbableExtendRight.transform.position);
_initialResizableLocalScale = _resizableRoot.transform.localScale;
_separationToBoundsFactor = _initialGrabsSeparation / _resizableRoot.gameObject.GetLocalBounds(true).size.x;
}
/// <summary>
/// Subscribes to relevant events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
GrabbableRoot.Grabbing += Grabbable_Grabbing;
GrabbableRoot.Grabbed += Grabbable_Grabbed;
GrabbableRoot.Releasing += Grabbable_Releasing;
GrabbableRoot.Released += Grabbable_Released;
GrabbableExtendLeft.Grabbing += Grabbable_Grabbing;
GrabbableExtendLeft.Grabbed += Grabbable_Grabbed;
GrabbableExtendLeft.Releasing += Grabbable_Releasing;
GrabbableExtendLeft.Released += Grabbable_Released;
GrabbableExtendRight.Grabbed += Grabbable_Grabbed;
GrabbableExtendRight.Grabbing += Grabbable_Grabbing;
GrabbableExtendRight.Releasing += Grabbable_Releasing;
GrabbableExtendRight.Released += Grabbable_Released;
_hapticsCoroutine = StartCoroutine(HapticsCoroutine());
}
/// <summary>
/// Unsubscribes from relevant events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
GrabbableRoot.Grabbing -= Grabbable_Grabbing;
GrabbableRoot.Grabbed -= Grabbable_Grabbed;
GrabbableRoot.Releasing -= Grabbable_Releasing;
GrabbableRoot.Released -= Grabbable_Released;
GrabbableExtendLeft.Grabbing -= Grabbable_Grabbing;
GrabbableExtendLeft.Grabbed -= Grabbable_Grabbed;
GrabbableExtendLeft.Releasing -= Grabbable_Releasing;
GrabbableExtendLeft.Released -= Grabbable_Released;
GrabbableExtendRight.Grabbed -= Grabbable_Grabbed;
GrabbableExtendRight.Grabbing -= Grabbable_Grabbing;
GrabbableExtendRight.Releasing -= Grabbable_Releasing;
GrabbableExtendRight.Released -= Grabbable_Released;
StopCoroutine(_hapticsCoroutine);
}
/// <summary>
/// Scales the resizable using the initial scale if it's different than 1.0
/// </summary>
protected override void Start()
{
base.Start();
if (!Mathf.Approximately(1.0f, _startScale))
{
float halfOffset = (_startScale * _initialGrabsSeparation - _initialGrabsSeparation) * 0.5f;
GrabbableExtendLeft.transform.localPosition -= Vector3.right * halfOffset;
GrabbableExtendRight.transform.localPosition += Vector3.right * halfOffset;
}
}
#endregion
#region Coroutines
/// <summary>
/// Coroutine that sends haptic feedback in case of scaling.
/// </summary>
/// <returns>Coroutine IEnumerator</returns>
private IEnumerator HapticsCoroutine()
{
void SendHapticClip(UxrGrabbableObject grabbableObject, UxrHandSide handSide, float speed)
{
if (_hapticsIntensity < 0.001f || !UxrGrabManager.Instance.GetGrabbingHand(grabbableObject, 0, out UxrGrabber grabber) || grabber.Avatar.AvatarMode != UxrAvatarMode.Local)
{
return;
}
float quantityPos = HapticsManipulationMaxSpeed - HapticsManipulationMinSpeed <= 0.0f ? 0.0f : (speed - HapticsManipulationMinSpeed) / (HapticsManipulationMaxSpeed - HapticsManipulationMinSpeed);
if (quantityPos > 0.0f)
{
float frequencyPos = Mathf.Lerp(HapticsManipulationMinFrequency, HapticsManipulationMaxFrequency, Mathf.Clamp01(quantityPos));
float amplitudePos = Mathf.Lerp(0.1f, 1.0f, Mathf.Clamp01(quantityPos)) * _hapticsIntensity;
UxrAvatar.LocalAvatarInput.SendHapticFeedback(handSide, frequencyPos, amplitudePos, UxrConstants.InputControllers.HapticSampleDurationSeconds);
}
}
float lastDistance = Vector3.Distance(GrabbableExtendLeft.transform.position, GrabbableExtendRight.transform.position);
while (true)
{
if (_grabbableExtendLeft != null && _grabbableExtendRight != null && _grabbableExtendLeft.IsBeingGrabbed && _grabbableExtendRight.IsBeingGrabbed)
{
float currentDistance = Vector3.Distance(GrabbableExtendLeft.transform.position, GrabbableExtendRight.transform.position);
float speed = Mathf.Abs(lastDistance - currentDistance) / UxrConstants.InputControllers.HapticSampleDurationSeconds;
SendHapticClip(_grabbableExtendLeft, UxrHandSide.Left, speed);
SendHapticClip(_grabbableExtendRight, UxrHandSide.Right, speed);
lastDistance = Vector3.Distance(GrabbableExtendLeft.transform.position, GrabbableExtendRight.transform.position);
}
yield return new WaitForSeconds(UxrConstants.InputControllers.HapticSampleDurationSeconds);
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called right after the avatars and manipulation update. Scale the object at this point.
/// </summary>
private void UxrManager_AvatarsUpdated()
{
UpdateResizableScale();
}
/// <summary>
/// Called when any grabbable is about to be grabbed. It is responsible for sending the appropriate
/// <see cref="UxrGrabbableResizable" /> manipulation events if necessary.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Grabbable_Grabbing(object sender, UxrManipulationEventArgs e)
{
if (e.IsGrabbedStateChanged)
{
_grabbingCount++;
if (_grabbingCount == 1)
{
Grabbing?.Invoke(this, e);
}
}
}
/// <summary>
/// Called right after any grabbable was grabbed. It is responsible for sending the appropriate
/// <see cref="UxrGrabbableResizable" /> manipulation events if necessary.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Grabbable_Grabbed(object sender, UxrManipulationEventArgs e)
{
if (e.IsGrabbedStateChanged)
{
_grabbedCount++;
if (_grabbedCount == 1)
{
Grabbed?.Invoke(this, e);
}
}
}
/// <summary>
/// Called when any grabbable is about to be released. It is responsible for sending the appropriate
/// <see cref="UxrGrabbableResizable" /> manipulation events if necessary.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Grabbable_Releasing(object sender, UxrManipulationEventArgs e)
{
if (e.IsGrabbedStateChanged)
{
_grabbingCount--;
if (_grabbingCount == 0)
{
Releasing?.Invoke(this, e);
}
}
}
/// <summary>
/// Called right after any grabbable was released. It is responsible for sending the appropriate
/// <see cref="UxrGrabbableResizable" /> manipulation events if necessary.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Grabbable_Released(object sender, UxrManipulationEventArgs e)
{
if (e.IsGrabbedStateChanged)
{
_grabbedCount--;
if (_grabbedCount == 0)
{
Released?.Invoke(this, e);
}
}
}
#endregion
#region Private Methods
/// <summary>
/// Updates the resizable scale based on the current separation between the left and right extensions.
/// </summary>
private void UpdateResizableScale()
{
float currentGrabSeparation = Vector3.Distance(GrabbableExtendLeft.transform.position, GrabbableExtendRight.transform.position);
// Move the center in between the two extensions
Vector3 localCenter = transform.InverseTransformPoint((GrabbableExtendLeft.transform.position + GrabbableExtendRight.transform.position) * 0.5f);
Vector3 resizableLocalPos = _resizableRoot.transform.localPosition;
resizableLocalPos.x = localCenter.x;
_resizableRoot.transform.localPosition = resizableLocalPos;
// Scale the object
float localScaleZ = _resizableRoot.transform.localScale.z;
Vector3 resizableLocalScale = _initialResizableLocalScale * (currentGrabSeparation / _initialGrabsSeparation * _separationToBoundsFactor);
resizableLocalScale.z = localScaleZ;
_resizableRoot.transform.localScale = resizableLocalScale;
}
#endregion
#region Private Types & Data
private const float HapticsManipulationMinSpeed = 0.03f;
private const float HapticsManipulationMaxSpeed = 1.0f;
private const float HapticsManipulationMinFrequency = 10;
private const float HapticsManipulationMaxFrequency = 100;
private Vector3 _initialResizableLocalScale;
private float _initialGrabsSeparation;
private float _separationToBoundsFactor;
private int _grabbingCount;
private int _grabbedCount;
private Coroutine _hapticsCoroutine;
#endregion
}
}
#pragma warning restore 67

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b6293fca64f4b03488a84eb8363cea23
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrRestoreOnRelease.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Animation.Interpolation;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Core.Math;
using UnityEngine;
namespace UltimateXR.Manipulation.Helpers
{
/// <summary>
/// Component that will smoothly restore the original position and orientation of a <see cref="UxrGrabbableObject" />
/// when released.
/// </summary>
[RequireComponent(typeof(UxrGrabbableObject))]
public class UxrRestoreOnRelease : UxrGrabbableObjectComponent<UxrRestoreOnRelease>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrEasing _transitionType = UxrEasing.Linear;
[SerializeField] private float _transitionSeconds = 0.1f;
#endregion
#region Unity
/// <summary>
/// Updates the transition if it's active.
/// </summary>
private void Update()
{
if (_isTransitioning)
{
_transitionTimer -= Time.deltaTime;
float t = 1.0f;
if (_transitionTimer <= 0.0f)
{
_transitionTimer = 0.0f;
_isTransitioning = false;
}
else
{
t = UxrInterpolator.Interpolate(0.0f, 1.0f, 1.0f - _transitionTimer / _transitionSeconds, _transitionType);
}
GrabbableObject.transform.localPosition = Vector3.LerpUnclamped(_initialLocalPosition, GrabbableObject.InitialLocalPosition, t);
if (_singleRotationAxis != -1 && t >= 0.0f && t <= 1.0f)
{
// Do not rotate manually here to let UxrGrabbableObject keep track of single axis rotation.
// We allow using localRotation outside of the [0, 1] range to support overshooting since otherwise GrabbableObject.SingleRotationAxisDegrees
// will clamp the rotation.
GrabbableObject.SingleRotationAxisDegrees = Mathf.LerpUnclamped(_initialSingleAxisDegrees, 0.0f, t);
}
else
{
GrabbableObject.transform.localRotation = Quaternion.SlerpUnclamped(_initialLocalRotation, GrabbableObject.InitialLocalRotation, t);
}
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Called by the base class whenever the object is grabbed.
/// </summary>
/// <param name="e">Contains all grab event parameters</param>
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
{
base.OnObjectGrabbed(e);
if (e.IsGrabbedStateChanged)
{
_isTransitioning = false;
}
}
/// <summary>
/// Called by the base class whenever the object is released.
/// </summary>
/// <param name="e">Contains all grab event parameters</param>
protected override void OnObjectReleased(UxrManipulationEventArgs e)
{
base.OnObjectReleased(e);
if (e.IsGrabbedStateChanged)
{
_isTransitioning = true;
_transitionTimer = _transitionSeconds;
_initialLocalPosition = e.GrabbableObject.transform.localPosition;
_initialLocalRotation = e.GrabbableObject.transform.localRotation;
_singleRotationAxis = e.GrabbableObject.SingleRotationAxisIndex;
if (_singleRotationAxis != -1)
{
_initialSingleAxisDegrees = e.GrabbableObject.SingleRotationAxisDegrees;
}
// Avoid transitions getting in the way or being executed after the restore ended.
e.GrabbableObject.FinishSmoothTransitions();
}
}
#endregion
#region Private Types & Data
private bool _isTransitioning;
private float _transitionTimer = -1.0f;
private Vector3 _initialLocalPosition;
private Quaternion _initialLocalRotation;
private int _singleRotationAxis;
private float _initialSingleAxisDegrees;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 9a39a5225324a9c44b5248d45300663a
timeCreated: 1532426377
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrReturnGrabbableObject.ReturnPolicy.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Manipulation.Helpers
{
public partial class UxrReturnGrabbableObject
{
#region Public Types & Data
/// <summary>
/// Enumerates the different policies when returning an object to a previous anchor.
/// </summary>
public enum ReturnPolicy
{
/// <summary>
/// Return to last anchor where the object was placed.
/// </summary>
LastAnchor = 0,
/// <summary>
/// Return to the original anchor where the object was placed.
/// </summary>
OriginalAnchor,
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3fe66d825bdb4c3dba0d8252ba3cb221
timeCreated: 1693091097

View File

@@ -0,0 +1,157 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrReturnGrabbableObject.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UnityEngine;
namespace UltimateXR.Manipulation.Helpers
{
/// <summary>
/// Component that will always return an <see cref="UxrGrabbableObject" /> to the
/// <see cref="UxrGrabbableObjectAnchor" /> it was grabbed from whenever it is released.
/// </summary>
[RequireComponent(typeof(UxrGrabbableObject))]
public partial class UxrReturnGrabbableObject : UxrGrabbableObjectComponent<UxrReturnGrabbableObject>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _smoothTransition = true;
[SerializeField] private float _returnDelaySeconds = -1.0f;
[SerializeField] private ReturnPolicy _returnPolicy = ReturnPolicy.LastAnchor;
#endregion
#region Public Methods
/// <summary>
/// Cancels a return if the given object has a <see cref="UxrReturnGrabbableObject" /> component and a return
/// programmed.
/// </summary>
/// <param name="grabbableObject">Object to try to cancel the return of</param>
public static void CancelReturn(UxrGrabbableObject grabbableObject)
{
if (grabbableObject.gameObject.TryGetComponent<UxrReturnGrabbableObject>(out var returnComponent))
{
returnComponent.CancelReturn();
}
}
#endregion
#region Coroutines
/// <summary>
/// Coroutine that returns the object to the original target after some time.
/// </summary>
/// <param name="grabbableObject">Object to return</param>
/// <param name="propagateEvents">Whether to propagate manipulation events</param>
/// <returns>Coroutine IEnumerator</returns>
private IEnumerator ReturnCoroutine(UxrGrabbableObject grabbableObject, bool propagateEvents)
{
yield return new WaitForSeconds(_returnDelaySeconds);
UxrGrabbableObjectAnchor anchor = GetReturnAnchor();
if (anchor != null && anchor.CurrentPlacedObject == null)
{
UxrGrabManager.Instance.PlaceObject(grabbableObject, anchor, _smoothTransition ? UxrPlacementOptions.Smooth : UxrPlacementOptions.None, propagateEvents);
}
_returnCoroutine = null;
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Called by the base class whenever the object is grabbed.
/// </summary>
/// <param name="e">Contains all grab event parameters</param>
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
{
base.OnObjectGrabbed(e);
if (_returnCoroutine != null)
{
StopCoroutine(_returnCoroutine);
}
}
/// <summary>
/// Called by the base class whenever the object is released.
/// </summary>
/// <param name="e">Contains all grab event parameters</param>
protected override void OnObjectReleased(UxrManipulationEventArgs e)
{
base.OnObjectReleased(e);
if (e.IsGrabbedStateChanged)
{
if (e.GrabbableAnchor != null)
{
_lastObjectAnchor = e.GrabbableAnchor;
}
// Check also dependent grabs. We may be grabbing the object using another grip
if (!UxrGrabManager.Instance.IsHandGrabbing(UxrAvatar.LocalAvatar, GrabbableObject, UxrHandSide.Left, true) &&
!UxrGrabManager.Instance.IsHandGrabbing(UxrAvatar.LocalAvatar, GrabbableObject, UxrHandSide.Right, true))
{
if (_returnDelaySeconds <= 0.0f)
{
// Return to original place
UxrGrabManager.Instance.PlaceObject(e.GrabbableObject, GetReturnAnchor(), _smoothTransition ? UxrPlacementOptions.Smooth : UxrPlacementOptions.None, true);
}
else
{
_returnCoroutine = StartCoroutine(ReturnCoroutine(e.GrabbableObject, true));
}
}
}
}
#endregion
#region Private Methods
/// <summary>
/// Gets the anchor where the object should be returned.
/// </summary>
/// <returns>Destination anchor</returns>
private UxrGrabbableObjectAnchor GetReturnAnchor()
{
if (_returnPolicy == ReturnPolicy.LastAnchor && _lastObjectAnchor != null && _lastObjectAnchor.CurrentPlacedObject != null)
{
return _lastObjectAnchor;
}
return GrabbableObject.StartAnchor;
}
/// <summary>
/// Cancels a return if there was one programmed.
/// </summary>
private void CancelReturn()
{
if (_returnCoroutine != null)
{
StopCoroutine(_returnCoroutine);
_returnCoroutine = null;
}
}
#endregion
#region Private Types & Data
private UxrGrabbableObjectAnchor _lastObjectAnchor;
private Coroutine _returnCoroutine;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 6d08f3c02197a3f4ea71f397b2416ce4
timeCreated: 1532426377
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,178 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IUxrGrabbable.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Interface for all objects that can be grabbed/manipulated using the <see cref="UxrGrabManager" />.
/// </summary>
public interface IUxrGrabbable
{
#region Public Types & Data
/// <summary>
/// Event called when the object is about to be grabbed.
/// The following properties from <see cref="UxrManipulationEventArgs" /> will contain meaningful data:
/// <list type="bullet">
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableObject" />: Object that is about to be grabbed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableAnchor" />: Target where the object is currently placed. Null
/// if it isn't on an anchor.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.Grabber" />: Grabber that is about to grab the object.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabPointIndex" />: Grab point index of the object that is about to be
/// grabbed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.IsMultiHands" />: true if it is already being grabbed with one hand and
/// it will be grabbed with both hands after. False if no hand is currently grabbing it.
/// </item>
/// </list>
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Grabbing;
/// <summary>
/// Event called right after the object was grabbed. The grab event parameters use the same values as
/// <see cref="Grabbing" />.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Grabbed;
/// <summary>
/// Event called when the object is about to be released. An object is released when the last grip is released and
/// there is no compatible <see cref="UxrGrabbableObjectAnchor" /> near enough to place it on.
/// The following properties from <see cref="UxrManipulationEventArgs" /> will contain meaningful data:
/// <list type="bullet">
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableObject" />: Object that is about to be released.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableAnchor" />: Anchor where the object was originally grabbed
/// from. Null if it wasn't on a target.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.Grabber" />: Grabber that is about to release the object.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabPointIndex" />: Grab point index of the object that is being
/// grabbed by the <see cref="UxrGrabber" />.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.IsMultiHands" />: true if it is already being grabbed with another hand
/// that will keep it holding. False if no other hand is currently grabbing it.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.IsSwitchHands" />: True if it was released because another
/// <see cref="UxrGrabber" /> grabbed it, false otherwise. if
/// <see cref="UxrManipulationEventArgs.IsMultiHands" /> is
/// true then <see cref="UxrManipulationEventArgs.IsSwitchHands" /> will tell if it was released by both hands
/// (false) or if it was just released by one hand and the other one still keeps it grabbed (true).
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.ReleaseVelocity" />: Velocity the object is being released with.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.ReleaseAngularVelocity" />: Angular velocity the object is being
/// released with.
/// </item>
/// </list>
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Releasing;
/// <summary>
/// Event called right after the object was released. An object is released when the last grip is released and there is
/// no compatible <see cref="UxrGrabbableObjectAnchor" /> near enough to place it on.
/// The grab event parameters use the same values as <see cref="Releasing" />.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Released;
/// <summary>
/// Event called when the object is about to be placed. An object is placed when the last grip is released and there is
/// a compatible <see cref="UxrGrabbableObjectAnchor" /> near enough to place it on.
/// The following properties from <see cref="UxrManipulationEventArgs" /> will contain meaningful data:
/// <list type="bullet">
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableObject" />: Object that is about to be removed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabbableAnchor" />: Anchor where the object is currently placed.
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.Grabber" />: Grabber that is about to remove the object by grabbing it.
/// This can be null if the object is removed through code using
/// <see cref="UxrGrabManager.RemoveObjectFromAnchor" />,
/// <see cref="UxrGrabbableObject.RemoveFromAnchor" /> or <see cref="UxrGrabbableObjectAnchor.RemoveObject" />>
/// </item>
/// <item>
/// <see cref="UxrManipulationEventArgs.GrabPointIndex" />: Only if the object is being removed by grabbing it:
/// Grab point index of the object that is about to be grabbed by the <see cref="UxrGrabber" />.
/// </item>
/// </list>
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Placing;
/// <summary>
/// Event called right after the object was placed. An object is placed when the last grip is released and there is a
/// compatible <see cref="UxrGrabbableObjectAnchor" /> near enough to place it on.
/// The grab event parameters use the same values as <see cref="Placed" />.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Placed;
/// <summary>
/// Gets the associated <see cref="GameObject" />. Since all components that implement the interface will be assigned
/// to GameObjects, this allows to access them using the interface.
/// It doesn't follow the property PascalCase naming to make it compatible with Unity.
/// </summary>
public GameObject gameObject { get; }
/// <summary>
/// Gets the associated <see cref="Transform" /> component. Since all components that implement the interface will be
/// assigned to GameObjects, this allows to access their transform using the interface.
/// It doesn't follow the property PascalCase naming to make it compatible with Unity.
/// </summary>
public Transform transform { get; }
/// <summary>
/// Gets whether the object is being grabbed.
/// </summary>
public bool IsBeingGrabbed { get; }
/// <summary>
/// Gets or sets whether the object can be grabbed.
/// </summary>
public bool IsGrabbable { get; set; }
/// <summary>
/// Gets or sets whether the rigidbody that drives the object (if any) is kinematic.
/// </summary>
public bool IsKinematic { get; set; }
#endregion
#region Public Methods
/// <summary>
/// Resets the object to its initial position/rotation and state. If the object is currently being grabbed, it will be
/// released.
/// </summary>
/// <param name="propagateEvents">Should <see cref="UxrManipulationEventArgs" /> events be generated?</param>
public void ResetPositionAndState(bool propagateEvents);
/// <summary>
/// Releases the object from all its grabs if there are any.
/// </summary>
/// <param name="propagateEvents">Should <see cref="UxrManipulationEventArgs" /> events be generated?</param>
public void ReleaseGrabs(bool propagateEvents);
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 16baea219bfb42c5b79fb1193ec061c2
timeCreated: 1630350158

View File

@@ -0,0 +1,33 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IUxrGrabbableModifier.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Interface that can be implemented in components that modify a <see cref="UxrGrabbableObject" /> in the same
/// <see cref="GameObject" /> so that the inspector shows which information is being controlled by the modifier.
/// </summary>
public interface IUxrGrabbableModifier
{
#region Public Types & Data
/// <summary>
/// Gets the flags representing the parts of the <see cref="UxrGrabbableObject" /> that are overriden/controlled by the
/// modifier.
/// </summary>
UxrGrabbableModifierFlags GrabbableModifierFlags { get; }
/// <summary>
/// Gets a list of additional grabbable objects affected by the modifier. These grabbable objects can only be up or
/// down in the same hierarchy as the modifier.
/// </summary>
IEnumerable<UxrGrabbableObject> AdditionalTargets { get; }
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c539d9344c5836e42a7b9d91e9a94f38
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrApplyConstraintsEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Event arguments for <see cref="UxrGrabbableObject" /> <see cref="UxrGrabbableObject.ConstraintsApplying" /> and
/// <see cref="UxrGrabbableObject.ConstraintsApplied" />.
/// </summary>
public class UxrApplyConstraintsEventArgs : EventArgs
{
#region Public Types & Data
/// <summary>
/// Gets the grabbable object being constrained.
/// </summary>
public UxrGrabbableObject GrabbableObject { get; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="grabbableObject">The object being constrained</param>
public UxrApplyConstraintsEventArgs(UxrGrabbableObject grabbableObject)
{
GrabbableObject = grabbableObject;
}
/// <summary>
/// Default constructor is private.
/// </summary>
private UxrApplyConstraintsEventArgs()
{
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: daad5d4facc24ae7beea828711414271
timeCreated: 1643287360

View File

@@ -0,0 +1,173 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabManager.GrabbableObjectAnchorInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Serialization;
namespace UltimateXR.Manipulation
{
public partial class UxrGrabManager
{
#region Private Types & Data
/// <summary>
/// Stores information to handle grab events (<see cref="UxrManipulationEventArgs" />) for
/// <see cref="UxrGrabbableObjectAnchor" /> objects:
/// <list type="bullet">
/// <item>
/// <see cref="UxrGrabManager.PlacedObjectRangeEntered" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.PlacedObjectRangeLeft" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.AnchorRangeEntered" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.AnchorRangeLeft" />
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// Implements <see cref="IUxrSerializable" /> to help <see cref="UxrGrabManager" />'s implementation of the
/// <see cref="IUxrStateSave" /> interface (<see cref="UxrGrabManager.SerializeState" />).
/// For now this information is not serialized, because most of it can be inferred at runtime on the client side, but
/// it might get used in the future.
/// </remarks>
private class GrabbableObjectAnchorInfo : IUxrSerializable
{
#region Public Types & Data
/// <summary>
/// Gets or sets whether the given <see cref="UxrGrabbableObjectAnchor" /> had a compatible
/// <see cref="UxrGrabbableObject" /> within a valid drop distance the last frame.
/// </summary>
public bool HadCompatibleObjectNearLastFrame
{
get => _hadCompatibleObjectNearLastFrame;
set => _hadCompatibleObjectNearLastFrame = value;
}
/// <summary>
/// Gets or sets whether the given <see cref="UxrGrabbableObjectAnchor" /> has currently a compatible
/// <see cref="UxrGrabbableObject" /> within a valid drop distance.
/// </summary>
public bool HasCompatibleObjectNear
{
get => _hasCompatibleObjectNear;
set => _hasCompatibleObjectNear = value;
}
/// <summary>
/// Gets or sets the <see cref="UxrGrabber" /> that currently can grab the <see cref="UxrGrabbableObject" /> placed on
/// the given <see cref="UxrGrabbableObjectAnchor" />. Null if there is none.
/// </summary>
public UxrGrabber GrabberNear
{
get => _grabberNear;
set => _grabberNear = value;
}
/// <summary>
/// Gets or sets the <see cref="UxrGrabber" /> that could grab the <see cref="UxrGrabbableObject" /> placed on the
/// given <see cref="UxrGrabbableObjectAnchor" /> during last frame. Null if there was none.
/// </summary>
public UxrGrabber LastValidGrabberNear
{
get => _lastValidGrabberNear;
set => _lastValidGrabberNear = value;
}
/// <summary>
/// Gets or sets the grab point index of the <see cref="UxrGrabbableObject" /> that is placed on the given
/// <see cref="UxrGrabbableObjectAnchor" /> that can currently be grabbed by <see cref="GrabberNear" />. -1 If there is
/// none.
/// </summary>
public int GrabPointNear
{
get => _grabPointNear;
set => _grabPointNear = value;
}
/// <summary>
/// Gets or sets the grab point index of the <see cref="UxrGrabbableObject" /> that is placed on the given
/// <see cref="UxrGrabbableObjectAnchor" /> that could be grabbed by <see cref="GrabberNear" /> during last frame. -1
/// if there was none.
/// </summary>
public int LastValidGrabPointNear
{
get => _lastValidGrabPointNear;
set => _lastValidGrabPointNear = value;
}
/// <summary>
/// Gets or sets the <see cref="UxrGrabber" /> that currently is grabbing an <see cref="UxrGrabbableObject" /> that can
/// be placed on the given <see cref="UxrGrabbableObjectAnchor" />. Null if there is none.
/// </summary>
public UxrGrabber FullGrabberNear
{
get => _fullGrabberNear;
set => _fullGrabberNear = value;
}
/// <summary>
/// Gets or sets the <see cref="UxrGrabber" /> that currently is grabbing an <see cref="UxrGrabbableObject" /> that
/// could be placed on the given <see cref="UxrGrabbableObjectAnchor" /> during last frame. Null if there was none.
/// </summary>
public UxrGrabber LastFullGrabberNear
{
get => _lastFullGrabberNear;
set => _lastFullGrabberNear = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Default constructor required for serialization.
/// </summary>
public GrabbableObjectAnchorInfo()
{
}
#endregion
#region Implicit IUxrSerializable
/// <inheritdoc />
public int SerializationVersion => 0;
/// <inheritdoc />
public void Serialize(IUxrSerializer serializer, int serializationVersion)
{
serializer.Serialize(ref _hadCompatibleObjectNearLastFrame);
serializer.Serialize(ref _hasCompatibleObjectNear);
serializer.SerializeUniqueComponent(ref _grabberNear);
serializer.SerializeUniqueComponent(ref _lastValidGrabberNear);
serializer.Serialize(ref _grabPointNear);
serializer.Serialize(ref _lastValidGrabPointNear);
serializer.SerializeUniqueComponent(ref _fullGrabberNear);
serializer.SerializeUniqueComponent(ref _lastFullGrabberNear);
}
#endregion
#region Private Types & Data
private bool _hadCompatibleObjectNearLastFrame;
private bool _hasCompatibleObjectNear;
private UxrGrabber _grabberNear;
private UxrGrabber _lastValidGrabberNear;
private int _grabPointNear = -1;
private int _lastValidGrabPointNear = -1;
private UxrGrabber _fullGrabberNear;
private UxrGrabber _lastFullGrabberNear;
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 45cc7569f3ff41109c9a7ee2527ecd93
timeCreated: 1643541487

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e76b34c563814f43899427e340cc6aba
timeCreated: 1677409671

View File

@@ -0,0 +1,988 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabManager.Querying.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Extensions.Unity;
using UltimateXR.Manipulation.HandPoses;
using UnityEngine;
namespace UltimateXR.Manipulation
{
public partial class UxrGrabManager
{
#region Public Methods
/// <summary>
/// Checks whether an <see cref="UxrAvatar" /> can grab something using the given hand.
/// </summary>
/// <param name="avatar">Avatar to check</param>
/// <param name="handSide">Whether to check the left or right hand</param>
/// <returns>Whether something can be grabbed</returns>
public bool CanGrabSomething(UxrAvatar avatar, UxrHandSide handSide)
{
foreach (UxrGrabber grabber in UxrGrabber.GetComponents(avatar))
{
if (grabber.Side == handSide)
{
return CanGrabSomething(grabber);
}
}
return false;
}
/// <summary>
/// Checks whether a <see cref="UxrGrabber" /> can grab something using the given grabber.
/// </summary>
/// <param name="grabber">Grabber to check</param>
/// <returns>Whether something can be grabbed</returns>
public bool CanGrabSomething(UxrGrabber grabber)
{
foreach (UxrGrabbableObject grabbableObject in UxrGrabbableObject.EnabledComponents)
{
if (grabbableObject.IsGrabbable)
{
for (int point = 0; point < grabbableObject.GrabPointCount; ++point)
{
if (grabbableObject.CanBeGrabbedByGrabber(grabber, point))
{
return true;
}
}
}
}
return false;
}
/// <summary>
/// Gets the closest grabbable object that can be grabbed by an <see cref="UxrAvatar" /> using the given hand.
/// </summary>
/// <param name="avatar">Avatar to check</param>
/// <param name="handSide">Whether to check the left hand or right hand</param>
/// <param name="grabbableObject">Returns the closest grabbable object or null if none was found</param>
/// <returns>Whether a grabbable object was found</returns>
public bool GetClosestGrabbableObject(UxrAvatar avatar, UxrHandSide handSide, out UxrGrabbableObject grabbableObject)
{
grabbableObject = null;
foreach (UxrGrabber grabber in UxrGrabber.GetComponents(avatar))
{
if (grabber.Side == handSide)
{
return GetClosestGrabbableObject(avatar, handSide, out grabbableObject, out int _);
}
}
return false;
}
/// <summary>
/// Gets the closest grabbable object that can be grabbed by an <see cref="UxrAvatar" /> using the given hand.
/// </summary>
/// <param name="avatar">Avatar to check</param>
/// <param name="handSide">Whether to check the left hand or right hand</param>
/// <param name="grabbableObject">Returns the closest grabbable object or null if none was found</param>
/// <param name="grabPoint">Returns the grab point that can be grabbed</param>
/// <param name="candidates">List of grabbable objects to process or null to process all current enabled grabbable objects</param>
/// <returns>Whether a grabbable object was found</returns>
public bool GetClosestGrabbableObject(UxrAvatar avatar, UxrHandSide handSide, out UxrGrabbableObject grabbableObject, out int grabPoint, IEnumerable<UxrGrabbableObject> candidates = null)
{
grabbableObject = null;
grabPoint = 0;
foreach (UxrGrabber grabber in UxrGrabber.GetComponents(avatar))
{
if (grabber.Side == handSide)
{
return GetClosestGrabbableObject(grabber, out grabbableObject, out grabPoint, candidates);
}
}
return false;
}
/// <summary>
/// Gets the closest grabbable object that can be grabbed by a <see cref="UxrGrabber" />.
/// </summary>
/// <param name="grabber">Grabber to check</param>
/// <param name="grabbableObject">Returns the closest grabbable object or null if none was found</param>
/// <param name="grabPoint">Returns the grab point that can be grabbed</param>
/// <param name="candidates">List of grabbable objects to process or null to process all current enabled grabbable objects</param>
/// <returns>Whether a grabbable object was found</returns>
public bool GetClosestGrabbableObject(UxrGrabber grabber, out UxrGrabbableObject grabbableObject, out int grabPoint, IEnumerable<UxrGrabbableObject> candidates = null)
{
int maxPriority = int.MinValue;
float minDistanceWithoutRotation = float.MaxValue; // Between different objects we don't take orientations into account
grabbableObject = null;
grabPoint = 0;
// Iterate over objects
foreach (UxrGrabbableObject candidate in candidates ?? UxrGrabbableObject.EnabledComponents)
{
float minDistance = float.MaxValue; // For the same object we will not just consider the distance but also how close the grabber is to the grip orientation
// Iterate over grab points
for (int point = 0; point < candidate.GrabPointCount; ++point)
{
if (candidate.CanBeGrabbedByGrabber(grabber, point))
{
candidate.GetDistanceFromGrabber(grabber, point, out float distance, out float distanceWithoutRotation);
if (candidate.Priority > maxPriority)
{
grabbableObject = candidate;
grabPoint = point;
minDistance = distance;
minDistanceWithoutRotation = distanceWithoutRotation;
maxPriority = candidate.Priority;
}
else if (candidate.Priority == maxPriority)
{
if ((grabbableObject == candidate && distance < minDistance) || (grabbableObject != candidate && distanceWithoutRotation < minDistanceWithoutRotation))
{
grabbableObject = candidate;
grabPoint = point;
minDistance = distance;
minDistanceWithoutRotation = distanceWithoutRotation;
}
}
}
}
}
return grabbableObject != null;
}
/// <summary>
/// Gets whether grabbing a given <see cref="UxrGrabbableObject" /> using a certain <see cref="UxrGrabber" /> will make
/// the grabber's renderer show up as hidden due to the parameters set in the inspector.
/// </summary>
/// <param name="grabber">Grabber to check</param>
/// <param name="grabbableObject">Grabbable object to check</param>
/// <returns>Whether the renderer would be hidden when grabbed</returns>
public bool ShouldHideHandRenderer(UxrGrabber grabber, UxrGrabbableObject grabbableObject)
{
if (_currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo))
{
int grabPoint = manipulationInfo.GetGrabbedPoint(grabber);
if (grabPoint != -1)
{
return grabbableObject.GetGrabPoint(grabPoint).HideHandGrabberRenderer;
}
}
return false;
}
/// <summary>
/// Gets the grab pose name required when grabbing the given <see cref="UxrGrabbableObject" /> using the
/// <see cref="UxrGrabber" />.
/// </summary>
/// <param name="grabber">Grabber</param>
/// <param name="grabbableObject">Grabbable object</param>
/// <returns>Grab pose name or null to use the default grab pose specified in the avatar belonging to the grabber</returns>
public string GetOverrideGrabPoseName(UxrGrabber grabber, UxrGrabbableObject grabbableObject)
{
if (_currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo))
{
int grabPoint = manipulationInfo.GetGrabbedPoint(grabber);
if (grabPoint != -1)
{
UxrHandPoseAsset handPoseAsset = grabbableObject.GetGrabPoint(grabPoint).GetGripPoseInfo(grabber.Avatar).HandPose;
return handPoseAsset != null ? handPoseAsset.name : null;
}
}
return null;
}
/// <summary>
/// Gets the blend value for the <see cref="UxrHandPoseType.Blend" /> pose used when grabbing the given
/// <see cref="UxrGrabbableObject" /> using the <see cref="UxrGrabber" />.
/// Blending is used to transition between different states such as open/closed or similar.
/// </summary>
/// <param name="grabber">Grabber</param>
/// <param name="grabbableObject">Grabbable object</param>
/// <returns>Blending value [0.0, 1.0]</returns>
public float GetOverrideGrabPoseBlendValue(UxrGrabber grabber, UxrGrabbableObject grabbableObject)
{
if (_currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo))
{
int grabPoint = manipulationInfo.GetGrabbedPoint(grabber);
if (grabPoint != -1)
{
return grabbableObject.GetGrabPoint(grabPoint).GetGripPoseInfo(grabber.Avatar).PoseBlendValue;
}
}
return 0.0f;
}
/// <summary>
/// Checks whether the given <see cref="UxrAvatar" /> hand is currently grabbing something.
/// </summary>
/// <param name="avatar">Avatar to check</param>
/// <param name="handSide">Whether to check the left hand or right hand</param>
/// <returns>Whether it is currently grabbing something</returns>
public bool IsHandGrabbing(UxrAvatar avatar, UxrHandSide handSide)
{
foreach (UxrGrabber grabber in UxrGrabber.GetComponents(avatar))
{
if (grabber.Side == handSide && grabber.GrabbedObject != null)
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if an avatar's hand is grabbing a grabbable object.
/// </summary>
/// <param name="avatar">Avatar to check</param>
/// <param name="grabbableObject">Object to check if it is being grabbed</param>
/// <param name="handSide">Whether to check the left hand or right hand</param>
/// <param name="alsoCheckDependentGrab">
/// Whether to also check for any parent or child <see cref="UxrGrabbableObject" /> that
/// is physically connected.
/// </param>
/// <returns>Whether the object is being grabbed by the avatar using the given hand</returns>
public bool IsHandGrabbing(UxrAvatar avatar, UxrGrabbableObject grabbableObject, UxrHandSide handSide, bool alsoCheckDependentGrab)
{
foreach (UxrGrabber grabber in UxrGrabber.GetComponents(avatar))
{
if (grabber.Side == handSide && grabber.GrabbedObject != null)
{
// Grabbing directly with the hand
if (grabber.GrabbedObject == grabbableObject)
{
return true;
}
if (alsoCheckDependentGrab)
{
// Grabbing a parent/child?
if (GetParentsBeingGrabbedChain(grabber.GrabbedObject).Contains(grabbableObject) || GetChildrenBeingGrabbed(grabber.GrabbedObject).Contains(grabbableObject))
{
return true;
}
}
}
}
return false;
}
/// <summary>
/// Gets all the objects currently being grabbed.
/// </summary>
/// <returns>Objects being grabbed</returns>
public IEnumerator<UxrGrabbableObject> GetObjectsBeingGrabbed()
{
foreach (var manipulation in _currentManipulations)
{
yield return manipulation.Key;
}
}
/// <summary>
/// Gets the object being grabbed by an avatar.
/// </summary>
/// <param name="avatar">Avatar to get the grabbed object of</param>
/// <param name="handSide">Whether to check the left hand or right hand</param>
/// <param name="grabbableObject">Returns the object being grabbed, or null if not found</param>
/// <returns>Whether there is an object being grabbed by the avatar using the given hand</returns>
public bool GetObjectBeingGrabbed(UxrAvatar avatar, UxrHandSide handSide, out UxrGrabbableObject grabbableObject)
{
grabbableObject = null;
foreach (UxrGrabber grabber in UxrGrabber.GetComponents(avatar))
{
if (grabber.Side == handSide && grabber.GrabbedObject != null)
{
grabbableObject = grabber.GrabbedObject;
return true;
}
}
return false;
}
/// <summary>
/// Gets the number of hands currently grabbing an object.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="alsoCheckDependentGrabs">
/// Also checks for hands that are grabbing other child objects that also control the direction of the given parent
/// object.
/// </param>
/// <returns>Number of hands grabbing the object</returns>
public int GetHandsGrabbingCount(UxrGrabbableObject grabbableObject, bool alsoCheckDependentGrabs = true)
{
int result = 0;
foreach (UxrGrabber grabber in UxrGrabber.EnabledComponents)
{
if (grabber.GrabbedObject != null)
{
// Grabbing directly with the hand
if (grabber.GrabbedObject == grabbableObject)
{
result++;
}
else if (alsoCheckDependentGrabs)
{
// Grabbing a dependent grabbable?
if (GetParentsBeingGrabbedChain(grabber.GrabbedObject).Contains(grabbableObject) || GetChildrenBeingGrabbed(grabber.GrabbedObject).Contains(grabbableObject))
{
result++;
}
}
}
}
return result;
}
/// <summary>
/// Checks whether the given grabbable object is being grabbed.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <returns>Whether it is being grabbed</returns>
public bool IsBeingGrabbed(UxrGrabbableObject grabbableObject)
{
return _currentManipulations != null && grabbableObject != null && _currentManipulations.ContainsKey(grabbableObject);
}
/// <summary>
/// Checks whether the given grabbable object is being grabbed using the given grab point.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="point">Grab point of the grabbable object to check</param>
/// <returns>Whether the grab point is being grabbed using the given grab point</returns>
public bool IsBeingGrabbed(UxrGrabbableObject grabbableObject, int point)
{
if (_currentManipulations == null || grabbableObject == null)
{
return false;
}
return _currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo) && manipulationInfo.GrabbedPoints.Contains(point);
}
/// <summary>
/// Checks whether the given grabbable object is being grabbed by an avatar.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="avatar">The avatar to check</param>
/// <returns>Whether it is being grabbed by the avatar</returns>
public bool IsBeingGrabbedBy(UxrGrabbableObject grabbableObject, UxrAvatar avatar)
{
if (_currentManipulations == null || grabbableObject == null)
{
return false;
}
return _currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo) && manipulationInfo.Grabbers.Any(grabber => grabber.Avatar == avatar);
}
/// <summary>
/// Checks whether the given grabbable object is being grabbed by a specific grabber.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="grabber">The grabber to check</param>
/// <returns>Whether it is being grabbed by the given grabber</returns>
public bool IsBeingGrabbedBy(UxrGrabbableObject grabbableObject, UxrGrabber grabber)
{
if (_currentManipulations == null || grabbableObject == null)
{
return false;
}
return _currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo) && manipulationInfo.Grabbers.Any(grb => grabber == grb);
}
/// <summary>
/// Checks whether the given grabbable object is being grabbed using any other grab point than the specified.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="point">Grab point of the grabbable object not to check</param>
/// <returns>Whether any other grab point is being grabbed</returns>
public bool IsBeingGrabbedByOtherThan(UxrGrabbableObject grabbableObject, int point)
{
if (_currentManipulations == null || grabbableObject == null)
{
return false;
}
return _currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo) && manipulationInfo.GrabbedPoints.Any(grabPoint => point != grabPoint);
}
/// <summary>
/// Checks whether the given grabbable object is being grabbed using any other grab point and any other grabber than
/// the specified.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="point">Point should be any other than this</param>
/// <param name="grabber">Grabber should be any other than this</param>
/// <returns>Whether the object is being grabbed with the specified conditions</returns>
public bool IsBeingGrabbedByOtherThan(UxrGrabbableObject grabbableObject, int point, UxrGrabber grabber)
{
if (_currentManipulations == null || grabbableObject == null)
{
return false;
}
if (_currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo))
{
foreach (RuntimeGrabInfo grabInfo in manipulationInfo.Grabs)
{
if (grabInfo.Grabber != grabber || grabInfo.GrabbedPoint != point)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Gets the hands that are grabbing an object.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="isLeft">Whether it is being grabbed using the left hand</param>
/// <param name="isRight">Whether it is being grabbed using the right hand</param>
/// <returns>Whether it is being grabbed</returns>
public bool GetGrabbingHand(UxrGrabbableObject grabbableObject, out bool isLeft, out bool isRight)
{
isLeft = false;
isRight = false;
foreach (UxrGrabber grabber in UxrGrabber.EnabledComponents)
{
if (grabber.GrabbedObject == grabbableObject)
{
if (grabber.Side == UxrHandSide.Left)
{
isLeft = true;
}
else
{
isRight = true;
}
}
}
return isLeft || isRight;
}
/// <summary>
/// Gets the grabber that is grabbing an object using a specific grab point.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="point">Grab point to check</param>
/// <param name="grabber">Grabber to check</param>
/// <returns>Whether it is being grabbed with the specified conditions</returns>
public bool GetGrabbingHand(UxrGrabbableObject grabbableObject, int point, out UxrGrabber grabber)
{
grabber = null;
foreach (UxrGrabber grabberCandidate in UxrGrabber.EnabledComponents)
{
if (grabberCandidate.GrabbedObject == grabbableObject)
{
foreach (RuntimeGrabInfo grabInfo in GetGrabs(grabbableObject))
{
if (grabInfo.GrabbedPoint == point)
{
grabber = grabInfo.Grabber;
return true;
}
}
}
}
return false;
}
/// <summary>
/// Gets the hand grabbing the given object using a given grab point.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="point">The grab point used to grab the object</param>
/// <param name="handSide">Returns the hand that is used to grab the object</param>
/// <returns>Whether there is a hand grabbing the object</returns>
public bool GetGrabbingHand(UxrGrabbableObject grabbableObject, int point, out UxrHandSide handSide)
{
handSide = UxrHandSide.Left;
if (GetGrabbingHand(grabbableObject, point, out UxrGrabber grabber))
{
handSide = grabber.Side;
return true;
}
return false;
}
/// <summary>
/// Gets the grabbers that are grabbing the object using a specific grab point.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="point">The grab point or -1 to get all grabbed points</param>
/// <param name="grabbers">
/// Returns the list of grabbers. If the list is null a new list is created, otherwise the grabbers
/// are added to the list.
/// </param>
/// <returns>Whether one or more grabbers were found</returns>
public bool GetGrabbingHands(UxrGrabbableObject grabbableObject, int point, out List<UxrGrabber> grabbers)
{
grabbers = null;
foreach (RuntimeGrabInfo grabInfo in GetGrabs(grabbableObject))
{
if (grabInfo.GrabbedPoint == point || point == -1)
{
grabbers ??= new List<UxrGrabber>();
if (!grabbers.Contains(grabInfo.Grabber))
{
grabbers.Add(grabInfo.Grabber);
}
}
}
return grabbers != null && grabbers.Count > 0;
}
/// <summary>
/// Gets the grabbers that are grabbing the object using a specific grab point.
/// </summary>
/// <param name="grabbableObject">The grabbable object</param>
/// <param name="point">The grab point or -1 to get all grabbed points</param>
/// <returns>List of grabbers</returns>
public IEnumerable<UxrGrabber> GetGrabbingHands(UxrGrabbableObject grabbableObject, int point = -1)
{
foreach (RuntimeGrabInfo grabInfo in GetGrabs(grabbableObject))
{
if (grabInfo.GrabbedPoint == point || point == -1)
{
yield return grabInfo.Grabber;
}
}
}
/// <summary>
/// Gets the grab point that the <see cref="UxrGrabber" /> is currently grabbing on a <see cref="UxrGrabbableObject" />
/// .
/// </summary>
/// <param name="grabber">Grabber to get the grabbed point from</param>
/// <returns>Grab point index that is being grabbed or -1 if there is no object currently being grabbed</returns>
public int GetGrabbedPoint(UxrGrabber grabber)
{
if (grabber && grabber.GrabbedObject != null && _currentManipulations.TryGetValue(grabber.GrabbedObject, out RuntimeManipulationInfo manipulationInfo))
{
foreach (RuntimeGrabInfo grabInfo in manipulationInfo.Grabs)
{
if (grabInfo.Grabber == grabber)
{
return grabInfo.GrabbedPoint;
}
}
}
return -1;
}
/// <summary>
/// Gets the number of grab points that are currently being grabbed from a <see cref="UxrGrabbableObject" />.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <returns>Number of grab points being grabbed</returns>
public int GetGrabbedPointCount(UxrGrabbableObject grabbableObject)
{
if (_currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo))
{
return manipulationInfo.Grabs.Count;
}
return 0;
}
/// <summary>
/// Gets the number of hands that are grabbing the given object.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="includeChildGrabs">Whether to also count dependent child grabbable objects that are being grabbed</param>
/// <returns>Number of hands grabbing the object</returns>
public int GetGrabbingHandCount(UxrGrabbableObject grabbableObject, bool includeChildGrabs = true)
{
if (_currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo))
{
return manipulationInfo.Grabs.Count + (includeChildGrabs ? GetChildrenBeingGrabbed(grabbableObject).Count() : 0);
}
return 0;
}
/// <summary>
/// Gets the <see cref="UxrGrabbableObjectAnchor" /> where the given <see cref="UxrGrabbableObject" /> was grabbed
/// from.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <returns>Anchor the grabbable object was grabbed from</returns>
public UxrGrabbableObjectAnchor GetGrabbedObjectAnchorFrom(UxrGrabbableObject grabbableObject)
{
if (_currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo))
{
return manipulationInfo.SourceAnchor;
}
return null;
}
/// <summary>
/// Gets the current world-space velocity, in units per second, of an object that is being grabbed.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="smooth">Whether to smooth the velocity using a previous frame data window for improved behavior</param>
/// <returns>Velocity in world-space units per second</returns>
public Vector3 GetGrabbedObjectVelocity(UxrGrabbableObject grabbableObject, bool smooth = true)
{
if (_currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo) && manipulationInfo.Grabs.Any())
{
return smooth ? manipulationInfo.Grabs[0].Grabber.SmoothVelocity : manipulationInfo.Grabs[0].Grabber.Velocity;
}
return Vector3.zero;
}
/// <summary>
/// Gets the current world-space angular velocity, in degrees per second, of an object that is being grabbed.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="smooth">Whether to smooth the velocity using a previous frame data window for improved behavior</param>
/// <returns>Angular velocity in world-space euler angle degrees per second</returns>
public Vector3 GetGrabbedObjectAngularVelocity(UxrGrabbableObject grabbableObject, bool smooth = true)
{
if (_currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo) && manipulationInfo.Grabs.Any())
{
return smooth ? manipulationInfo.Grabs[0].Grabber.SmoothAngularVelocity : manipulationInfo.Grabs[0].Grabber.AngularVelocity;
}
return Vector3.zero;
}
#endregion
#region Private Methods
/// <summary>
/// Tries to get the grab information of a specific <see cref="UxrGrabber" />.
/// </summary>
/// <param name="grabber">Grabber to get the grab information from</param>
/// <param name="grabInfo">Returns the grab information</param>
/// <returns>True if successful, false if the grabber is not valid or isn't grabbing any object</returns>
private bool TryGetGrabInfo(UxrGrabber grabber, out RuntimeGrabInfo grabInfo)
{
grabInfo = null;
if (grabber && grabber.GrabbedObject && _currentManipulations.TryGetValue(grabber.GrabbedObject, out RuntimeManipulationInfo manipulationInfo))
{
grabInfo = manipulationInfo.GetGrabInfo(grabber);
return true;
}
return false;
}
/// <summary>
/// Enumerates all grabs on the given grabbable object.
/// </summary>
/// <param name="grabbableObject">Grabbable object to get the grab information from</param>
/// <returns>List of grabs</returns>
private IEnumerable<RuntimeGrabInfo> GetGrabs(UxrGrabbableObject grabbableObject)
{
if (grabbableObject != null && _currentManipulations.TryGetValue(grabbableObject, out RuntimeManipulationInfo manipulationInfo))
{
foreach (RuntimeGrabInfo grabInfo in manipulationInfo.Grabs)
{
yield return grabInfo;
}
}
}
/// <summary>
/// Gets the parent being grabbed of a given <see cref="UxrGrabbableObject" />.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <returns>Parent <see cref="UxrGrabbableObject" /> that is being grabbed or null if there isn't any</returns>
private UxrGrabbableObject GetParentBeingGrabbed(UxrGrabbableObject grabbableObject)
{
if (grabbableObject != null && grabbableObject.transform.parent != null)
{
UxrGrabbableObject parentGrabbableObject = grabbableObject.transform.parent.GetComponentInParent<UxrGrabbableObject>();
if (parentGrabbableObject != null)
{
if (IsBeingGrabbed(parentGrabbableObject))
{
return parentGrabbableObject;
}
return GetParentBeingGrabbed(parentGrabbableObject);
}
}
return null;
}
/// <summary>
/// Gets the chain of parents of a given <see cref="UxrGrabbableObject" /> that are being grabbed in bottom to top
/// hierarchical order.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <returns>Chain of parents being grabbed in bottom to top hierarchical order</returns>
private IEnumerable<UxrGrabbableObject> GetParentsBeingGrabbedChain(UxrGrabbableObject grabbableObject)
{
UxrGrabbableObject current = grabbableObject;
while (current != null)
{
current = GetParentBeingGrabbed(current);
if (current != null)
{
yield return current;
}
}
}
/// <summary>
/// Gets a <see cref="UxrGrabbableObject" />'s list of children being grabbed.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <returns>List of children that are being grabbed</returns>
private IEnumerable<UxrGrabbableObject> GetChildrenBeingGrabbed(UxrGrabbableObject grabbableObject)
{
if (grabbableObject == null)
{
yield break;
}
foreach (UxrGrabbableObject child in grabbableObject.AllChildren)
{
if (child != grabbableObject && _currentManipulations.ContainsKey(child))
{
yield return child;
}
}
}
/// <summary>
/// Gets a <see cref="UxrGrabbableObject" />'s list of direct grabbable children that are being grabbed
/// and control the direction of the grabbable object.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <returns>List of direct grabbable children that are being grabbed and control the grabbable object direction</returns>
private IEnumerable<UxrGrabbableObject> GetDirectChildrenLookAtBeingGrabbed(UxrGrabbableObject grabbableObject)
{
foreach (RuntimeManipulationInfo childManipulation in GetDirectChildrenLookAtManipulations(grabbableObject))
{
yield return childManipulation.GrabbableObject;
}
}
/// <summary>
/// Gets a <see cref="UxrGrabbableObject" />'s list of direct grabbable children manipulations that
/// control the direction of the grabbable object.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <returns>
/// List of manipulations of direct grabbable children that are being grabbed and control the grabbable object
/// direction
/// </returns>
private IEnumerable<RuntimeManipulationInfo> GetDirectChildrenLookAtManipulations(UxrGrabbableObject grabbableObject)
{
if (grabbableObject == null)
{
yield break;
}
foreach (UxrGrabbableObject child in grabbableObject.DirectChildrenLookAts)
{
if (_currentManipulations.TryGetValue(child, out RuntimeManipulationInfo childManipulationInfo))
{
yield return childManipulationInfo;
}
}
}
/// <summary>
/// Gets the world-space snap position of a given <see cref="UxrGrabber" /> grabbing the object.
/// Depending on the object's snap settings, this will be the grab point snap position (either
/// object to hand or hand to object) or the same grip position relative to the object when the
/// grab was performed.
/// </summary>
/// <param name="grabber">Grabber to get the snap position of</param>
/// <returns>Snap position or <see cref="Vector3.zero" /> if the grabber isn't currently grabbing an object</returns>
private Vector3 GetGrabbedPointGrabAlignPosition(UxrGrabber grabber)
{
if (grabber.GrabbedObject == null || !_currentManipulations.TryGetValue(grabber.GrabbedObject, out RuntimeManipulationInfo manipulationInfo) || !TryGetGrabInfo(grabber, out RuntimeGrabInfo grabInfo))
{
return Vector3.zero;
}
if (grabber.GrabbedObject.GetGrabPointSnapModeAffectsPosition(manipulationInfo.GetGrabbedPoint(grabber), UxrHandSnapDirection.ObjectToHand))
{
// Snap to grab point so that object goes to hand
return TransformExt.GetWorldPosition(grabInfo.GrabAlignParentTransformUsed, grabInfo.RelativeGrabAlignPosition);
}
if (grabber.GrabbedObject.GetGrabPointSnapModeAffectsPosition(manipulationInfo.GetGrabbedPoint(grabber), UxrHandSnapDirection.HandToObject))
{
// Snap to grab point so that hand goes to object
Transform snapTransform = grabber.GrabbedObject.GetGrabPointGrabAlignTransform(grabber.Avatar, manipulationInfo.GetGrabbedPoint(grabber), grabber.Side);
return snapTransform.TransformPoint(grabInfo.RelativeUsedGrabAlignPosition);
}
// Keep same grip position relative to the object as when it was grabbed
return grabber.GrabbedObject.transform.TransformPoint(GetGrabPointRelativeGrabberPosition(grabber));
}
/// <summary>
/// Gets the world-space snap rotation of a given <see cref="UxrGrabber" /> grabbing the object.
/// Depending on the object's snap settings, this will be the grab point snap rotation (either
/// object to hand or hand to object) or the same grip rotation relative to the object when the
/// grab was performed.
/// </summary>
/// <param name="grabber">Grabber to get the snap rotation of</param>
/// <returns>Snap rotation or <see cref="Quaternion.identity" /> if the grabber isn't currently grabbing an object</returns>
private Quaternion GetGrabbedPointGrabAlignRotation(UxrGrabber grabber)
{
if (grabber.GrabbedObject == null || !_currentManipulations.TryGetValue(grabber.GrabbedObject, out RuntimeManipulationInfo manipulationInfo) || !TryGetGrabInfo(grabber, out RuntimeGrabInfo grabInfo))
{
return Quaternion.identity;
}
if (grabber.GrabbedObject.GetGrabPointSnapModeAffectsRotation(manipulationInfo.GetGrabbedPoint(grabber), UxrHandSnapDirection.ObjectToHand))
{
// Snap to grab point so that object rotates to hand
return TransformExt.GetWorldRotation(grabInfo.GrabAlignParentTransformUsed, grabInfo.RelativeGrabAlignRotation);
}
if (grabber.GrabbedObject.GetGrabPointSnapModeAffectsRotation(manipulationInfo.GetGrabbedPoint(grabber), UxrHandSnapDirection.HandToObject))
{
// Snap to grab point so that hand rotates to object
Transform snapTransform = grabber.GrabbedObject.GetGrabPointGrabAlignTransform(grabber.Avatar, manipulationInfo.GetGrabbedPoint(grabber), grabber.Side);
return snapTransform.rotation * grabInfo.RelativeUsedGrabAlignRotation;
}
// Keep same grip rotation relative to the object as when it was grabbed
return grabber.GrabbedObject.transform.rotation * GetGrabPointRelativeGrabberRotation(grabber);
}
/// <summary>
/// Gets the position that is used to compute proximity from a <see cref="UxrGrabber" /> to the grabbed point.
/// </summary>
/// <param name="grabber">Grabber to get the proximity point for</param>
/// <returns>
/// Position required to compute the proximity to or <see cref="Vector3.zero" /> if the grabber isn't currently
/// grabbing an object
/// </returns>
private Vector3 GetGrabbedPointGrabProximityPosition(UxrGrabber grabber)
{
if (TryGetGrabInfo(grabber, out RuntimeGrabInfo grabInfo))
{
return grabber.GrabbedObject.transform.TransformPoint(grabInfo.RelativeProximityPosition);
}
return Vector3.zero;
}
/// <summary>
/// Gets the relative position of the object to the grabber at the time it was grabbed.
/// </summary>
/// <param name="grabber">Grabber with the object being grabbed</param>
/// <returns>
/// Relative position or <see cref="Vector3.zero" /> if the grabber isn't currently grabbing an object.
/// </returns>
private Vector3 GetGrabPointRelativeGrabPosition(UxrGrabber grabber)
{
if (TryGetGrabInfo(grabber, out RuntimeGrabInfo grabInfo))
{
return grabInfo.RelativeGrabPosition;
}
return Vector3.zero;
}
/// <summary>
/// Gets the relative rotation of the object to the grabber at the time it was grabbed.
/// </summary>
/// <param name="grabber">Grabber with the object being grabbed</param>
/// <returns>
/// Relative rotation or <see cref="Quaternion.identity" /> if the grabber isn't currently grabbing an object.
/// </returns>
private Quaternion GetGrabPointRelativeGrabRotation(UxrGrabber grabber)
{
if (TryGetGrabInfo(grabber, out RuntimeGrabInfo grabInfo))
{
return grabInfo.RelativeGrabRotation;
}
return Quaternion.identity;
}
/// <summary>
/// Gets the relative position of the grabber to the object it is grabbing at the time it was grabbed.
/// </summary>
/// <param name="grabber">Grabber with the object being grabbed</param>
/// <returns>
/// Relative position or <see cref="Vector3.zero" /> if the grabber isn't currently grabbing an object.
/// </returns>
private Vector3 GetGrabPointRelativeGrabberPosition(UxrGrabber grabber)
{
if (TryGetGrabInfo(grabber, out RuntimeGrabInfo grabInfo))
{
return grabInfo.RelativeGrabberPosition;
}
return Vector3.zero;
}
/// <summary>
/// Gets the relative rotation of the grabber to the object it is grabbing at the time it was grabbed.
/// </summary>
/// <param name="grabber">Grabber with the object being grabbed</param>
/// <returns>
/// Relative rotation or <see cref="Quaternion.identity" /> if the grabber isn't currently grabbing an object.
/// </returns>
private Quaternion GetGrabPointRelativeGrabberRotation(UxrGrabber grabber)
{
if (TryGetGrabInfo(grabber, out RuntimeGrabInfo grabInfo))
{
return grabInfo.RelativeGrabberRotation;
}
return Quaternion.identity;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 05b5882a83c34bcda86d39ab0eee3772
timeCreated: 1677407121

View File

@@ -0,0 +1,563 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabManager.RuntimeGrabInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Serialization;
using UltimateXR.Core.StateSave;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Manipulation
{
public partial class UxrGrabManager
{
#region Private Types & Data
/// <summary>
/// Stores information of a grab performed on an object.
/// </summary>
/// <remarks>
/// Implements <see cref="IUxrSerializable" /> to help <see cref="UxrGrabManager" />'s implementation of the
/// <see cref="IUxrStateSave" /> interface (<see cref="UxrGrabManager.SerializeState" />).
/// </remarks>
private sealed class RuntimeGrabInfo : IUxrSerializable
{
#region Public Types & Data
/// <summary>
/// Gets the grabber grabbing the <see cref="UxrGrabbableObject" />.
/// </summary>
public UxrGrabber Grabber
{
get => _grabber;
set => _grabber = value;
}
/// <summary>
/// Gets the <see cref="UxrGrabbableObject" /> grabbed point.
/// </summary>
public int GrabbedPoint
{
get => _grabbedPoint;
set => _grabbedPoint = value;
}
// *************************************************************************************************************************
// Transform information about the grip.
// *************************************************************************************************************************
/// <summary>
/// Gets or sets the <see cref="UxrGrabbableObject" /> rotation relative to the <see cref="UxrGrabber" /> at the moment
/// it was grabbed.
/// </summary>
public Quaternion RelativeGrabRotation
{
get => _relativeGrabRotation;
private set => _relativeGrabRotation = value;
}
/// <summary>
/// Gets or sets the <see cref="UxrGrabbableObject" /> position in local <see cref="UxrGrabber" /> space at the moment
/// it was grabbed.
/// </summary>
public Vector3 RelativeGrabPosition
{
get => _relativeGrabPosition;
private set => _relativeGrabPosition = value;
}
/// <summary>
/// Gets or sets the <see cref="UxrGrabber" /> rotation relative to the <see cref="UxrGrabbableObject" /> at the moment
/// it was grabbed.
/// </summary>
public Quaternion RelativeGrabberRotation
{
get => _relativeGrabberRotation;
private set => _relativeGrabberRotation = value;
}
/// <summary>
/// Gets or sets the <see cref="UxrGrabber" /> position in local <see cref="UxrGrabbableObject" /> space at the moment
/// it was grabbed.
/// </summary>
public Vector3 RelativeGrabberPosition
{
get => _relativeGrabberPosition;
private set => _relativeGrabberPosition = value;
}
/// <summary>
/// Gets or sets the transform relative to which <see cref="RelativeGrabAlignPosition" /> and
/// <see cref="RelativeGrabAlignRotation" /> are specified.
/// </summary>
public Transform GrabAlignParentTransformUsed
{
get => _grabAlignParentTransformUsed;
private set => _grabAlignParentTransformUsed = value;
}
/// <summary>
/// Gets or sets the snap rotation relative to the <see cref="GrabAlignParentTransformUsed" /> at the moment it was
/// grabbed.
/// </summary>
public Quaternion RelativeGrabAlignRotation
{
get => _relativeGrabAlignRotation;
private set => _relativeGrabAlignRotation = value;
}
/// <summary>
/// Gets or sets the snap position in local <see cref="GrabAlignParentTransformUsed" /> space at the moment it was
/// grabbed.
/// </summary>
public Vector3 RelativeGrabAlignPosition
{
get => _relativeGrabAlignPosition;
private set => _relativeGrabAlignPosition = value;
}
/// <summary>
/// Gets or sets the computed snap rotation relative to the object's snap rotation, which might be different if
/// the computed snap rotation came from an <see cref="UxrGrabPointShape" />.
/// </summary>
public Quaternion RelativeUsedGrabAlignRotation
{
get => _relativeUsedGrabAlignRotation;
private set => _relativeUsedGrabAlignRotation = value;
}
/// <summary>
/// Gets or sets the computed snap position relative to the object's snap position, which might be different if
/// the computed snap position came from an <see cref="UxrGrabPointShape" />.
/// </summary>
public Vector3 RelativeUsedGrabAlignPosition
{
get => _relativeUsedGrabAlignPosition;
private set => _relativeUsedGrabAlignPosition = value;
}
/// <summary>
/// Gets or sets the proximity rotation relative to the <see cref="UxrGrabbableObject" /> at the moment it was grabbed.
/// </summary>
public Vector3 RelativeProximityPosition
{
get => _relativeProximityPosition;
private set => _relativeProximityPosition = value;
}
/// <summary>
/// Gets or sets the source in local <see cref="UxrGrabber" /> coordinates where the source of leverage will be
/// computed for <see cref="UxrRotationProvider.HandPositionAroundPivot" /> manipulation. This will improve rotation
/// behaviour when the hands are rotated because otherwise the source of leverage is the grabber itself and rotating
/// the hand will keep the grabber more or less stationary.
/// </summary>
public Vector3 GrabberLocalLeverageSource
{
get => _grabberLocalLeverageSource;
private set => _grabberLocalLeverageSource = value;
}
/// <summary>
/// Gets or sets the leverage source <see cref="GrabberLocalLeverageSource" /> in local coordinates of the parent
/// transform of the grabbable at the moment the <see cref="UxrGrabbableObject" /> was grabbed.
/// </summary>
public Vector3 GrabberLocalParentLeverageSourceOnGrab
{
get => _grabberLocalParentLeverageSourceOnGrab;
private set => _grabberLocalParentLeverageSourceOnGrab = value;
}
/// <summary>
/// Gets or sets the leverage point in local coordinates that this child grabbable will use when the parent
/// grabbable rotation provider is HandPositionAroundPivot.
/// </summary>
public Vector3 ParentGrabbableLookAtLocalLeveragePoint
{
get => _parentGrabbableLookAtLocalLeveragePoint;
set => _parentGrabbableLookAtLocalLeveragePoint = value;
}
/// <summary>
/// Gets or sets the leverage point in local grabbable parent coordinates that this child grabbable will use
/// when the parent grabbable rotation provider is HandPositionAroundPivot.
/// </summary>
public Vector3 ParentGrabbableLookAtParentLeveragePoint
{
get => _parentGrabbableLookAtParentLeveragePoint;
set => _parentGrabbableLookAtParentLeveragePoint = value;
}
/// <summary>
/// Gets or sets the look-at contribution in world coordinates of this child grabbable object to the parent
/// grabbable look-at algorithm for the current frame. Only for HandPositionAroundPivot in parent grabbable objects.
/// </summary>
public Vector3 ParentGrabbableLeverageContribution
{
get => _parentGrabbableLeverageContribution;
set => _parentGrabbableLeverageContribution = value;
}
/// <summary>
/// Gets or sets the rotation contribution of this object to the parent grabbable look-at algorithm for the
/// current frame. Only for HandPositionAroundPivot in parent grabbable objects.
/// </summary>
public Quaternion ParentGrabbableLookAtRotationContribution
{
get => _parentGrabbableLookAtRotationContribution;
set => _parentGrabbableLookAtRotationContribution = value;
}
/// <summary>
/// Gets or sets the rotation angle contribution, in objects constrained to a single axis rotation, during the current
/// grab.
/// </summary>
public float SingleRotationAngleContribution
{
get => _singleRotationAngleContribution;
set => _singleRotationAngleContribution = value;
}
/// <summary>
/// Gets or sets the <see cref="SingleRotationAngleContribution" /> value the last time it was accumulated into
/// the object internal angle. This allows angle contributions to work using absolute values instead of delta values
/// to have better precision.
/// </summary>
public float LastAccumulatedAngle
{
get => _lastAccumulatedAngle;
set => _lastAccumulatedAngle = value;
}
// *************************************************************************************************************************
// Parent dependency information.
// *************************************************************************************************************************
/// <summary>
/// Gets the grab position in grabbable parent space before updating this object being grabbed. It is used to compute
/// the lookAt contribution of this grab on the parent, when the parent is being grabbed. The grab position
/// is computed before constraints are applied to the object to compute the contribution correctly.
/// </summary>
public Vector3 ParentLocalGrabPositionBeforeUpdate
{
get => _parentLocalGrabPositionBeforeUpdate;
set => _parentLocalGrabPositionBeforeUpdate = value;
}
/// <summary>
/// Gets the grab position in grabbable parent space after updating this object being grabbed. See
/// <see cref="ParentLocalGrabPositionBeforeUpdate" />.
/// </summary>
public Vector3 ParentLocalGrabPositionAfterUpdate
{
get => _parentLocalGrabPositionAfterUpdate;
set => _parentLocalGrabPositionAfterUpdate = value;
}
/// <summary>
/// Gets the grabbable parent position in local grabbable child space before updating the child being grabbed.
/// It is used to compute the contribution of a child on a parent when the parent is not being grabbed.
/// </summary>
public Vector3 ChildLocalParentPosition
{
get => _childLocalParentPosition;
set => _childLocalParentPosition = value;
}
/// <summary>
/// Gets the grabbable parent rotation in local grabbable child space before updating the child being grabbed.
/// It is used to compute the contribution of a child on a parent when the parent is not being grabbed.
/// </summary>
public Quaternion ChildLocalParentRotation
{
get => _childLocalParentRotation;
set => _childLocalParentRotation = value;
}
// *************************************************************************************************************************
// For smooth transitions from object to hand or object to target or hand to object where we want to avoid instant snapping.
// *************************************************************************************************************************
/// <summary>
/// Gets or sets the <see cref="UxrGrabbableObject" /> local position at the moment it was grabbed.
/// </summary>
public Vector3 LocalPositionOnGrab
{
get => _localPositionOnGrab;
private set => _localPositionOnGrab = value;
}
/// <summary>
/// Gets or sets the <see cref="UxrGrabbableObject" /> local rotation at the moment it was grabbed.
/// </summary>
public Quaternion LocalRotationOnGrab
{
get => _localRotationOnGrab;
private set => _localRotationOnGrab = value;
}
/// <summary>
/// Gets or sets the world-space snap position at the moment the <see cref="UxrGrabbableObject" /> was grabbed.
/// </summary>
public Vector3 AlignPositionOnGrab
{
get => _alignPositionOnGrab;
private set => _alignPositionOnGrab = value;
}
/// <summary>
/// Gets or sets the world-space snap rotation at the moment the <see cref="UxrGrabbableObject" /> was grabbed.
/// </summary>
public Quaternion AlignRotationOnGrab
{
get => _alignRotationOnGrab;
private set => _alignRotationOnGrab = value;
}
/// <summary>
/// Gets or sets the hand bone position in local avatar coordinates at the moment the <see cref="UxrGrabbableObject" />
/// was grabbed.
/// </summary>
public Vector3 HandBoneLocalAvatarPositionOnGrab
{
get => _handBoneLocalAvatarPositionOnGrab;
private set => _handBoneLocalAvatarPositionOnGrab = value;
}
/// <summary>
/// Gets or sets the hand bone rotation in local avatar coordinates at the moment the <see cref="UxrGrabbableObject" />
/// was grabbed.
/// </summary>
public Quaternion HandBoneLocalAvatarRotationOnGrab
{
get => _handBoneLocalAvatarRotationOnGrab;
private set => _handBoneLocalAvatarRotationOnGrab = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="grabber">The grabber</param>
/// <param name="grabbedPoint">The grabbed point</param>
public RuntimeGrabInfo(UxrGrabber grabber, int grabbedPoint)
{
Grabber = grabber;
GrabbedPoint = grabbedPoint;
}
/// <summary>
/// Default constructor required for serialization.
/// </summary>
private RuntimeGrabInfo()
{
}
#endregion
#region Implicit IUxrSerializable
/// <inheritdoc />
public int SerializationVersion => 0;
/// <inheritdoc />
public void Serialize(IUxrSerializer serializer, int serializationVersion)
{
serializer.SerializeUniqueComponent(ref _grabber);
serializer.Serialize(ref _grabbedPoint);
serializer.Serialize(ref _relativeGrabRotation);
serializer.Serialize(ref _relativeGrabPosition);
serializer.Serialize(ref _relativeGrabberRotation);
serializer.Serialize(ref _relativeGrabberPosition);
// Trick to be able to restore _grabAlignParentTransformUsed because we can't serialize a reference without IUxrUniqueID
{
serializer.SerializeUniqueComponent(ref _grabbableObject);
if (serializer.IsReading && _grabbableObject)
{
Transform grabAlignTransform = _grabbableObject.GetGrabPointGrabAlignTransform(_grabber.Avatar, _grabbedPoint, _grabber.Side);
GrabAlignParentTransformUsed = grabAlignTransform == _grabbableObject.transform ? _grabbableObject.transform : grabAlignTransform.parent;
}
}
serializer.Serialize(ref _relativeGrabAlignRotation);
serializer.Serialize(ref _relativeGrabAlignPosition);
serializer.Serialize(ref _relativeUsedGrabAlignRotation);
serializer.Serialize(ref _relativeUsedGrabAlignPosition);
serializer.Serialize(ref _relativeProximityPosition);
serializer.Serialize(ref _grabberLocalLeverageSource);
serializer.Serialize(ref _grabberLocalParentLeverageSourceOnGrab);
serializer.Serialize(ref _parentGrabbableLookAtLocalLeveragePoint);
serializer.Serialize(ref _parentGrabbableLookAtParentLeveragePoint);
serializer.Serialize(ref _parentGrabbableLeverageContribution);
serializer.Serialize(ref _parentGrabbableLookAtRotationContribution);
serializer.Serialize(ref _singleRotationAngleContribution);
serializer.Serialize(ref _lastAccumulatedAngle);
serializer.Serialize(ref _parentLocalGrabPositionBeforeUpdate);
serializer.Serialize(ref _parentLocalGrabPositionAfterUpdate);
serializer.Serialize(ref _childLocalParentPosition);
serializer.Serialize(ref _childLocalParentRotation);
serializer.Serialize(ref _localPositionOnGrab);
serializer.Serialize(ref _localRotationOnGrab);
serializer.Serialize(ref _alignPositionOnGrab);
serializer.Serialize(ref _alignRotationOnGrab);
serializer.Serialize(ref _handBoneLocalAvatarPositionOnGrab);
serializer.Serialize(ref _handBoneLocalAvatarRotationOnGrab);
}
#endregion
#region Public Methods
/// <summary>
/// Computes the grab information.
/// </summary>
/// <param name="grabber">Grabber responsible for grabbing the object</param>
/// <param name="grabbableObject">The object being grabbed</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>
/// <param name="sourceGrabEventArgs">
/// If non-null, the grab will use the information on the event to ensure that
/// it is performed in exactly the same way. This is used in multi-player environments.
/// </param>
public void Compute(UxrGrabber grabber, UxrGrabbableObject grabbableObject, int grabPoint, Vector3 snapPosition, Quaternion snapRotation, UxrManipulationEventArgs sourceGrabEventArgs = null)
{
Transform grabAlignTransform = grabbableObject.GetGrabPointGrabAlignTransform(grabber.Avatar, grabPoint, grabber.Side);
Vector3 originalPosition = grabbableObject.transform.position;
Quaternion originalRotation = grabbableObject.transform.rotation;
if (sourceGrabEventArgs != null)
{
// Grab is synchronizing with external grab. Position object momentarily in the exact same relative position with the grabber as the source external data.
grabbableObject.transform.position = grabber.transform.TransformPoint(sourceGrabEventArgs.GrabberLocalObjectPosition);
grabbableObject.transform.rotation = grabber.transform.rotation * sourceGrabEventArgs.GrabberLocalObjectRotation;
}
// Update snap position/orientation if it's an external grab, to keep it in sync with exactly the same grip
if (sourceGrabEventArgs != null)
{
snapPosition = grabber.transform.TransformPoint(sourceGrabEventArgs.GrabberLocalSnapPosition);
snapRotation = grabber.transform.rotation * sourceGrabEventArgs.GrabberLocalSnapRotation;
}
Matrix4x4 snapMatrix = Matrix4x4.TRS(snapPosition, snapRotation, grabAlignTransform.lossyScale);
Vector3 localProximity = grabAlignTransform.InverseTransformPoint(grabbableObject.GetGrabPointGrabProximityTransform(grabber, grabPoint).position);
RelativeGrabRotation = Quaternion.Inverse(grabber.transform.rotation) * grabbableObject.transform.rotation;
RelativeGrabPosition = grabber.transform.InverseTransformPoint(grabbableObject.transform.position);
RelativeGrabberRotation = Quaternion.Inverse(grabbableObject.transform.rotation) * grabber.transform.rotation;
RelativeGrabberPosition = grabbableObject.transform.InverseTransformPoint(grabber.transform.position);
GrabAlignParentTransformUsed = grabAlignTransform == grabbableObject.transform ? grabbableObject.transform : grabAlignTransform.parent;
RelativeGrabAlignPosition = TransformExt.GetLocalPosition(GrabAlignParentTransformUsed, snapPosition);
RelativeGrabAlignRotation = TransformExt.GetLocalRotation(GrabAlignParentTransformUsed, snapRotation);
RelativeUsedGrabAlignRotation = Quaternion.Inverse(grabAlignTransform.rotation) * snapRotation;
RelativeUsedGrabAlignPosition = grabAlignTransform.InverseTransformPoint(snapPosition);
RelativeProximityPosition = grabbableObject.transform.InverseTransformPoint(snapMatrix.MultiplyPoint(localProximity));
GrabberLocalLeverageSource = Vector3.zero;
grabbableObject.CheckComputeAutoRotationProvider(snapPosition);
if (grabbableObject.RotationProvider == UxrRotationProvider.HandPositionAroundPivot && grabbableObject.GetGrabPointSnapModeAffectsRotation(grabPoint))
{
// Check if the leverage is provided by the inner side of the palm (where the thumb is) or the outer side.
// We do that by checking the difference in distance of both to the rotation pivot. If it is above a threshold, it is provided by either one of the two.
// If it is below a threshold it is provide by the grabber itself.
float separation = UxrConstants.Hand.HandWidth;
float distanceInner = Vector3.Distance(grabbableObject.transform.position, snapPosition + snapRotation * grabber.LocalPalmThumbDirection * (separation * 0.5f));
float distanceOuter = Vector3.Distance(grabbableObject.transform.position, snapPosition - snapRotation * grabber.LocalPalmThumbDirection * (separation * 0.5f));
if (Mathf.Abs(distanceInner - distanceOuter) > separation * 0.5f)
{
GrabberLocalLeverageSource = grabber.LocalPalmThumbDirection * (separation * 0.5f * (distanceInner > distanceOuter ? 1.0f : -1.0f));
}
}
GrabberLocalParentLeverageSourceOnGrab = TransformExt.GetLocalPosition(grabbableObject.transform.parent, grabber.transform.TransformPoint(GrabberLocalLeverageSource));
LocalPositionOnGrab = grabbableObject.transform.localPosition;
LocalRotationOnGrab = grabbableObject.transform.localRotation;
AlignPositionOnGrab = snapPosition;
AlignRotationOnGrab = snapRotation;
HandBoneLocalAvatarPositionOnGrab = grabber.Avatar.transform.InverseTransformPoint(grabber.HandBone.position);
HandBoneLocalAvatarRotationOnGrab = Quaternion.Inverse(grabber.Avatar.transform.rotation) * grabber.HandBone.rotation;
if (grabbableObject.UsesGrabbableParentDependency && grabbableObject.ControlParentDirection)
{
// Compute leverage point in local grabbable parent coordinates when parent is rotated using the children.
// We will use the largest vector of these two: (leverage point, local child position).
Vector3 localParentLeveragePosition = grabbableObject.GrabbableParent.transform.InverseTransformPoint(grabber.transform.TransformPoint(GrabberLocalLeverageSource));
Vector3 localParentChildPosition = grabbableObject.GrabbableParent.transform.InverseTransformPoint(grabbableObject.transform.position);
bool useLeveragePosition = localParentLeveragePosition.magnitude > localParentChildPosition.magnitude;
ParentGrabbableLookAtParentLeveragePoint = useLeveragePosition ? localParentLeveragePosition : localParentChildPosition;
ParentGrabbableLookAtLocalLeveragePoint = useLeveragePosition ? grabbableObject.transform.InverseTransformPoint(grabber.transform.TransformPoint(GrabberLocalLeverageSource)) : Vector3.zero;
}
SingleRotationAngleContribution = 0.0f;
LastAccumulatedAngle = 0.0f;
if (sourceGrabEventArgs != null)
{
// Place back again.
grabbableObject.transform.position = originalPosition;
grabbableObject.transform.rotation = originalRotation;
}
// Additional help for serialization
_grabbableObject = grabbableObject;
}
#endregion
#region Private Types & Data
private UxrGrabber _grabber;
private int _grabbedPoint;
private Quaternion _relativeGrabRotation;
private Vector3 _relativeGrabPosition;
private Quaternion _relativeGrabberRotation;
private Vector3 _relativeGrabberPosition;
private Transform _grabAlignParentTransformUsed;
private Quaternion _relativeGrabAlignRotation;
private Vector3 _relativeGrabAlignPosition;
private Quaternion _relativeUsedGrabAlignRotation;
private Vector3 _relativeUsedGrabAlignPosition;
private Vector3 _relativeProximityPosition;
private Vector3 _grabberLocalLeverageSource;
private Vector3 _grabberLocalParentLeverageSourceOnGrab;
private Vector3 _parentGrabbableLookAtLocalLeveragePoint;
private Vector3 _parentGrabbableLookAtParentLeveragePoint;
private Vector3 _parentGrabbableLeverageContribution;
private Quaternion _parentGrabbableLookAtRotationContribution;
private float _singleRotationAngleContribution;
private float _lastAccumulatedAngle;
private Vector3 _parentLocalGrabPositionBeforeUpdate;
private Vector3 _parentLocalGrabPositionAfterUpdate;
private Vector3 _childLocalParentPosition;
private Quaternion _childLocalParentRotation;
private Vector3 _localPositionOnGrab;
private Quaternion _localRotationOnGrab;
private Vector3 _alignPositionOnGrab;
private Quaternion _alignRotationOnGrab;
private Vector3 _handBoneLocalAvatarPositionOnGrab;
private Quaternion _handBoneLocalAvatarRotationOnGrab;
// To be able to retrieve _grabAlignParentTransformUsed when serializing, because it doesn't have any way to serialize the reference:
private UxrGrabbableObject _grabbableObject;
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b3608ac3c02642aa8d90a0cdd133d495
timeCreated: 1677343619

View File

@@ -0,0 +1,445 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabManager.RuntimeManipulationInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core.Serialization;
using UltimateXR.Extensions.System.Math;
using UnityEngine;
namespace UltimateXR.Manipulation
{
public partial class UxrGrabManager
{
#region Private Types & Data
/// <summary>
/// Stores information of grabs performed on a <see cref="UxrGrabbableObject" /> at runtime.
/// An object being manipulated can have multiple grabs, registered in <see cref="Grabs" />.
/// </summary>
/// <remarks>
/// Implements <see cref="IUxrSerializable" /> to help <see cref="UxrGrabManager" />'s implementation of the
/// <see cref="IUxrStateSave" /> interface (<see cref="UxrGrabManager.SerializeState" />).
/// </remarks>
[Serializable]
private sealed class RuntimeManipulationInfo : IUxrSerializable
{
#region Public Types & Data
/// <summary>
/// Gets the grabbers currently manipulating the object.
/// </summary>
public IEnumerable<UxrGrabber> Grabbers
{
get
{
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
yield return grabInfo.Grabber;
}
}
}
/// <summary>
/// Gets the points currently being grabbed on the object.
/// </summary>
public IEnumerable<int> GrabbedPoints
{
get
{
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
yield return grabInfo.GrabbedPoint;
}
}
}
/// <summary>
/// Gets the current grabs manipulating the object.
/// </summary>
public List<RuntimeGrabInfo> Grabs => _grabs;
/// <summary>
/// Gets the target from where the <see cref="UxrGrabbableObject" /> was grabbed.
/// </summary>
public UxrGrabbableObjectAnchor SourceAnchor => _sourceAnchor;
/// <summary>
/// Gets the current rotation angle, in objects constrained to a single rotation axis, contributed by all the grabbers
/// manipulating the object.
/// </summary>
public float CurrentSingleRotationAngleContributions
{
get
{
float accumulation = 0.0f;
int contributionCount = 0;
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
accumulation += grabInfo.SingleRotationAngleContribution - grabInfo.LastAccumulatedAngle;
contributionCount++;
}
if (contributionCount > 1)
{
accumulation /= contributionCount;
}
return accumulation;
}
}
/// <summary>
/// Gets the grabbed object.
/// </summary>
public UxrGrabbableObject GrabbableObject => _grabbableObject;
/// <summary>
/// Gets the rotation pivot when child grabbable objects manipulate this object's orientation.
/// </summary>
public Vector3 LocalManipulationRotationPivot
{
get => _localManipulationRotationPivot;
set => _localManipulationRotationPivot = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="grabber">Grabber of the grab</param>
/// <param name="grabPoint">Grab point index of the <see cref="UxrGrabbableObject" /> that was grabbed.</param>
/// <param name="sourceAnchor">Target if the grabbed object was placed on any.</param>
public RuntimeManipulationInfo(UxrGrabber grabber, int grabPoint, UxrGrabbableObjectAnchor sourceAnchor = null)
{
Grabs.Add(new RuntimeGrabInfo(grabber, grabPoint));
_grabbableObject = grabber.GrabbedObject;
_sourceAnchor = sourceAnchor;
}
/// <summary>
/// Default constructor required for serialization.
/// </summary>
private RuntimeManipulationInfo()
{
}
#endregion
#region Implicit IUxrSerializable
/// <inheritdoc />
public int SerializationVersion => 0;
/// <inheritdoc />
public void Serialize(IUxrSerializer serializer, int serializationVersion)
{
serializer.Serialize(ref _grabs);
serializer.SerializeUniqueComponent(ref _sourceAnchor);
serializer.SerializeUniqueComponent(ref _grabbableObject);
serializer.Serialize(ref _localManipulationRotationPivot);
}
#endregion
#region Public Methods
/// <summary>
/// Registers a new grab.
/// </summary>
/// <param name="grabber">Grabber that performed the grab</param>
/// <param name="grabPoint">The point of the <see cref="UxrGrabbableObject" /> that was grabbed.</param>
/// <param name="append">
/// Whether to append or insert at the beginning. If there is more than one grab point and none of
/// them is the 0 index (main grab), the main grab will be the first one in the list.
/// </param>
/// <returns>The newly created grab info entry</returns>
public RuntimeGrabInfo RegisterNewGrab(UxrGrabber grabber, int grabPoint, bool append = true)
{
RuntimeGrabInfo runtimeGrabInfo = new RuntimeGrabInfo(grabber, grabPoint);
if (append)
{
Grabs.Add(runtimeGrabInfo);
}
else
{
Grabs.Insert(0, runtimeGrabInfo);
}
return runtimeGrabInfo;
}
/// <summary>
/// Removes a grab.
/// </summary>
/// <param name="grabber">Grabber that released the grab</param>
public void RemoveGrab(UxrGrabber grabber)
{
Grabs.RemoveAll(g => g.Grabber == grabber);
}
/// <summary>
/// Removes all grabs registered.
/// </summary>
public void RemoveAll()
{
Grabs.Clear();
}
/// <summary>
/// Notifies a the start of a new grab, in order to compute all required data.
/// </summary>
/// <param name="grabber">Grabber responsible for grabbing the object</param>
/// <param name="grabbableObject">The object being grabbed</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>
/// <param name="sourceGrabEventArgs">
/// If non-null, the grab will use the information on the event to ensure that
/// it is performed in exactly the same way. This is used in multi-player environments.
/// </param>
/// <returns>Grab information</returns>
public RuntimeGrabInfo NotifyBeginGrab(UxrGrabber grabber, UxrGrabbableObject grabbableObject, int grabPoint, Vector3 snapPosition, Quaternion snapRotation, UxrManipulationEventArgs sourceGrabEventArgs = null)
{
RuntimeGrabInfo grabInfo = GetGrabInfo(grabber);
if (grabInfo == null)
{
grabInfo = RegisterNewGrab(grabber, grabPoint);
}
// If it's an object constrained to a single rotation axis, accumulate current contributions first
AccumulateSingleRotationAngle(grabbableObject);
// Compute data
grabbableObject.NotifyBeginGrab(grabber, grabPoint, snapPosition, snapRotation);
grabInfo.Compute(grabber, grabbableObject, grabPoint, snapPosition, snapRotation, sourceGrabEventArgs);
// Smooth transitions
if (Instance.Features.HasFlag(UxrManipulationFeatures.SmoothTransitions))
{
grabbableObject.StartSmoothManipulationTransition();
grabber.StartSmoothManipulationTransition();
if (grabbableObject.GrabbableParent != null && grabbableObject.UsesGrabbableParentDependency && grabbableObject.ControlParentDirection)
{
grabbableObject.GrabbableParent.StartSmoothManipulationTransition();
}
}
return grabInfo;
}
/// <summary>
/// Notifies the end of a grab.
/// </summary>
/// <param name="grabber">Grabber that released the object</param>
/// <param name="grabbableObject">Object that was released</param>
/// <param name="grabPoint">Grab point that was released</param>
public void NotifyEndGrab(UxrGrabber grabber, UxrGrabbableObject grabbableObject, int grabPoint)
{
// If it's an object constrained to a single rotation axis, accumulate current contributions first
AccumulateSingleRotationAngle(grabbableObject);
// Notify object
grabbableObject.NotifyEndGrab(grabber, grabPoint);
// Smooth transitions
if (Instance.Features.HasFlag(UxrManipulationFeatures.SmoothTransitions))
{
grabbableObject.StartSmoothManipulationTransition();
grabber.StartSmoothManipulationTransition();
if (grabbableObject.GrabbableParent != null && grabbableObject.UsesGrabbableParentDependency && grabbableObject.ControlParentDirection)
{
grabbableObject.GrabbableParent.StartSmoothManipulationTransition();
}
}
}
/// <summary>
/// Gets the grab information of a specific grabber.
/// </summary>
/// <param name="grabber">Grabber</param>
/// <returns>Grab info or null if not found</returns>
public RuntimeGrabInfo GetGrabInfo(UxrGrabber grabber)
{
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
if (grabInfo.Grabber == grabber)
{
return grabInfo;
}
}
return null;
}
/// <summary>
/// Gets the point grabbed by the given grabber.
/// </summary>
/// <param name="grabber">Grabber</param>
/// <returns>Grabbed point in <see cref="UxrGrabbableObject" /> or -1 if not found</returns>
public int GetGrabbedPoint(UxrGrabber grabber)
{
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
if (grabInfo.Grabber == grabber)
{
return grabInfo.GrabbedPoint;
}
}
return -1;
}
/// <summary>
/// Gets the grabber grabbing the given point.
/// </summary>
/// <param name="grabPoint">Grab point in <see cref="UxrGrabbableObject" /></param>
/// <returns>Grabber grabbing the given point or null if not found</returns>
public UxrGrabber GetGrabberGrabbingPoint(int grabPoint)
{
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
if (grabInfo.GrabbedPoint == grabPoint)
{
return grabInfo.Grabber;
}
}
return null;
}
/// <summary>
/// Checks if the given grabber is being used to manipulate the object.
/// </summary>
/// <param name="grabber">Grabber to check</param>
/// <returns>Whether the given grabber is being used to manipulate the object</returns>
public bool IsGrabberUsed(UxrGrabber grabber)
{
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
if (grabInfo.Grabber == grabber)
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if the given grab point is being grabbed on the object.
/// </summary>
/// <param name="grabPoint">Grab point to check</param>
/// <returns>Whether the given grab point is being grabbed on the object</returns>
public bool IsPointGrabbed(int grabPoint)
{
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
if (grabInfo.GrabbedPoint == grabPoint)
{
return true;
}
}
return false;
}
/// <summary>
/// Registers a grabber swap to indicate that a different hand is now grabbing the point.
/// </summary>
/// <param name="oldGrabber">Old grabber that was grabbing</param>
/// <param name="newGrabber">New grabber that the grab switched to</param>
public void SwapGrabber(UxrGrabber oldGrabber, UxrGrabber newGrabber)
{
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
if (grabInfo.Grabber == oldGrabber)
{
grabInfo.Grabber = newGrabber;
return;
}
}
}
/// <summary>
/// Registers a grabber swap to indicate that a different hand is now grabbing another point.
/// </summary>
/// <param name="oldGrabber">Old grabber that was grabbing</param>
/// <param name="oldGrabPoint">Old grab point of the <see cref="UxrGrabbableObject" /> grabbed by the old grabber</param>
/// <param name="newGrabber">New grabber that the grab switched to</param>
/// <param name="newGrabPoint">New grab point of the <see cref="UxrGrabbableObject" /> the grab switched to</param>
public void SwapGrabber(UxrGrabber oldGrabber, int oldGrabPoint, UxrGrabber newGrabber, int newGrabPoint)
{
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
if (grabInfo.Grabber == oldGrabber)
{
grabInfo.Grabber = newGrabber;
grabInfo.GrabbedPoint = newGrabPoint;
return;
}
}
}
/// <summary>
/// Accumulates the contributions of all grabbers in an object constrained to a single angle rotation.
/// </summary>
public void AccumulateSingleRotationAngle(UxrGrabbableObject grabbableObject)
{
int singleRotationAxisIndex = grabbableObject.SingleRotationAxisIndex;
if (singleRotationAxisIndex == -1)
{
return;
}
float accumulation = 0.0f;
int contributionCount = 0;
foreach (RuntimeGrabInfo grabInfo in Grabs)
{
accumulation += grabInfo.SingleRotationAngleContribution - grabInfo.LastAccumulatedAngle;
grabInfo.LastAccumulatedAngle = grabInfo.SingleRotationAngleContribution;
contributionCount++;
}
if (contributionCount > 0)
{
accumulation /= contributionCount;
grabbableObject.SingleRotationAngleCumulative = (grabbableObject.SingleRotationAngleCumulative + accumulation).Clamped(grabbableObject.RotationAngleLimitsMin[singleRotationAxisIndex],
grabbableObject.RotationAngleLimitsMax[singleRotationAxisIndex]);
}
}
#endregion
#region Private Types & Data
private List<RuntimeGrabInfo> _grabs = new List<RuntimeGrabInfo>();
private UxrGrabbableObjectAnchor _sourceAnchor;
private UxrGrabbableObject _grabbableObject;
private Vector3 _localManipulationRotationPivot;
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 82ee23bcf0d63fc479a4229506949086
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabManager.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
namespace UltimateXR.Manipulation
{
public partial class UxrGrabManager
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
// Manipulations are already handled through events, we don't serialize them in incremental changes
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
// We don't want to compare dictionaries, we save the manipulations info always by using null as name to avoid overhead.
SerializeStateValue(level, options, null, ref _currentManipulations);
}
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e159c4422a8ab5441a9bef231f06f8ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 25d17f0adba76654ea7135bc31b42b79
timeCreated: 1493366419
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabMode.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Manipulation
{
/// <summary>
/// What controller input we need to grab and release.
/// </summary>
public enum UxrGrabMode
{
/// <summary>
/// Object is grabbed while the grab button is pressed.
/// </summary>
GrabWhilePressed,
/// <summary>
/// One click on the grab button to grab, and another click to release it.
/// </summary>
GrabToggle,
/// <summary>
/// Object will keep being grabbed. It can be released manually through <see cref="UxrGrabbableObject.ReleaseGrabs" />.
/// </summary>
GrabAndKeepAlways
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4334c8f6f8d646af8dcee070b1e9d8a7
timeCreated: 1643287323

View File

@@ -0,0 +1,119 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabPointIndex.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Enables nicer formatting of the grab point this shape is bound to in the editor. It will show strings like Main,
/// Additional 0,
/// Additional 1... etc. because there is an CustomPropertyDrawer for this class (see GrabPointIndexDrawer).
/// </summary>
[Serializable]
public class UxrGrabPointIndex
{
#region Inspector Properties/Serialized Fields
[SerializeField] private int _index;
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="index">Grab point index</param>
public UxrGrabPointIndex(int index)
{
_index = index;
}
#endregion
#region Public Methods
/// <summary>
/// Gets the display name of a given grabbable object index.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="index">Grab point index</param>
/// <returns>Display name</returns>
public static string GetIndexDisplayName(UxrGrabbableObject grabbableObject, int index)
{
// If we have a custom name set up in the editor, use it
UxrGrabPointInfo grabPointInfo = grabbableObject.GetGrabPoint(index);
if (grabPointInfo != null && !string.IsNullOrEmpty(grabPointInfo.EditorName))
{
return grabPointInfo.EditorName;
}
// Use better formatted default name
if (index == 0)
{
return MainGrabPointName;
}
return AdditionalGrabPointPrefix + " " + (index - 1);
}
/// <summary>
/// Gets the grab point index of a given display name.
/// </summary>
/// <param name="name">Display name to get the index for</param>
/// <returns>Grab point index</returns>
public static int GetIndexFromDisplayName(string name)
{
if (name == MainGrabPointName)
{
return 0;
}
if (name.StartsWith(AdditionalGrabPointPrefix))
{
return int.Parse(name.Remove(0, AdditionalGrabPointPrefix.Length + 1)) + 1;
}
return -1;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Converts from <see cref="UxrGrabPointIndex" /> to integer.
/// </summary>
/// <param name="grabPointIndex">Grab point index object</param>
/// <returns>Integer</returns>
public static implicit operator int(UxrGrabPointIndex grabPointIndex)
{
return grabPointIndex._index;
}
/// <summary>
/// Converts from integer to <see cref="UxrGrabPointIndex" />.
/// </summary>
/// <param name="index">Grab point index</param>
/// <returns>Grab point index object</returns>
public static implicit operator UxrGrabPointIndex(int index)
{
return new UxrGrabPointIndex(index);
}
#endregion
#region Private Types & Data
private const string MainGrabPointName = "Main Grab Point";
private const string AdditionalGrabPointPrefix = "Additional Grab Point";
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5f0f83509c4a40c8bc7289cc2afc553e
timeCreated: 1647620152

View File

@@ -0,0 +1,410 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabPointInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Devices;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Defines a <see cref="UxrGrabbableObject" /> grab point. A grab point describes a point of an object which
/// can be grabbed. Objects can have multiple grab points to allow it to be grabbed from different angles.
/// Grab points can be further expanded by using a <see cref="UxrGrabPointShape" />, which gives flexibility
/// by allowing it to be grabbed around or along an axis passing through that point, for example.
/// </summary>
[Serializable]
public class UxrGrabPointInfo
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _editorFoldout = true;
[SerializeField] private string _editorName = "";
[SerializeField] private UxrGrabMode _grabMode = UxrGrabMode.GrabWhilePressed;
[SerializeField] private bool _useDefaultGrabButtons = true;
[SerializeField] private bool _bothHandsCompatible = true;
[SerializeField] private UxrHandSide _handSide = UxrHandSide.Left;
[SerializeField] private UxrInputButtons _inputButtons = UxrInputButtons.Grip;
[SerializeField] private bool _hideHandGrabberRenderer;
[SerializeField] private UxrGripPoseInfo _defaultGripPoseInfo = new UxrGripPoseInfo(null);
[SerializeField] private UxrSnapToHandMode _snapMode = UxrSnapToHandMode.PositionAndRotation;
[SerializeField] private UxrHandSnapDirection _snapDirection = UxrHandSnapDirection.ObjectToHand;
[SerializeField] private UxrSnapReference _snapReference = UxrSnapReference.UseOtherTransform;
[SerializeField] private List<UxrGripPoseInfo> _avatarGripPoseEntries = new List<UxrGripPoseInfo>();
[SerializeField] private bool _alignToController;
[SerializeField] private Transform _alignToControllerAxes;
[SerializeField] private UxrGrabProximityMode _grabProximityMode = UxrGrabProximityMode.UseProximity;
[SerializeField] private BoxCollider _grabProximityBox;
[SerializeField] private float _maxDistanceGrab = 0.2f;
[SerializeField] private bool _grabProximityTransformUseSelf = true;
[SerializeField] private Transform _grabProximityTransform;
[SerializeField] private bool _grabberProximityUseDefault = true;
[SerializeField] private int _grabberProximityIndex;
[SerializeField] private GameObject _enableOnHandNear;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the <see cref="UxrGrabber" /> proximity index used to compute the distance to the object. -1 for default (the
/// grabber itself) or any other value for additional transforms in the <see cref="UxrGrabber" /> component.
/// </summary>
public int GrabberProximityTransformIndex => GrabberProximityUseDefault ? -1 : GrabberProximityIndex;
/// <summary>
/// Gets how many grip pose entries there are. 1 (the default grip pose info) plus all the registered avatar ones.
/// </summary>
public int GripPoseInfoCount => 1 + _avatarGripPoseEntries.Count;
/// <summary>
/// Gets the registered avatars for specific grip poses and properties.
/// </summary>
public List<UxrGripPoseInfo> AvatarGripPoseEntries => _avatarGripPoseEntries;
/// <summary>
/// Gets or sets whether foldout control for a given grab point is folded out or not. We use this in the
/// editor to check if we need to render the preview grab pose meshes for a given grab point.
/// Grab points that are not folded out are not rendered.
/// </summary>
public bool IsEditorFoldedOut
{
get => _editorFoldout;
set => _editorFoldout = value;
}
/// <summary>
/// Gets or sets the grab point display name in the inspector.
/// </summary>
public string EditorName
{
get => _editorName;
set => _editorName = value;
}
/// <summary>
/// Gets or sets the grab mode.
/// </summary>
public UxrGrabMode GrabMode
{
get => _grabMode;
set => _grabMode = value;
}
/// <summary>
/// Gets or sets whether to use the default grab buttons to grab the object using the grab point.
/// </summary>
public bool UseDefaultGrabButtons
{
get => _useDefaultGrabButtons;
set => _useDefaultGrabButtons = value;
}
/// <summary>
/// Gets or sets whether both hands are compatible with the grab point.
/// </summary>
public bool BothHandsCompatible
{
get => _bothHandsCompatible;
set => _bothHandsCompatible = value;
}
/// <summary>
/// If <see cref="BothHandsCompatible" /> is false, tells which hand is used to grab the object using the grab point.
/// </summary>
public UxrHandSide HandSide
{
get => _handSide;
set => _handSide = value;
}
/// <summary>
/// If <see cref="UseDefaultGrabButtons" /> is false, tells which buttons are used to grab the object using the grab
/// point.
/// </summary>
public UxrInputButtons InputButtons
{
get => _inputButtons;
set => _inputButtons = value;
}
/// <summary>
/// Gets or sets whether to hide the hand while it is grabbing the object using the grab point.
/// </summary>
public bool HideHandGrabberRenderer
{
get => _hideHandGrabberRenderer;
set => _hideHandGrabberRenderer = value;
}
/// <summary>
/// Gets or sets the default grip pose info, which is the grip pose info used when an avatar interacts with an object
/// and is not registered to have specific properties.
/// </summary>
public UxrGripPoseInfo DefaultGripPoseInfo
{
get => _defaultGripPoseInfo;
set => _defaultGripPoseInfo = value;
}
/// <summary>
/// Gets or sets how the object will snap to the hand when it is grabbed using the grab point.
/// </summary>
public UxrSnapToHandMode SnapMode
{
get => _snapMode;
set => _snapMode = value;
}
/// <summary>
/// Gets or sets whether the object will snap to the hand or the hand will snap to the object when it is grabbed using
/// the grab point. Only used when any kind of snapping is enabled.
/// </summary>
public UxrHandSnapDirection SnapDirection
{
get => _snapDirection;
set => _snapDirection = value;
}
/// <summary>
/// Gets or sets which reference to use for snapping when the object is grabbed using the grab point.
/// </summary>
public UxrSnapReference SnapReference
{
get => _snapReference;
set => _snapReference = value;
}
/// <summary>
/// Gets or sets whether to align the grab to the controller axes, useful when grabbing objects that require aiming,
/// such as weapons.
/// </summary>
public bool AlignToController
{
get => _alignToController;
set => _alignToController = value;
}
/// <summary>
/// Gets or sets the transform in the grabbable object to use that will align to the controller axes (x = right, y =
/// up, z = forward).
/// </summary>
public Transform AlignToControllerAxes
{
get => _alignToControllerAxes;
set => _alignToControllerAxes = value;
}
/// <summary>
/// Gets or sets the proximity mode to use.
/// </summary>
public UxrGrabProximityMode GrabProximityMode
{
get => _grabProximityMode;
set => _grabProximityMode = value;
}
/// <summary>
/// Gets or sets the box collider used when <see cref="GrabProximityMode" /> is
/// <see cref="UxrGrabProximityMode.BoxConstrained" />.
/// </summary>
public BoxCollider GrabProximityBox
{
get => _grabProximityBox;
set => _grabProximityBox = value;
}
/// <summary>
/// Gets or sets the maximum distance the object can be grabbed using this the grab point.
/// </summary>
public float MaxDistanceGrab
{
get => _maxDistanceGrab;
set => _maxDistanceGrab = value;
}
/// <summary>
/// Gets or sets whether to use the own <see cref="UxrGrabbableObject" /> transform when computing the distance to
/// <see cref="UxrGrabber" /> components.
/// </summary>
public bool GrabProximityTransformUseSelf
{
get => _grabProximityTransformUseSelf;
set => _grabProximityTransformUseSelf = value;
}
/// <summary>
/// Gets or sets the <see cref="Transform" /> that will be used to compute the distance to <see cref="UxrGrabber" />
/// components when <see cref="GrabberProximityUseDefault" /> is false.
/// </summary>
public Transform GrabProximityTransform
{
get => _grabProximityTransform;
set => _grabProximityTransform = value;
}
/// <summary>
/// Gets or sets whether to use the <see cref="UxrGrabber" /> transform when computing the distance to the grab point.
/// Otherwise it can specify additional proximity transforms using <see cref="GrabberProximityIndex" />.
/// </summary>
public bool GrabberProximityUseDefault
{
get => _grabberProximityUseDefault;
set => _grabberProximityUseDefault = value;
}
/// <summary>
/// Gets or sets which additional proximity transform from <see cref="UxrGrabber" /> to use when
/// <see cref="GrabberProximityUseDefault" /> is false.
/// </summary>
public int GrabberProximityIndex
{
get => _grabberProximityIndex;
set => _grabberProximityIndex = value;
}
/// <summary>
/// Gets or sets the <see cref="GameObject" /> to enable or disable when the object is grabbed or not using the grab
/// point.
/// </summary>
public GameObject EnableOnHandNear
{
get => _enableOnHandNear;
set => _enableOnHandNear = value;
}
#endregion
#region Public Methods
/// <summary>
/// Checks whether to create a grip pose entry for the given avatar prefab.
/// </summary>
/// <param name="avatarGuid">Prefab GUID to generate a grip pose entry for</param>
public void CheckAddGripPoseInfo(string avatarGuid)
{
if (string.IsNullOrEmpty(avatarGuid))
{
return;
}
// Only add if the given avatar prefab isn't found. If any parent prefabs are also registered, the new registered prefab will prevail over the parent prefab entries.
if (_avatarGripPoseEntries.All(e => e.AvatarPrefabGuid != avatarGuid))
{
_avatarGripPoseEntries.Add(new UxrGripPoseInfo(avatarGuid));
}
}
/// <summary>
/// Gets a given grip pose info entry.
/// </summary>
/// <param name="i">Index to retrieve</param>
/// <returns>Grip pose info. If the index is 0 or not valid, it will return the default grip pose info</returns>
public UxrGripPoseInfo GetGripPoseInfo(int i)
{
if (i == 0)
{
return DefaultGripPoseInfo;
}
if (i > 0 && i <= _avatarGripPoseEntries.Count)
{
return _avatarGripPoseEntries[i - 1];
}
return null;
}
/// <summary>
/// Gets a given grip pose info entry.
/// </summary>
/// <param name="prefabGuid">Prefab Guid whose info to retrieve</param>
/// <returns>Grip pose info or null if it wasn't found</returns>
public UxrGripPoseInfo GetGripPoseInfo(string prefabGuid)
{
return _avatarGripPoseEntries.FirstOrDefault(i => i.AvatarPrefabGuid == prefabGuid);
}
/// <summary>
/// Gets the grip pose info for the given avatar instance or prefab.
/// </summary>
/// <param name="avatar">Avatar to get the grip pose info for</param>
/// <param name="usePrefabInheritance">
/// If the given avatar prefab info wasn't found, whether to look for the pose info for any prefab above the first
/// prefab in the hierarchy. This allows child prefabs to inherit poses and manipulation settings of parent prefabs
/// </param>
/// <returns>
/// Grip pose info. If <see cref="usePrefabInheritance" /> is false it will return null if the given prefab wasn't
/// found. If <see cref="usePrefabInheritance" /> is true, it will return <see cref="DefaultGripPoseInfo" /> if nor the
/// prefab nor a parent prefab entry were found
/// </returns>
public UxrGripPoseInfo GetGripPoseInfo(UxrAvatar avatar, bool usePrefabInheritance = true)
{
foreach (string avatarPrefabGuid in avatar.GetPrefabGuidChain())
{
foreach (UxrGripPoseInfo gripPoseInfo in _avatarGripPoseEntries)
{
if (gripPoseInfo.AvatarPrefabGuid == avatarPrefabGuid)
{
return gripPoseInfo;
}
}
if (!usePrefabInheritance)
{
return null;
}
}
return DefaultGripPoseInfo;
}
/// <summary>
/// Gets all the grip pose infos that can be used with the given avatar.
/// </summary>
/// <param name="avatar">The avatar to check</param>
/// <param name="usePrefabInheritance">Whether to check for compatibility using all the parents in the prefab hierarchy</param>
/// <returns>List of <see cref="UxrGripPoseInfo" /> that are potentially compatible with the given avatar</returns>
public IEnumerable<UxrGripPoseInfo> GetCompatibleGripPoseInfos(UxrAvatar avatar, bool usePrefabInheritance = true)
{
foreach (string avatarPrefabGuid in avatar.GetPrefabGuidChain())
{
foreach (UxrGripPoseInfo gripPoseInfo in _avatarGripPoseEntries)
{
if (gripPoseInfo.AvatarPrefabGuid == avatarPrefabGuid)
{
yield return gripPoseInfo;
}
}
if (!usePrefabInheritance)
{
yield break;
}
}
}
/// <summary>
/// Removes the grip pose entry of a given avatar prefab.
/// </summary>
/// <param name="avatarPrefabGuid">Prefab GUID whose information to remove</param>
public void RemoveGripPoseInfo(string avatarPrefabGuid)
{
if (string.IsNullOrEmpty(avatarPrefabGuid))
{
return;
}
_avatarGripPoseEntries.RemoveAll(e => e.AvatarPrefabGuid == avatarPrefabGuid);
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5d63d8d35d6f4c2dbbd7deea167440b0
timeCreated: 1643287822

View File

@@ -0,0 +1,77 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabPointShape.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Base class to create more advanced grips (cylindrical, box...).
/// An <see cref="UxrGrabbableObject" /> enables grabbing an object. A grabPoint inside the
/// <see cref="UxrGrabbableObject" /> defines where and how the object will snap to the hand. Additionally, if there is
/// an <see cref="UxrGrabPointShape" /> based component on the same object, it will "expand" the snapping from a
/// single point to a more complex shape like a an axis, a cylinder, a box... This way an object can be picked up from
/// many different places just by specifying a snap point and some additional properties.
/// </summary>
[RequireComponent(typeof(UxrGrabbableObject))]
public abstract class UxrGrabPointShape : UxrComponent<UxrGrabbableObject, UxrGrabPointShape>
{
#region Inspector Properties/Serialized Fields
[SerializeField] protected UxrGrabPointIndex _grabPointIndex = new UxrGrabPointIndex(0);
#endregion
#region Public Types & Data
/// <summary>
/// Gets the grab point from the <see cref="UxrGrabbableObject" /> this object extends.
/// </summary>
public UxrGrabPointIndex GrabPoint => _grabPointIndex;
#endregion
#region Public Methods
/// <summary>
/// Gets the distance from a <see cref="UxrGrabber" /> to a grab point, defined by transform used for snapping and the
/// transform used to compute proximity.
/// </summary>
/// <param name="grabber">Grabber to compute the distance from</param>
/// <param name="snapTransform">The <see cref="Transform" /> on the grabbable object that is used to align to the grabber</param>
/// <param name="objectDistanceTransform">
/// The <see cref="Transform" /> on the grabbable object that is used to compute the
/// distance to the <see cref="UxrGrabber" />
/// </param>
/// <param name="grabberDistanceTransform">
/// The <see cref="Transform" /> on the grabber that is used to compute the distance
/// to the <see cref="UxrGrabbableObject" />
/// </param>
/// <returns>Distance value</returns>
public abstract float GetDistanceFromGrabber(UxrGrabber grabber, Transform snapTransform, Transform objectDistanceTransform, Transform grabberDistanceTransform);
/// <summary>
/// Gets the closest snap position and rotation that should be used when a <see cref="UxrGrabber" /> tries to a grab
/// point, defined by transform used for snapping and the transform used to compute proximity.
/// </summary>
/// <param name="grabber">Grabber to compute the snapping for</param>
/// <param name="snapTransform">The <see cref="Transform" /> on the grabbable object that is used to align to the grabber</param>
/// <param name="distanceTransform">
/// The <see cref="Transform" /> on the grabbable object that is used to compute the
/// distance to the grabber
/// </param>
/// <param name="grabberDistanceTransform">
/// The <see cref="Transform" /> on the grabber that is used to compute the distance
/// to the <see cref="UxrGrabbableObject" />
/// </param>
/// <param name="position">Snap position</param>
/// <param name="rotation">Snap rotation</param>
/// <returns>Distance value</returns>
public abstract void GetClosestSnap(UxrGrabber grabber, Transform snapTransform, Transform distanceTransform, Transform grabberDistanceTransform, out Vector3 position, out Quaternion rotation);
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 53bb82318421f864ba18e07e56c095b7
timeCreated: 1538560526
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,217 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabPointShapeAxisAngle.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Math;
using UltimateXR.Extensions.Unity.Math;
using UnityEngine;
#pragma warning disable 414 // Disable warnings due to unused values
namespace UltimateXR.Manipulation
{
/// <summary>
/// Grab shape used to grab cylindrical objects. The cylinder is described by an axis and a length. It is possible to
/// specify if the object can be grabbed in both directions or a direction only.
/// </summary>
public class UxrGrabPointShapeAxisAngle : UxrGrabPointShape
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Transform _center;
[SerializeField] private UxrAxis _centerAxis = UxrAxis.Z;
[SerializeField] private bool _bidirectional;
[SerializeField] private float _angleMin = -180.0f;
[SerializeField] private float _angleMax = 180.0f;
[SerializeField] private float _angleInterval = 0.01f;
[SerializeField] private float _offsetMin = -0.1f;
[SerializeField] private float _offsetMax = 0.1f;
[SerializeField] private float _offsetInterval = 0.001f;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the axis center.
/// </summary>
public Transform Center => _center != null ? _center : transform;
/// <summary>
/// Gets or sets the axis around which the grab can rotate.
/// </summary>
public UxrAxis CenterAxis
{
get => _centerAxis;
set => _centerAxis = value;
}
/// <summary>
/// Gets or sets the minimum angle the grip, defined by the <see cref="UxrGrabbableObject" />, can rotate around
/// <see cref="CenterAxis" />.
/// </summary>
public float AngleMin
{
get => _angleMin;
set => _angleMin = value;
}
/// <summary>
/// Gets or sets the maximum angle the grip, defined by the <see cref="UxrGrabbableObject" />, can rotate around
/// <see cref="CenterAxis" />.
/// </summary>
public float AngleMax
{
get => _angleMax;
set => _angleMax = value;
}
/// <summary>
/// Gets or sets the discrete angle interval steps the grip can rotate around <see cref="CenterAxis" />.
/// </summary>
public float AngleInterval
{
get => _angleInterval;
set => _angleInterval = value;
}
/// <summary>
/// Gets or sets the minimum offset from the center, and along <see cref="CenterAxis" />, the grip can move.
/// </summary>
public float OffsetMin
{
get => _offsetMin;
set => _offsetMin = value;
}
/// <summary>
/// Gets or sets the maximum offset from the center, and along <see cref="CenterAxis" />, the grip can move.
/// </summary>
public float OffsetMax
{
get => _offsetMax;
set => _offsetMax = value;
}
/// <summary>
/// Gets or sets the discrete offset steps along along <see cref="CenterAxis" /> the grip can move.
/// </summary>
public float OffsetInterval
{
get => _offsetInterval;
set => _offsetInterval = value;
}
#endregion
#region Public Overrides UxrGrabPointShape
/// <inheritdoc />
public override float GetDistanceFromGrabber(UxrGrabber grabber, Transform snapTransform, Transform objectDistanceTransform, Transform grabberDistanceTransform)
{
// TODO: Consider rotation difference
return grabberDistanceTransform.position.DistanceToSegment(GetSegmentA(objectDistanceTransform.position), GetSegmentB(objectDistanceTransform.position));
}
/// <inheritdoc />
public override void GetClosestSnap(UxrGrabber grabber, Transform snapTransform, Transform distanceTransform, Transform grabberDistanceTransform, out Vector3 position, out Quaternion rotation)
{
// Compute best fitting rotation
Vector3 worldAxis = Center.TransformDirection(CenterAxis);
Vector3 localSnapAxis = snapTransform.InverseTransformDirection(worldAxis);
Vector3 worldGrabberAxis = grabber.transform.TransformDirection(localSnapAxis);
bool reverseGrip = _bidirectional && Vector3.Angle(worldGrabberAxis, -worldAxis) < Vector3.Angle(worldGrabberAxis, worldAxis);
// worldGrabberAxis contains the axis in world coordinates if it was being grabbed with the current grabber orientation
// projection contains the rotation that the grabber would need to rotate to align to the axis using the closest angle
Quaternion projection = Quaternion.FromToRotation(worldGrabberAxis, reverseGrip ? -worldAxis : worldAxis);
/*
if (reverseGrip)
{
Vector3 right = projection * Vector3.right;
Vector3 up = projection * Vector3.up;
Vector3 forward = projection * Vector3.forward;
}*/
// Compute the rotation required to rotate the grabber to the best suited grip on the axis with the given properties
rotation = projection * grabber.transform.rotation;
// Compute perpendicular vectors to the axis to get the angle from snap rotation to projected snap rotation.
Vector3 worldPerpendicular = Center.TransformDirection(CenterAxis.Perpendicular);
Vector3 localPerpendicular = snapTransform.InverseTransformDirection(worldPerpendicular);
Quaternion grabberRotation = grabber.transform.rotation;
grabber.transform.rotation = rotation;
// Compute angle and clamp it.
float angle = Vector3.SignedAngle(worldPerpendicular, grabber.transform.TransformDirection(localPerpendicular), worldAxis);
float clampedAngle = Mathf.Clamp(angle, AngleMin, AngleMax);
rotation = Quaternion.AngleAxis(clampedAngle - angle, worldAxis) * rotation;
// TODO: use _angleInterval
grabber.transform.rotation = grabberRotation;
// Compute grabber position by rotating the snap position around the axis
Vector3 projectedSnap = snapTransform.position.ProjectOnLine(Center.position, worldAxis);
Vector3 fromAxisToSnap = snapTransform.position - projectedSnap;
Vector3 grabberPos = grabber.transform.position;
if (reverseGrip)
{
fromAxisToSnap = Quaternion.AngleAxis(180.0f, worldPerpendicular) * fromAxisToSnap;
}
position = grabberPos.ProjectOnSegment(GetSegmentA(projectedSnap), GetSegmentB(projectedSnap)) + fromAxisToSnap.GetRotationAround(worldAxis, clampedAngle);
}
#endregion
#region Unity
/// <summary>
/// Called when the object is selected, to draw the gizmos in the scene window.
/// </summary>
private void OnDrawGizmosSelected()
{
UxrGrabbableObject grabbableObject = GetComponent<UxrGrabbableObject>();
if (_grabPointIndex >= 0 && _grabPointIndex < grabbableObject.GrabPointCount)
{
Gizmos.DrawLine(GetSegmentA(transform.position), GetSegmentB(transform.position));
}
}
#endregion
#region Private Methods
/// <summary>
/// Gets one side of the grabbable segment in world space if it started in <paramref name="center" />.
/// </summary>
/// <param name="center">Center in world space to consider</param>
private Vector3 GetSegmentA(Vector3 center)
{
return center + Center.TransformDirection(CenterAxis) * OffsetMin;
}
/// <summary>
/// Gets the other side of the grabbable segment in world space if it started in <paramref name="center" />.
/// </summary>
/// <param name="center">Center in world space to consider</param>
private Vector3 GetSegmentB(Vector3 center)
{
return center + Center.TransformDirection(CenterAxis) * OffsetMax;
}
#endregion
}
}
#pragma warning restore 414

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: f163104d7494aa8478b336859f93866b
timeCreated: 1538560526
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabProximityMode.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Enumerates how the distance from a <see cref="UxrGrabber" /> to a <see cref="UxrGrabbableObject" /> can be
/// computed.
/// </summary>
public enum UxrGrabProximityMode
{
/// <summary>
/// Use the proximity transform in the <see cref="UxrGrabber" />.
/// </summary>
UseProximity,
/// <summary>
/// Use a <see cref="BoxCollider" /> inside which the <see cref="UxrGrabber" /> needs to be in order to grab the
/// object.
/// </summary>
BoxConstrained
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6bf3239359e64939a0569b1e7239fd17
timeCreated: 1643287251

View File

@@ -0,0 +1,29 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableModifierFlags.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Flags that represent parts in an <see cref="UxrGrabbableObject" /> that can be modified/hidden by components in the
/// same <see cref="GameObject" /> that implement the <see cref="IUxrGrabbableModifier" /> interface.
/// </summary>
[Flags]
public enum UxrGrabbableModifierFlags
{
None = 0,
DummyParentGrabbable = 1 << 0,
ParentControl = 1 << 1,
Priority = 1 << 2,
MultiGrab = 1 << 3,
TranslationConstraint = 1 << 8,
RotationConstraint = 1 << 12,
TranslationResistance = 1 << 16,
RotationResistance = 1 << 17,
Anchored = 1 << 18
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9ce8d8e6c71c72a47afaa54519830af0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,101 @@

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableObject.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.StateSave;
namespace UltimateXR.Manipulation
{
public partial class UxrGrabbableObject
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override UxrTransformSpace TransformStateSaveSpace => GetLocalTransformIfParentedOr(UxrTransformSpace.Local);
/// <inheritdoc />
protected override bool RequiresTransformSerialization(UxrStateSaveLevel level)
{
// Save always
return level >= UxrStateSaveLevel.ChangesSincePreviousSave;
}
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
if (level <= UxrStateSaveLevel.ChangesSincePreviousSave)
{
// All the variables below are not needed in incremental snapshots.
// In a replay system, these variables will be updated by events.
return;
}
// Backing fields for public properties
SerializeStateValue(level, options, nameof(_currentAnchor), ref _currentAnchor);
SerializeStateValue(level, options, nameof(_isPlaceable), ref _isPlaceable);
SerializeStateValue(level, options, nameof(_isLockedInPlace), ref _isLockedInPlace);
SerializeStateValue(level, options, nameof(_priority), ref _priority);
SerializeStateValue(level, options, nameof(_useParenting), ref _useParenting);
SerializeStateValue(level, options, nameof(_tag), ref _tag);
SerializeStateValue(level, options, nameof(_translationConstraintMode), ref _translationConstraintMode);
SerializeStateValue(level, options, nameof(_translationLimitsMin), ref _translationLimitsMin);
SerializeStateValue(level, options, nameof(_translationLimitsMax), ref _translationLimitsMax);
SerializeStateValue(level, options, nameof(_rotationConstraintMode), ref _rotationConstraintMode);
SerializeStateValue(level, options, nameof(_rotationAngleLimitsMin), ref _rotationAngleLimitsMin);
SerializeStateValue(level, options, nameof(_rotationAngleLimitsMax), ref _rotationAngleLimitsMax);
SerializeStateValue(level, options, nameof(_translationResistance), ref _translationResistance);
SerializeStateValue(level, options, nameof(_rotationResistance), ref _rotationResistance);
SerializeStateValue(level, options, nameof(_dropSnapMode), ref _dropSnapMode);
// Backing fields for internal properties
SerializeStateValue(level, options, nameof(_singleRotationAngleCumulative), ref _singleRotationAngleCumulative);
SerializeStateValue(level, options, nameof(_placementOptions), ref _placementOptions);
// Backing fields for IUxrGrabbable interface
SerializeStateValue(level, options, nameof(_isGrabbable), ref _isGrabbable);
bool isKinematic = _rigidBodySource && _rigidBodySource.isKinematic;
bool isSleeping = _rigidBodySource && _rigidBodySource.IsSleeping();
if (!isReading && options.HasFlag(UxrStateSaveOptions.FirstFrame))
{
// To avoid changes, because IsSleeping() will return false the first frame. We force it to be sleeping the first frame.
isSleeping = true;
}
SerializeStateValue(level, options, nameof(isKinematic), ref isKinematic);
SerializeStateValue(level, options, nameof(isSleeping), ref isSleeping);
if (isReading && _rigidBodySource)
{
_rigidBodySource.isKinematic = isKinematic;
if (isSleeping)
{
_rigidBodySource.Sleep();
}
}
if (isReading)
{
LocalPositionBeforeUpdate = transform.localPosition;
LocalRotationBeforeUpdate = transform.localRotation;
}
// Private vars
SerializeStateValue(level, options, nameof(_grabPointEnabledStates), ref _grabPointEnabledStates);
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 40079bae94fc4122b55073ddcc7d1ab1
timeCreated: 1705483727

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 79f5a30897a3c024c805c866226dd743
timeCreated: 1492963941
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableObjectAnchor.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
namespace UltimateXR.Manipulation
{
public partial class UxrGrabbableObjectAnchor
{
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
// Manipulations are already handled through events, we don't serialize them in incremental changes
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
SerializeStateValue(level, options, nameof(_currentPlacedObject), ref _currentPlacedObject);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d47f4c201c93468da2061e15ce8d6263
timeCreated: 1706169124

View File

@@ -0,0 +1,422 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableObjectAnchor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Component that, added to a <see cref="GameObject" />, will enable <see cref="UxrGrabbableObject" /> objects to be
/// placed on it.
/// Some of the main features of grabbable object anchors are:
/// <list type="bullet">
/// <item>
/// Placement mechanics are handled automatically by the <see cref="UxrGrabManager" />. There is no special
/// requirement to set it up in a scene, the grab manager will be available as soon as it is required.
/// </item>
/// <item>
/// Compatible tags allow to model which objects can be placed on the anchor. If the list is empty, the
/// anchor is compatible with all other <see cref="UxrGrabbableObject" /> that do not have a tag.
/// </item>
/// <item>
/// Events such as <see cref="Placed" /> and <see cref="Removed" /> allow to write logic when a user interacts
/// with the anchor. Each one has pre and post events.
/// </item>
/// <item>
/// <see cref="ActivateOnCompatibleNear" />, <see cref="ActivateOnCompatibleNotNear" />,
/// <see cref="ActivateOnHandNearAndGrabbable" />, <see cref="ActivateOnPlaced" /> and
/// <see cref="ActivateOnEmpty" /> can be used to activate/deactivate objects on manipulation events. They can
/// be assigned during edit-time using the inspector and also at runtime.
/// </item>
/// <item>
/// <see cref="AddPlacingValidator" /> and <see cref="RemovePlacingValidator" /> can be used to model complex
/// compatibility behaviour that changes at runtime.
/// </item>
/// </list>
/// </summary>
public partial class UxrGrabbableObjectAnchor : UxrComponent<UxrGrabbableObjectAnchor>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private List<string> _compatibleTags = new List<string>();
[SerializeField] private float _maxPlaceDistance = 0.1f;
[SerializeField] private bool _alignTransformUseSelf = true;
[SerializeField] private Transform _alignTransform;
[SerializeField] private bool _dropProximityTransformUseSelf = true;
[SerializeField] private Transform _dropProximityTransform;
[SerializeField] private GameObject _activateOnCompatibleNear;
[SerializeField] private GameObject _activateOnCompatibleNotNear;
[SerializeField] private GameObject _activateOnHandNearAndGrabbable;
[SerializeField] private GameObject _activateOnPlaced;
[SerializeField] private GameObject _activateOnEmpty;
#endregion
#region Public Types & Data
/// <summary>
/// Event called right before an object is placed on the anchor.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Placing;
/// <summary>
/// Event called right after an object was placed on the anchor.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Placed;
/// <summary>
/// Event called right before the currently placed object is removed from the anchor.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Removing;
/// <summary>
/// Event called right after the currently placed object is removed from the anchor.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Removed;
/// <summary>
/// Event called right after an object that was placed on the anchor ended its smooth placing transition.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> SmoothPlaceTransitionEnded;
/// <summary>
/// Gets the <see cref="Transform" /> that will be used to snap the <see cref="UxrGrabbableObject" /> placed on it.
/// </summary>
public Transform AlignTransform
{
get
{
if (_alignTransform == null || _alignTransformUseSelf)
{
return transform;
}
return _alignTransform;
}
}
/// <summary>
/// Gets the <see cref="Transform" /> that will be used to compute the distance to <see cref="UxrGrabbableObject" />
/// that can be placed on it, in order to determine if they are close enough.
/// </summary>
public Transform DropProximityTransform
{
get
{
if (_dropProximityTransform == null || _dropProximityTransformUseSelf)
{
return transform;
}
return _dropProximityTransform;
}
}
/// <summary>
/// Gets the <see cref="UxrGrabbableObject" /> that is currently placed on the anchor.
/// </summary>
public UxrGrabbableObject CurrentPlacedObject
{
get => _currentPlacedObject;
internal set => _currentPlacedObject = value;
}
/// <summary>
/// Gets or sets the maximum distance from which an object that is released will be placed on the anchor.
/// </summary>
public float MaxPlaceDistance
{
get => _maxPlaceDistance;
set => _maxPlaceDistance = value;
}
/// <summary>
/// Gets or sets the object that will be enabled or disabled depending on if there is a grabbed compatible
/// <see cref="UxrGrabbableObject" /> close enough to be placed on it.
/// </summary>
public GameObject ActivateOnCompatibleNear
{
get => _activateOnCompatibleNear;
set => _activateOnCompatibleNear = value;
}
/// <summary>
/// Gets or sets the object that will be enabled or disabled depending on if there isn't a grabbed compatible
/// <see cref="UxrGrabbableObject" /> close enough to be placed on it.
/// </summary>
public GameObject ActivateOnCompatibleNotNear
{
get => _activateOnCompatibleNotNear;
set => _activateOnCompatibleNotNear = value;
}
/// <summary>
/// Gets or sets the object that will be enabled or disabled depending on if there is a
/// <see cref="UxrGrabbableObject" /> currently placed on the anchor and a <see cref="UxrGrabber" /> close enough to
/// grab it.
/// </summary>
public GameObject ActivateOnHandNearAndGrabbable
{
get => _activateOnHandNearAndGrabbable;
set => _activateOnHandNearAndGrabbable = value;
}
/// <summary>
/// Gets or sets the object that will be enabled or disabled depending on if there is a
/// <see cref="UxrGrabbableObject" /> currently placed on the anchor.
/// </summary>
public GameObject ActivateOnPlaced
{
get => _activateOnPlaced;
set => _activateOnPlaced = value;
}
/// <summary>
/// Gets or sets the object that will be enabled or disabled depending on if there isn't a
/// <see cref="UxrGrabbableObject" /> currently placed on the anchor.
/// </summary>
public GameObject ActivateOnEmpty
{
get => _activateOnEmpty;
set => _activateOnEmpty = value;
}
#endregion
#region Public Methods
/// <summary>
/// Adds compatible tags to the list of compatible tags that control which objects can be placed on the anchor.
/// </summary>
/// <param name="tags">Tags to add</param>
/// <exception cref="ArgumentNullException">tags is null</exception>
public void AddCompatibleTags(params string[] tags)
{
if (tags == null)
{
throw new ArgumentNullException(nameof(tags));
}
_compatibleTags.AddRange(tags);
}
/// <summary>
/// Removes compatible tags from the list of compatible tags that control which objects can be placed on the anchor.
/// </summary>
/// <param name="tags">Tags to remove</param>
/// <exception cref="ArgumentNullException">tags is null</exception>
public void RemoveCompatibleTags(params string[] tags)
{
if (tags == null)
{
throw new ArgumentNullException(nameof(tags));
}
foreach (string tag in tags)
{
_compatibleTags.Remove(tag);
}
}
/// <summary>
/// Removes the currently placed object, if there is any, from the anchor.
/// </summary>
/// <param name="propagateEvents">Whether the call should generate any events or not</param>
public void RemoveObject(bool propagateEvents)
{
if (CurrentPlacedObject != null)
{
UxrGrabManager.Instance.RemoveObjectFromAnchor(CurrentPlacedObject, propagateEvents);
}
}
/// <summary>
/// Adds a placing validator to the internal list of validators. Placing validators are functions that are used in
/// addition to compatibility tags in order to determine if a <see cref="UxrGrabbableObject" /> can be placed on the
/// anchor.
/// An object can be placed on an anchor if the tag is compatible and if it is allowed by all of the placing
/// validators.
/// </summary>
/// <param name="validator">
/// New placing validator function to add. It takes an <see cref="UxrGrabbableObject" /> as input
/// and returns a boolean telling whether it can be placed or not
/// </param>
/// <exception cref="ArgumentNullException">The validator function is null</exception>
public void AddPlacingValidator(Func<UxrGrabbableObject, bool> validator)
{
if (validator == null)
{
throw new ArgumentNullException(nameof(validator));
}
_placingValidators.Add(validator);
}
/// <summary>
/// Removes a placing validator added using <see cref="AddPlacingValidator" />.
/// </summary>
/// <param name="validator">Validator to remove</param>
/// <exception cref="ArgumentNullException">the validator function is null</exception>
public void RemovePlacingValidator(Func<UxrGrabbableObject, bool> validator)
{
if (validator == null)
{
throw new ArgumentNullException(nameof(validator));
}
_placingValidators.Remove(validator);
}
/// <summary>
/// Checks whether the given <see cref="UxrGrabbableObject" /> is compatible with the anchor, which means that it can
/// potentially be placed on it if there is no other object placed.
/// </summary>
/// <param name="grabbableObject">Object to check</param>
/// <returns>Whether the object is compatible with the anchor</returns>
public bool IsCompatibleObject(UxrGrabbableObject grabbableObject)
{
return grabbableObject != null && _placingValidators.All(v => v(grabbableObject)) && IsCompatibleObjectTag(grabbableObject.Tag);
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
UxrGrabManager.Instance.Poke();
if (_activateOnCompatibleNear != null)
{
_activateOnCompatibleNear.SetActive(false);
}
if (_activateOnCompatibleNotNear != null)
{
_activateOnCompatibleNotNear.SetActive(false);
}
if (_activateOnHandNearAndGrabbable != null)
{
_activateOnHandNearAndGrabbable.SetActive(false);
}
}
/// <summary>
/// Removes the validators.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
_placingValidators.Clear();
}
/// <summary>
/// Performs additional initialization.
/// </summary>
protected override void Start()
{
base.Start();
if (_activateOnPlaced != null)
{
_activateOnPlaced.SetActive(CurrentPlacedObject != null);
}
if (_activateOnEmpty != null)
{
_activateOnEmpty.SetActive(CurrentPlacedObject == null);
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for <see cref="Placing" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaisePlacingEvent(UxrManipulationEventArgs e)
{
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);
_smoothPlaceEventArgs = e;
}
/// <summary>
/// Event trigger for <see cref="Removing" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseRemovingEvent(UxrManipulationEventArgs e)
{
Removing?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="Removed" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseRemovedEvent(UxrManipulationEventArgs e)
{
Removed?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="SmoothPlaceTransitionEnded" />.
/// </summary>
internal void RaiseSmoothTransitionPlaceEnded()
{
SmoothPlaceTransitionEnded?.Invoke(this, _smoothPlaceEventArgs);
}
#endregion
#region Private Methods
/// <summary>
/// Checking whether the given tag is compatible with the anchor.
/// </summary>
/// <param name="otherTag">Tag to check whether it is compatible</param>
/// <returns>Whether the tag is compatible</returns>
private bool IsCompatibleObjectTag(string otherTag)
{
if (_compatibleTags == null || _compatibleTags.Count == 0)
{
return string.IsNullOrEmpty(otherTag);
}
return _compatibleTags.Contains(otherTag);
}
#endregion
#region Private Types & Data
private readonly List<Func<UxrGrabbableObject, bool>> _placingValidators = new List<Func<UxrGrabbableObject, bool>>();
private UxrManipulationEventArgs _smoothPlaceEventArgs;
private UxrGrabbableObject _currentPlacedObject;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c7b06a52e9ced0e46a369c9a6e98a92c
timeCreated: 1492963957
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableObjectPreviewMesh.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Component used internally by the editor. They are added to keep track of grab pose preview meshes and delete them
/// when the preview is no longer needed.
/// These work together with <see cref="UxrGrabbableObjectPreviewMeshProxy" /> to avoid non-uniform scaling problems
/// when previewing grab poses in UxrGrabbableObject hierarchies.
/// </summary>
public class UxrGrabbableObjectPreviewMesh : UxrComponent
{
#region Public Types & Data
public MeshFilter MeshFilterComponent => PreviewMeshProxy.MeshFilterComponent;
/// <summary>
/// Gets or sets the preview mesh object used by the editor (editor type UxrPreviewHandGripMesh).
/// </summary>
public object PreviewMesh
{
get => PreviewMeshProxy.PreviewMesh;
set => PreviewMeshProxy.PreviewMesh = value;
}
public UxrGrabbableObjectPreviewMeshProxy PreviewMeshProxy { get; set; }
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 892bad91ee54a3645a13bf83f4bf9d2c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,95 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableObjectPreviewMeshProxy.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Component used internally by the editor. They are added to keep track of grab pose preview meshes and
/// delete them when the preview is no longer needed.
/// <see cref="UxrGrabbableObjectPreviewMesh" /> components will be hidden hanging from each
/// <see cref="UxrGrabbableObjectSnapTransform" />. They could have the mesh themselves but when dealing with
/// non-uniform scaling in grabbable hierarchies, the preview mesh would be distorted when hanging directly.
/// Instead, each <see cref="UxrGrabbableObjectPreviewMesh" /> component will additionally point to a hidden
/// root GameObject with a <see cref="UxrGrabbableObjectPreviewMeshProxy" /> that will have the mesh data.
/// Being a root GameObject will avoid non-uniform scaling problems.
/// </summary>
[ExecuteInEditMode]
public class UxrGrabbableObjectPreviewMeshProxy : UxrComponent
{
#region Public Types & Data
/// <summary>
/// Gets the mesh filter.
/// </summary>
public MeshFilter MeshFilterComponent => GetCachedComponent<MeshFilter>();
/// <summary>
/// Gets or sets the preview mesh component that the proxy is following.
/// </summary>
public UxrGrabbableObjectPreviewMesh PreviewMeshComponent { get; set; }
/// <summary>
/// Gets or sets the preview mesh object used by the editor (editor type UxrPreviewHandGripMesh).
/// </summary>
public object PreviewMesh { get; set; }
#endregion
#region Unity
/// <summary>
/// Makes sure to hide the GameObject initially during play mode when working from the editor.
/// </summary>
protected override void Awake()
{
if (Application.isPlaying)
{
base.Awake();
if (Application.isEditor && Application.isPlaying)
{
gameObject.SetActive(false);
}
}
}
#if UNITY_EDITOR
/// <summary>
/// Follow the source object and monitors deletion.
/// </summary>
private void Update()
{
if (PreviewMeshComponent == null)
{
if (Application.isPlaying)
{
Destroy(gameObject);
}
else
{
DestroyImmediate(gameObject);
}
}
else
{
transform.SetPositionAndRotation(PreviewMeshComponent.transform.position, PreviewMeshComponent.transform.rotation);
}
}
#endif
#endregion
#region Private Types & Data
private MeshFilter _meshFilterComponent;
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 18483e5bbc7fc314e9f60f6d48d4c2f4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableObjectSnapTransform.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Component used internally by the editor. It renders grab pose meshes in the Scene Window when a grab point's snap
/// transform is selected and moved/rotated.
/// It also allows to modify the blend factor in blend poses and gives access to some handy repositioning tools.
/// </summary>
public class UxrGrabbableObjectSnapTransform : UxrComponent
{
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 212ce82ef45e9684daf388b699410086
timeCreated: 1492963941
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,112 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabber.PhysicsSample.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Manipulation
{
public partial class UxrGrabber
{
#region Private Types & Data
/// <summary>
/// Stores physics data of a frame to perform smooth throw computations.
/// </summary>
private class PhysicsSample
{
#region Public Types & Data
/// <summary>
/// Gets the sampled center of mass world position.
/// </summary>
public Vector3 CenterOfMass { get; }
/// <summary>
/// Gets the sampled finger tip world position.
/// </summary>
public Vector3 Tip { get; }
/// <summary>
/// Gets the sampled rotation.
/// </summary>
public Quaternion Rotation { get; }
/// <summary>
/// Gets the sampled angular speed in Euler angles.
/// </summary>
public Vector3 EulerSpeed { get; }
/// <summary>
/// Gets the sampled linear velocity in world space.
/// </summary>
public Vector3 Velocity { get; }
/// <summary>
/// Gets the sampled total velocity in world space. It is the result of combining the linear velocity plus the velocity
/// due to the throw axis angular speed. This should be used to compute throw release velocity.
/// </summary>
public Vector3 TotalVelocity { get; }
/// <summary>
/// Gets the elapsed time in seconds with respect to the previous sample.
/// </summary>
public float DeltaTime { get; }
/// <summary>
/// Gets or sets the sample age in seconds.
/// </summary>
public float Age { get; set; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="lastSample">Last frame data, to compute velocities</param>
/// <param name="sampledTransform">Transform, from the grabbed object if there is one currently being grabbed, otherwise from the grabber</param>
/// <param name="centerOfMass">World position of the throwing center of mass</param>
/// <param name="tip">World position of the finger tip approximation, to account for angular velocity in the throw</param>
/// <param name="deltaTime">Time in seconds since last frame</param>
public PhysicsSample(PhysicsSample lastSample, Transform sampledTransform, Vector3 centerOfMass, Vector3 tip, float deltaTime)
{
Age = 0.0f;
Rotation = sampledTransform.rotation;
DeltaTime = deltaTime;
CenterOfMass = centerOfMass;
Tip = tip;
if (lastSample != null)
{
// Angular
Vector3 v1 = lastSample.Tip - lastSample.CenterOfMass;
Vector3 v2 = tip - centerOfMass;
Vector3 rotationAxis = Vector3.Cross(v2, v1);
Quaternion relative = Quaternion.Inverse(lastSample.Rotation) * sampledTransform.rotation;
relative.ToAngleAxis(out float angle, out Vector3 axis);
EulerSpeed = (angle * sampledTransform.TransformDirection(axis)) / deltaTime;
// Linear. TODO: Improve using a mix of linear and angular components?
Velocity = ((tip - lastSample.Tip) / deltaTime);
TotalVelocity = Velocity;
}
else
{
EulerSpeed = Vector3.zero;
Velocity = Vector3.zero;
TotalVelocity = Velocity;
}
}
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9ffa327fd18544c3a2a9885c574182b7
timeCreated: 1656494341

View File

@@ -0,0 +1,29 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabber.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
namespace UltimateXR.Manipulation
{
public partial class UxrGrabber
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
// Manipulations are already handled through events, we don't serialize them in incremental changes
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
SerializeStateValue(level, options, nameof(_grabbedObject), ref _grabbedObject);
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5dd692b0dfad41d689221fe0ae33564e
timeCreated: 1705937331

View File

@@ -0,0 +1,529 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabber.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Avatar.Rig;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Extensions.Unity;
using UltimateXR.Extensions.Unity.Math;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// <para>
/// Component that added to an <see cref="UxrAvatar" /> allows to interact with <see cref="UxrGrabbableObject" />
/// entities. Normally there are two per avatar, one on each hand. They are usually added to the hand object since
/// it is the <see cref="UxrGrabber" /> transform where grabbable objects will be snapped to when snapping is used.
/// </para>
/// <para>
/// By default, the grabber transform is also used to compute distances to grabbable objects. Additional proximity
/// transforms can be specified on the grabber so that grabbable objects can choose which one is used. This can be
/// useful in some scenarios: In an aircraft cockpit most knobs and buttons will prefer the distance from the tip
/// of the index finger, while bigger objects will prefer from the palm of the hand.
/// </para>
/// </summary>
public partial class UxrGrabber : UxrAvatarComponent<UxrGrabber>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Renderer _handRenderer;
[SerializeField] private GameObject[] _objectsToDisableOnGrab;
[SerializeField] private List<Transform> _optionalProximityTransforms;
#endregion
#region Public Types & Data
/// <summary>
/// Gets from all the positive and negative axes in the grabber's transform, the axis in local-space that is pointing
/// to the fingers, excluding the thumb.
/// </summary>
public Vector3 LocalFingerDirection
{
get
{
if (Avatar == null || Avatar.AvatarRigInfo == null)
{
return transform.forward;
}
return transform.GetClosestLocalAxis(Avatar.AvatarRigInfo.GetArmInfo(Side).HandUniversalLocalAxes.WorldForward);
}
}
/// <summary>
/// Gets from all the positive and negative axes in the grabber's transform, the axis in world-space that is pointing
/// to the fingers, excluding the thumb.
/// </summary>
public Vector3 FingerDirection => transform.TransformDirection(LocalFingerDirection);
/// <summary>
/// Gets from all the positive and negative axes in the grabber's transform, the axis in local-space that is pointing
/// outwards from the palm.
/// </summary>
public Vector3 LocalPalmOutDirection
{
get
{
if (Avatar == null || Avatar.AvatarRigInfo == null)
{
return -transform.up;
}
return transform.GetClosestLocalAxis(-Avatar.AvatarRigInfo.GetArmInfo(Side).HandUniversalLocalAxes.WorldUp);
}
}
/// <summary>
/// Gets from all the positive and negative axes in the grabber's transform, the axis in world-space that is pointing
/// outwards from the palm..
/// </summary>
public Vector3 PalmOutDirection => transform.TransformDirection(LocalPalmOutDirection);
/// <summary>
/// Gets from all the positive and negative axes in the grabber's transform, the axis in local-space that is pointing
/// towards the thumb.
/// </summary>
public Vector3 LocalPalmThumbDirection
{
get
{
Vector3 direction = transform.right;
if (Avatar != null && Avatar.AvatarRigInfo != null)
{
direction = transform.GetClosestLocalAxis(Avatar.AvatarRigInfo.GetArmInfo(Side).HandUniversalLocalAxes.WorldRight);
}
return Side == UxrHandSide.Left ? direction : -direction;
}
}
/// <summary>
/// Gets from all the positive and negative axes in the grabber's transform, the axis in world-space that is pointing
/// towards the thumb.
/// </summary>
public Vector3 PalmThumbDirection => transform.TransformDirection(LocalPalmThumbDirection);
/// <summary>
/// <para>
/// Gets, based on <see cref="FingerDirection" /> and <see cref="PalmOutDirection" />, which mirroring snap
/// transforms
/// should use with the grabber if they want to be mirrored.
/// </para>
/// Snap transforms are GameObjects in <see cref="UxrGrabbableObject" /> that determine where the hand should be placed
/// during grabs by making the <see cref="UxrGrabber" />'s transform align with the snap <see cref="Transform" />.
/// Mirroring snap transforms is used to quickly create/modify grab positions/orientations.
/// </summary>
/// <returns>Which mirroring TransformExt.ApplyMirroring() should use</returns>
public TransformExt.MirrorType RequiredMirrorType
{
get
{
Vector3 other = Vector3.Cross(LocalPalmOutDirection, LocalFingerDirection);
if (Mathf.Abs(other.z) > 0.5)
{
return TransformExt.MirrorType.MirrorXY;
}
if (Mathf.Abs(other.y) > 0.5)
{
return TransformExt.MirrorType.MirrorXZ;
}
return TransformExt.MirrorType.MirrorYZ;
}
}
/// <summary>
/// Gets whether the grabber component is on the left or right hand.
/// </summary>
public UxrHandSide OppositeSide => Side == UxrHandSide.Left ? UxrHandSide.Right : UxrHandSide.Left;
/// <summary>
/// Gets whether the grabber component is on the left or right hand.
/// </summary>
public UxrHandSide Side
{
get
{
if (!_sideInitialized || (Application.isEditor && !Application.isPlaying))
{
InitializeSide();
}
return _side;
}
private set
{
_side = value;
_sideInitialized = true;
}
}
/// <summary>
/// Gets the avatar hand bone that corresponds to the grabber.
/// </summary>
public Transform HandBone => Avatar.GetHandBone(Side);
/// <summary>
/// Gets the relative position of the hand bone to the grabber.
/// </summary>
public Vector3 HandBoneRelativePos => HandBone != null ? transform.InverseTransformPoint(HandBone.position) : Vector3.zero;
/// <summary>
/// Gets the relative rotation of the hand bone to the grabber.
/// </summary>
public Quaternion HandBoneRelativeRot => HandBone != null ? Quaternion.Inverse(transform.rotation) * HandBone.rotation : Quaternion.identity;
/// <summary>
/// Gets or sets the hand renderer.
/// </summary>
public Renderer HandRenderer
{
get
{
// Try to get it automatically if it is unassigned or disabled.
if ((_handRenderer == null || !_handRenderer.gameObject.activeInHierarchy) && Avatar != null)
{
SkinnedMeshRenderer handRenderer = UxrAvatarRig.TryToGetHandRenderer(Avatar, Side);
if (handRenderer != null)
{
_handRenderer = handRenderer;
}
}
return _handRenderer;
}
set => _handRenderer = value;
}
/// <summary>
/// Gets the opposite hand grabber in the same avatar.
/// </summary>
public UxrGrabber OppositeHandGrabber { get; private set; }
/// <summary>
/// The unprocessed grabber position. This is the position the grabber has taking only the hand controller tracking
/// sensor into account.
/// The hand position is updated by the <see cref="UxrGrabManager" /> and may be forced into a certain position if the
/// object being grabbed has constraints, altering also the <see cref="UxrGrabber" /> position. Sometimes it is
/// preferred to use the unprocessed grabber position.
/// </summary>
public Vector3 UnprocessedGrabberPosition { get; internal set; }
/// <summary>
/// Gets the unprocessed grabber rotation. See <see cref="UnprocessedGrabberPosition" />.
/// </summary>
public Quaternion UnprocessedGrabberRotation { get; internal set; }
/// <summary>
/// Gets the currently grabbed object if there is one. null if no object is being grabbed.
/// </summary>
public UxrGrabbableObject GrabbedObject
{
get => _grabbedObject;
set
{
_grabbedObject = value;
if (_objectsToDisableOnGrab != null)
{
foreach (GameObject go in _objectsToDisableOnGrab)
{
go.SetActive(value == null);
}
}
}
}
/// <summary>
/// Gets <see cref="UxrGrabber" />'s current frame velocity.
/// </summary>
public Vector3 Velocity { get; private set; } = Vector3.zero;
/// <summary>
/// Gets <see cref="UxrGrabber" />'s current frame angular velocity.
/// </summary>
public Vector3 AngularVelocity { get; private set; } = Vector3.zero;
/// <summary>
/// Gets <see cref="UxrGrabber" />'s velocity smoothed using averaged previous frame data.
/// </summary>
public Vector3 SmoothVelocity { get; private set; } = Vector3.zero;
/// <summary>
/// Gets <see cref="UxrGrabber" />'s angular velocity smoothed using averaged previous frame data.
/// </summary>
public Vector3 SmoothAngularVelocity { get; private set; } = Vector3.zero;
#endregion
#region Internal Types & Data
/// <summary>
/// Gets whether the grabber is currently being smoothly interpolated in an object manipulation.
/// </summary>
internal bool IsInSmoothManipulationTransition => SmoothManipulationTimer >= 0.0f;
#endregion
#region Public Overrides Object
/// <inheritdoc />
public override string ToString()
{
string avatarName = Avatar != null ? $"{Avatar.name} " : string.Empty;
return $"{avatarName}{Side.ToString().ToLower()} hand grabber";
}
#endregion
#region Public Methods
/// <summary>
/// Gets the given proximity transform, used to compute distances to<see cref="UxrGrabbableObject" /> entities
/// </summary>
/// <param name="proximityIndex">
/// Proximity transform index. -1 for the default (the grabber's transform) and 0 to n for any
/// optional proximity transform.
/// </param>
/// <returns>Proximity transform. If the index is out of range it will return the default transform</returns>
public Transform GetProximityTransform(int proximityIndex = -1)
{
if (proximityIndex >= 0 && proximityIndex < _optionalProximityTransforms.Count)
{
return _optionalProximityTransforms[proximityIndex];
}
return transform;
}
#endregion
#region Internal Methods
/// <summary>
/// Updates the hand renderer enabled state.
/// </summary>
internal void UpdateHandGrabberRenderer()
{
if (_handRenderer != null && Avatar && (Avatar.RenderMode == UxrAvatarRenderModes.Avatar || Avatar.RenderMode == UxrAvatarRenderModes.AllControllersAndAvatar))
{
if (GrabbedObject == null)
{
_handRenderer.enabled = true;
}
else
{
_handRenderer.enabled = !UxrGrabManager.Instance.ShouldHideHandRenderer(this, GrabbedObject);
}
}
}
/// <summary>
/// Updates the throw physics information.
/// </summary>
internal void UpdateThrowPhysicsInfo()
{
Transform sampledTransform = GrabbedObject != null ? GrabbedObject.transform : transform;
Vector3 centerOfMassPosition = transform.TransformPoint(ThrowCenterOfMassLocalPosition);
Vector3 throwTipPosition = transform.TransformPoint(ThrowTipLocalPosition);
PhysicsSample newSample = new PhysicsSample(_physicsSampleWindow.LastOrDefault(), sampledTransform, centerOfMassPosition, throwTipPosition, Time.deltaTime);
// Update timers
_physicsSampleWindow.ForEach(s => s.Age += Time.deltaTime);
// Remove samples out of the time window
_physicsSampleWindow.RemoveAll(s => s.Age > SampleWindowSeconds);
// Add new sample
_physicsSampleWindow.Add(newSample);
// Compute instant and smoothed values:
Velocity = newSample.Velocity;
AngularVelocity = newSample.EulerSpeed;
SmoothVelocity = Vector3Ext.Average(_physicsSampleWindow.Select(s => s.TotalVelocity));
Quaternion relative = Quaternion.Inverse(_physicsSampleWindow.First().Rotation) * _physicsSampleWindow.Last().Rotation;
relative.ToAngleAxis(out float angle, out Vector3 axis);
SmoothAngularVelocity = angle * sampledTransform.TransformDirection(axis) / _physicsSampleWindow.First().Age;
}
/// <summary>
/// Starts a smooth manipulation transition in a grab or a release, to make sure the hand transitions smoothly.
/// </summary>
internal void StartSmoothManipulationTransition()
{
SmoothManipulationTimer = UxrConstants.SmoothManipulationTransitionSeconds;
SmoothTransitionLocalAvatarHandBonePos = Avatar.transform.InverseTransformPoint(transform.TransformPoint(HandBoneRelativePos));
SmoothTransitionLocalAvatarHandBoneRot = Quaternion.Inverse(Avatar.transform.rotation) * transform.rotation * HandBoneRelativeRot;
}
/// <summary>
/// Updates the smooth manipulation transitions if they exist.
/// </summary>
internal void UpdateSmoothManipulationTransition(float deltaTime)
{
if (SmoothManipulationTimer >= 0.0f)
{
SmoothManipulationTimer -= deltaTime;
if (SmoothManipulationTimer > 0.0f)
{
float t = SmoothManipulationT;
HandBone.SetPositionAndRotation(Vector3.Lerp(Avatar.transform.TransformPoint(SmoothTransitionLocalAvatarHandBonePos), transform.TransformPoint(HandBoneRelativePos), t),
Quaternion.Slerp(Avatar.transform.rotation * SmoothTransitionLocalAvatarHandBoneRot, transform.rotation * HandBoneRelativeRot, t));
}
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
UxrAvatarRig.GetHandSide(transform, out UxrHandSide handSide);
Side = handSide;
if (Avatar != null)
{
// Compute grabber info
UxrGrabber[] avatarGrabbers = Avatar.GetComponentsInChildren<UxrGrabber>();
OppositeHandGrabber = avatarGrabbers.FirstOrDefault(g => g != null && Side != g.Side);
GrabbedObject = null;
// Compute throw physics info
if (Avatar.GetHand(handSide).GetPalmCenter(out Vector3 palmCenter) && Avatar.GetHand(handSide).GetPalmToFingerDirection(out Vector3 palmToFinger))
{
ThrowCenterOfMassLocalPosition = transform.InverseTransformPoint(palmCenter);
ThrowTipLocalPosition = transform.InverseTransformPoint(palmCenter + palmToFinger * ThrowAxisLength);
}
}
}
/// <summary>
/// Called when the object is destroyed. Releases any grabbed objects.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
if (GrabbedObject != null)
{
UxrGrabManager.Instance.ReleaseObject(this, GrabbedObject, true);
}
}
/// <summary>
/// Called when the object is disabled. Releases any grabbed objects.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
if (GrabbedObject != null)
{
UxrGrabManager.Instance.ReleaseObject(this, GrabbedObject, true);
}
}
#endregion
#region Private Methods
/// <summary>
/// Assigns the hand the grabber belongs to.
/// </summary>
private void InitializeSide()
{
if (UxrAvatarRig.GetHandSide(transform, out UxrHandSide handSide))
{
Side = handSide;
}
}
#endregion
#region Private Types & Data
/// <summary>
/// Gets the smooth manipulation transition interpolation value.
/// </summary>
private float SmoothManipulationT
{
get
{
if (SmoothManipulationTimer <= 0.0f)
{
return 1.0f;
}
return 1.0f - Mathf.Clamp01(SmoothManipulationTimer / UxrConstants.SmoothManipulationTransitionSeconds);
}
}
/// <summary>
/// Gets or sets the decreasing smooth manipulation transition timer.
/// </summary>
private float SmoothManipulationTimer { get; set; } = -1.0f;
/// <summary>
/// Gets or sets the position of the hand bone in local avatar space at the start of a smooth transition.
/// </summary>
private Vector3 SmoothTransitionLocalAvatarHandBonePos { get; set; }
/// <summary>
/// Gets or sets the rotation of the hand bone in local avatar space at the start of a smooth transition.
/// </summary>
private Quaternion SmoothTransitionLocalAvatarHandBoneRot { get; set; }
/// <summary>
/// Gets or sets the throw center of mass (palm center) in the grabber's local coordinate system.
/// </summary>
private Vector3 ThrowCenterOfMassLocalPosition { get; set; } = Vector3.zero;
/// <summary>
/// Gets or sets the throw center of mass (palm center) in the grabber local coordinate system.
/// </summary>
private Vector3 ThrowTipLocalPosition { get; set; } = Vector3.zero;
/// <summary>
/// Distance from the center of mass (palm) to the fingers to compute throw angular speed.
/// </summary>
private const float ThrowAxisLength = 0.1f;
/// <summary>
/// History physics sample window in seconds.
/// </summary>
private const float SampleWindowSeconds = 0.15f;
private readonly List<PhysicsSample> _physicsSampleWindow = new List<PhysicsSample>();
private bool _sideInitialized;
private UxrHandSide _side;
private UxrGrabbableObject _grabbedObject;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a89958cfe8683aa4b910cbe234c93f11
timeCreated: 1492964946
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,107 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGripPoseInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Manipulation.HandPoses;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Describes how an object is grabbed. It tells the pose that will be used and how it will be snapped to the hand.
/// The key is stored in the object, ideally we would have Dictionary(key, GripPoseInfo) but since Unity does not
/// serialize Dictionaries we use a List(GripPoseInfo) containing the key (<see cref="AvatarPrefabGuid" />) as well.
/// </summary>
[Serializable]
public class UxrGripPoseInfo
{
#region Inspector Properties/Serialized Fields
[SerializeField] private string _avatarPrefabGuid;
[SerializeField] private UxrHandPoseAsset _handPose;
[SerializeField] private float _poseBlendValue;
[SerializeField] private Transform _gripAlignTransformHandLeft;
[SerializeField] private Transform _gripAlignTransformHandRight;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the GUID of the avatar prefab the grip pose info belongs to.
/// </summary>
public string AvatarPrefabGuid => _avatarPrefabGuid;
/// <summary>
/// Gets or sets the left grab pose preview mesh.
/// </summary>
public Mesh GrabPoseMeshLeft { get; set; }
/// <summary>
/// Gets or sets the right grab pose preview mesh.
/// </summary>
public Mesh GrabPoseMeshRight { get; set; }
/// <summary>
/// Gets or sets the pose that will be used when grabbing.
/// </summary>
public UxrHandPoseAsset HandPose
{
get => _handPose;
set => _handPose = value;
}
/// <summary>
/// Gets or sets the pose blend value if the pose has the possibility of blending. Blending is used to blend between
/// open/closed grips or other animations.
/// </summary>
public float PoseBlendValue
{
get => _poseBlendValue;
set => _poseBlendValue = value;
}
/// <summary>
/// Gets or sets the <see cref="Transform" /> that will be used to align the object grab point to the left
/// <see cref="UxrGrabber" /> that grabbed it.
/// </summary>
public Transform GripAlignTransformHandLeft
{
get => _gripAlignTransformHandLeft;
set => _gripAlignTransformHandLeft = value;
}
/// <summary>
/// Gets or sets the <see cref="Transform" /> that will be used to align the object grab point to the right
/// <see cref="UxrGrabber" /> that grabbed it.
/// </summary>
public Transform GripAlignTransformHandRight
{
get => _gripAlignTransformHandRight;
set => _gripAlignTransformHandRight = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="avatarPrefabGuid">
/// Avatar prefab GUID. Using prefabs allows to share poses among instances and also prefab variants to inherit poses
/// from their parent prefabs in the chain
/// </param>
public UxrGripPoseInfo(string avatarPrefabGuid)
{
if (!string.IsNullOrEmpty(avatarPrefabGuid))
{
_avatarPrefabGuid = avatarPrefabGuid;
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b91d5f23482c403b95cc973ed54d668f
timeCreated: 1643287841

View File

@@ -0,0 +1,24 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHandSnapDirection.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Manipulation
{
/// <summary>
/// Enumerates the different ways snapping can be handled when grabbing an object.
/// For constrained objects, this will always be hand to the object
/// </summary>
public enum UxrHandSnapDirection
{
/// <summary>
/// The object will snap to the hand.
/// </summary>
ObjectToHand,
/// <summary>
/// The hand will snap to the object.
/// </summary>
HandToObject
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a3afe97ed32147e8b534c0a083cae927
timeCreated: 1643287266

View File

@@ -0,0 +1,580 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrManipulationEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core.Serialization;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// <para>
/// Event parameters for most manipulation events:
/// </para>
/// <see cref="UxrGrabManager" />:
/// <list type="bullet">
/// <item>
/// <see cref="UxrGrabManager.GrabTrying" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.ObjectGrabbing" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.ObjectGrabbed" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.ObjectReleasing" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.ObjectReleased" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.ObjectPlacing" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.ObjectPlaced" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.ObjectRemoving" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.ObjectRemoved" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.AnchorRangeEntered" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.AnchorRangeLeft" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.PlacedObjectRangeEntered" />
/// </item>
/// <item>
/// <see cref="UxrGrabManager.PlacedObjectRangeLeft" />
/// </item>
/// </list>
/// <see cref="UxrGrabbableObject" />:
/// <list type="bullet">
/// <item>
/// <see cref="UxrGrabbableObject.Grabbing" />
/// </item>
/// <item>
/// <see cref="UxrGrabbableObject.Grabbed" />
/// </item>
/// <item>
/// <see cref="UxrGrabbableObject.Releasing" />
/// </item>
/// <item>
/// <see cref="UxrGrabbableObject.Released" />
/// </item>
/// <item>
/// <see cref="UxrGrabbableObject.Placing" />
/// </item>
/// <item>
/// <see cref="UxrGrabbableObject.Placed" />
/// </item>
/// </list>
/// <see cref="UxrGrabbableObjectAnchor" />:
/// <list type="bullet">
/// <item>
/// <see cref="UxrGrabbableObjectAnchor.Placing" />
/// </item>
/// <item>
/// <see cref="UxrGrabbableObjectAnchor.Placed" />
/// </item>
/// <item>
/// <see cref="UxrGrabbableObjectAnchor.Removing" />
/// </item>
/// <item>
/// <see cref="UxrGrabbableObjectAnchor.Removed" />
/// </item>
/// <item>
/// <see cref="UxrGrabbableObjectAnchor.SmoothPlaceTransitionEnded" />
/// </item>
/// </list>
/// </summary>
public class UxrManipulationEventArgs : EventArgs, IUxrSerializable
{
#region Public Types & Data
/// <summary>
/// Gets whether the manipulation changed an object from not being grabbed at all to being grabbed or vice-versa.
/// This is useful to filter out grabs or releases on an object that is still being grabbed using another hand.<br/>
/// <see cref="IsGrabbedStateChanged" /> is true if <see cref="IsMultiHands" /> and <see cref="IsSwitchHands" /> are
/// both false.
/// </summary>
public bool IsGrabbedStateChanged => !IsMultiHands && !IsSwitchHands;
/// <summary>
/// The type of event.
/// </summary>
public UxrManipulationEventType EventType
{
get => _eventType;
private set => _eventType = value;
}
/// <summary>
/// Gets the grabbable object related to the event. Can be null if the event doesn't use this property. Check the event
/// documentation to see how the property is used.
/// </summary>
public UxrGrabbableObject GrabbableObject
{
get => _grabbableObject;
private set => _grabbableObject = value;
}
/// <summary>
/// Gets the grabbable object anchor related to the event. Can be null if the event doesn't use this property. Check
/// the event documentation to see how the property is used.
/// </summary>
public UxrGrabbableObjectAnchor GrabbableAnchor
{
get => _grabbableAnchor;
private set => _grabbableAnchor = value;
}
/// <summary>
/// Gets the grabber related to the event. Can be null if the event doesn't use this property. Check the event
/// documentation to see how the property is used.
/// </summary>
public UxrGrabber Grabber
{
get => _grabber;
private set => _grabber = value;
}
/// <summary>
/// Gets the grabbable object's grab point index related to the event. Can be meaningless if the event doesn't use this
/// property. Check the event documentation to see how the property is used.
/// </summary>
public int GrabPointIndex
{
get => _grabPointIndex;
private set => _grabPointIndex = value;
}
/// <summary>
/// Gets whether the manipulation used more than one hand. Can be meaningless if the event doesn't use this property.
/// Check the event documentation to see how the property is used.
/// </summary>
public bool IsMultiHands
{
get => _isMultiHands;
private set => _isMultiHands = value;
}
/// <summary>
/// Gets whether the event was the result of passing the object from one hand to the other. Can be meaningless if the
/// event doesn't use this property. Check the event documentation to see how the property is used.
/// </summary>
public bool IsSwitchHands
{
get => _isSwitchHands;
private set => _isSwitchHands = value;
}
/// <summary>
/// Gets the release velocity for release events.
/// </summary>
public Vector3 ReleaseVelocity
{
get => _releaseVelocity;
private set => _releaseVelocity = value;
}
/// <summary>
/// Gets the release angular velocity for release events.
/// </summary>
public Vector3 ReleaseAngularVelocity
{
get => _releaseAngularVelocity;
private set => _releaseAngularVelocity = value;
}
/// <summary>
/// Gets the placement flags in place events.
/// </summary>
public UxrPlacementOptions PlacementOptions
{
get => _placementOptions;
private set => _placementOptions = value;
}
#endregion
#region Internal Types & Data
/// <summary>
/// Gets the UxrGrabbableObject position in local UxrGrabber space at the moment of grabbing.
/// This is used in multi-player environments to make sure to reproduce the same grab action.
/// </summary>
internal Vector3 GrabberLocalObjectPosition
{
get => _grabberLocalObjectPosition;
private set => _grabberLocalObjectPosition = value;
}
/// <summary>
/// Gets the UxrGrabbableObject rotation in local UxrGrabber space at the moment of grabbing.
/// This is used in multi-player environments to make sure to reproduce the same grab action.
/// </summary>
internal Quaternion GrabberLocalObjectRotation
{
get => _grabberLocalObjectRotation;
private set => _grabberLocalObjectRotation = value;
}
/// <summary>
/// Gets the grab snap position in local UxrGrabber space at the moment of grabbing.
/// This is used in multi-player environments to make sure to reproduce the same grab action.
/// </summary>
internal Vector3 GrabberLocalSnapPosition
{
get => _grabberLocalSnapPosition;
private set => _grabberLocalSnapPosition = value;
}
/// <summary>
/// Gets the grab snap rotation in local UxrGrabber space at the moment of grabbing.
/// This is used in multi-player environments to make sure to reproduce the same grab action.
/// </summary>
internal Quaternion GrabberLocalSnapRotation
{
get => _grabberLocalSnapRotation;
private set => _grabberLocalSnapRotation = value;
}
/// <summary>
/// Gets the release start position for release events.
/// </summary>
internal Vector3 ReleaseStartPosition
{
get => _releaseStartPosition;
private set => _releaseStartPosition = value;
}
/// <summary>
/// Gets the release start rotation for release events.
/// </summary>
internal Quaternion ReleaseStartRotation
{
get => _releaseStartRotation;
private set => _releaseStartRotation = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Default constructor is private.
/// </summary>
private UxrManipulationEventArgs()
{
}
/// <summary>
/// Constructor that initializes the event type.
/// </summary>
private UxrManipulationEventArgs(UxrManipulationEventType eventType)
{
EventType = eventType;
}
#endregion
#region Explicit IUxrSerializable
/// <inheritdoc />
int IUxrSerializable.SerializationVersion => 0;
/// <inheritdoc />
void IUxrSerializable.Serialize(IUxrSerializer serializer, int serializationVersion)
{
serializer.SerializeEnum(ref _eventType);
serializer.SerializeUniqueComponent(ref _grabbableObject);
serializer.SerializeUniqueComponent(ref _grabbableAnchor);
serializer.SerializeUniqueComponent(ref _grabber);
serializer.Serialize(ref _grabPointIndex);
serializer.Serialize(ref _isMultiHands);
serializer.Serialize(ref _isSwitchHands);
if (EventType == UxrManipulationEventType.Grab)
{
serializer.Serialize(ref _grabberLocalSnapPosition);
serializer.Serialize(ref _grabberLocalSnapRotation);
serializer.Serialize(ref _grabberLocalObjectPosition);
serializer.Serialize(ref _grabberLocalObjectRotation);
}
else if (EventType == UxrManipulationEventType.Release)
{
serializer.Serialize(ref _releaseStartPosition);
serializer.Serialize(ref _releaseStartRotation);
serializer.Serialize(ref _releaseVelocity);
serializer.Serialize(ref _releaseAngularVelocity);
}
else if (EventType == UxrManipulationEventType.Place)
{
serializer.SerializeEnum(ref _placementOptions);
}
}
#endregion
#region Public Methods
/// <summary>
/// Constructor for Grab events.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="grabbableAnchor">Grabbable object anchor</param>
/// <param name="grabber">Grabber</param>
/// <param name="grabPointIndex">Grab point index</param>
/// <param name="isMultiHands">Whether the object was already grabbed with one or more hands</param>
/// <param name="isSwitchHands">Whether the event was a result of passing the grabbable object from one hand to the other</param>
/// <param name="grabberLocalSnapPosition">Grab snap position in local UxrGrabber space at the moment of grabbing</param>
/// <param name="grabberLocalSnapRotation">Grab snap rotation in local UxrGrabber space at the moment of grabbing</param>
public static UxrManipulationEventArgs FromGrab(UxrGrabbableObject grabbableObject,
UxrGrabbableObjectAnchor grabbableAnchor,
UxrGrabber grabber,
int grabPointIndex,
bool isMultiHands,
bool isSwitchHands,
Vector3 grabberLocalSnapPosition,
Quaternion grabberLocalSnapRotation)
{
UxrManipulationEventArgs eventArgs = new UxrManipulationEventArgs(UxrManipulationEventType.Grab);
eventArgs.GrabbableObject = grabbableObject;
eventArgs.GrabbableAnchor = grabbableAnchor;
eventArgs.Grabber = grabber;
eventArgs.GrabPointIndex = grabPointIndex;
eventArgs.IsMultiHands = isMultiHands;
eventArgs.IsSwitchHands = isSwitchHands;
// Internal vars
if (grabbableObject != null && grabber != null)
{
eventArgs.GrabberLocalSnapPosition = grabberLocalSnapPosition;
eventArgs.GrabberLocalSnapRotation = grabberLocalSnapRotation;
eventArgs.GrabberLocalObjectPosition = grabber.transform.InverseTransformPoint(grabbableObject.transform.position);
eventArgs.GrabberLocalObjectRotation = Quaternion.Inverse(grabber.transform.rotation) * grabbableObject.transform.rotation;
}
return eventArgs;
}
/// <summary>
/// Constructor for Release events.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="grabbableAnchor">Grabbable object anchor</param>
/// <param name="grabber">Grabber</param>
/// <param name="grabPointIndex">Grab point index</param>
/// <param name="isMultiHands">Whether the object will still be grabbed with one or more hands</param>
/// <param name="isSwitchHands">Whether the event was a result of passing the grabbable object from one hand to the other</param>
/// <param name="releaseVelocity">The release velocity</param>
/// <param name="releaseAngularVelocity">The release angular velocity</param>
public static UxrManipulationEventArgs FromRelease(UxrGrabbableObject grabbableObject,
UxrGrabbableObjectAnchor grabbableAnchor,
UxrGrabber grabber,
int grabPointIndex,
bool isMultiHands,
bool isSwitchHands,
Vector3 releaseVelocity = default(Vector3),
Vector3 releaseAngularVelocity = default(Vector3))
{
UxrManipulationEventArgs eventArgs = new UxrManipulationEventArgs(UxrManipulationEventType.Release);
eventArgs.GrabbableObject = grabbableObject;
eventArgs.GrabbableAnchor = grabbableAnchor;
eventArgs.Grabber = grabber;
eventArgs.GrabPointIndex = grabPointIndex;
eventArgs.IsMultiHands = isMultiHands;
eventArgs.IsSwitchHands = isSwitchHands;
eventArgs.ReleaseVelocity = releaseVelocity;
eventArgs.ReleaseAngularVelocity = releaseAngularVelocity;
// Internal vars
if (grabbableObject != null)
{
eventArgs.ReleaseStartPosition = grabbableObject.transform.position;
eventArgs.ReleaseStartRotation = grabbableObject.transform.rotation;
}
return eventArgs;
}
/// <summary>
/// Constructor for Place events.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="grabbableAnchor">Grabbable object anchor</param>
/// <param name="grabber">Grabber</param>
/// <param name="grabPointIndex">Grab point index</param>
/// <param name="placementOptions">The placement options flags</param>
public static UxrManipulationEventArgs FromPlace(UxrGrabbableObject grabbableObject,
UxrGrabbableObjectAnchor grabbableAnchor,
UxrGrabber grabber,
int grabPointIndex,
UxrPlacementOptions placementOptions)
{
UxrManipulationEventArgs eventArgs = new UxrManipulationEventArgs(UxrManipulationEventType.Place);
eventArgs.GrabbableObject = grabbableObject;
eventArgs.GrabbableAnchor = grabbableAnchor;
eventArgs.Grabber = grabber;
eventArgs.GrabPointIndex = grabPointIndex;
eventArgs.IsMultiHands = false;
eventArgs.IsSwitchHands = false;
eventArgs.PlacementOptions = placementOptions;
return eventArgs;
}
/// <summary>
/// Constructor for Release events.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="grabbableAnchor">Grabbable object anchor</param>
/// <param name="grabber">Grabber</param>
/// <param name="grabPointIndex">Grab point index</param>
/// <param name="isMultiHands">Whether the event was a result of a manipulation with more than one hand</param>
/// <param name="isSwitchHands">Whether the event was a result of passing the grabbable object from one hand to the other</param>
public static UxrManipulationEventArgs FromRemove(UxrGrabbableObject grabbableObject,
UxrGrabbableObjectAnchor grabbableAnchor,
UxrGrabber grabber,
int grabPointIndex = 0,
bool isMultiHands = false,
bool isSwitchHands = false)
{
UxrManipulationEventArgs eventArgs = new UxrManipulationEventArgs(UxrManipulationEventType.Remove);
eventArgs.GrabbableObject = grabbableObject;
eventArgs.GrabbableAnchor = grabbableAnchor;
eventArgs.Grabber = grabber;
eventArgs.GrabPointIndex = grabPointIndex;
eventArgs.IsMultiHands = isMultiHands;
eventArgs.IsSwitchHands = isSwitchHands;
return eventArgs;
}
/// <summary>
/// Constructor for PlacedObjectRangeEntered/Left, AnchorRangeEntered/Left and GrabTrying events.
/// </summary>
/// <param name="grabbableObject">Grabbable object</param>
/// <param name="grabbableAnchor">Grabbable object anchor</param>
/// <param name="grabber">Grabber</param>
/// <param name="grabPointIndex">Grab point index</param>
/// <param name="isMultiHands">Whether the event was a result of a manipulation with more than one hand</param>
/// <param name="isSwitchHands">Whether the event was a result of passing the grabbable object from one hand to the other</param>
public static UxrManipulationEventArgs FromOther(UxrManipulationEventType eventType,
UxrGrabbableObject grabbableObject,
UxrGrabbableObjectAnchor grabbableAnchor,
UxrGrabber grabber,
int grabPointIndex = 0,
bool isMultiHands = false,
bool isSwitchHands = false)
{
UxrManipulationEventArgs eventArgs = new UxrManipulationEventArgs(eventType);
eventArgs.GrabbableObject = grabbableObject;
eventArgs.GrabbableAnchor = grabbableAnchor;
eventArgs.Grabber = grabber;
eventArgs.GrabPointIndex = grabPointIndex;
eventArgs.IsMultiHands = isMultiHands;
eventArgs.IsSwitchHands = isSwitchHands;
return eventArgs;
}
/// <summary>
/// Gets a string that describes the event.
/// </summary>
/// <param name="includeIds">Whether to include information of the component unique IDs</param>
/// <returns>String with a description of the event</returns>
public string ToString(bool includeIds = false)
{
switch (EventType)
{
case UxrManipulationEventType.Grab: return $"Grabbing {GetGrabbableObjectLogInfo(this, includeIds)}{GetGrabberLogInfo(this, includeIds)}";
case UxrManipulationEventType.Release: return $"Releasing {GetGrabbableObjectLogInfo(this, includeIds)}{GetGrabberLogInfo(this, includeIds)}";
case UxrManipulationEventType.Place: return $"Placing {GetGrabbableObjectLogInfo(this, includeIds)} on {GetAnchorLogInfo(this, includeIds)}{GetGrabberLogInfo(this, includeIds)}";
case UxrManipulationEventType.Remove: return $"Removing {GetGrabbableObjectLogInfo(this, includeIds)} from {GetAnchorLogInfo(this, includeIds)}{GetGrabberLogInfo(this, includeIds)}";
}
return "Unknown event";
}
#endregion
#region Private Methods
/// <summary>
/// Gets the log string describing the grabber.
/// </summary>
/// <param name="e">Event with the grabber to log</param>
/// <param name="includeIds">Whether to include information of the component unique IDs</param>
/// <returns>Log string</returns>
private static string GetGrabberLogInfo(UxrManipulationEventArgs e, bool includeIds)
{
string id = e.Grabber != null && includeIds ? $" (id {e.Grabber.UniqueId})" : string.Empty;
return e.Grabber != null ? $" using {e.Grabber}{id}" : string.Empty;
}
/// <summary>
/// Gets the log string describing the grabber.
/// </summary>
/// <param name="e">Event with the grabber to log</param>
/// <param name="includeIds">Whether to include information of the component unique IDs</param>
/// <returns>Log string</returns>
private static string GetAnchorLogInfo(UxrManipulationEventArgs e, bool includeIds)
{
string id = e.GrabbableAnchor != null && includeIds ? $" (id {e.GrabbableAnchor.UniqueId})" : string.Empty;
return e.GrabbableAnchor != null ? $"{e.GrabbableAnchor.name}{id}" : string.Empty;
}
/// <summary>
/// Gets the log string describing the grabbable object.
/// </summary>
/// <param name="e">Event with the grabbable object to log</param>
/// <param name="includeIds">Whether to include information of the component unique IDs</param>
/// <returns>Log string</returns>
private static string GetGrabbableObjectLogInfo(UxrManipulationEventArgs e, bool includeIds)
{
string id = e.GrabbableObject != null && includeIds ? $" (id {e.GrabbableObject.UniqueId})" : string.Empty;
string grabPointInfo = e.GrabbableObject != null && e.GrabbableObject.GrabPointCount > 1 && e.GrabPointIndex >= 0 ? $" (grab point {e.GrabPointIndex})" : string.Empty;
return e.GrabbableObject != null ? $"{e.GrabbableObject.name}{id}{grabPointInfo}" : string.Empty;
}
#endregion
#region Private Types & Data
private UxrManipulationEventType _eventType;
private UxrGrabbableObject _grabbableObject;
private UxrGrabbableObjectAnchor _grabbableAnchor;
private UxrGrabber _grabber;
private int _grabPointIndex;
private bool _isMultiHands;
private bool _isSwitchHands;
private Vector3 _releaseVelocity;
private Vector3 _releaseAngularVelocity;
private UxrPlacementOptions _placementOptions;
private Vector3 _grabberLocalObjectPosition;
private Quaternion _grabberLocalObjectRotation;
private Vector3 _grabberLocalSnapPosition;
private Quaternion _grabberLocalSnapRotation;
private Vector3 _releaseStartPosition;
private Quaternion _releaseStartRotation;
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c45448cc1fe54193ab3ce3edf21184c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrManipulationEventType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Manipulation
{
/// <summary>
/// Enumerates the different manipulation event types.
/// </summary>
public enum UxrManipulationEventType
{
Grab,
Release,
Place,
Remove,
GrabTrying,
AnchorRangeEntered,
AnchorRangeLeft,
PlacedObjectRangeEntered,
PlacedObjectRangeLeft
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 49a06d54a230465b91af5ffa68d67600
timeCreated: 1650790693

View File

@@ -0,0 +1,59 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrManipulationFeatures.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Enumerates the different manipulation features that can be used when the <see cref="UxrGrabManager" /> is being
/// updated.
/// </summary>
[Flags]
public enum UxrManipulationFeatures
{
/// <summary>
/// Update the transform of <see cref="UxrGrabbableObject" /> objects based on user interactions using grabbers.
/// </summary>
ObjectManipulation = 1 << 0,
/// <summary>
/// Applies constraints defined in the <see cref="UxrGrabbableObject" /> component.
/// </summary>
ObjectConstraints = 1 << 1,
/// <summary>
/// Applies resistance defined by <see cref="UxrGrabbableObject.TranslationResistance" /> and
/// <see cref="UxrGrabbableObject.RotationResistance" />.
/// </summary>
ObjectResistance = 1 << 2,
/// <summary>
/// Applies constraints defined by users through <see cref="UxrGrabbableObject.ConstraintsApplying" />/
/// <see cref="UxrGrabbableObject.ConstraintsApplied" />/<see cref="UxrGrabbableObject.ConstraintsFinished" />.
/// </summary>
UserConstraints = 1 << 3,
/// <summary>
/// Forces to keep the grips in place to avoid hands drifting from an object that has constraints applied.
/// </summary>
KeepGripsInPlace = 1 << 4,
/// <summary>
/// Smooth transitions in grabbing hands and objects that are being manipulated.
/// </summary>
SmoothTransitions = 1 << 5,
/// <summary>
/// Updates the affordances.
/// </summary>
Affordances = 1 << 6,
/// <summary>
/// Uses all features.
/// </summary>
All = 0x7FFFFFFF
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 57b24cb1c4044e20b5d05970658e667a
timeCreated: 1709380090

View File

@@ -0,0 +1,42 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrPlacementOptions.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Enumerates the different ways a <see cref="UxrGrabbableObject" /> can transition when being placed on an
/// <see cref="UxrGrabbableObjectAnchor" />.
/// </summary>
[Flags]
public enum UxrPlacementOptions
{
/// <summary>
/// Place immediately. If the object is being grabbed, release it.
/// </summary>
None = 0,
/// <summary>
/// Place using smooth transition (interpolation).
/// </summary>
Smooth = 1 << 0,
/// <summary>
/// Do not release the object when placing.
/// </summary>
DontRelease = 1 << 1,
/// <summary>
/// Overrides <see cref="UxrSnapToAnchorMode"/> so that it forces to snap the position.
/// </summary>
ForceSnapPosition = 1 << 2,
/// <summary>
/// Overrides <see cref="UxrSnapToAnchorMode"/> so that it forces to snap the rotation.
/// </summary>
ForceSnapRotation = 1 << 3
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1110ad82fc5745bcb1e9e1d1251915ac
timeCreated: 1643645683

Some files were not shown because too many files have changed in this diff Show More