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

1319 lines
55 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAvatarRig.ReferenceSolving.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Animation.IK;
using UltimateXR.Core;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.Unity;
using UltimateXR.Extensions.Unity.Render;
using UnityEngine;
namespace UltimateXR.Avatar.Rig
{
partial class UxrAvatarRig
{
#region Public Methods
/// <summary>
/// Tries to get the <see cref="SkinnedMeshRenderer" /> that represents the given hand.
/// </summary>
/// <param name="avatar">Avatar</param>
/// <param name="handSide">Which hand side to retrieve</param>
/// <returns>The renderer if found or null</returns>
public static SkinnedMeshRenderer TryToGetHandRenderer(UxrAvatar avatar, UxrHandSide handSide)
{
if (avatar == null)
{
return null;
}
SkinnedMeshRenderer mostInfluentialSkin = null;
int maxInfluenceCount = 0;
foreach (SkinnedMeshRenderer skin in avatar.GetAllAvatarRendererComponents())
{
if (!skin.gameObject.activeInHierarchy)
{
continue;
}
int influenceCount = 0;
foreach (Transform bone in avatar.GetHand(handSide).Transforms)
{
influenceCount += MeshExt.GetBoneInfluenceVertexCount(skin, bone);
}
if (influenceCount > maxInfluenceCount)
{
maxInfluenceCount = influenceCount;
mostInfluentialSkin = skin;
}
}
return mostInfluentialSkin;
}
/// <summary>
/// Tries to solve which bones from a <see cref="SkinnedMeshRenderer" /> are remaining parts of the arm that still have
/// no references.
/// </summary>
/// <param name="arm">Arm to solve</param>
/// <param name="skin">Source skin to navigate the bones looking for missing elements that are not in the arm</param>
public static void TryToResolveArm(UxrAvatarArm arm, SkinnedMeshRenderer skin)
{
// First top to bottom pass
bool handResolveTried = false;
if (arm.Clavicle != null)
{
if (handResolveTried == false)
{
TryToResolveHand(arm.Hand, arm.Clavicle, arm.Clavicle, skin);
handResolveTried = true;
}
if (arm.UpperArm == null)
{
if (arm.Hand.Wrist != null)
{
arm.UpperArm = GetChildTransformWithNodeUnderHierarchy(arm.Clavicle, arm.Hand.Wrist);
}
else
{
arm.UpperArm = GetNextLimbBoneIfOnlyOne(arm.Clavicle, skin);
}
}
}
if (arm.UpperArm != null)
{
if (handResolveTried == false)
{
TryToResolveHand(arm.Hand, arm.UpperArm, arm.UpperArm, skin);
handResolveTried = true;
}
if (arm.Forearm == null)
{
if (arm.Hand.Wrist != null)
{
arm.UpperArm = GetChildTransformWithNodeUnderHierarchy(arm.UpperArm, arm.Hand.Wrist);
}
else
{
arm.Forearm = GetNextLimbBoneIfOnlyOne(arm.UpperArm, skin);
}
}
}
else
{
if (arm.Hand.Wrist != null)
{
arm.UpperArm = GetChildTransformWithNodeUnderHierarchy(arm.Clavicle, arm.Hand.Wrist);
}
else
{
arm.UpperArm = GetNextLimbBoneIfOnlyOne(arm.Clavicle, skin);
}
}
if (arm.Forearm != null)
{
if (handResolveTried == false)
{
TryToResolveHand(arm.Hand, arm.Forearm, arm.Forearm, skin);
handResolveTried = true;
}
if (arm.Hand.Wrist == null)
{
arm.Hand.Wrist = GetNextLimbBoneIfOnlyOne(arm.Forearm, skin);
}
}
else
{
arm.Forearm = GetNextLimbBoneIfOnlyOne(arm.UpperArm, skin);
}
if (arm.Hand.Wrist != null)
{
TryToResolveHand(arm.Hand, arm.Hand.Wrist, arm.Hand.Wrist, skin);
}
// Bottom to top pass
if (arm.Forearm == null && arm.Hand.Wrist != null)
{
arm.Forearm = GetPreviousLimbBone(arm.Hand.Wrist, skin);
}
if (arm.UpperArm == null && arm.Forearm != null)
{
arm.UpperArm = GetPreviousLimbBone(arm.Forearm, skin);
}
if (arm.Clavicle == null && arm.UpperArm != null)
{
arm.Clavicle = GetPreviousLimbBone(arm.UpperArm, skin);
}
if (arm.UpperArm != null && arm.Hand.Wrist != null && arm.Hand.Wrist.parent != null && arm.Hand.Wrist.parent.parent == arm.UpperArm)
{
arm.Forearm = arm.Hand.Wrist.parent;
}
}
/// <summary>
/// Tries to solve missing bone elements of a hand using a <see cref="SkinnedMeshRenderer" /> as source.
/// </summary>
/// <param name="hand">Hand to resolve</param>
/// <param name="root">The wrist, root of the hand</param>
/// <param name="current">
/// The current transform being processed. The original call is using the same as
/// <paramref name="root" />.
/// </param>
/// <param name="skin">Source skin to navigate the bones looking for missing elements that are not in the hand</param>
/// <returns>Whether the hand was correctly solved</returns>
public static bool TryToResolveHand(UxrAvatarHand hand, Transform root, Transform current, SkinnedMeshRenderer skin)
{
// Try to find 5 fingers hanging from current. If not found, try searching recursively
if (current == null)
{
return false;
}
if (hand.HasFingerData())
{
return true;
}
if (IsBoneInList(skin, root) && IsBoneInList(skin, current))
{
List<List<Transform>> handFingerBones = new List<List<Transform>>();
int fingersFound = 0;
for (int i = 0; i < current.childCount; ++i)
{
if (CanBeFinger(current.GetChild(i), skin, handFingerBones))
{
fingersFound++;
}
else
{
// Maybe metacarpals are not skinned? look in their children
for (int j = 0; j < current.GetChild(i).childCount; ++j)
{
if (CanBeFinger(current.GetChild(i).GetChild(j), skin, handFingerBones))
{
fingersFound++;
}
}
}
}
if (fingersFound == HandFingerCount)
{
// Now resolve which finger is which. We use the closest finger root bone to the hand bone as the thumb.
// From there, we compute the distances from the thumb distal bone to the other finger roots to know index, middle, ring and little.
List<Transform> fingerRoots = new List<Transform>();
List<Transform> fingerDistals = new List<Transform>();
for (int i = 0; i < fingersFound; ++i)
{
fingerRoots.Add(handFingerBones[i][0]);
fingerDistals.Add(handFingerBones[i].Count == 4 ? handFingerBones[i][3] : handFingerBones[i][2]);
}
int thumbFinger = ClosestTransformIndex(current, fingerDistals.ToArray());
SetupFinger(hand, UxrFingerType.Thumb, handFingerBones, fingerRoots[thumbFinger]);
fingerDistals.RemoveAt(thumbFinger);
fingerRoots.RemoveAt(thumbFinger);
int indexFinger = ClosestTransformIndex(handFingerBones[thumbFinger][2], fingerDistals.ToArray());
SetupFinger(hand, UxrFingerType.Index, handFingerBones, fingerRoots[indexFinger]);
fingerDistals.RemoveAt(indexFinger);
fingerRoots.RemoveAt(indexFinger);
int middleFinger = ClosestTransformIndex(handFingerBones[thumbFinger][2], fingerDistals.ToArray());
SetupFinger(hand, UxrFingerType.Middle, handFingerBones, fingerRoots[middleFinger]);
fingerDistals.RemoveAt(middleFinger);
fingerRoots.RemoveAt(middleFinger);
int ringFinger = ClosestTransformIndex(handFingerBones[thumbFinger][2], fingerDistals.ToArray());
SetupFinger(hand, UxrFingerType.Ring, handFingerBones, fingerRoots[ringFinger]);
fingerDistals.RemoveAt(ringFinger);
fingerRoots.RemoveAt(ringFinger);
SetupFinger(hand, UxrFingerType.Little, handFingerBones, fingerRoots[0]);
if (hand.Wrist == null)
{
hand.Wrist = current;
}
return true;
}
}
for (int i = 0; i < current.childCount; ++i)
{
if (TryToResolveHand(hand, root, current.GetChild(i), skin))
{
return true;
}
}
return false;
}
/// <summary>
/// Tries to infer rig elements by doing some checks on names and bone hierarchy.
/// This is useful when we have a rig that has no full humanoid avatar set up on its animator .
/// </summary>
public static void TryToInferMissingRigElements(UxrAvatarRig rig, IEnumerable<SkinnedMeshRenderer> skins)
{
if (rig == null)
{
return;
}
// Head
if (rig.Head.Neck == null)
{
rig.Head.Neck = TryToResolveBoneUniqueOr(skins, "neck");
}
if (rig.Head.Head == null)
{
rig.Head.Head = TryToResolveBoneUniqueOr(skins, "head");
}
if (rig.Head.Jaw == null)
{
rig.Head.Jaw = TryToResolveBoneUniqueOr(skins, "jaw");
}
if (rig.Head.LeftEye == null)
{
rig.Head.LeftEye = TryToResolveBoneUniqueAnd(skins, "eye", "left");
}
if (rig.Head.LeftEye == null)
{
rig.Head.LeftEye = TryToResolveBoneUniqueAnd(skins, "eye", "l");
}
if (rig.Head.RightEye == null)
{
rig.Head.RightEye = TryToResolveBoneUniqueAnd(skins, "eye", "right");
}
if (rig.Head.RightEye == null)
{
rig.Head.RightEye = TryToResolveBoneUniqueAnd(skins, "eye", "r");
}
// Arms
if (rig.LeftArm.Clavicle == null)
{
rig.LeftArm.Clavicle = TryToResolveBoneUniqueAnd(skins, "clavicle", "left");
}
if (rig.LeftArm.Clavicle == null)
{
rig.LeftArm.Clavicle = TryToResolveBoneUniqueAnd(skins, "collarbone", "left");
}
if (rig.LeftArm.Clavicle == null)
{
rig.LeftArm.Clavicle = TryToResolveBoneUniqueAnd(skins, "clavicle", "l");
}
if (rig.LeftArm.Clavicle == null)
{
rig.LeftArm.Clavicle = TryToResolveBoneUniqueAnd(skins, "collarbone", "l");
}
if (rig.LeftArm.UpperArm == null)
{
rig.LeftArm.UpperArm = TryToResolveBoneUniqueAnd(skins, "upper", "left", "arm");
}
if (rig.LeftArm.UpperArm == null)
{
rig.LeftArm.UpperArm = TryToResolveBoneUniqueAnd(skins, "upper", "l", "arm");
}
if (rig.LeftArm.Forearm == null)
{
rig.LeftArm.Forearm = TryToResolveBoneUniqueAnd(skins, "forearm", "left");
}
if (rig.LeftArm.Forearm == null)
{
rig.LeftArm.Forearm = TryToResolveBoneUniqueAnd(skins, "forearm", "l");
}
if (rig.LeftArm.Hand.Wrist == null)
{
rig.LeftArm.Hand.Wrist = TryToResolveBoneUniqueAnd(skins, "hand", "left");
}
if (rig.LeftArm.Hand.Wrist == null)
{
rig.LeftArm.Hand.Wrist = TryToResolveBoneUniqueAnd(skins, "wrist", "left");
}
if (rig.LeftArm.Hand.Wrist == null)
{
rig.LeftArm.Hand.Wrist = TryToResolveBoneUniqueAnd(skins, "hand", "l");
}
if (rig.LeftArm.Hand.Wrist == null)
{
rig.LeftArm.Hand.Wrist = TryToResolveBoneUniqueAnd(skins, "wrist", "l");
}
if (rig.RightArm.Clavicle == null)
{
rig.RightArm.Clavicle = TryToResolveBoneUniqueAnd(skins, "clavicle", "right");
}
if (rig.RightArm.Clavicle == null)
{
rig.RightArm.Clavicle = TryToResolveBoneUniqueAnd(skins, "collarbone", "right");
}
if (rig.RightArm.Clavicle == null)
{
rig.RightArm.Clavicle = TryToResolveBoneUniqueAnd(skins, "clavicle", "r");
}
if (rig.RightArm.Clavicle == null)
{
rig.RightArm.Clavicle = TryToResolveBoneUniqueAnd(skins, "collarbone", "r");
}
if (rig.RightArm.UpperArm == null)
{
rig.RightArm.UpperArm = TryToResolveBoneUniqueAnd(skins, "upper", "right", "arm");
}
if (rig.RightArm.UpperArm == null)
{
rig.RightArm.UpperArm = TryToResolveBoneUniqueAnd(skins, "upper", "r", "arm");
}
if (rig.RightArm.Forearm == null)
{
rig.RightArm.Forearm = TryToResolveBoneUniqueAnd(skins, "forearm", "right");
}
if (rig.RightArm.Forearm == null)
{
rig.RightArm.Forearm = TryToResolveBoneUniqueAnd(skins, "forearm", "r");
}
if (rig.RightArm.Hand.Wrist == null)
{
rig.RightArm.Hand.Wrist = TryToResolveBoneUniqueAnd(skins, "hand", "right");
}
if (rig.RightArm.Hand.Wrist == null)
{
rig.RightArm.Hand.Wrist = TryToResolveBoneUniqueAnd(skins, "wrist", "right");
}
if (rig.RightArm.Hand.Wrist == null)
{
rig.RightArm.Hand.Wrist = TryToResolveBoneUniqueAnd(skins, "hand", "r");
}
if (rig.RightArm.Hand.Wrist == null)
{
rig.RightArm.Hand.Wrist = TryToResolveBoneUniqueAnd(skins, "wrist", "r");
}
// Try to resolve arms using topology
foreach (SkinnedMeshRenderer skin in skins)
{
TryToResolveArm(rig._leftArm, skin);
TryToResolveArm(rig._rightArm, skin);
}
// Legs
if (rig.LeftLeg.UpperLeg == null)
{
rig.LeftLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "upper", "left", "leg");
}
if (rig.LeftLeg.UpperLeg == null)
{
rig.LeftLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "leg", "left");
}
if (rig.LeftLeg.UpperLeg == null)
{
rig.LeftLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "thigh", "left");
}
if (rig.LeftLeg.UpperLeg == null)
{
rig.LeftLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "upper", "l", "leg");
}
if (rig.LeftLeg.UpperLeg == null)
{
rig.LeftLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "leg", "l");
}
if (rig.LeftLeg.UpperLeg == null)
{
rig.LeftLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "thigh", "l");
}
if (rig.LeftLeg.LowerLeg == null)
{
rig.LeftLeg.LowerLeg = TryToResolveBoneUniqueAnd(skins, "lower", "left", "leg");
}
if (rig.LeftLeg.LowerLeg == null)
{
rig.LeftLeg.LowerLeg = TryToResolveBoneUniqueAnd(skins, "calf", "left");
}
if (rig.LeftLeg.LowerLeg == null)
{
rig.LeftLeg.LowerLeg = TryToResolveBoneUniqueAnd(skins, "lower", "l", "leg");
}
if (rig.LeftLeg.LowerLeg == null)
{
rig.LeftLeg.LowerLeg = TryToResolveBoneUniqueAnd(skins, "calf", "l");
}
if (rig.LeftLeg.Foot == null)
{
rig.LeftLeg.Foot = TryToResolveBoneUniqueAnd(skins, "foot", "left");
}
if (rig.LeftLeg.Foot == null)
{
rig.LeftLeg.Foot = TryToResolveBoneUniqueAnd(skins, "ankle", "left");
}
if (rig.LeftLeg.Foot == null)
{
rig.LeftLeg.Foot = TryToResolveBoneUniqueAnd(skins, "foot", "l");
}
if (rig.LeftLeg.Foot == null)
{
rig.LeftLeg.Foot = TryToResolveBoneUniqueAnd(skins, "ankle", "l");
}
if (rig.LeftLeg.Toes == null)
{
rig.LeftLeg.Toes = TryToResolveBoneUniqueAnd(skins, "toe", "left");
}
if (rig.LeftLeg.Toes == null)
{
rig.LeftLeg.Toes = TryToResolveBoneUniqueAnd(skins, "toe", "l");
}
if (rig.RightLeg.UpperLeg == null)
{
rig.RightLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "upper", "right", "leg");
}
if (rig.RightLeg.UpperLeg == null)
{
rig.RightLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "leg", "right");
}
if (rig.RightLeg.UpperLeg == null)
{
rig.RightLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "thigh", "right");
}
if (rig.RightLeg.UpperLeg == null)
{
rig.RightLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "upper", "r", "leg");
}
if (rig.RightLeg.UpperLeg == null)
{
rig.RightLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "leg", "r");
}
if (rig.RightLeg.UpperLeg == null)
{
rig.RightLeg.UpperLeg = TryToResolveBoneUniqueAnd(skins, "thigh", "r");
}
if (rig.RightLeg.LowerLeg == null)
{
rig.RightLeg.LowerLeg = TryToResolveBoneUniqueAnd(skins, "lower", "right", "leg");
}
if (rig.RightLeg.LowerLeg == null)
{
rig.RightLeg.LowerLeg = TryToResolveBoneUniqueAnd(skins, "calf", "right");
}
if (rig.RightLeg.LowerLeg == null)
{
rig.RightLeg.LowerLeg = TryToResolveBoneUniqueAnd(skins, "lower", "r", "leg");
}
if (rig.RightLeg.LowerLeg == null)
{
rig.RightLeg.LowerLeg = TryToResolveBoneUniqueAnd(skins, "calf", "r");
}
if (rig.RightLeg.Foot == null)
{
rig.RightLeg.Foot = TryToResolveBoneUniqueAnd(skins, "foot", "right");
}
if (rig.RightLeg.Foot == null)
{
rig.RightLeg.Foot = TryToResolveBoneUniqueAnd(skins, "ankle", "right");
}
if (rig.RightLeg.Foot == null)
{
rig.RightLeg.Foot = TryToResolveBoneUniqueAnd(skins, "foot", "r");
}
if (rig.RightLeg.Foot == null)
{
rig.RightLeg.Foot = TryToResolveBoneUniqueAnd(skins, "ankle", "r");
}
if (rig.RightLeg.Toes == null)
{
rig.RightLeg.Toes = TryToResolveBoneUniqueAnd(skins, "toe", "right");
}
if (rig.RightLeg.Toes == null)
{
rig.RightLeg.Toes = TryToResolveBoneUniqueAnd(skins, "toe", "r");
}
// Hips/pelvis
if (rig.Hips == null)
{
rig._hips = TryToResolveBoneUniqueOr(skins, "hips", "pelvis");
}
// Try to resolve spine-chest using topology
Transform spineTop = rig.Head.Neck != null ? rig.Head.Neck : rig.Head.Head;
Transform spineBottom = rig.Hips;
if (spineTop != null)
{
if (rig.UpperChest == null && spineTop.parent != spineBottom)
{
rig._upperChest = spineTop.parent;
}
if (rig.Chest == null && rig.UpperChest != null && rig.UpperChest.parent != null && rig.UpperChest.parent != spineBottom)
{
rig._chest = rig.UpperChest.parent;
}
if (rig.Spine == null && rig.Chest != null && rig.Chest.parent != null && rig.Chest.parent != spineBottom)
{
rig._spine = rig.Chest.parent;
}
}
// Try to resolve hips-spine-chest using name
if (rig.UpperChest == null)
{
rig._upperChest = TryToResolveBoneUniqueOr(skins, "upperchest");
}
if (rig.Chest == null)
{
rig._chest = TryToResolveBoneUniqueOr(skins, "chest");
}
if (rig.Spine == null)
{
rig._spine = TryToResolveBoneUniqueOr(skins, "spine");
}
// Try to find wrist torsion elements
foreach (UxrAvatarArm arm in rig.GetArms())
{
if (arm.Forearm != null && arm.Hand != null)
{
UxrWristTorsionIKSolver[] wristTorsionSolvers = arm.Forearm.GetComponentsInChildren<UxrWristTorsionIKSolver>();
if (wristTorsionSolvers.Length == 0)
{
List<Transform> forearmChildren = new List<Transform>();
arm.Forearm.GetAllChildren(ref forearmChildren);
// Find nodes that can potentially be for progressive forearm twist
IEnumerable<Transform> torsionNodes = forearmChildren.Where(t => !t.HasParent(arm.Hand.Wrist) && t.name.ToLower().Contains("torsion"));
if (!torsionNodes.Any())
{
torsionNodes = forearmChildren.Where(t => !t.HasParent(arm.Hand.Wrist) && t.name.ToLower().Contains("twist"));
}
// Create components and assign torsion amount to found nodes. Be careful to distinguish between nodes hanging from the same
// parent and nodes in a hierarchy because the torsion amount will be inherited.
foreach (Transform torsionNode in torsionNodes)
{
UxrWristTorsionIKSolver newTorsionSolver = torsionNode.gameObject.AddComponent<UxrWristTorsionIKSolver>();
IEnumerable<UxrWristTorsionIKSolver> parentSolvers = torsionNodes.Where(t => t != torsionNode && t.HasChild(torsionNode)).Select(t => t.GetComponent<UxrWristTorsionIKSolver>()).Where(s => s != null);
float totalAmount = Vector3.Distance(torsionNode.position, arm.Forearm.position) / Vector3.Distance(arm.Hand.Wrist.position, arm.Forearm.position);
float netAmount = Mathf.Max(0.0f, totalAmount - parentSolvers.Sum(s => s.Amount));
newTorsionSolver.Amount = netAmount;
}
}
}
}
}
/// <summary>
/// Tries to sets up all rig elements from the <see cref="Animator" /> of a humanoid model.
/// </summary>
/// <param name="rig">Rig to set</param>
/// <param name="animator">Source to get the rig elements from</param>
/// <returns>Whether the animator contained humanoid data</returns>
public static bool SetupRigElementsFromAnimator(UxrAvatarRig rig, Animator animator)
{
if (animator == null || animator.isHuman == false)
{
return false;
}
// Head
rig.Head.LeftEye = animator.GetBoneTransform(HumanBodyBones.LeftEye);
rig.Head.RightEye = animator.GetBoneTransform(HumanBodyBones.RightEye);
rig.Head.Jaw = animator.GetBoneTransform(HumanBodyBones.Jaw);
rig.Head.Neck = animator.GetBoneTransform(HumanBodyBones.Neck);
rig.Head.Head = animator.GetBoneTransform(HumanBodyBones.Head);
// Body
rig._upperChest = animator.GetBoneTransform(HumanBodyBones.UpperChest);
rig._chest = animator.GetBoneTransform(HumanBodyBones.Chest);
rig._spine = animator.GetBoneTransform(HumanBodyBones.Spine);
rig._hips = animator.GetBoneTransform(HumanBodyBones.Hips);
// Left arm
rig.LeftArm.Clavicle = animator.GetBoneTransform(HumanBodyBones.LeftShoulder);
rig.LeftArm.UpperArm = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm);
rig.LeftArm.Forearm = animator.GetBoneTransform(HumanBodyBones.LeftLowerArm);
rig.LeftArm.Hand.Wrist = animator.GetBoneTransform(HumanBodyBones.LeftHand);
rig.LeftArm.Hand.Thumb.Proximal = animator.GetBoneTransform(HumanBodyBones.LeftThumbProximal);
rig.LeftArm.Hand.Thumb.Intermediate = animator.GetBoneTransform(HumanBodyBones.LeftThumbIntermediate);
rig.LeftArm.Hand.Thumb.Distal = animator.GetBoneTransform(HumanBodyBones.LeftThumbDistal);
rig.LeftArm.Hand.Index.Proximal = animator.GetBoneTransform(HumanBodyBones.LeftIndexProximal);
rig.LeftArm.Hand.Index.Intermediate = animator.GetBoneTransform(HumanBodyBones.LeftIndexIntermediate);
rig.LeftArm.Hand.Index.Distal = animator.GetBoneTransform(HumanBodyBones.LeftIndexDistal);
rig.LeftArm.Hand.Middle.Proximal = animator.GetBoneTransform(HumanBodyBones.LeftMiddleProximal);
rig.LeftArm.Hand.Middle.Intermediate = animator.GetBoneTransform(HumanBodyBones.LeftMiddleIntermediate);
rig.LeftArm.Hand.Middle.Distal = animator.GetBoneTransform(HumanBodyBones.LeftMiddleDistal);
rig.LeftArm.Hand.Ring.Proximal = animator.GetBoneTransform(HumanBodyBones.LeftRingProximal);
rig.LeftArm.Hand.Ring.Intermediate = animator.GetBoneTransform(HumanBodyBones.LeftRingIntermediate);
rig.LeftArm.Hand.Ring.Distal = animator.GetBoneTransform(HumanBodyBones.LeftRingDistal);
rig.LeftArm.Hand.Little.Proximal = animator.GetBoneTransform(HumanBodyBones.LeftLittleProximal);
rig.LeftArm.Hand.Little.Intermediate = animator.GetBoneTransform(HumanBodyBones.LeftLittleIntermediate);
rig.LeftArm.Hand.Little.Distal = animator.GetBoneTransform(HumanBodyBones.LeftLittleDistal);
// Right arm
rig.RightArm.Clavicle = animator.GetBoneTransform(HumanBodyBones.RightShoulder);
rig.RightArm.UpperArm = animator.GetBoneTransform(HumanBodyBones.RightUpperArm);
rig.RightArm.Forearm = animator.GetBoneTransform(HumanBodyBones.RightLowerArm);
rig.RightArm.Hand.Wrist = animator.GetBoneTransform(HumanBodyBones.RightHand);
rig.RightArm.Hand.Thumb.Proximal = animator.GetBoneTransform(HumanBodyBones.RightThumbProximal);
rig.RightArm.Hand.Thumb.Intermediate = animator.GetBoneTransform(HumanBodyBones.RightThumbIntermediate);
rig.RightArm.Hand.Thumb.Distal = animator.GetBoneTransform(HumanBodyBones.RightThumbDistal);
rig.RightArm.Hand.Index.Proximal = animator.GetBoneTransform(HumanBodyBones.RightIndexProximal);
rig.RightArm.Hand.Index.Intermediate = animator.GetBoneTransform(HumanBodyBones.RightIndexIntermediate);
rig.RightArm.Hand.Index.Distal = animator.GetBoneTransform(HumanBodyBones.RightIndexDistal);
rig.RightArm.Hand.Middle.Proximal = animator.GetBoneTransform(HumanBodyBones.RightMiddleProximal);
rig.RightArm.Hand.Middle.Intermediate = animator.GetBoneTransform(HumanBodyBones.RightMiddleIntermediate);
rig.RightArm.Hand.Middle.Distal = animator.GetBoneTransform(HumanBodyBones.RightMiddleDistal);
rig.RightArm.Hand.Ring.Proximal = animator.GetBoneTransform(HumanBodyBones.RightRingProximal);
rig.RightArm.Hand.Ring.Intermediate = animator.GetBoneTransform(HumanBodyBones.RightRingIntermediate);
rig.RightArm.Hand.Ring.Distal = animator.GetBoneTransform(HumanBodyBones.RightRingDistal);
rig.RightArm.Hand.Little.Proximal = animator.GetBoneTransform(HumanBodyBones.RightLittleProximal);
rig.RightArm.Hand.Little.Intermediate = animator.GetBoneTransform(HumanBodyBones.RightLittleIntermediate);
rig.RightArm.Hand.Little.Distal = animator.GetBoneTransform(HumanBodyBones.RightLittleDistal);
// Left leg
rig.LeftLeg.UpperLeg = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg);
rig.LeftLeg.LowerLeg = animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg);
rig.LeftLeg.Foot = animator.GetBoneTransform(HumanBodyBones.LeftFoot);
rig.LeftLeg.Toes = animator.GetBoneTransform(HumanBodyBones.LeftToes);
// Right leg
rig.RightLeg.UpperLeg = animator.GetBoneTransform(HumanBodyBones.RightUpperLeg);
rig.RightLeg.LowerLeg = animator.GetBoneTransform(HumanBodyBones.RightLowerLeg);
rig.RightLeg.Foot = animator.GetBoneTransform(HumanBodyBones.RightFoot);
rig.RightLeg.Toes = animator.GetBoneTransform(HumanBodyBones.RightToes);
return true;
}
#endregion
#region Private Methods
/// <summary>
/// Tries to fine a bone with a unique name in the hierarchy.
/// </summary>
/// <param name="skins">Skins with the bones where to look for</param>
/// <param name="name">
/// The name ignoring uppercase/lowercase. It will either look for a unique bone that is exactly the
/// name, ends with the name or contains the name but always uniquely.
/// </param>
/// <param name="alternatives">Different alternative names to use in case <paramref name="name" /> isn't found</param>
/// <returns>The transform or null if it wasn't found or there were two or more candidates</returns>
private static Transform TryToResolveBoneUniqueOr(IEnumerable<SkinnedMeshRenderer> skins, string name, params string[] alternatives)
{
TryToResolveNonControllerHandBoneUniqueOr(skins, out Transform candidate, out int candidateOccurrences, name, alternatives);
return candidate != null && candidateOccurrences == 1 ? candidate : null;
}
/// <summary>
/// Tries to fine a bone with a matching or similar name in the hierarchy.
/// </summary>
/// <param name="skins">Skins with the bones where to look for</param>
/// <param name="candidate">Returns the most significant candidate</param>
/// <param name="candidateCount">Returns the total number of candidates with same value that were found</param>
/// <param name="name">
/// The name ignoring uppercase/lowercase. It will either look for a unique bone that is exactly the
/// name, ends with the name or contains the name but always uniquely.
/// </param>
/// <param name="alternatives">Different alternative names to use in case <paramref name="name" /> isn't found</param>
private static void TryToResolveNonControllerHandBoneUniqueOr(IEnumerable<SkinnedMeshRenderer> skins, out Transform candidate, out int candidateCount, string name, params string[] alternatives)
{
candidate = null;
candidateCount = 0;
void UpdateCandidate(bool isFullMatch, Transform bone, ref Transform currentCandidate, ref int currentCount, ref int currentFullMatchCount)
{
if (bone == currentCandidate)
{
return;
}
if (isFullMatch)
{
// Full match
if (currentFullMatchCount > 0 && currentCandidate.HasParent(bone))
{
// Favor parent: use candidate and reset counter
currentFullMatchCount = 1;
currentCount = 1;
currentCandidate = bone;
}
else if (!(currentCandidate != null && currentCandidate.HasChild(bone)))
{
// No parent candidate present: use candidate but increment counter
currentFullMatchCount++;
currentCount = currentFullMatchCount;
currentCandidate = bone;
}
}
else if (currentFullMatchCount == 0)
{
// Semi match
if (currentCandidate != null && currentCandidate.HasParent(bone))
{
// Favor parent: use candidate and reset counter
currentFullMatchCount = 0;
currentCount = 1;
currentCandidate = bone;
}
else if (!(currentCandidate != null && currentCandidate.HasChild(bone)))
{
// No parent candidate present: use candidate but increment counter
currentCount++;
currentCandidate = bone;
}
}
}
Dictionary<Transform, int> dictionaryProcessed = new Dictionary<Transform, int>();
int candidateFullMatchCount = 0; // Number of times it ends exactly with <name>. We will treat this differently than if it's somewhere in between
foreach (SkinnedMeshRenderer skin in skins)
{
// We append also all the children to the bones. This is necessary because sometimes a node is not in the bone list
// but still will drive the child bone due to being the parent.
// A dictionary will make sure that we don't process the same node twice.
List<Transform> allChildren = new List<Transform>();
if (skin.rootBone != null)
{
allChildren.Add(skin.rootBone);
skin.rootBone.GetAllChildren(ref allChildren);
}
foreach (Transform bone in allChildren)
{
if (dictionaryProcessed.ContainsKey(bone))
{
continue;
}
dictionaryProcessed.Add(bone, 1);
// Look for name or alternatives
string nameToLower = bone.name.ToLower();
if (IsWordEnd(nameToLower, name.ToLower()))
{
UpdateCandidate(true, bone, ref candidate, ref candidateCount, ref candidateFullMatchCount);
continue;
}
if (nameToLower.Contains(name.ToLower()))
{
UpdateCandidate(false, bone, ref candidate, ref candidateCount, ref candidateFullMatchCount);
continue;
}
foreach (string altName in alternatives)
{
if (IsWordEnd(nameToLower, altName.ToLower()))
{
UpdateCandidate(true, bone, ref candidate, ref candidateCount, ref candidateFullMatchCount);
}
else if (nameToLower.Contains(altName.ToLower()))
{
UpdateCandidate(false, bone, ref candidate, ref candidateCount, ref candidateFullMatchCount);
}
}
}
}
}
/// <summary>
/// Checks if the given 'name' contains 'part' as a word. We consider 'part' as a word in 'name' if:
/// <list type="bullet">
/// <item>'name' ends with 'part'.</item>
/// <item>
/// 'name' contains 'part' and 'part' has a separator character next to it. We consider a separator any
/// character that is not a letter or digit.
/// </item>
/// </list>
/// </summary>
/// <param name="name">String to process</param>
/// <param name="part">String that should be a part</param>
/// <returns>Whether <paramref name="name" /> meets the requirements</returns>
private static bool IsWordEnd(string name, string part)
{
if (name.Contains(part))
{
if (name.EndsWith(part))
{
return true;
}
int pos = name.IndexOf(part);
return pos != -1 && !char.IsLetterOrDigit(name[pos + part.Length]);
}
return false;
}
/// <summary>
/// Tries to fine a bone with a unique name in the hierarchy.
/// </summary>
/// <param name="skins">Skins with the bones where to look for</param>
/// <param name="name">
/// The name ignoring uppercase/lowercase. It will either look for a unique bone that is exactly the
/// name, ends with the name or contains the name but always uniquely.
/// </param>
/// <param name="additionalStrings">
/// Additional strings that also need to meet the same requirement as
/// <paramref name="name" />.
/// </param>
/// <returns>The transform or null if it wasn't found or there were two or more candidates</returns>
private static Transform TryToResolveBoneUniqueAnd(IEnumerable<SkinnedMeshRenderer> skins, string name, params string[] additionalStrings)
{
TryToResolveNonControllerHandBoneUniqueAnd(skins, out Transform candidate, out int candidateCount, name, additionalStrings);
return candidate != null && candidateCount == 1 ? candidate : null;
}
/// <summary>
/// Tries to fine a bone with a matching or similar name in the hierarchy and, optionally, additional strings that are
/// all also required to be part of the name.
/// </summary>
/// <param name="skins">Skins with the bones where to look for</param>
/// <param name="candidate">Returns the most significant candidate</param>
/// <param name="candidateCount">Returns the total number of candidates with same value that were found</param>
/// <param name="name">
/// The name ignoring uppercase/lowercase. It will either look for a unique bone that is exactly the
/// name, ends with the name or contains the name.
/// </param>
/// <param name="additionalStrings">
/// Additional strings that also need to meet the same requirement as
/// <paramref name="name" />.
/// </param>
private static void TryToResolveNonControllerHandBoneUniqueAnd(IEnumerable<SkinnedMeshRenderer> skins, out Transform candidate, out int candidateCount, string name, params string[] additionalStrings)
{
candidate = null;
candidateCount = 0;
int maxOccurrences = 0;
Dictionary<Transform, int> dictionaryProcessed = new Dictionary<Transform, int>();
bool CheckIfNameMeetsRequirements(string nodeName, out int foundOccurrences)
{
foundOccurrences = nodeName.GetOccurrenceCount(name, false);
if (foundOccurrences == 0)
{
return false;
}
// Look for additional strings that we need to look for and are mandatory
foreach (string additionalString in additionalStrings)
{
int additionalOccurrences = nodeName.GetOccurrenceCount(additionalString, false);
if (additionalOccurrences == 0)
{
return false;
}
foundOccurrences += additionalOccurrences;
}
return true;
}
foreach (SkinnedMeshRenderer skin in skins)
{
// We append also all the children to the bones. This is necessary because sometimes a node is not in the bone list
// but still will drive the child bone due to being the parent.
// A dictionary will make sure that we don't process the same node twice.
List<Transform> allChildren = new List<Transform>();
if (skin.rootBone != null)
{
allChildren.Add(skin.rootBone);
skin.rootBone.GetAllChildren(ref allChildren);
}
foreach (Transform bone in allChildren)
{
if (dictionaryProcessed.ContainsKey(bone))
{
continue;
}
dictionaryProcessed.Add(bone, 1);
// Find occurrences of the given name
if (!CheckIfNameMeetsRequirements(bone.name, out int occurrences))
{
continue;
}
// Update the candidate
if (candidate == null)
{
candidate = bone;
candidateCount = 1;
maxOccurrences = occurrences;
}
else if (candidate.HasParent(bone))
{
candidate = bone;
candidateCount = 1;
maxOccurrences = occurrences;
}
else if (bone.HasParent(candidate))
{
// Do nothing, we keep the parent.
}
else
{
if (occurrences > maxOccurrences)
{
candidate = bone;
candidateCount = 1;
maxOccurrences = occurrences;
}
else if (occurrences == maxOccurrences)
{
candidateCount++;
}
}
}
}
}
/// <summary>
/// Sets up a finger.
/// </summary>
/// <param name="hand">Hand the finger is part of</param>
/// <param name="fingerType">The finger type</param>
/// <param name="handFingerBones">The list of finger bones in the hand</param>
/// <param name="fingerRootBone">The root bone of all fingers</param>
private static void SetupFinger(UxrAvatarHand hand, UxrFingerType fingerType, List<List<Transform>> handFingerBones, Transform fingerRootBone)
{
foreach (List<Transform> fingerBones in handFingerBones)
{
if (fingerBones[0] == fingerRootBone)
{
switch (fingerType)
{
case UxrFingerType.Thumb:
hand.Thumb.SetupFingerBones(fingerBones);
break;
case UxrFingerType.Index:
hand.Index.SetupFingerBones(fingerBones);
break;
case UxrFingerType.Middle:
hand.Middle.SetupFingerBones(fingerBones);
break;
case UxrFingerType.Ring:
hand.Ring.SetupFingerBones(fingerBones);
break;
case UxrFingerType.Little:
hand.Little.SetupFingerBones(fingerBones);
break;
case UxrFingerType.None: break;
}
}
}
}
/// <summary>
/// Checks whether the given bone can be the root bone of a finger.
/// </summary>
/// <param name="fingerRootCandidate">The root bone candidate</param>
/// <param name="skin">The skin where the bones are</param>
/// <param name="handFingerBones">
/// If the given bone can be the root bone of a finger, a list of finger bones are added to
/// the list
/// </param>
/// <returns>Whether the given bone can be the root bone of a finger</returns>
private static bool CanBeFinger(Transform fingerRootCandidate, SkinnedMeshRenderer skin, List<List<Transform>> handFingerBones)
{
if (IsBoneInList(skin, fingerRootCandidate))
{
// fingerRootCandidate is a bone. Now we will enumerate all nodes without children starting from fingerRootCandidate and try
// to find 3 consecutive parents going upwards ending at fingerRootCandidate or any of its sub-hierarchy nodes.
// If found, we will consider this a finger. It may or may not end up having fingerRootCandidate as proximalBone.
List<Transform> potentialFingerDistalBones = new List<Transform>();
fingerRootCandidate.GetTransformsWithoutChildren(ref potentialFingerDistalBones);
for (int candidate = 0; candidate < potentialFingerDistalBones.Count; ++candidate)
{
Transform distalCandidate = potentialFingerDistalBones[candidate];
while ((!IsBoneInList(skin, distalCandidate) || MeshExt.GetBoneInfluenceVertexCount(skin, distalCandidate, 0.01f) == 0) && distalCandidate != fingerRootCandidate)
{
distalCandidate = distalCandidate.parent;
}
if (distalCandidate != fingerRootCandidate)
{
if (distalCandidate.parent != fingerRootCandidate && distalCandidate.parent != null)
{
Transform intermediateCandidate = distalCandidate.parent;
List<Transform> fingerBones = new List<Transform>();
if (intermediateCandidate.parent != fingerRootCandidate && intermediateCandidate.parent != null)
{
// Metacarpal
fingerBones.Add(intermediateCandidate.parent.parent);
}
fingerBones.Add(intermediateCandidate.parent);
fingerBones.Add(intermediateCandidate);
fingerBones.Add(distalCandidate);
handFingerBones.Add(fingerBones);
return true;
}
}
}
}
return false;
}
/// <summary>
/// Gets the child from a bone that has a given node somewhere down in the hierarchy.
/// </summary>
/// <param name="bone">The bone where the search will start</param>
/// <param name="node">The node that the child should have down in the hierarchy</param>
/// <returns>Child if it meets the requirements or null if not</returns>
private static Transform GetChildTransformWithNodeUnderHierarchy(Transform bone, Transform node)
{
if (bone != null)
{
for (int i = 0; i < bone.childCount; ++i)
{
Transform child = bone.GetChild(i);
if (child != node && child.HasChild(node))
{
return child;
}
}
}
return null;
}
/// <summary>
/// Gets the next bone in a hierarchical chain of bones.
/// </summary>
/// <param name="bone">The bone where the search will start</param>
/// <param name="skin">The SkinnedMeshRenderer the bones are for</param>
/// <param name="lastInChain">
/// If true, the search will look for a bone next to the specified one that
/// has no other child bones part of the skin. If false, the search will look for a bone next to
/// the specified one that has another child bone -and only one- part of the skin.
/// </param>
/// <returns>Bone if it meets the requirements or null if not</returns>
private static Transform GetNextLimbBoneIfOnlyOne(Transform bone, SkinnedMeshRenderer skin, bool lastInChain = false)
{
if (bone != null)
{
int childBones = 0;
int childBoneIndex = -1;
if (IsBoneInList(skin, bone))
{
// Check childs in bone
for (int i = 0; i < bone.childCount; ++i)
{
Transform childBone = bone.GetChild(i);
int childChildBones = 0;
// Check childs in child
for (int j = 0; j < childBone.childCount; ++j)
{
if (IsBoneInList(skin, childBone.GetChild(j)))
{
childChildBones++;
}
}
// Does this child meet the conditions?
if (IsBoneInList(skin, childBone))
{
if ((lastInChain && childChildBones == 0) || (!lastInChain && childChildBones > 0))
{
childBones++;
childBoneIndex = i;
}
}
}
}
if (childBones == 1)
{
return bone.GetChild(childBoneIndex);
}
// Try another approach. Look for non-skinned bones.
if (bone.childCount == 1)
{
if (lastInChain && bone.GetChild(0).childCount == 0)
{
return bone.GetChild(0);
}
if (!lastInChain && bone.GetChild(0).childCount > 0)
{
return bone.GetChild(0);
}
}
}
return null;
}
/// <summary>
/// Gets the parent of a bone in the hierarchy if it is also part of the bones in a skin.
/// </summary>
/// <param name="bone">Bone to get the parent from</param>
/// <param name="skin">Skin where the bones are</param>
/// <returns>Gets the parent of the bone if it meets the requirements or null if not</returns>
private static Transform GetPreviousLimbBone(Transform bone, SkinnedMeshRenderer skin)
{
if (bone != null && bone.parent != null && IsBoneInList(skin, bone.parent))
{
return bone.parent;
}
return null;
}
/// <summary>
/// Gets the index of the closest bone in a list to a reference.
/// </summary>
/// <param name="bone">Reference bone</param>
/// <param name="otherBones">List where to find the closest bone</param>
/// <returns>Index of the closest bone in the list or -1 if the list is empty</returns>
private static int ClosestTransformIndex(Transform bone, params Transform[] otherBones)
{
if (otherBones.Length == 0)
{
return -1;
}
int closestIndex = 0;
float minDistance = Vector3.Distance(bone.transform.position, otherBones[0].transform.position);
for (int i = 1; i < otherBones.Length; ++i)
{
float distance = Vector3.Distance(bone.transform.position, otherBones[i].transform.position);
if (distance < minDistance)
{
minDistance = distance;
closestIndex = i;
}
}
return closestIndex;
}
/// <summary>
/// Checks whether a transform is in the list of bones of a <see cref="SkinnedMeshRenderer" />.
/// </summary>
/// <param name="skin">Skin where to look</param>
/// <param name="transformToCheck">Bone to check for</param>
/// <returns>Whether the bone was found in the skin</returns>
private static bool IsBoneInList(SkinnedMeshRenderer skin, Transform transformToCheck)
{
if (transformToCheck.name.Contains("ignore", StringComparison.OrdinalIgnoreCase))
{
return false;
}
foreach (Transform bone in skin.bones)
{
if (bone == transformToCheck)
{
return true;
}
}
return false;
}
#endregion
}
}