using System.Collections.Generic; using UnityEngine; namespace BrewMonster.Scripts { public class SkeletonBuilder : MonoBehaviour { public List bones = new List(); public List bindPoses = new List(); public Transform rootBone; public Dictionary hooks = new Dictionary(); /// /// This function retrieves an array of transforms for the specified bone names. /// It iterates through the input names, finds the matching transform in the global bones list, /// and then appends the root bone transform as the final element before returning the assembled array. /// /// /// public Transform[] GetBones(string[] boneNames) { if (boneNames == null || boneNames.Length == 0) { // Return all bones if boneNames is empty or null Transform[] allBoneTransforms = new Transform[bones.Count + 1]; for (int i = 0; i < bones.Count; i++) { allBoneTransforms[i] = bones[i]; } allBoneTransforms[bones.Count] = rootBone; return allBoneTransforms; } else { // Get only the bones with names in the boneNames array Transform[] boneTransforms = new Transform[boneNames.Length + 1]; for (int i = 0; i < boneNames.Length; i++) { boneTransforms[i] = bones.Find(bone => bone.name == boneNames[i]); } boneTransforms[boneNames.Length] = rootBone; return boneTransforms; } } public void GetBoneNoGC(string boneName, out Transform boneTransform) { boneTransform = bones.Find(bone => bone.name == boneName); } /// /// Get hook Transform by name /// 根据名称获取挂点变换 /// /// Hook name / 挂点名称 /// Search in child models / 在子模型中搜索 /// Hook Transform or null if not found / 挂点变换,未找到返回null public Transform GetHook(string hookName, bool recursive = true) { if (string.IsNullOrEmpty(hookName)) { return null; } // Direct lookup // 直接查找 if (hooks.TryGetValue(hookName, out Transform hook)) { return hook; } else { Transform childHook = FindChildRecursive(transform, hookName); if (childHook != null) { hooks.Add(hookName, childHook); return childHook; } else { } } // Recursive search in child models (if recursive flag is true) // 在子模型中递归搜索(如果递归标志为true) if (recursive) { // Search in child SkeletonBuilders (weapons, pets, etc.) // 在子SkeletonBuilder中搜索(武器、宠物等) SkeletonBuilder[] childBuilders = GetComponentsInChildren(true); foreach (SkeletonBuilder childBuilder in childBuilders) { if (childBuilder == this) continue; // Skip self Transform childHook = childBuilder.GetHook(hookName, false); // Don't recurse again if (childHook != null) return childHook; } } return null; } Transform FindChildRecursive(Transform parent, string name) { foreach (Transform child in parent) { if (child.name == name) return child; Transform found = FindChildRecursive(child, name); if (found != null) return found; } return null; } #if MODEL_RENDERER_PROJECT private A3DSkeleton _skeleton; private int _rootBoneIndex; public void BuildSkeleton(A3DSkeleton skeleton) { if (skeleton == null) { Debug.LogError("No skeleton data found."); return; } _skeleton = skeleton; Dictionary boneGameObjects = new Dictionary(); GameObject boneGO = null; // Create GameObject for each bone for (int i = 0; i < skeleton.m_aBones.Count; i++) { boneGO = new GameObject(skeleton.m_aBones[i].m_strName); boneGameObjects[i] = boneGO; } A3DBone bone; // first find the root bone and set up the relative TM for each bone for (int i = 0; i < skeleton.m_aBones.Count; i++) { bone = skeleton.m_aBones[i]; bone.m_relativeTM = CreateMatrixFromA3DMatrix4X4(bone.boneData.matRelative); Console.WriteLine($"Bone {bone.m_strName} relative\n{bone.m_relativeTM}"); if (bone.boneData.iParent < 0) { _rootBoneIndex = i; rootBone = boneGameObjects[i].transform; } } // Build upToRootTM matrix for each bone. Start from the root bone. // for now it's not required BuildUpToRootMatrix(skeleton.m_aBones[_rootBoneIndex]); // setup parent-child relationship foreach (var kvp in boneGameObjects) { int boneIndex = kvp.Key; boneGO = kvp.Value; int parentIndex = skeleton.m_aBones[boneIndex].boneData.iParent; if (parentIndex >= 0) { boneGO.transform.parent = boneGameObjects[parentIndex].transform; } else { boneGO.transform.parent = this.transform; // Set root bones to be children of the skeleton GameObject } } // Set up position of each bone foreach (var kvp in boneGameObjects) { int boneIndex = kvp.Key; boneGO = kvp.Value; bone = skeleton.m_aBones[boneIndex]; Matrix4x4 inverseInitMatrix = CreateMatrixFromA3DMatrix4X4(bone.boneData.matBoneInit).inverse; Vector3 boneWorldPosition = inverseInitMatrix.MultiplyPoint3x4(Vector3.zero); boneGO.transform.position = boneWorldPosition; boneGO.transform.rotation = Quaternion.LookRotation(inverseInitMatrix.GetColumn(2), inverseInitMatrix.GetColumn(1)); boneGO.transform.localScale = inverseInitMatrix.lossyScale; // print out data as human readable PrintOutData(boneGO, bone.m_upToRootTM, inverseInitMatrix, inverseInitMatrix.rotation.eulerAngles, bone); } // Add bones to the list and build bind poses for each bone. for (int i = 0; i < skeleton.m_aBones.Count; i++) { boneGO = boneGameObjects[i]; bones.Add(boneGO.transform); if (i != _rootBoneIndex) { // Calculate bind pose using worldToLocalMatrix bindPoses.Add(boneGO.transform.worldToLocalMatrix * rootBone.localToWorldMatrix); } else // Root bone { bindPoses.Add(boneGO.transform.worldToLocalMatrix); } } // now we have to create the hooks for the skeleton for (int i = 0; i < skeleton.m_aHooks.Count; i++) { A3DSkeletonHook hook = skeleton.m_aHooks[i]; GameObject hookGO = new GameObject(hook.m_strName); hookGO.transform.parent = hook.Data.iBone >= 0 ? boneGameObjects[hook.Data.iBone].transform : rootBone; hookGO.transform.localPosition = CreateMatrixFromA3DMatrix4X4(hook.Data.matHookTM).GetColumn(3); hookGO.transform.localRotation = Quaternion.LookRotation(CreateMatrixFromA3DMatrix4X4(hook.Data.matHookTM).GetColumn(2), CreateMatrixFromA3DMatrix4X4(hook.Data.matHookTM).GetColumn(1)); hookGO.transform.localScale = CreateMatrixFromA3DMatrix4X4(hook.Data.matHookTM).lossyScale; // Store hook in dictionary for lookup // 将挂点存储在字典中以便查找 hooks[hook.m_strName] = hookGO.transform; } } public Matrix4x4[] GetBindPoses(string[] boneNames) { Matrix4x4[] boneBindPoses = null; if (boneNames?.Length > 0) { boneBindPoses = new Matrix4x4[boneNames.Length + 1]; for (int i = 0; i < boneNames.Length; i++) { boneBindPoses[i] = bindPoses[bones.FindIndex(bone => bone.name == boneNames[i])]; } boneBindPoses[boneNames.Length] = bindPoses[_rootBoneIndex]; } else { boneBindPoses = new Matrix4x4[bones.Count + 1]; for (int i = 0; i < bones.Count; i++) { boneBindPoses[i] = bindPoses[i]; } boneBindPoses[bones.Count] = bindPoses[_rootBoneIndex]; } return boneBindPoses; } private Matrix4x4 CreateMatrixFromA3DMatrix4X4(A3DMATRIX4 mat) { return new Matrix4x4( new Vector4(mat.m[0], mat.m[1], mat.m[2], mat.m[3]), new Vector4(mat.m[4], mat.m[5], mat.m[6], mat.m[7]), new Vector4(mat.m[8], mat.m[9], mat.m[10], mat.m[11]), new Vector4(mat.m[12], mat.m[13], mat.m[14], mat.m[15]) ); } /// /// Build the upToRootTM matrix for each bone in the skeleton.
/// Just send in the root bone and this function will run recursively to build the upToRootTM matrix for each bone. ///
/// private void BuildUpToRootMatrix(A3DBone rootBone) { if (_skeleton == null) { Debug.LogError("No skeleton data found."); return; } // Get the bone's parent index int parentIndex = rootBone.boneData.iParent; if (parentIndex < 0) { rootBone.m_upToRootTM = rootBone.m_relativeTM; } else { // Get the parent bone A3DBone parentBone = _skeleton.m_aBones[parentIndex]; // Calculate the upToRootTM matrix rootBone.m_upToRootTM = parentBone.m_upToRootTM * rootBone.m_relativeTM; } for (int i = 0; i < rootBone.m_aChildren.Length; i++) { BuildUpToRootMatrix(_skeleton.m_aBones[rootBone.m_aChildren[i]]); } } private void PrintOutData(GameObject boneGO, Matrix4x4 relativeMatrix, Matrix4x4 initMatrix, Vector3 initRotation, A3DBone bone) { // debug log the relative matrix in a human readable format Console.WriteLine($"Bone {boneGO.name} relative\n{relativeMatrix}\ninit\n{initMatrix}"); Console.WriteLine($"{boneGO.name} Flipped: {bone.boneData.byFlags}"); // print out matBoneInit position Console.WriteLine($"Bone {boneGO.name} matBoneInit position: {initMatrix.GetColumn(3)}"); // print out matBoneInit rotation Console.WriteLine($"Bone {boneGO.name} matBoneInit rotation: {initRotation}"); } #endif } }