/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2021. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Leap.Unity.HandsModule { public static class HandBinderAutoBinder { //The minimum amount of Transforms required to be able to match Transforms back to a Leap data point const int MINIMUM_TRANSFORMS = 3; /// /// This function is used to search the child Transforms of the HandBinder script to automatically try and assign them for the user /// /// The HandBinder that the found Transform target will get assigned to public static void AutoBind(HandBinder handBinder) { handBinder.ResetHand(); BoneNameDefinitions boneDefinitions = new BoneNameDefinitions(); //Get all children of the hand var children = new List(); children.Add(handBinder.transform); children.AddRange(GetAllChildren(handBinder.transform)); var thumbBones = SortBones(SelectBones(children, boneDefinitions.DefinitionThumb), false, true); var indexBones = SortBones(SelectBones(children, boneDefinitions.DefinitionIndex), handBinder.UseMetaBones); var middleBones = SortBones(SelectBones(children, boneDefinitions.DefinitionMiddle), handBinder.UseMetaBones); var ringBones = SortBones(SelectBones(children, boneDefinitions.DefinitionRing), handBinder.UseMetaBones); var pinkyBones = SortBones(SelectBones(children, boneDefinitions.DefinitionPinky), handBinder.UseMetaBones); var wrist = SelectBones(children, boneDefinitions.DefinitionWrist).FirstOrDefault(); var elbow = SelectBones(children, boneDefinitions.DefinitionElbow).FirstOrDefault(); handBinder.BoundHand.fingers[0].boundBones = AssignTransformToBoundBone(thumbBones); handBinder.BoundHand.fingers[1].boundBones = AssignTransformToBoundBone(indexBones); handBinder.BoundHand.fingers[2].boundBones = AssignTransformToBoundBone(middleBones); handBinder.BoundHand.fingers[3].boundBones = AssignTransformToBoundBone(ringBones); handBinder.BoundHand.fingers[4].boundBones = AssignTransformToBoundBone(pinkyBones); handBinder.BoundHand.wrist = AssignBoundBone(wrist); handBinder.BoundHand.elbow = AssignBoundBone(elbow); if (wrist != null && elbow != null) { handBinder.ElbowLength = (wrist.position - elbow.position).magnitude; } EstimateWristRotationOffset(handBinder); CalculateElbowLength(handBinder); handBinder.GetLeapHand(); handBinder.UpdateHand(); handBinder.DebugModelTransforms = true; handBinder.SetEditorPose = true; } /// /// Get all the children of a transform /// /// /// public static List GetAllChildren(Transform _t) { List ts = new List(); foreach (Transform t in _t) { ts.Add(t); if (t.childCount > 0) { ts.AddRange(GetAllChildren(t)); } } return ts; } /// /// The Autobinder uses this to select the children that match the finger definitions /// /// The found children /// The criteria to match to the children /// private static Transform[] SelectBones(List children, string[] definitions) { var bones = new List(); for (int definitionIndex = 0; definitionIndex < definitions.Length; definitionIndex++) { foreach (var child in children) { //We have found all the bones we need if (bones.Count == 4) { break; } var definition = definitions[definitionIndex].ToUpper(); if (child.name.ToUpper().Contains(definition)) { bones.Add(child); } } } return bones.ToArray(); } /// /// Sort through the bones to identify which BoneType they all belong to /// /// The bones to sort /// Is it a thumb /// private static Transform[] SortBones(Transform[] bones, bool useMeta = false, bool isThumb = false) { Transform meta = null; Transform proximal = null; Transform middle = null; Transform distal = null; if (bones.Length == MINIMUM_TRANSFORMS) { if (useMeta == false) { meta = null; proximal = bones[0]; middle = bones[1]; distal = bones[2]; } else { meta = bones[0]; proximal = bones[1]; middle = bones[2]; distal = null; } } else if (bones.Length > MINIMUM_TRANSFORMS) { if (isThumb == true) { proximal = bones[0]; middle = bones[1]; distal = bones[2]; } else { meta = bones[0]; proximal = bones[1]; middle = bones[2]; distal = bones[3]; } } var boundObjects = new Transform[] { meta, proximal, middle, distal }; return boundObjects; } /// /// Bind a transform in the scene to the Hand Binder /// /// The transform you want to assign /// The index of the finger you want to assign /// The index of the bone you want to assign /// The Hand Binder this information will be added to /// public static BoundBone[] AssignTransformToBoundBone(Transform[] boneTransform) { var boundFingers = new BoundBone[] { AssignBoundBone(boneTransform[0]), AssignBoundBone(boneTransform[1]), AssignBoundBone(boneTransform[2]), AssignBoundBone(boneTransform[3]), }; return boundFingers; } public static BoundBone AssignBoundBone(Transform transform) { var newBone = new BoundBone(); if (transform != null) { newBone.boundTransform = transform; newBone.startTransform = new TransformStore(); newBone.startTransform.position = transform.localPosition; newBone.startTransform.rotation = transform.localRotation.eulerAngles; } return newBone; } /// /// Estimate the rotation offset needed to get the rigged hand into the same orientation as the leap hand /// public static void EstimateWristRotationOffset(HandBinder handBinder) { Transform indexBone = handBinder.BoundHand.fingers[(int)Finger.FingerType.TYPE_INDEX].boundBones[(int)Bone.BoneType.TYPE_PROXIMAL].boundTransform; Transform middleBone = handBinder.BoundHand.fingers[(int)Finger.FingerType.TYPE_MIDDLE].boundBones[(int)Bone.BoneType.TYPE_PROXIMAL].boundTransform; Transform pinkyBone = handBinder.BoundHand.fingers[(int)Finger.FingerType.TYPE_PINKY].boundBones[(int)Bone.BoneType.TYPE_PROXIMAL].boundTransform; Transform wrist = handBinder.BoundHand.wrist.boundTransform; if (middleBone != null && indexBone != null && pinkyBone != null && wrist != null) { //Calculate model's rotation var forward = (middleBone.position - wrist.position); var right = (indexBone.position - pinkyBone.position); if (handBinder.Handedness == Chirality.Right) { right = -right; } var up = Vector3.Cross(forward, right); Vector3.OrthoNormalize(ref up, ref forward, ref right); var modelRotation = Quaternion.LookRotation(forward, up); //Calculate the difference between the Calculated hand basis and the wrists rotation handBinder.WristRotationOffset = (Quaternion.Inverse(modelRotation) * wrist.transform.rotation).eulerAngles; //Assuming the fingers have been created using the same rotation axis as the wrist handBinder.GlobalFingerRotationOffset = handBinder.WristRotationOffset; } } public static void CalculateElbowLength(HandBinder handBinder) { if (handBinder.BoundHand.elbow.boundTransform != null && handBinder.BoundHand.wrist.boundTransform != null) { handBinder.ElbowLength = (handBinder.BoundHand.wrist.boundTransform.position - handBinder.BoundHand.elbow.boundTransform.position).magnitude; } } } }