diff --git a/AssetStudio/Classes/Avatar.cs b/AssetStudio/Classes/Avatar.cs index 97a3674..e9e0b2b 100644 --- a/AssetStudio/Classes/Avatar.cs +++ b/AssetStudio/Classes/Avatar.cs @@ -362,5 +362,10 @@ namespace AssetStudio } return null; } + + public string FindBonePath(uint hash) + { + return m_TOS.Find(pair => pair.Key == hash).Value; + } } } diff --git a/AssetStudio/Classes/Transform.cs b/AssetStudio/Classes/Transform.cs index 1b83db7..9498e50 100644 --- a/AssetStudio/Classes/Transform.cs +++ b/AssetStudio/Classes/Transform.cs @@ -1,4 +1,5 @@ -using System; +using SharpDX; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -7,12 +8,19 @@ namespace AssetStudio { public class Transform { - public PPtr m_GameObject = new PPtr(); + public PPtr m_GameObject; public float[] m_LocalRotation; public float[] m_LocalPosition; public float[] m_LocalScale; - public List m_Children = new List(); - public PPtr m_Father = new PPtr();//can be transform or type 224 (as seen in Minions) + public List m_Children; + public PPtr m_Father; + + public Transform(Vector3 t, Quaternion q, Vector3 s) + { + m_LocalPosition = new[] { t.X, t.Y, t.Z }; + m_LocalRotation = new[] { q.X, q.Y, q.Z, q.W }; + m_LocalScale = new[] { s.X, s.Y, s.Z }; + } public Transform(AssetPreloadData preloadData) { @@ -24,6 +32,7 @@ namespace AssetStudio m_LocalPosition = new[] { reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle() }; m_LocalScale = new[] { reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle() }; int m_ChildrenCount = reader.ReadInt32(); + m_Children = new List(m_ChildrenCount); for (int j = 0; j < m_ChildrenCount; j++) { m_Children.Add(sourceFile.ReadPPtr()); diff --git a/AssetStudio/StudioClasses/ModelConverter.cs b/AssetStudio/StudioClasses/ModelConverter.cs index 5ab061a..ab81617 100644 --- a/AssetStudio/StudioClasses/ModelConverter.cs +++ b/AssetStudio/StudioClasses/ModelConverter.cs @@ -22,6 +22,7 @@ namespace AssetStudio private Dictionary morphChannelInfo = new Dictionary(); private HashSet animationClipHashSet = new HashSet(); private Dictionary bonePathHash = new Dictionary(); + private bool deoptimize; public ModelConverter(GameObject m_GameObject) { @@ -198,6 +199,18 @@ namespace AssetStudio return frame; } + private ImportedFrame ConvertFrame(Transform trans, string name) + { + var frame = new ImportedFrame(); + frame.Name = name; + frame.InitChildren(0); + var m_EulerRotation = QuatToEuler(new[] { trans.m_LocalRotation[0], -trans.m_LocalRotation[1], -trans.m_LocalRotation[2], trans.m_LocalRotation[3] }); + frame.LocalRotation = new[] { m_EulerRotation[0], m_EulerRotation[1], m_EulerRotation[2] }; + frame.LocalScale = new[] { trans.m_LocalScale[0], trans.m_LocalScale[1], trans.m_LocalScale[2] }; + frame.LocalPosition = new[] { -trans.m_LocalPosition[0], trans.m_LocalPosition[1], trans.m_LocalPosition[2] }; + return frame; + } + private void ConvertFrames(Transform trans, ImportedFrame parent) { var frame = ConvertFrames(trans); @@ -354,10 +367,6 @@ namespace AssetStudio { //Bone iMesh.BoneList = new List(sMesh.m_Bones.Length); - /*if (sMesh.m_Bones.Length >= 256) - { - throw new Exception("Too many bones (" + mesh.m_BindPose.Length + ")"); - }*/ for (int i = 0; i < sMesh.m_Bones.Length; i++) { var bone = new ImportedBone(); @@ -405,6 +414,52 @@ namespace AssetStudio iMesh.BoneList.Add(bone); } + if (sMesh.m_Bones.Length == 0 && mesh.m_BindPose?.Length > 0 && mesh.m_BoneNameHashes?.Length > 0) + { + //TODO move to Init method use Animator.m_HasTransformHierarchy to judge + if (!deoptimize) + { + DeoptimizeTransformHierarchy(); + deoptimize = true; + } + //TODO Repeat code with above + for (int i = 0; i < mesh.m_BindPose.Length; i++) + { + var bone = new ImportedBone(); + var boneHash = mesh.m_BoneNameHashes[i]; + bone.Name = GetNameFromBonePathHashes(boneHash); + if (string.IsNullOrEmpty(bone.Name)) + { + bone.Name = avatar?.FindBoneName(boneHash); + } + if (string.IsNullOrEmpty(bone.Name)) + { + //throw new Exception("A Bone could neither be found by hash in Avatar nor by index in SkinnedMeshRenderer."); + continue; + } + var om = new float[4, 4]; + var m = mesh.m_BindPose[i]; + om[0, 0] = m[0, 0]; + om[0, 1] = -m[1, 0]; + om[0, 2] = -m[2, 0]; + om[0, 3] = m[3, 0]; + om[1, 0] = -m[0, 1]; + om[1, 1] = m[1, 1]; + om[1, 2] = m[2, 1]; + om[1, 3] = m[3, 1]; + om[2, 0] = -m[0, 2]; + om[2, 1] = m[1, 2]; + om[2, 2] = m[2, 2]; + om[2, 3] = m[3, 2]; + om[3, 0] = -m[0, 3]; + om[3, 1] = m[1, 3]; + om[3, 2] = m[2, 3]; + om[3, 3] = m[3, 3]; + bone.Matrix = om; + iMesh.BoneList.Add(bone); + } + } + //Morphs if (mesh.m_Shapes != null) { @@ -537,7 +592,7 @@ namespace AssetStudio return GetTransformPath(Father) + "/" + m_GameObject.m_Name; } - return String.Empty + m_GameObject.m_Name; + return m_GameObject.m_Name; } private ImportedMaterial ConvertMaterial(Material mat) @@ -968,5 +1023,56 @@ namespace AssetStudio double newValue = count * 360.0 + cur; return (float)newValue; } + + private void DeoptimizeTransformHierarchy() + { + if (avatar == null) + return; + // 1. Figure out the skeletonPaths from the unstripped avatar + var skeletonPaths = new List(); + foreach (var id in avatar.m_Avatar.m_AvatarSkeleton.m_ID) + { + var path = avatar.FindBonePath(id); + skeletonPaths.Add(path); + } + // 2. Restore the original transform hierarchy + // Prerequisite: skeletonPaths follow pre-order traversal + var rootFrame = FrameList[0]; + rootFrame.ClearChild(); + for (var i = 1; i < skeletonPaths.Count; i++) // start from 1, skip the root transform because it will always be there. + { + var path = skeletonPaths[i]; + var strs = path.Split('/'); + string transformName; + ImportedFrame parentFrame; + if (strs.Length == 1) + { + transformName = path; + parentFrame = rootFrame; + } + else + { + transformName = strs.Last(); + var parentFrameName = strs[strs.Length - 2]; + parentFrame = ImportedHelpers.FindFrame(parentFrameName, rootFrame); + } + + var skeletonPose = avatar.m_Avatar.m_DefaultPose; + var xform = skeletonPose.m_X[i]; + if (!(xform.t is Vector3 t)) + { + var v4 = (Vector4)xform.t; + t = (Vector3)v4; + } + if (!(xform.s is Vector3 s)) + { + var v4 = (Vector4)xform.s; + s = (Vector3)v4; + } + var curTransform = new Transform(t, xform.q, s); + var frame = ConvertFrame(curTransform, transformName); + parentFrame.AddChild(frame); + } + } } } diff --git a/AssetStudioUtility/Imported.cs b/AssetStudioUtility/Imported.cs index bb5622a..a27bd34 100644 --- a/AssetStudioUtility/Imported.cs +++ b/AssetStudioUtility/Imported.cs @@ -64,6 +64,11 @@ namespace AssetStudio return children.IndexOf(obj); } + public void ClearChild() + { + children.Clear(); + } + public IEnumerator GetEnumerator() { return children.GetEnumerator(); @@ -199,7 +204,7 @@ namespace AssetStudio public static class ImportedHelpers { - public static ImportedFrame FindFrame(String name, ImportedFrame root) + public static ImportedFrame FindFrame(string name, ImportedFrame root) { ImportedFrame frame = root; if ((frame != null) && (frame.Name == name)) @@ -218,7 +223,7 @@ namespace AssetStudio return null; } - public static ImportedMesh FindMesh(String frameName, List importedMeshList) + public static ImportedMesh FindMesh(string frameName, List importedMeshList) { foreach (ImportedMesh mesh in importedMeshList) { @@ -252,7 +257,7 @@ namespace AssetStudio return null; } - public static ImportedMaterial FindMaterial(String name, List importedMats) + public static ImportedMaterial FindMaterial(string name, List importedMats) { foreach (ImportedMaterial mat in importedMats) {