|
13 | 13 | namespace Warudo.Plugins.Core.Assets.Character { |
14 | 14 | // Much of the code here is brought from UniVRM. Thanks! |
15 | 15 | public static class BoneNormalization { |
16 | | - /// <summary> |
17 | | - /// Apply hierarchical bone normalization by storing non-standardized rotations in HumanBones |
18 | | - /// and propagating inverse rotations to children to maintain their world positions. |
19 | | - /// </summary> |
20 | 16 | public static void Apply(GameObject go, Animator animator) { |
21 | | - ApplyHierarchicalNormalization(go, animator); |
22 | | - } |
23 | | - |
24 | | - /// <summary> |
25 | | - /// Normalizes bones hierarchically by: |
26 | | - /// 1. Storing the non-identity rotation of each HumanBone |
27 | | - /// 2. Setting the HumanBone's local rotation to identity |
28 | | - /// 3. Applying the inverse rotation to children to preserve their world positions |
29 | | - /// 4. Recursively processing HumanBone children |
30 | | - /// </summary> |
31 | | - private static void ApplyHierarchicalNormalization(GameObject go, Animator animator) { |
32 | | - // Build a HashSet of all HumanBone transforms for quick lookup |
33 | | - var humanBoneSet = new HashSet<Transform>(); |
34 | | - for (var i = 0; i < (int)HumanBodyBones.LastBone; i++) { |
35 | | - var bone = animator.GetBoneTransform((HumanBodyBones)i); |
36 | | - if (bone != null) { |
37 | | - humanBoneSet.Add(bone); |
38 | | - } |
39 | | - } |
40 | | - |
41 | | - // Collect all HumanBone transforms in hierarchy order (parent to child) |
42 | | - var humanBones = new List<(HumanBodyBones boneType, Transform transform)>(); |
43 | | - for (var i = 0; i < (int)HumanBodyBones.LastBone; i++) { |
44 | | - var bone = animator.GetBoneTransform((HumanBodyBones)i); |
45 | | - if (bone != null) { |
46 | | - humanBones.Add(((HumanBodyBones)i, bone)); |
47 | | - } |
48 | | - } |
49 | | - |
50 | | - // Sort by hierarchy depth to process parents before children |
51 | | - humanBones.Sort((a, b) => GetHierarchyDepth(a.transform).CompareTo(GetHierarchyDepth(b.transform))); |
52 | | - |
53 | | - Debug.Log($"Normalizing {humanBones.Count} humanoid bones hierarchically..."); |
54 | | - |
55 | | - foreach (var (boneType, boneTransform) in humanBones) { |
56 | | - if (boneTransform.localRotation == Quaternion.identity) { |
57 | | - continue; // Already normalized |
58 | | - } |
59 | | - |
60 | | - var nonStandardRotation = boneTransform.localRotation; |
61 | | - Debug.Log($"Normalizing bone {boneTransform.name} ({boneType}): {nonStandardRotation.eulerAngles}"); |
62 | | - |
63 | | - // Store the non-standard rotation (we could save this somewhere if needed) |
64 | | - // For now, we just apply the normalization |
65 | | - |
66 | | - // Apply inverse rotation to all children to preserve their world positions |
67 | | - NormalizeBoneAndChildren(boneTransform, humanBoneSet); |
68 | | - } |
69 | | - |
70 | | - Debug.Log("Bone normalization complete."); |
71 | | - } |
72 | | - |
73 | | - /// <summary> |
74 | | - /// Normalizes a bone and recursively updates its children to preserve world positions. |
75 | | - /// </summary> |
76 | | - private static void NormalizeBoneAndChildren(Transform boneTransform, HashSet<Transform> humanBoneSet) { |
77 | | - var originalRotation = boneTransform.localRotation; |
78 | | - |
79 | | - // If the bone is already normalized, no need to process |
80 | | - if (originalRotation == Quaternion.identity) { |
81 | | - return; |
82 | | - } |
83 | | - |
84 | | - // First, recursively process all HumanBone children first (bottom-up approach) |
85 | | - var humanBoneChildren = new List<Transform>(); |
86 | | - foreach (Transform child in boneTransform) { |
87 | | - if (humanBoneSet.Contains(child)) { |
88 | | - humanBoneChildren.Add(child); |
89 | | - } |
90 | | - } |
91 | | - |
92 | | - // Process HumanBone children first |
93 | | - foreach (var child in humanBoneChildren) { |
94 | | - NormalizeBoneAndChildren(child, humanBoneSet); |
95 | | - } |
96 | | - |
97 | | - // Calculate the inverse rotation that will be applied to children |
98 | | - var inverseRotation = Quaternion.Inverse(originalRotation); |
99 | | - |
100 | | - // Now normalize this bone |
101 | | - boneTransform.localRotation = Quaternion.identity; |
102 | | - |
103 | | - // Apply inverse rotation to all direct children (both HumanBones and non-HumanBones) |
104 | | - // to preserve their world positions |
105 | | - foreach (Transform child in boneTransform) { |
106 | | - if (!humanBoneChildren.Contains(child)) { |
107 | | - // For non-HumanBone children, apply the inverse rotation |
108 | | - child.localRotation = inverseRotation * child.localRotation; |
109 | | - } |
110 | | - // HumanBone children were already processed recursively |
111 | | - } |
112 | | - } |
113 | | - |
114 | | - /// <summary> |
115 | | - /// Gets the hierarchy depth of a transform (root = 0). |
116 | | - /// </summary> |
117 | | - private static int GetHierarchyDepth(Transform transform) { |
118 | | - var depth = 0; |
119 | | - var current = transform; |
120 | | - while (current.parent != null) { |
121 | | - depth++; |
122 | | - current = current.parent; |
123 | | - } |
124 | | - return depth; |
125 | | - } |
126 | | - |
127 | | - // Legacy method for reference (original implementation) |
128 | | - private static void ApplyLegacy(GameObject go, Animator animator) { |
129 | 17 | var (normalized, boneMap) = NormalizeHierarchy(go, animator); |
130 | 18 |
|
131 | 19 | foreach (var src in go.transform.Traverse()) { |
|
0 commit comments