// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core.Math;
using UnityEngine;
namespace UltimateXR.Manipulation.HandPoses
{
///
/// 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.
///
[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
///
/// Gets the original relative transform to the hand bone. We use it mainly to compute
/// preview meshes more conveniently.
///
public Matrix4x4 TransformRelativeToHand => _transformRelativeToHand;
///
/// Gets the universal right vector. The vector that points in our well-known right direction, in the coordinate system
/// of the finger.
///
public Vector3 Right => _right;
///
/// Gets the universal up vector. The vector that points in our well-known up direction, in the coordinate system of
/// the finger.
///
public Vector3 Up => _up;
///
/// Gets the universal forward vector. The vector that points in our well-known forward direction, in the coordinate
/// system of the finger.
///
public Vector3 Forward => _forward;
#endregion
#region Constructors & Finalizer
///
/// Creates a well-known axes system for a node, to handle transforms independently of the coordinate system being used
/// by a hand rig.
///
/// Hand node
/// Parent node
/// Current node being created
///
/// In local coordinates, which parent axes point to the well-known right, up and forward directions
///
///
/// In local coordinates, which node axes point to the well-known right, up and forward directions
///
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
///
/// Creates a well-known axes system for a node, to handle transforms independently of the coordinate system being used
/// by a hand rig.
///
/// Hand node
/// Parent node
/// Current node being created
///
/// In local coordinates, which parent axes point to the well-known right, up and forward
/// directions
///
///
/// In local coordinates, which node axes point to the well-known right, up and forward
/// directions
///
/// Whether to compute only the value
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));
}
}
///
/// Mirrors the descriptor. Useful to switch between left and right hand data.
///
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;
}
///
/// Interpolates the axes data towards another descriptor.
///
/// Descriptor to interpolate the data to
/// Interpolation factor [0.0, 1.0]
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);
}
///
/// Checks if the content of two FingerNodeDescriptors is equal (they describe the same axes).
///
/// UxrFingerNodeDescriptor to compare it to
/// Boolean telling if the two FingerNodeDescriptors describe the same axes
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
///
/// 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.
///
/// Vector A
/// Vector B
/// Inequality value
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
}
}