private int GetMatchKey(BoneMatch parentMatch, Transform t, BoneMappingItem goalItem)
        {
            SimpleProfiler.Begin("GetMatchKey");
            int key = goalItem.bone;

            key += t.GetInstanceID() * 1000;
            if (parentMatch != null)
            {
                key += parentMatch.bone.GetInstanceID() * 1000000;
                if (parentMatch.parent != null)
                {
                    key += parentMatch.parent.bone.GetInstanceID() * 1000000000;
                }
            }
            SimpleProfiler.End();
            return(key);
        }
        private void EvaluateBoneMatch(BoneMatch match, bool confirmedChoice)
        {
            match.score        = 0;
            match.siblingScore = 0;

            // Things to copy from identical match: score, siblingScore, children, doMap

            // Iterate child BoneMappingitems
            List <List <BoneMatch> > childMatchesLists = new List <List <BoneMatch> >();
            int intendedChildCount = 0;

            foreach (int c in match.item.GetChildren(m_MappingData))
            {
                BoneMappingItem i = m_MappingData[c];
                if (i.parent == match.item.bone)
                {
                    intendedChildCount++;
                    // RECURSIVE CALL
                    List <BoneMatch> childMatches = RecursiveFindPotentialBoneMatches(match, i, confirmedChoice);
                    if (childMatches == null || childMatches.Count == 0)
                    {
                        continue;
                    }
                    childMatchesLists.Add(childMatches);
                }
            }

            // Best best child matches
            bool sameAsParentOrChild = (match.bone == match.humanBoneParent.bone);

            if (childMatchesLists.Count > 0)
            {
                SimpleProfiler.Begin("GetBestChildMatches");
                match.children = GetBestChildMatches(match, childMatchesLists);
                SimpleProfiler.End();

                // Handle child matches
                foreach (BoneMatch childMatch in match.children)
                {
                    // RECURSIVE CALL for debugging purposes
                    if (kDebug && confirmedChoice)
                    {
                        EvaluateBoneMatch(childMatch, confirmedChoice);
                    }

                    // Transfer info from best child match to parent
                    match.score += childMatch.score;
                    if (kDebug)
                    {
                        match.debugTracker.AddRange(childMatch.debugTracker);
                    }
                    if (childMatch.bone == match.bone && childMatch.item.bone >= 0)
                    {
                        sameAsParentOrChild = true;
                    }
                }
            }

            SimpleProfiler.Begin("ScoreBoneMatch");
            // Keyword score the bone if it's not optional or if it's different from both parent bone and all child bones
            if (!match.item.optional || !sameAsParentOrChild)
            {
                ScoreBoneMatch(match);
            }
            SimpleProfiler.End();

            // Rate bone according to how well it matches goal direction
            SimpleProfiler.Begin("MatchesDir");
            if (match.item.dir != Vector3.zero)
            {
                Vector3 goalDir = match.item.dir;
                if (m_MappingIndexOffset >= (int)HumanBodyBones.LeftThumbProximal && m_MappingIndexOffset < (int)HumanBodyBones.RightThumbProximal)
                {
                    goalDir.x *= -1;
                }
                Vector3 dir = (match.bone.position - match.humanBoneParent.bone.position).normalized;
                dir = Quaternion.Inverse(m_Orientation) * dir;
                float dirMatchingScore = Vector3.Dot(dir, goalDir) * (match.item.optional ? 5 : 10);

                match.siblingScore += dirMatchingScore;
                if (kDebug)
                {
                    match.debugTracker.Add("* " + dirMatchingScore + ": " + GetMatchString(match) + " matched dir (" + (match.bone.position - match.humanBoneParent.bone.position).normalized + " , " + goalDir + ")");
                }

                if (dirMatchingScore > 0)
                {
                    match.score += 10;
                    if (kDebug)
                    {
                        match.debugTracker.Add(10 + ": " + GetMatchString(match) + " matched dir (" + (match.bone.position - match.humanBoneParent.bone.position).normalized + " , " + goalDir + ")");
                    }
                }
            }
            SimpleProfiler.End();

            // Give small score if bone matches side it belongs to.
            SimpleProfiler.Begin("MatchesSide");
            if (m_MappingIndexOffset == 0)
            {
                int sideMatchingScore = GetBoneSideMatchPoints(match);
                if (match.parent.item.side == Side.None || sideMatchingScore < 0)
                {
                    match.siblingScore += sideMatchingScore;
                    if (kDebug)
                    {
                        match.debugTracker.Add("* " + sideMatchingScore + ": " + GetMatchString(match) + " matched side");
                    }
                }
            }
            SimpleProfiler.End();

            // These criteria can not push a bone above the threshold, but they can help to break ties.
            if (match.score > 0)
            {
                // Reward optional bones being included
                if (match.item.optional && !sameAsParentOrChild)
                {
                    match.score += 5;
                    if (kDebug)
                    {
                        match.debugTracker.Add(5 + ": " + GetMatchString(match) + " optional bone is included");
                    }
                }

                // Handle end bones
                if (intendedChildCount == 0 && match.bone.childCount > 0)
                {
                    // Reward end bones having a dummy child transform
                    match.score += 1;
                    if (kDebug)
                    {
                        match.debugTracker.Add(1 + ": " + GetMatchString(match) + " has dummy child bone");
                    }
                }

                // Give score to bones length ratio according to match with goal ratio.
                SimpleProfiler.Begin("LengthRatio");
                if (match.item.lengthRatio != 0)
                {
                    float parentLength = Vector3.Distance(match.bone.position, match.humanBoneParent.bone.position);
                    if (parentLength == 0 && match.bone != match.humanBoneParent.bone)
                    {
                        match.score -= 1000;
                        if (kDebug)
                        {
                            match.debugTracker.Add((-1000) + ": " + GetMatchString(match.humanBoneParent) + " has zero length");
                        }
                    }

                    float grandParentLength = Vector3.Distance(match.humanBoneParent.bone.position, match.humanBoneParent.humanBoneParent.bone.position);
                    if (grandParentLength > 0)
                    {
                        float logRatio     = Mathf.Log(parentLength / grandParentLength, 2);
                        float logGoalRatio = Mathf.Log(match.item.lengthRatio, 2);
                        float ratioScore   = 10 * Mathf.Clamp(1 - 0.6f * Mathf.Abs(logRatio - logGoalRatio), 0, 1);
                        match.score += ratioScore;
                        if (kDebug)
                        {
                            match.debugTracker.Add(ratioScore + ": parent " + GetMatchString(match.humanBoneParent) + " matched lengthRatio - " + parentLength + " / " + grandParentLength + " = " + (parentLength / grandParentLength) + " (" + logRatio + ") goal: " + match.item.lengthRatio + " (" + logGoalRatio + ")");
                        }
                    }
                }
                SimpleProfiler.End();
            }

            // Only map optional bones if they're not the same as the parent or child.
            if (match.item.bone >= 0 && (!match.item.optional || !sameAsParentOrChild))
            {
                match.doMap = true;
            }
        }
        // Returns possible matches sorted with best-scoring ones first in the list
        private List <BoneMatch> RecursiveFindPotentialBoneMatches(BoneMatch parentMatch, BoneMappingItem goalItem, bool confirmedChoice)
        {
            List <BoneMatch> matches = new List <BoneMatch>();

            // We want to search with breadh first search so we have to use a queue
            Queue <QueuedBone> queue = new Queue <QueuedBone>();

            // Find matches
            queue.Enqueue(new QueuedBone(parentMatch.bone, 0));
            while (queue.Count > 0)
            {
                QueuedBone current = queue.Dequeue();
                Transform  t       = current.bone;
                if (current.level >= goalItem.minStep && (m_TreatDummyBonesAsReal || m_ValidBones == null || (m_ValidBones.ContainsKey(t) && m_ValidBones[t])))
                {
                    BoneMatch match;
                    var       key = GetMatchKey(parentMatch, t, goalItem);
                    if (m_BoneMatchDict.ContainsKey(key))
                    {
                        match = m_BoneMatchDict[key];
                    }
                    else
                    {
                        match = new BoneMatch(parentMatch, t, goalItem);

                        // RECURSIVE CALL
                        EvaluateBoneMatch(match, false);
                        m_BoneMatchDict[key] = match;
                    }

                    if (match.score > 0 || kDebug)
                    {
                        matches.Add(match);
                    }
                }
                SimpleProfiler.Begin("Queue");
                if (current.level < goalItem.maxStep)
                {
                    foreach (Transform child in t)
                    {
                        if (m_ValidBones == null || m_ValidBones.ContainsKey(child))
                        {
                            if (!m_TreatDummyBonesAsReal && m_ValidBones != null && !m_ValidBones[child])
                            {
                                queue.Enqueue(new QueuedBone(child, current.level));
                            }
                            else
                            {
                                queue.Enqueue(new QueuedBone(child, current.level + 1));
                            }
                        }
                    }
                }
                SimpleProfiler.End();
            }

            if (matches.Count == 0)
            {
                return(null);
            }

            // Sort by match score with best matches first
            SimpleProfiler.Begin("SortAndTrim");
            matches.Sort();
            if (matches[0].score <= 0)
            {
                return(null);
            }

            if (kDebug && confirmedChoice)
            {
                DebugMatchChoice(matches);
            }

            // Keep top 3 priorities only for optimization
            while (matches.Count > 3)
            {
                matches.RemoveAt(matches.Count - 1);
            }
            matches.TrimExcess();
            SimpleProfiler.End();

            return(matches);
        }