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); }