public List <int[]> GetBonePairs(bool flipZ = false, int symmetryMargin = 16) { var sigList = new List <BoneSignature>(); sigList = CollectBoneSignatures(Bones[0], 0, -1, sigList).OrderBy(item => item.Index).ToList(); var result = new List <int[]>(); // Traverse through collected signatures in 2 passes for (int i = 0; i <= symmetryMargin; i++) { foreach (var sig in sigList) { // Entry already was paired, bypass it if (result.Any(pair => (pair[0] != -1 && pair[1] == sig.Index) || (pair[1] != -1 && pair[0] == sig.Index))) { continue; } // Find out potentially matching bone signatures var others = sigList.Where(item => sig.Index != item.Index && sig.ChildIndices.Count == item.ChildIndices.Count && // Child count should always match! sig.Depth == item.Depth && // Own depth should always be the same! sigList[sig.ParentIndex].Depth == sigList[item.ParentIndex].Depth).ToList(); // Parent depth should always be the same! bool boneAdded = false; foreach (var other in others) { float compX1 = !flipZ ? sig.Pivot.X : sig.Pivot.Z; float compX2 = !flipZ ? other.Pivot.X : other.Pivot.Z; float compZ1 = !flipZ ? sig.Pivot.Z : sig.Pivot.X; float compZ2 = !flipZ ? other.Pivot.Z : other.Pivot.X; if ((i != symmetryMargin && // Prioritize bones with same parent index and pivot symmetry. // Pivot symmetry is declared if X pivot position is mirrored for both bones // and bones are well apart on Z axis (to correctly identify models like scorpion or crocodile). // Unfortunately, there's a case when both bones are symmetrical but are pivoted // from single point (e.g. DEMIGOD3 spear), in this case we can't predict symmetry correctly, // so such cases are bypassed. sig.ParentIndex == other.ParentIndex && Math.Abs(Math.Abs(compX1) - Math.Abs(compX2)) <= i && Math.Abs(compX1 - compX2) >= i && MathC.WithinEpsilon(compZ1, compZ2, symmetryMargin)) || // On last pass, prioritize bones with already found matching parent mesh pairs. (i == symmetryMargin && ((result.Any(pair => (pair[0] == sig.ParentIndex && pair[1] == other.ParentIndex) || (pair[0] == other.ParentIndex && pair[1] == sig.ParentIndex)))) || // Additionally do final exact comparison of Y/Z values and X distance (fixes TR3 Shiva without breaking anything else) (MathC.WithinEpsilon(compZ1, compZ2, symmetryMargin) && MathC.WithinEpsilon(sig.Pivot.Y, other.Pivot.Y, symmetryMargin) && Math.Abs(compX1 - compX2) >= symmetryMargin))) { int p0 = result.IndexOf(pair => pair[0] == sig.Index); int p1 = result.IndexOf(pair => pair[0] == other.Index); if (p0 == -1 && p1 == -1) { result.Add(new int[2] { sig.Index, other.Index }); // No entry in pair list } else if (p0 == -1) { result[p1] = new int[2] { sig.Index, other.Index } } ; // Entry was already in list else if (p1 == -1) { result[p0] = new int[2] { sig.Index, other.Index } } ; // Found match was already in list boneAdded = true; break; } } if (!boneAdded && i == symmetryMargin) { result.Add(new int[2] { sig.Index, -1 }); // Entry isn't found, create new one on last pass } } } // PARANOIA: try to flter out stray decoupled pairs result = result.Where(item => !(item[1] == -1 && result.Any(item2 => (item2[0] == item[0] || item2[1] == item[0]) && item2[1] != -1))).ToList(); return(result); }