Files

313 lines
12 KiB
C#

using System.Collections.Generic;
using UnityEngine;
namespace BrewMonster.Scripts
{
public class SkeletonBuilder : MonoBehaviour
{
public List<Transform> bones = new List<Transform>();
public List<Matrix4x4> bindPoses = new List<Matrix4x4>();
public Transform rootBone;
public Dictionary<string, Transform> hooks = new Dictionary<string, Transform>();
/// <summary>
/// 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.
/// </summary>
/// <param name="boneNames"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Get hook Transform by name
/// 根据名称获取挂点变换
/// </summary>
/// <param name="hookName">Hook name / 挂点名称</param>
/// <param name="recursive">Search in child models / 在子模型中搜索</param>
/// <returns>Hook Transform or null if not found / 挂点变换,未找到返回null</returns>
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<SkeletonBuilder>(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<int, GameObject> boneGameObjects = new Dictionary<int, GameObject>();
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])
);
}
/// <summary>
/// Build the upToRootTM matrix for each bone in the skeleton. <br/>
/// Just send in the root bone and this function will run recursively to build the upToRootTM matrix for each bone.
/// </summary>
/// <param name="rootBone"></param>
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
}
}