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

308 lines
14 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAvatarArmInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core;
using UltimateXR.Core.Math;
using UltimateXR.Extensions.Unity.Math;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Avatar.Rig
{
/// <summary>
/// Stores information of an avatar rig's arm.
/// </summary>
[Serializable]
public class UxrAvatarArmInfo
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrAvatar _avatar;
[SerializeField] private UxrHandSide _side;
[SerializeField] private float _upperArmLength;
[SerializeField] private float _forearmLength;
[SerializeField] private UxrUniversalLocalAxes _fingerUniversalLocalAxes;
[SerializeField] private UxrUniversalLocalAxes _handUniversalLocalAxes;
[SerializeField] private UxrUniversalLocalAxes _armUniversalLocalAxes;
[SerializeField] private UxrUniversalLocalAxes _forearmUniversalLocalAxes;
[SerializeField] private UxrUniversalLocalAxes _clavicleUniversalLocalAxes;
[SerializeField] private UxrAvatarFingerInfo _thumbInfo;
[SerializeField] private UxrAvatarFingerInfo _indexInfo;
[SerializeField] private UxrAvatarFingerInfo _middleInfo;
[SerializeField] private UxrAvatarFingerInfo _ringInfo;
[SerializeField] private UxrAvatarFingerInfo _littleInfo;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the thumb finger information.
/// </summary>
public UxrAvatarFingerInfo ThumbInfo => _thumbInfo;
/// <summary>
/// Gets the index finger information.
/// </summary>
public UxrAvatarFingerInfo IndexInfo => _indexInfo;
/// <summary>
/// Gets the middle finger information.
/// </summary>
public UxrAvatarFingerInfo MiddleInfo => _middleInfo;
/// <summary>
/// Gets the ring finger information.
/// </summary>
public UxrAvatarFingerInfo RingInfo => _ringInfo;
/// <summary>
/// Gets the little finger information.
/// </summary>
public UxrAvatarFingerInfo LittleInfo => _littleInfo;
/// <summary>
/// Enumerates all the finger information.
/// </summary>
public IEnumerable<UxrAvatarFingerInfo> Fingers
{
get
{
yield return _thumbInfo;
yield return _indexInfo;
yield return _middleInfo;
yield return _ringInfo;
yield return _littleInfo;
}
}
/// <summary>
/// Gets the upper arm length. From shoulder to elbow.
/// </summary>
public float UpperArmLength
{
get => _upperArmLength;
private set => _upperArmLength = value;
}
/// <summary>
/// Gets the forearm length. From elbow to wrist.
/// </summary>
public float ForearmLength
{
get => _forearmLength;
private set => _forearmLength = value;
}
/// <summary>
/// Gets the universal coordinate system for the fingers: right = axis around which the finger curls, up =
/// knuckles up, forward = finger direction.
/// </summary>
public UxrUniversalLocalAxes FingerUniversalLocalAxes
{
get => _fingerUniversalLocalAxes;
private set => _fingerUniversalLocalAxes = value;
}
/// <summary>
/// Gets the universal coordinate system for the hand: right = from wrist to right (thumb direction), up = -palm
/// facing vector, forward = finger direction.
/// </summary>
public UxrUniversalLocalAxes HandUniversalLocalAxes
{
get => _handUniversalLocalAxes;
private set => _handUniversalLocalAxes = value;
}
/// <summary>
/// Gets the universal coordinate system for the arm: forward is arm->elbow, up is elbow rotation axis
/// </summary>
public UxrUniversalLocalAxes ArmUniversalLocalAxes
{
get => _armUniversalLocalAxes;
private set => _armUniversalLocalAxes = value;
}
/// <summary>
/// Gets the universal coordinate system for the forearm: forward is arm->hand, up is elbow rotation axis
/// </summary>
public UxrUniversalLocalAxes ForearmUniversalLocalAxes
{
get => _forearmUniversalLocalAxes;
private set => _forearmUniversalLocalAxes = value;
}
/// <summary>
/// Gets the universal coordinate system for the clavicle: forward is clavicle->arm, up is avatar up axis
/// </summary>
public UxrUniversalLocalAxes ClavicleUniversalLocalAxes
{
get => _clavicleUniversalLocalAxes;
private set => _clavicleUniversalLocalAxes = value;
}
// Updated every frame
/// <summary>
/// Gets the wrist torsion info.
/// </summary>
public UxrWristTorsionInfo WristTorsionInfo { get; private set; } = new UxrWristTorsionInfo();
#endregion
#region Internal Methods
/// <summary>
/// Computes and stores all the arm information of an avatar.
/// </summary>
/// <param name="avatar">Avatar whose arm to compute the information of</param>
/// <param name="side">Which side to compute</param>
internal void Compute(UxrAvatar avatar, UxrHandSide side)
{
_avatar = avatar;
_side = side;
SolveHandAndFingerAxes(avatar.GetHand(side), side);
ComputeArmRigInfo(avatar, avatar.GetArm(side), side);
}
#endregion
#region Private Methods
/// <summary>
/// Gets the outwards-pointing elbow rotation axis in world coordinates.
/// </summary>
/// <param name="avatar">The avatar the arm nodes belong to</param>
/// <param name="forearm">The arm's forearm transform</param>
/// <param name="armForward">The arm forward looking vector, the one pointing from shoulder to elbow</param>
/// <param name="forearmForward">The forearm forward looking vector, the one pointing from elbow to hand</param>
/// <returns>Elbow rotation axis</returns>
private static Vector3 GetWorldElbowAxis(UxrAvatar avatar, Transform forearm, Vector3 armForward, Vector3 forearmForward)
{
bool isLeft = avatar.transform.InverseTransformPoint(forearm.position).x < 0.0f;
float elbowAngle = Vector3.Angle(armForward, forearmForward);
Vector3 elbowAxis = Vector3.Cross(forearmForward, isLeft ? -armForward : armForward).normalized;
Transform leftHand = avatar.GetHandBone(UxrHandSide.Left);
Transform rightHand = avatar.GetHandBone(UxrHandSide.Right);
if (leftHand && rightHand)
{
Vector3 avatarRight = (rightHand.position - leftHand.position).normalized;
Vector3 forward = Vector3.Cross(avatarRight, Vector3.up);
elbowAxis = Vector3.Cross(isLeft ? forearmForward : -forearmForward, forward).normalized;
}
else if (elbowAngle < ElbowMinAngleThreshold)
{
// Assume T-pose if elbow angle is too small
elbowAxis = Vector3.up;
}
return forearm.TransformDirection(forearm.InverseTransformDirection(elbowAxis).GetClosestAxis());
}
/// <summary>
/// Tries to find out which axes are pointing right/up/forward in the hand and finger nodes. These "universal" axes
/// will be used to rotate the nodes, so that any coordinate system can be used no matter how the hand was authored.
/// </summary>
/// <param name="hand">The hand to compute the axes for</param>
/// <param name="side">Whether it is a left hand or right hand</param>
/// <returns>Boolean telling whether the axes could be solved. If any necessary transform is missing it will fail</returns>
private bool SolveHandAndFingerAxes(UxrAvatarHand hand, UxrHandSide side)
{
Transform indexProximal = hand.Index.Proximal;
Transform indexDistal = hand.Index.Distal;
Transform middleProximal = hand.Middle.Proximal;
Transform ringProximal = hand.Ring.Proximal;
if (!hand.Wrist || !indexProximal || !indexDistal || !middleProximal || !ringProximal)
{
return false;
}
float handCenter = 0.5f; // [0, 1]
Vector3 handAxesRight = hand.Wrist.InverseTransformDirection(indexProximal.position - middleProximal.position).GetClosestAxis() * (side == UxrHandSide.Left ? 1.0f : -1.0f);
Vector3 handAxesForward = hand.Wrist.InverseTransformDirection((Vector3.Lerp(ringProximal.position, middleProximal.position, handCenter) - hand.Wrist.position).normalized);
Vector3 handAxesUp = Vector3.Cross(handAxesForward, handAxesRight).normalized;
handAxesRight = Vector3.Cross(handAxesUp, handAxesForward).normalized;
HandUniversalLocalAxes = UxrUniversalLocalAxes.FromAxes(hand.Wrist, handAxesRight, handAxesUp, handAxesForward);
Vector3 fingerAxesRight = indexProximal.InverseTransformDirection(indexProximal.position - middleProximal.position).GetClosestAxis() * (side == UxrHandSide.Left ? 1.0f : -1.0f);
Vector3 fingerAxesForward = indexProximal.InverseTransformDirection(indexDistal.position - indexProximal.position).GetClosestAxis();
Vector3 fingerAxesUp = Vector3.Cross(fingerAxesForward, fingerAxesRight);
FingerUniversalLocalAxes = UxrUniversalLocalAxes.FromAxes(indexProximal, fingerAxesRight, fingerAxesUp, fingerAxesForward);
return true;
}
/// <summary>
/// Computes and stores information of the arm's rig.
/// </summary>
/// <param name="avatar">Avatar whose right arm to compute information of</param>
/// <param name="arm">Arm to compute the information of</param>
/// <param name="side">Which side it is</param>
private void ComputeArmRigInfo(UxrAvatar avatar, UxrAvatarArm arm, UxrHandSide side)
{
if (arm.UpperArm != null && arm.Forearm != null && arm.Hand.Wrist != null)
{
Vector3 armForward = (arm.Forearm.position - arm.UpperArm.position).normalized;
Vector3 forearmForward = (arm.Hand.Wrist.position - arm.Forearm.position).normalized;
Vector3 elbowAxis = GetWorldElbowAxis(avatar, arm.Forearm, armForward, forearmForward);
Vector3 armLocalForward = arm.UpperArm.InverseTransformDirection(armForward).GetClosestAxis();
Vector3 armLocalElbowAxis = arm.UpperArm.InverseTransformDirection(elbowAxis).GetClosestAxis();
Vector3 forearmLocalForward = arm.Forearm.InverseTransformDirection(forearmForward).GetClosestAxis();
Vector3 forearmLocalElbowAxis = arm.Forearm.InverseTransformDirection(elbowAxis).GetClosestAxis();
ArmUniversalLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.UpperArm, armLocalElbowAxis, armLocalForward);
ForearmUniversalLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.Forearm, armLocalElbowAxis, forearmLocalForward);
UpperArmLength = Vector3.Distance(arm.UpperArm.position, arm.Forearm.position);
ForearmLength = Vector3.Distance(arm.Forearm.position, arm.Hand.Wrist.position);
if (arm.Clavicle != null)
{
Vector3 clavicleForward = (arm.UpperArm.position - arm.Clavicle.position).normalized;
Vector3 clavicleLocalForwardAxis = arm.Clavicle.InverseTransformDirection(clavicleForward).GetClosestAxis();
Vector3 clavicleLocalUpAxis = arm.Clavicle.InverseTransformDirection(avatar.transform.up).GetClosestAxis();
ClavicleUniversalLocalAxes = UxrUniversalLocalAxes.FromUpForward(arm.Clavicle, clavicleLocalUpAxis, clavicleLocalForwardAxis);
}
}
UxrGrabber grabber = avatar.GetGrabber(side);
if (grabber && grabber.HandRenderer && grabber.HandRenderer is SkinnedMeshRenderer handRenderer)
{
_thumbInfo = new UxrAvatarFingerInfo();
_indexInfo = new UxrAvatarFingerInfo();
_middleInfo = new UxrAvatarFingerInfo();
_ringInfo = new UxrAvatarFingerInfo();
_littleInfo = new UxrAvatarFingerInfo();
_thumbInfo.Compute(avatar, handRenderer, side, UxrFingerType.Thumb);
_indexInfo.Compute(avatar, handRenderer, side, UxrFingerType.Index);
_middleInfo.Compute(avatar, handRenderer, side, UxrFingerType.Middle);
_ringInfo.Compute(avatar, handRenderer, side, UxrFingerType.Ring);
_littleInfo.Compute(avatar, handRenderer, side, UxrFingerType.Little);
}
}
#endregion
#region Private Types & Data
/// <summary>
/// Minimum angle between arm and forearm to compute elbow axis using cross product.
/// </summary>
private const float ElbowMinAngleThreshold = 3.0f;
#endregion
}
}