diff --git a/AssetStudio.sln b/AssetStudio.sln index 5789699..10c86ca 100644 --- a/AssetStudio.sln +++ b/AssetStudio.sln @@ -7,6 +7,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetStudio", "AssetStudio\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetStudio-x86", "AssetStudio\AssetStudio-x86.csproj", "{F5E07FB2-95E4-417F-943F-D439D9A03BBA}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AssetStudioFBX", "AssetStudioFBX\AssetStudioFBX.vcxproj", "{4F8EF5EF-732B-49CF-9EB3-B23E19AE6267}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetStudioUtility", "AssetStudioUtility\AssetStudioUtility.csproj", "{9131C403-7FE8-444D-9AF5-5FE5DF76FF24}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AssetStudioFBX-x86", "AssetStudioFBX\AssetStudioFBX-x86.vcxproj", "{276DE52E-3FFE-4C3D-9076-62BCB7A5B991}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +27,18 @@ Global {F5E07FB2-95E4-417F-943F-D439D9A03BBA}.Debug|Any CPU.Build.0 = Debug|x86 {F5E07FB2-95E4-417F-943F-D439D9A03BBA}.Release|Any CPU.ActiveCfg = Release|x86 {F5E07FB2-95E4-417F-943F-D439D9A03BBA}.Release|Any CPU.Build.0 = Release|x86 + {4F8EF5EF-732B-49CF-9EB3-B23E19AE6267}.Debug|Any CPU.ActiveCfg = Debug|x64 + {4F8EF5EF-732B-49CF-9EB3-B23E19AE6267}.Debug|Any CPU.Build.0 = Debug|x64 + {4F8EF5EF-732B-49CF-9EB3-B23E19AE6267}.Release|Any CPU.ActiveCfg = Release|x64 + {4F8EF5EF-732B-49CF-9EB3-B23E19AE6267}.Release|Any CPU.Build.0 = Release|x64 + {9131C403-7FE8-444D-9AF5-5FE5DF76FF24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9131C403-7FE8-444D-9AF5-5FE5DF76FF24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9131C403-7FE8-444D-9AF5-5FE5DF76FF24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9131C403-7FE8-444D-9AF5-5FE5DF76FF24}.Release|Any CPU.Build.0 = Release|Any CPU + {276DE52E-3FFE-4C3D-9076-62BCB7A5B991}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {276DE52E-3FFE-4C3D-9076-62BCB7A5B991}.Debug|Any CPU.Build.0 = Debug|Win32 + {276DE52E-3FFE-4C3D-9076-62BCB7A5B991}.Release|Any CPU.ActiveCfg = Release|Win32 + {276DE52E-3FFE-4C3D-9076-62BCB7A5B991}.Release|Any CPU.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AssetStudio/AssetStudio-x86.csproj b/AssetStudio/AssetStudio-x86.csproj index fa8e70a..96bafb2 100644 --- a/AssetStudio/AssetStudio-x86.csproj +++ b/AssetStudio/AssetStudio-x86.csproj @@ -167,6 +167,7 @@ + ShaderResource.resx True @@ -264,6 +265,16 @@ + + + {276de52e-3ffe-4c3d-9076-62bcb7a5b991} + AssetStudioFBX-x86 + + + {9131c403-7fe8-444d-9af5-5fe5df76ff24} + AssetStudioUtility + + xcopy /y "$(ProjectDir)Library" "$(TargetDir)" diff --git a/AssetStudio/AssetStudio.csproj b/AssetStudio/AssetStudio.csproj index 6f1e6c6..cdc3c6e 100644 --- a/AssetStudio/AssetStudio.csproj +++ b/AssetStudio/AssetStudio.csproj @@ -205,6 +205,7 @@ + Form @@ -264,6 +265,16 @@ + + + {4f8ef5ef-732b-49cf-9eb3-b23e19ae6267} + AssetStudioFBX + + + {9131c403-7fe8-444d-9af5-5fe5df76ff24} + AssetStudioUtility + + xcopy /y "$(ProjectDir)Library" "$(TargetDir)" diff --git a/AssetStudio/AssetStudioForm.Designer.cs b/AssetStudio/AssetStudioForm.Designer.cs index 78bff92..00c60de 100644 --- a/AssetStudio/AssetStudioForm.Designer.cs +++ b/AssetStudio/AssetStudioForm.Designer.cs @@ -103,6 +103,8 @@ this.treeTip = new System.Windows.Forms.ToolTip(this.components); this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); this.showOriginalFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.animatorWithAnimationClipToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuStrip1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); this.splitContainer1.Panel1.SuspendLayout(); @@ -317,7 +319,9 @@ this.exportToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.exportAllAssetsMenuItem, this.exportSelectedAssetsMenuItem, - this.exportFilteredAssetsMenuItem}); + this.exportFilteredAssetsMenuItem, + this.toolStripSeparator1, + this.animatorWithAnimationClipToolStripMenuItem}); this.exportToolStripMenuItem.Name = "exportToolStripMenuItem"; this.exportToolStripMenuItem.Size = new System.Drawing.Size(58, 21); this.exportToolStripMenuItem.Text = "Export"; @@ -837,6 +841,18 @@ this.showOriginalFileToolStripMenuItem.Text = "show original file"; this.showOriginalFileToolStripMenuItem.Click += new System.EventHandler(this.showOriginalFileToolStripMenuItem_Click); // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(162, 6); + // + // animatorWithAnimationClipToolStripMenuItem + // + this.animatorWithAnimationClipToolStripMenuItem.Name = "animatorWithAnimationClipToolStripMenuItem"; + this.animatorWithAnimationClipToolStripMenuItem.Size = new System.Drawing.Size(240, 22); + this.animatorWithAnimationClipToolStripMenuItem.Text = "Export Animator with AnimationClip"; + this.animatorWithAnimationClipToolStripMenuItem.Click += new System.EventHandler(this.ExportAnimatorwithAnimationClip_Click); + // // AssetStudioForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); @@ -956,6 +972,8 @@ private OpenTK.GLControl glControl1; private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; private System.Windows.Forms.ToolStripMenuItem showOriginalFileToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripMenuItem animatorWithAnimationClipToolStripMenuItem; } } diff --git a/AssetStudio/AssetStudioForm.cs b/AssetStudio/AssetStudioForm.cs index c057445..d1a40ea 100644 --- a/AssetStudio/AssetStudioForm.cs +++ b/AssetStudio/AssetStudioForm.cs @@ -1025,10 +1025,20 @@ namespace AssetStudio } else { - StatusStripUpdate("Unsupported sprite for preview"); + StatusStripUpdate("Unsupported sprite for preview."); } break; } + case ClassIDReference.Animator: + { + StatusStripUpdate("Can be exported as a FBX file."); + break; + } + case ClassIDReference.AnimationClip: + { + StatusStripUpdate("Select AnimationClip with selecting Animator to export"); + break; + } default: { var str = asset.ViewStruct(); @@ -1523,6 +1533,12 @@ namespace AssetStudio exportedCount++; } break; + case ClassIDReference.Animator: + if (ExportAnimator(asset, exportpath)) + { + exportedCount++; + } + break; default: if (ExportRawFile(asset, exportpath)) { @@ -1853,5 +1869,41 @@ namespace AssetStudio glControl1.Invalidate(); } } + + private void ExportAnimatorwithAnimationClip_Click(object sender, EventArgs e) + { + AssetPreloadData animator = null; + List animationList = new List(); + for (int i = 0; i < assetListView.SelectedIndices.Count; i++) + { + var index = assetListView.SelectedIndices[i]; + var asset = (AssetPreloadData)assetListView.Items[index]; + if (asset.Type2 == 95) //Animator + { + animator = asset; + } + else if (asset.Type2 == 74) //AnimationClip + { + animationList.Add(asset); + } + } + + if (animator != null) + { + var saveFolderDialog1 = new OpenFolderDialog(); + if (saveFolderDialog1.ShowDialog(this) == DialogResult.OK) + { + var savePath = saveFolderDialog1.Folder; + string exportpath = savePath + "\\Animator\\"; + SetProgressBarValue(0); + SetProgressBarMaximum(1); + ThreadPool.QueueUserWorkItem(state => + { + StatusStripUpdate(ExportAnimator(animator, animationList, exportpath) ? "Successfully exported" : "Nothing exported."); + ProgressBarPerformStep(); + }); + } + } + } } } diff --git a/AssetStudio/Classes/GameObject.cs b/AssetStudio/Classes/GameObject.cs index 2360ada..0fa3f40 100644 --- a/AssetStudio/Classes/GameObject.cs +++ b/AssetStudio/Classes/GameObject.cs @@ -8,6 +8,7 @@ namespace AssetStudio { public class GameObject : TreeNode { + public AssetPreloadData asset; public List m_Components = new List(); public PPtr m_Transform; public PPtr m_MeshRenderer; @@ -24,6 +25,7 @@ namespace AssetStudio { if (preloadData != null) { + asset = preloadData; var sourceFile = preloadData.sourceFile; var reader = preloadData.InitReader(); diff --git a/AssetStudio/Classes/Mesh.cs b/AssetStudio/Classes/Mesh.cs index e64f49b..dae4449 100644 --- a/AssetStudio/Classes/Mesh.cs +++ b/AssetStudio/Classes/Mesh.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.IO; using System.Collections; +using SharpDX; /*Notes about handedness Converting from left-handed to right-handed and vice versa requires either: @@ -122,6 +123,8 @@ namespace AssetStudio public float[] m_UV3; public float[] m_UV4; public float[] m_Tangents; + public uint[] m_BoneNameHashes; + public BlendShapeData m_Shapes; public class SubMesh { @@ -166,6 +169,100 @@ namespace AssetStudio public byte m_BitSize; } + public class BlendShapeData + { + public class BlendShapeVertex + { + public Vector3 vertex { get; set; } + public Vector3 normal { get; set; } + public Vector3 tangent { get; set; } + public uint index { get; set; } + + public BlendShapeVertex() { } + + public BlendShapeVertex(EndianBinaryReader reader) + { + vertex = reader.ReadVector3(); + normal = reader.ReadVector3(); + tangent = reader.ReadVector3(); + index = reader.ReadUInt32(); + } + } + + public class MeshBlendShape + { + public uint firstVertex { get; set; } + public uint vertexCount { get; set; } + public bool hasNormals { get; set; } + public bool hasTangents { get; set; } + + public MeshBlendShape() { } + + public MeshBlendShape(EndianBinaryReader reader) + { + firstVertex = reader.ReadUInt32(); + vertexCount = reader.ReadUInt32(); + hasNormals = reader.ReadBoolean(); + hasTangents = reader.ReadBoolean(); + reader.ReadBytes(2); + } + } + + public class MeshBlendShapeChannel + { + public string name { get; set; } + public uint nameHash { get; set; } + public int frameIndex { get; set; } + public int frameCount { get; set; } + + public MeshBlendShapeChannel() { } + + public MeshBlendShapeChannel(EndianBinaryReader reader) + { + name = reader.ReadStringToNull(); + nameHash = reader.ReadUInt32(); + frameIndex = reader.ReadInt32(); + frameCount = reader.ReadInt32(); + } + } + + public List vertices { get; set; } + public List shapes { get; set; } + public List channels { get; set; } + public List fullWeights { get; set; } + + public BlendShapeData(EndianBinaryReader reader) + { + int numVerts = reader.ReadInt32(); + vertices = new List(numVerts); + for (int i = 0; i < numVerts; i++) + { + vertices.Add(new BlendShapeVertex(reader)); + } + + int numShapes = reader.ReadInt32(); + shapes = new List(numShapes); + for (int i = 0; i < numShapes; i++) + { + shapes.Add(new MeshBlendShape(reader)); + } + + int numChannels = reader.ReadInt32(); + channels = new List(numChannels); + for (int i = 0; i < numChannels; i++) + { + channels.Add(new MeshBlendShapeChannel(reader)); + } + + int numWeights = reader.ReadInt32(); + fullWeights = new List(numWeights); + for (int i = 0; i < numWeights; i++) + { + fullWeights.Add(reader.ReadSingle()); + } + } + } + public float bytesToFloat(byte[] inputBytes) { float result = 0; @@ -177,7 +274,7 @@ namespace AssetStudio result = inputBytes[0] / 255.0f; break; case 2: - result = Half.ToHalf(inputBytes, 0); + result = System.Half.ToHalf(inputBytes, 0); break; case 4: result = BitConverter.ToSingle(inputBytes, 0); @@ -420,25 +517,7 @@ namespace AssetStudio #region BlendShapeData and BindPose for 4.3.0 and later else if (version[0] >= 5 || (version[0] == 4 && version[1] >= 3)) { - int m_ShapeVertices_size = reader.ReadInt32(); - if (m_ShapeVertices_size > 0) - { - //bool stop = true; - } - reader.Position += m_ShapeVertices_size * 40; //vertex positions, normals, tangents & uint index - - int shapes_size = reader.ReadInt32(); - reader.Position += shapes_size * 12; //uint firstVertex, vertexCount; bool hasNormals, hasTangents - - int channels_size = reader.ReadInt32(); - for (int c = 0; c < channels_size; c++) - { - string channel_name = reader.ReadAlignedString(); - reader.Position += 12; //uint nameHash; int frameIndex, frameCount - } - - int fullWeights_size = reader.ReadInt32(); - reader.Position += fullWeights_size * 4; //floats + m_Shapes = new BlendShapeData(reader);//TODO 4.3 down m_BindPose = new float[reader.ReadInt32()][,]; for (int i = 0; i < m_BindPose.Length; i++) @@ -451,7 +530,11 @@ namespace AssetStudio } int m_BoneNameHashes_size = reader.ReadInt32(); - reader.Position += m_BoneNameHashes_size * 4; //uints + m_BoneNameHashes = new uint[m_BoneNameHashes_size]; + for (int i = 0; i < m_BoneNameHashes_size; i++) + { + m_BoneNameHashes[i] = reader.ReadUInt32(); + } uint m_RootBoneNameHash = reader.ReadUInt32(); } diff --git a/AssetStudio/Classes/MeshRenderer.cs b/AssetStudio/Classes/MeshRenderer.cs index c1d2f61..1490760 100644 --- a/AssetStudio/Classes/MeshRenderer.cs +++ b/AssetStudio/Classes/MeshRenderer.cs @@ -5,7 +5,7 @@ using System.Text; namespace AssetStudio { - class MeshRenderer + public class MeshRenderer { public PPtr m_GameObject; public bool m_Enabled; @@ -15,6 +15,8 @@ namespace AssetStudio public ushort m_LightmapIndexDynamic; public PPtr[] m_Materials; + protected MeshRenderer() { } + public MeshRenderer(AssetPreloadData preloadData) { var sourceFile = preloadData.sourceFile; diff --git a/AssetStudio/Classes/SkinnedMeshRenderer.cs b/AssetStudio/Classes/SkinnedMeshRenderer.cs index e9b7441..f95d000 100644 --- a/AssetStudio/Classes/SkinnedMeshRenderer.cs +++ b/AssetStudio/Classes/SkinnedMeshRenderer.cs @@ -5,17 +5,11 @@ using System.Text; namespace AssetStudio { - public class SkinnedMeshRenderer + public class SkinnedMeshRenderer : MeshRenderer { - public PPtr m_GameObject; - public bool m_Enabled; - public byte m_CastShadows; - public bool m_ReceiveShadows; - public ushort m_LightmapIndex; - public ushort m_LightmapIndexDynamic; - public PPtr[] m_Materials; public PPtr m_Mesh; public PPtr[] m_Bones; + public List m_BlendShapeWeights; public SkinnedMeshRenderer(AssetPreloadData preloadData) { @@ -126,13 +120,17 @@ namespace AssetStudio } else { - if (version[0] > 4 || (version[0] == 4 && version[1] >= 3)) + if (version[0] > 4 || (version[0] == 4 && version[1] >= 3))//4.3 and up { - int m_BlendShapeWeights = reader.ReadInt32(); - reader.Position += m_BlendShapeWeights * 4; //floats + int numBSWeights = reader.ReadInt32(); + m_BlendShapeWeights = new List(numBSWeights); + for (int i = 0; i < numBSWeights; i++) + { + m_BlendShapeWeights.Add(reader.ReadSingle()); + } } - if (version[0] > 4 || (version[0] >= 3 && version[1] >= 5)) + /*if (version[0] > 4 || (version[0] >= 3 && version[1] >= 5)) { PPtr m_RootBone = sourceFile.ReadPPtr(); } @@ -143,7 +141,7 @@ namespace AssetStudio float[] m_Center = { reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle() }; float[] m_Extent = { reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle() }; bool m_DirtyAABB = reader.ReadBoolean(); - } + }*/ } } } diff --git a/AssetStudio/ExportOptions.Designer.cs b/AssetStudio/ExportOptions.Designer.cs index 3671ce9..7597da0 100644 --- a/AssetStudio/ExportOptions.Designer.cs +++ b/AssetStudio/ExportOptions.Designer.cs @@ -54,12 +54,26 @@ this.topng = new System.Windows.Forms.RadioButton(); this.tobmp = new System.Windows.Forms.RadioButton(); this.converttexture = new System.Windows.Forms.CheckBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.compatibility = new System.Windows.Forms.CheckBox(); + this.flatInbetween = new System.Windows.Forms.CheckBox(); + this.boneSize = new System.Windows.Forms.NumericUpDown(); + this.label2 = new System.Windows.Forms.Label(); + this.skins = new System.Windows.Forms.CheckBox(); + this.label1 = new System.Windows.Forms.Label(); + this.filterPrecision = new System.Windows.Forms.NumericUpDown(); + this.allBones = new System.Windows.Forms.CheckBox(); + this.allFrames = new System.Windows.Forms.CheckBox(); + this.EulerFilter = new System.Windows.Forms.CheckBox(); this.includeBox.SuspendLayout(); this.geometryBox.SuspendLayout(); this.advancedBox.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.scaleFactor)).BeginInit(); this.groupBox1.SuspendLayout(); this.panel1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.boneSize)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.filterPrecision)).BeginInit(); this.SuspendLayout(); // // includeBox @@ -204,7 +218,7 @@ this.advancedBox.Controls.Add(this.upAxis); this.advancedBox.Controls.Add(this.scaleFactor); this.advancedBox.Controls.Add(this.scaleLabel); - this.advancedBox.Location = new System.Drawing.Point(12, 284); + this.advancedBox.Location = new System.Drawing.Point(12, 285); this.advancedBox.Name = "advancedBox"; this.advancedBox.Size = new System.Drawing.Size(249, 78); this.advancedBox.TabIndex = 5; @@ -260,7 +274,7 @@ // // fbxOKbutton // - this.fbxOKbutton.Location = new System.Drawing.Point(332, 364); + this.fbxOKbutton.Location = new System.Drawing.Point(339, 369); this.fbxOKbutton.Name = "fbxOKbutton"; this.fbxOKbutton.Size = new System.Drawing.Size(75, 21); this.fbxOKbutton.TabIndex = 6; @@ -271,7 +285,7 @@ // fbxCancel // this.fbxCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.fbxCancel.Location = new System.Drawing.Point(420, 364); + this.fbxCancel.Location = new System.Drawing.Point(420, 369); this.fbxCancel.Name = "fbxCancel"; this.fbxCancel.Size = new System.Drawing.Size(75, 21); this.fbxCancel.TabIndex = 7; @@ -282,7 +296,7 @@ // showExpOpt // this.showExpOpt.AutoSize = true; - this.showExpOpt.Location = new System.Drawing.Point(12, 367); + this.showExpOpt.Location = new System.Drawing.Point(12, 372); this.showExpOpt.Name = "showExpOpt"; this.showExpOpt.Size = new System.Drawing.Size(222, 16); this.showExpOpt.TabIndex = 8; @@ -296,7 +310,7 @@ this.groupBox1.Controls.Add(this.converttexture); this.groupBox1.Location = new System.Drawing.Point(267, 12); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(228, 349); + this.groupBox1.Size = new System.Drawing.Size(228, 140); this.groupBox1.TabIndex = 9; this.groupBox1.TabStop = false; this.groupBox1.Text = "Convert"; @@ -306,7 +320,7 @@ this.convertAudio.AutoSize = true; this.convertAudio.Checked = true; this.convertAudio.CheckState = System.Windows.Forms.CheckState.Checked; - this.convertAudio.Location = new System.Drawing.Point(8, 81); + this.convertAudio.Location = new System.Drawing.Point(6, 78); this.convertAudio.Name = "convertAudio"; this.convertAudio.Size = new System.Drawing.Size(198, 28); this.convertAudio.TabIndex = 6; @@ -360,20 +374,152 @@ this.converttexture.AutoSize = true; this.converttexture.Checked = true; this.converttexture.CheckState = System.Windows.Forms.CheckState.Checked; - this.converttexture.Location = new System.Drawing.Point(8, 20); + this.converttexture.Location = new System.Drawing.Point(6, 20); this.converttexture.Name = "converttexture"; this.converttexture.Size = new System.Drawing.Size(192, 16); this.converttexture.TabIndex = 1; this.converttexture.Text = "Convert Texture (If support)"; this.converttexture.UseVisualStyleBackColor = true; // + // groupBox2 + // + this.groupBox2.Controls.Add(this.compatibility); + this.groupBox2.Controls.Add(this.flatInbetween); + this.groupBox2.Controls.Add(this.boneSize); + this.groupBox2.Controls.Add(this.label2); + this.groupBox2.Controls.Add(this.skins); + this.groupBox2.Controls.Add(this.label1); + this.groupBox2.Controls.Add(this.filterPrecision); + this.groupBox2.Controls.Add(this.allBones); + this.groupBox2.Controls.Add(this.allFrames); + this.groupBox2.Controls.Add(this.EulerFilter); + this.groupBox2.Location = new System.Drawing.Point(267, 158); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(228, 205); + this.groupBox2.TabIndex = 11; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Animator"; + // + // compatibility + // + this.compatibility.AutoSize = true; + this.compatibility.Location = new System.Drawing.Point(6, 177); + this.compatibility.Name = "compatibility"; + this.compatibility.Size = new System.Drawing.Size(102, 16); + this.compatibility.TabIndex = 13; + this.compatibility.Text = "compatibility"; + this.compatibility.UseVisualStyleBackColor = true; + // + // flatInbetween + // + this.flatInbetween.AutoSize = true; + this.flatInbetween.Location = new System.Drawing.Point(6, 155); + this.flatInbetween.Name = "flatInbetween"; + this.flatInbetween.Size = new System.Drawing.Size(102, 16); + this.flatInbetween.TabIndex = 12; + this.flatInbetween.Text = "flatInbetween"; + this.flatInbetween.UseVisualStyleBackColor = true; + // + // boneSize + // + this.boneSize.Location = new System.Drawing.Point(65, 128); + this.boneSize.Name = "boneSize"; + this.boneSize.Size = new System.Drawing.Size(46, 21); + this.boneSize.TabIndex = 11; + this.boneSize.Value = new decimal(new int[] { + 10, + 0, + 0, + 0}); + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(6, 130); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(53, 12); + this.label2.TabIndex = 10; + this.label2.Text = "boneSize"; + // + // skins + // + this.skins.AutoSize = true; + this.skins.Checked = true; + this.skins.CheckState = System.Windows.Forms.CheckState.Checked; + this.skins.Location = new System.Drawing.Point(6, 105); + this.skins.Name = "skins"; + this.skins.Size = new System.Drawing.Size(54, 16); + this.skins.TabIndex = 8; + this.skins.Text = "skins"; + this.skins.UseVisualStyleBackColor = true; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(26, 39); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(95, 12); + this.label1.TabIndex = 7; + this.label1.Text = "filterPrecision"; + // + // filterPrecision + // + this.filterPrecision.DecimalPlaces = 2; + this.filterPrecision.Increment = new decimal(new int[] { + 1, + 0, + 0, + 131072}); + this.filterPrecision.Location = new System.Drawing.Point(127, 37); + this.filterPrecision.Name = "filterPrecision"; + this.filterPrecision.Size = new System.Drawing.Size(51, 21); + this.filterPrecision.TabIndex = 6; + this.filterPrecision.Value = new decimal(new int[] { + 25, + 0, + 0, + 131072}); + // + // allBones + // + this.allBones.AutoSize = true; + this.allBones.Checked = true; + this.allBones.CheckState = System.Windows.Forms.CheckState.Checked; + this.allBones.Location = new System.Drawing.Point(6, 83); + this.allBones.Name = "allBones"; + this.allBones.Size = new System.Drawing.Size(72, 16); + this.allBones.TabIndex = 5; + this.allBones.Text = "allBones"; + this.allBones.UseVisualStyleBackColor = true; + // + // allFrames + // + this.allFrames.AutoSize = true; + this.allFrames.Location = new System.Drawing.Point(6, 61); + this.allFrames.Name = "allFrames"; + this.allFrames.Size = new System.Drawing.Size(78, 16); + this.allFrames.TabIndex = 4; + this.allFrames.Text = "allFrames"; + this.allFrames.UseVisualStyleBackColor = true; + // + // EulerFilter + // + this.EulerFilter.AutoSize = true; + this.EulerFilter.Location = new System.Drawing.Point(6, 20); + this.EulerFilter.Name = "EulerFilter"; + this.EulerFilter.Size = new System.Drawing.Size(90, 16); + this.EulerFilter.TabIndex = 3; + this.EulerFilter.Text = "EulerFilter"; + this.EulerFilter.UseVisualStyleBackColor = true; + // // ExportOptions // this.AcceptButton = this.fbxOKbutton; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.fbxCancel; - this.ClientSize = new System.Drawing.Size(513, 392); + this.ClientSize = new System.Drawing.Size(508, 398); + this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox1); this.Controls.Add(this.showExpOpt); this.Controls.Add(this.fbxCancel); @@ -399,6 +545,10 @@ this.groupBox1.PerformLayout(); this.panel1.ResumeLayout(false); this.panel1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.boneSize)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.filterPrecision)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -432,5 +582,16 @@ private System.Windows.Forms.RadioButton tobmp; private System.Windows.Forms.CheckBox convertAudio; private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.CheckBox compatibility; + private System.Windows.Forms.CheckBox flatInbetween; + private System.Windows.Forms.NumericUpDown boneSize; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.CheckBox skins; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.NumericUpDown filterPrecision; + private System.Windows.Forms.CheckBox allBones; + private System.Windows.Forms.CheckBox allFrames; + private System.Windows.Forms.CheckBox EulerFilter; } } \ No newline at end of file diff --git a/AssetStudio/ExportOptions.cs b/AssetStudio/ExportOptions.cs index a79211c..9495678 100644 --- a/AssetStudio/ExportOptions.cs +++ b/AssetStudio/ExportOptions.cs @@ -36,6 +36,14 @@ namespace AssetStudio break; } } + EulerFilter.Checked = (bool)Properties.Settings.Default["EulerFilter"]; + filterPrecision.Value = (decimal)Properties.Settings.Default["filterPrecision"]; + allFrames.Checked = (bool)Properties.Settings.Default["allFrames"]; + allBones.Checked = (bool)Properties.Settings.Default["allBones"]; + skins.Checked = (bool)Properties.Settings.Default["skins"]; + boneSize.Value = (decimal)Properties.Settings.Default["boneSize"]; + flatInbetween.Checked = (bool)Properties.Settings.Default["flatInbetween"]; + compatibility.Checked = (bool)Properties.Settings.Default["compatibility"]; } private void exportOpnions_CheckedChanged(object sender, EventArgs e) @@ -63,6 +71,14 @@ namespace AssetStudio break; } } + Properties.Settings.Default["EulerFilter"] = EulerFilter.Checked; + Properties.Settings.Default["filterPrecision"] = filterPrecision.Value; + Properties.Settings.Default["allFrames"] = allFrames.Checked; + Properties.Settings.Default["allBones"] = allBones.Checked; + Properties.Settings.Default["skins"] = skins.Checked; + Properties.Settings.Default["boneSize"] = boneSize.Value; + Properties.Settings.Default["flatInbetween"] = flatInbetween.Checked; + Properties.Settings.Default["compatibility"] = compatibility.Checked; Properties.Settings.Default.Save(); DialogResult = DialogResult.OK; Close(); diff --git a/AssetStudio/Library/SharpDX.dll b/AssetStudio/Library/SharpDX.dll new file mode 100644 index 0000000..baa26ad Binary files /dev/null and b/AssetStudio/Library/SharpDX.dll differ diff --git a/AssetStudio/Library/x64/libfbxsdk.dll b/AssetStudio/Library/x64/libfbxsdk.dll new file mode 100644 index 0000000..2604fbc Binary files /dev/null and b/AssetStudio/Library/x64/libfbxsdk.dll differ diff --git a/AssetStudio/Library/x86/libfbxsdk.dll b/AssetStudio/Library/x86/libfbxsdk.dll new file mode 100644 index 0000000..3bce617 Binary files /dev/null and b/AssetStudio/Library/x86/libfbxsdk.dll differ diff --git a/AssetStudio/Properties/Settings.Designer.cs b/AssetStudio/Properties/Settings.Designer.cs index d2cb99c..7131784 100644 --- a/AssetStudio/Properties/Settings.Designer.cs +++ b/AssetStudio/Properties/Settings.Designer.cs @@ -238,5 +238,101 @@ namespace AssetStudio.Properties { this["displayOriginalName"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool EulerFilter { + get { + return ((bool)(this["EulerFilter"])); + } + set { + this["EulerFilter"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0.25")] + public decimal filterPrecision { + get { + return ((decimal)(this["filterPrecision"])); + } + set { + this["filterPrecision"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool allFrames { + get { + return ((bool)(this["allFrames"])); + } + set { + this["allFrames"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool allBones { + get { + return ((bool)(this["allBones"])); + } + set { + this["allBones"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool skins { + get { + return ((bool)(this["skins"])); + } + set { + this["skins"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("10")] + public decimal boneSize { + get { + return ((decimal)(this["boneSize"])); + } + set { + this["boneSize"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool flatInbetween { + get { + return ((bool)(this["flatInbetween"])); + } + set { + this["flatInbetween"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool compatibility { + get { + return ((bool)(this["compatibility"])); + } + set { + this["compatibility"] = value; + } + } } } diff --git a/AssetStudio/Properties/Settings.settings b/AssetStudio/Properties/Settings.settings index 9bc51c0..6e1715f 100644 --- a/AssetStudio/Properties/Settings.settings +++ b/AssetStudio/Properties/Settings.settings @@ -56,5 +56,29 @@ False + + False + + + 0.25 + + + False + + + True + + + True + + + 10 + + + False + + + False + \ No newline at end of file diff --git a/AssetStudio/StudioClasses/Exporter.cs b/AssetStudio/StudioClasses/Exporter.cs index cc33be9..0323efa 100644 --- a/AssetStudio/StudioClasses/Exporter.cs +++ b/AssetStudio/StudioClasses/Exporter.cs @@ -316,5 +316,39 @@ namespace AssetStudio Directory.CreateDirectory(Path.GetDirectoryName(filename)); return false; } + + public static bool ExportAnimator(AssetPreloadData animator, string exportPath) + { + var EulerFilter = (bool)Properties.Settings.Default["EulerFilter"]; + var filterPrecision = (float)(decimal)Properties.Settings.Default["filterPrecision"]; + var allFrames = (bool)Properties.Settings.Default["allFrames"]; + var allBones = (bool)Properties.Settings.Default["allBones"]; + var skins = (bool)Properties.Settings.Default["skins"]; + var boneSize = (int)(decimal)Properties.Settings.Default["boneSize"]; + var flatInbetween = (bool)Properties.Settings.Default["flatInbetween"]; + var compatibility = (bool)Properties.Settings.Default["compatibility"]; + var m_Animator = new Animator(animator); + var convert = new ModelConverter(m_Animator); + exportPath = exportPath + Studio.FixFileName(animator.Text) + ".fbx"; + Fbx.Exporter.Export(exportPath, convert, EulerFilter, filterPrecision, ".fbx", allFrames, allBones, skins, boneSize, flatInbetween, compatibility); + return true; + } + + public static bool ExportAnimator(AssetPreloadData animator, List animations, string exportPath) + { + var EulerFilter = (bool)Properties.Settings.Default["EulerFilter"]; + var filterPrecision = (float)(decimal)Properties.Settings.Default["filterPrecision"]; + var allFrames = (bool)Properties.Settings.Default["allFrames"]; + var allBones = (bool)Properties.Settings.Default["allBones"]; + var skins = (bool)Properties.Settings.Default["skins"]; + var boneSize = (int)(decimal)Properties.Settings.Default["boneSize"]; + var flatInbetween = (bool)Properties.Settings.Default["flatInbetween"]; + var compatibility = (bool)Properties.Settings.Default["compatibility"]; + var m_Animator = new Animator(animator); + var convert = new ModelConverter(m_Animator, animations); + exportPath = exportPath + Studio.FixFileName(animator.Text) + ".fbx"; + Fbx.Exporter.Export(exportPath, convert, EulerFilter, filterPrecision, ".fbx", allFrames, allBones, skins, boneSize, flatInbetween, compatibility); + return true; + } } } diff --git a/AssetStudio/StudioClasses/ModelConverter.cs b/AssetStudio/StudioClasses/ModelConverter.cs new file mode 100644 index 0000000..1da635e --- /dev/null +++ b/AssetStudio/StudioClasses/ModelConverter.cs @@ -0,0 +1,876 @@ +using System; +using System.Collections.Generic; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using SharpDX; +using static AssetStudio.Studio; + +namespace AssetStudio +{ + /* TODO Handle all things in one loop + * Init with GameObject + * Other optimization + */ + class ModelConverter : IImported + { + public List FrameList { get; protected set; } = new List(); + public List MeshList { get; protected set; } = new List(); + public List MaterialList { get; protected set; } = new List(); + public List TextureList { get; protected set; } = new List(); + public List AnimationList { get; protected set; } = new List(); + public List MorphList { get; protected set; } = new List(); + + private Avatar avatar; + private Dictionary morphChannelInfo = new Dictionary(); + private HashSet animationClipHashSet = new HashSet(); + private Dictionary bonePathHash = new Dictionary(); + + public ModelConverter(Animator m_Animator) + { + InitWithAnimator(m_Animator); + CollectAnimationClip(m_Animator); + ConvertAnimations(); + } + + public ModelConverter(Animator m_Animator, List animationList) + { + InitWithAnimator(m_Animator); + foreach (var assetPreloadData in animationList) + { + animationClipHashSet.Add(assetPreloadData); + } + ConvertAnimations(); + } + + private void InitWithAnimator(Animator m_Animator) + { + if (assetsfileList.TryGetPD(m_Animator.m_Avatar, out var m_Avatar)) + avatar = new Avatar(m_Avatar); + + assetsfileList.TryGetGameObject(m_Animator.m_GameObject, out var m_GameObject); + assetsfileList.TryGetTransform(m_GameObject.m_Transform, out var m_Transform); + var rootTransform = m_Transform; + while (assetsfileList.TryGetTransform(rootTransform.m_Father, out var m_Father))//Get Root Transform + { + rootTransform = m_Father; + } + + CreateBonePathHash(rootTransform); + ConvertFrames(rootTransform, null); + CollectMorphInfo(rootTransform); + ConvertMeshRenderer(m_Transform); + } + + private void ConvertMeshRenderer(Transform m_Transform) + { + assetsfileList.TryGetGameObject(m_Transform.m_GameObject, out var m_GameObject); + foreach (var m_Component in m_GameObject.m_Components) + { + if (assetsfileList.TryGetPD(m_Component, out var assetPreloadData)) + { + switch (assetPreloadData.Type2) + { + case 23: //MeshRenderer + { + var m_Renderer = new MeshRenderer(assetPreloadData); + ConvertMeshRenderer(m_Renderer); + break; + } + case 137: //SkinnedMeshRenderer + { + var m_SkinnedMeshRenderer = new SkinnedMeshRenderer(assetPreloadData); + ConvertMeshRenderer(m_SkinnedMeshRenderer); + break; + } + case 111: //Animation + { + var m_Animation = new Animation(assetPreloadData); + foreach (var animation in m_Animation.m_Animations) + { + if (assetsfileList.TryGetPD(animation, out var animationClip)) + { + animationClipHashSet.Add(animationClip); + } + } + break; + } + } + } + } + foreach (var pptr in m_Transform.m_Children) + { + if (assetsfileList.TryGetTransform(pptr, out var child)) + ConvertMeshRenderer(child); + } + } + + private void CollectAnimationClip(Animator m_Animator) + { + if (assetsfileList.TryGetPD(m_Animator.m_Controller, out var assetPreloadData)) + { + if (assetPreloadData.Type2 == 221)//AnimatorOverrideController + { + var m_AnimatorOverrideController = new AnimatorOverrideController(assetPreloadData); + if (assetsfileList.TryGetPD(m_AnimatorOverrideController.m_Controller, out assetPreloadData)) + { + var m_AnimatorController = new AnimatorController(assetPreloadData); + foreach (var m_AnimationClip in m_AnimatorController.m_AnimationClips) + { + if (assetsfileList.TryGetPD(m_AnimationClip, out assetPreloadData)) + { + animationClipHashSet.Add(assetPreloadData); + } + } + } + /*foreach (var clip in m_AnimatorOverrideController.m_Clips) + { + if (assetsfileList.TryGetPD(clip[1], out assetPreloadData)) + { + animationList.Add(new AnimationClip(assetPreloadData)); + } + }*/ + } + else if (assetPreloadData.Type2 == 91)//AnimatorController + { + var m_AnimatorController = new AnimatorController(assetPreloadData); + foreach (var m_AnimationClip in m_AnimatorController.m_AnimationClips) + { + if (assetsfileList.TryGetPD(m_AnimationClip, out assetPreloadData)) + { + animationClipHashSet.Add(assetPreloadData); + } + } + } + } + } + + private void ConvertFrames(Transform trans, ImportedFrame parent) + { + var frame = new ImportedFrame(); + assetsfileList.TryGetGameObject(trans.m_GameObject, out var m_GameObject); + frame.Name = m_GameObject.m_Name; + frame.InitChildren(trans.m_Children.Count); + Quaternion mirroredRotation = new Quaternion(trans.m_LocalRotation[0], trans.m_LocalRotation[1], trans.m_LocalRotation[2], trans.m_LocalRotation[3]); + mirroredRotation.Y *= -1; + mirroredRotation.Z *= -1; + var m_LocalScale = new Vector3(trans.m_LocalScale[0], trans.m_LocalScale[1], trans.m_LocalScale[2]); + var m_LocalPosition = new Vector3(trans.m_LocalPosition[0], trans.m_LocalPosition[1], trans.m_LocalPosition[2]); + frame.Matrix = Matrix.Scaling(m_LocalScale) * Matrix.RotationQuaternion(mirroredRotation) * Matrix.Translation(-m_LocalPosition.X, m_LocalPosition.Y, m_LocalPosition.Z); + if (parent == null) + { + FrameList = new List(); + FrameList.Add(frame); + } + else + { + parent.AddChild(frame); + } + foreach (var pptr in trans.m_Children) + { + if (assetsfileList.TryGetTransform(pptr, out var child)) + ConvertFrames(child, frame); + } + } + + private void CollectMorphInfo(Transform m_Transform) + { + assetsfileList.TryGetGameObject(m_Transform.m_GameObject, out var m_GameObject); + if (assetsfileList.TryGetPD(m_GameObject.m_SkinnedMeshRenderer, out var assetPreloadData)) + { + var m_SkinnedMeshRenderer = new SkinnedMeshRenderer(assetPreloadData); + if (assetsfileList.TryGetPD(m_SkinnedMeshRenderer.m_Mesh, out var MeshPD)) + { + var mesh = new Mesh(MeshPD, true); + foreach (var channel in mesh.m_Shapes.channels) + { + morphChannelInfo.Add(channel.nameHash, channel.name); + } + } + } + + foreach (var pptr in m_Transform.m_Children) + { + if (assetsfileList.TryGetTransform(pptr, out var child)) + CollectMorphInfo(child); + } + } + + + private void ConvertMeshRenderer(MeshRenderer meshR) + { + var mesh = GetMesh(meshR); + if (mesh == null) + return; + var iMesh = new ImportedMesh(); + assetsfileList.TryGetGameObject(meshR.m_GameObject, out var m_GameObject2); + assetsfileList.TryGetTransform(m_GameObject2.m_Transform, out var meshTransform); + iMesh.Name = GetTransformPath(meshTransform); + iMesh.SubmeshList = new List(mesh.m_SubMeshes.Count); + int sum = 0; + for (int i = 0; i < mesh.m_SubMeshes.Count; i++) + { + var submesh = mesh.m_SubMeshes[i]; + var iSubmesh = new ImportedSubmesh(); + iSubmesh.Index = i; + iSubmesh.Visible = true; + Material mat = null; + if (i < meshR.m_Materials.Length) + { + if (assetsfileList.TryGetPD(meshR.m_Materials[i], out var MaterialPD)) + { + mat = new Material(MaterialPD); + } + } + ImportedMaterial iMat = ConvertMaterial(mat); + iSubmesh.Material = iMat.Name; + iSubmesh.VertexList = new List((int)submesh.vertexCount); + for (var j = mesh.m_SubMeshes[i].firstVertex; j < mesh.m_SubMeshes[i].firstVertex + mesh.m_SubMeshes[i].vertexCount; j++) + { + var iVertex = new ImportedVertexWithColour(); + //Vertices + int c = 3; + if (mesh.m_Vertices.Length == mesh.m_VertexCount * 4) + { + c = 4; + } + iVertex.Position = new Vector3(-mesh.m_Vertices[j * c], mesh.m_Vertices[j * c + 1], mesh.m_Vertices[j * c + 2]); + //Normals + if (mesh.m_Normals != null && mesh.m_Normals.Length > 0) + { + if (mesh.m_Normals.Length == mesh.m_VertexCount * 3) + { + c = 3; + } + else if (mesh.m_Normals.Length == mesh.m_VertexCount * 4) + { + c = 4; + } + iVertex.Normal = new Vector3(-mesh.m_Normals[j * c], mesh.m_Normals[j * c + 1], mesh.m_Normals[j * c + 2]); + } + //Colors + if (mesh.m_Colors != null && mesh.m_Colors.Length > 0) + { + if (mesh.m_Colors.Length == mesh.m_VertexCount * 3) + { + iVertex.Colour = new Color4(mesh.m_Colors[j * 3], mesh.m_Colors[j * 3 + 1], mesh.m_Colors[j * 3 + 2], 1.0f); + } + else + { + iVertex.Colour = new Color4(mesh.m_Colors[j * 4], mesh.m_Colors[j * 4 + 1], mesh.m_Colors[j * 4 + 2], mesh.m_Colors[j * 4 + 3]); + } + } + //UV + if (mesh.m_UV1 != null && mesh.m_UV1.Length == mesh.m_VertexCount * 2) + { + iVertex.UV = new[] { mesh.m_UV1[j * 2], -mesh.m_UV1[j * 2 + 1] }; + } + //Tangent + if (mesh.m_Tangents != null) + { + iVertex.Tangent = new Vector4(-mesh.m_Tangents[j * 4], mesh.m_Tangents[j * 4 + 1], mesh.m_Tangents[j * 4 + 2], mesh.m_Tangents[j * 4 + 3]); + } + //BoneInfluence + if (mesh.m_Skin.Length > 0) + { + var inf = mesh.m_Skin[j]; + iVertex.BoneIndices = new byte[inf.Count]; + iVertex.Weights = new float[inf.Count]; + for (var k = 0; k < inf.Count; k++) + { + iVertex.BoneIndices[k] = (byte)inf[k].boneIndex; + iVertex.Weights[k] = inf[k].weight; + } + } + iSubmesh.VertexList.Add(iVertex); + } + //Face + int numFaces = (int)mesh.m_SubMeshes[i].indexCount / 3; + iSubmesh.FaceList = new List(numFaces); + var end = sum + numFaces; + for (int f = sum; f < end; f++) + { + var face = new ImportedFace(); + face.VertexIndices = new int[3]; + face.VertexIndices[0] = (int)(mesh.m_Indices[f * 3 + 2] - submesh.firstVertex); + face.VertexIndices[1] = (int)(mesh.m_Indices[f * 3 + 1] - submesh.firstVertex); + face.VertexIndices[2] = (int)(mesh.m_Indices[f * 3] - submesh.firstVertex); + iSubmesh.FaceList.Add(face); + } + sum = end; + iMesh.SubmeshList.Add(iSubmesh); + } + + if (meshR is SkinnedMeshRenderer sMesh) + { + //Bone + iMesh.BoneList = new List(sMesh.m_Bones.Length); + /*if (meshR.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(); + 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)) + { + if (assetsfileList.TryGetTransform(sMesh.m_Bones[i], out var m_Transform)) + { + assetsfileList.TryGetGameObject(m_Transform.m_GameObject, out var m_GameObject); + bone.Name = m_GameObject.m_Name; + } + else + { + throw new NotSupportedException(); + } + } + var om = new Matrix(); + for (int x = 0; x < 4; x++) + { + for (int y = 0; y < 4; y++) + { + om[x, y] = mesh.m_BindPose[i][x, y]; + } + } + var m = Matrix.Transpose(om); + m.Decompose(out var s, out var q, out var t); + t.X *= -1; + q.Y *= -1; + q.Z *= -1; + bone.Matrix = Matrix.Scaling(s) * Matrix.RotationQuaternion(q) * Matrix.Translation(t); + iMesh.BoneList.Add(bone); + } + + //Morphs + if (mesh.m_Shapes.shapes.Count > 0) + { + ImportedMorph morph = null; + string lastGroup = ""; + for (int i = 0; i < mesh.m_Shapes.channels.Count; i++) + { + string group = BlendShapeNameGroup(mesh, i); + if (group != lastGroup) + { + morph = new ImportedMorph(); + MorphList.Add(morph); + morph.Name = iMesh.Name; + morph.ClipName = group; + morph.Channels = new List>(mesh.m_Shapes.channels.Count); + morph.KeyframeList = new List(mesh.m_Shapes.shapes.Count); + lastGroup = group; + } + + morph.Channels.Add(new Tuple(i < sMesh.m_BlendShapeWeights.Count ? sMesh.m_BlendShapeWeights[i] : 0f, morph.KeyframeList.Count, mesh.m_Shapes.channels[i].frameCount)); + for (int frameIdx = 0; frameIdx < mesh.m_Shapes.channels[i].frameCount; frameIdx++) + { + ImportedMorphKeyframe keyframe = new ImportedMorphKeyframe(); + keyframe.Name = BlendShapeNameExtension(mesh, i) + "_" + frameIdx; + int shapeIdx = mesh.m_Shapes.channels[i].frameIndex + frameIdx; + keyframe.VertexList = new List((int)mesh.m_Shapes.shapes[shapeIdx].vertexCount); + keyframe.MorphedVertexIndices = new List((int)mesh.m_Shapes.shapes[shapeIdx].vertexCount); + keyframe.Weight = shapeIdx < mesh.m_Shapes.fullWeights.Count ? mesh.m_Shapes.fullWeights[shapeIdx] : 100f; + int lastVertIndex = (int)(mesh.m_Shapes.shapes[shapeIdx].firstVertex + mesh.m_Shapes.shapes[shapeIdx].vertexCount); + for (int j = (int)mesh.m_Shapes.shapes[shapeIdx].firstVertex; j < lastVertIndex; j++) + { + var morphVert = mesh.m_Shapes.vertices[j]; + ImportedVertex vert = GetSourceVertex(iMesh.SubmeshList, (int)morphVert.index); + ImportedVertex destVert = new ImportedVertex(); + Vector3 morphPos = morphVert.vertex; + morphPos.X *= -1; + destVert.Position = vert.Position + morphPos; + Vector3 morphNormal = morphVert.normal; + morphNormal.X *= -1; + destVert.Normal = morphNormal; + Vector4 morphTangent = new Vector4(morphVert.tangent, 0); + morphTangent.X *= -1; + destVert.Tangent = morphTangent; + keyframe.VertexList.Add(destVert); + keyframe.MorphedVertexIndices.Add((ushort)morphVert.index); + } + + morph.KeyframeList.Add(keyframe); + } + } + } + } + + MeshList.Add(iMesh); + } + + private Mesh GetMesh(MeshRenderer meshR) + { + if (meshR is SkinnedMeshRenderer sMesh) + { + if (assetsfileList.TryGetPD(sMesh.m_Mesh, out var MeshPD)) + { + return new Mesh(MeshPD, true); + } + } + else + { + assetsfileList.TryGetGameObject(meshR.m_GameObject, out var m_GameObject); + foreach (var m_Component in m_GameObject.m_Components) + { + if (assetsfileList.TryGetPD(m_Component, out var assetPreloadData)) + { + if (assetPreloadData.Type2 == 33) //MeshFilter + { + var m_MeshFilter = new MeshFilter(assetPreloadData); + if (assetsfileList.TryGetPD(m_MeshFilter.m_Mesh, out var MeshPD)) + { + return new Mesh(MeshPD, true); + } + } + } + } + } + + return null; + } + + + private string GetTransformPath(Transform meshTransform) + { + assetsfileList.TryGetGameObject(meshTransform.m_GameObject, out var m_GameObject); + if (assetsfileList.TryGetTransform(meshTransform.m_Father, out var Father)) + { + return GetTransformPath(Father) + "/" + m_GameObject.m_Name; + } + + return String.Empty + m_GameObject.m_Name; + } + + private ImportedMaterial ConvertMaterial(Material mat) + { + ImportedMaterial iMat; + if (mat != null) + { + iMat = ImportedHelpers.FindMaterial(mat.m_Name, MaterialList); + if (iMat != null) + { + return iMat; + } + iMat = new ImportedMaterial(); + iMat.Name = mat.m_Name; + foreach (var col in mat.m_Colors) + { + var color = new Color4(col.second[0], col.second[1], col.second[2], col.second[3]); + switch (col.first) + { + case "_Color": + iMat.Diffuse = color; + break; + case "_SColor": + iMat.Ambient = color; + break; + case "_EmissionColor": + iMat.Emissive = color; + break; + case "_SpecColor": + iMat.Specular = color; + break; + case "_RimColor": + case "_OutlineColor": + case "_ShadowColor": + break; + } + } + + foreach (var flt in mat.m_Floats) + { + switch (flt.first) + { + case "_Shininess": + iMat.Power = flt.second; + break; + case "_RimPower": + case "_Outline": + break; + } + } + + //textures + iMat.Textures = new string[5]; + iMat.TexOffsets = new Vector2[5]; + iMat.TexScales = new Vector2[5]; + foreach (var texEnv in mat.m_TexEnvs) + { + Texture2D tex2D = null; + if (assetsfileList.TryGetPD(texEnv.m_Texture, out var TexturePD) && TexturePD.Type2 == 28)//TODO other Texture + { + tex2D = new Texture2D(TexturePD, true); + } + + if (tex2D == null) + { + continue; + } + int dest = texEnv.name == "_MainTex" ? 0 : texEnv.name == "_BumpMap" ? 4 : texEnv.name.Contains("Spec") ? 2 : texEnv.name.Contains("Norm") ? 3 : -1; + if (dest < 0 || iMat.Textures[dest] != null) + { + continue; + } + iMat.Textures[dest] = TexturePD.Text + ".png"; + iMat.TexOffsets[dest] = new Vector2(texEnv.m_Offset[0], texEnv.m_Offset[1]); + iMat.TexScales[dest] = new Vector2(texEnv.m_Scale[0], texEnv.m_Scale[1]); + ConvertTexture2D(tex2D, iMat.Textures[dest]); + } + + MaterialList.Add(iMat); + } + else + { + iMat = new ImportedMaterial(); + } + return iMat; + } + + private void ConvertTexture2D(Texture2D tex2D, string name) + { + var iTex = ImportedHelpers.FindTexture(name, TextureList); + if (iTex != null) + { + return; + } + + using (var memStream = new MemoryStream()) + { + var bitmap = tex2D.ConvertToBitmap(true); + if (bitmap != null) + { + bitmap.Save(memStream, ImageFormat.Png); + memStream.Position = 0; + iTex = new ImportedTexture(memStream, name); + TextureList.Add(iTex); + } + } + } + + private void ConvertAnimations() + { + foreach (var assetPreloadData in animationClipHashSet) + { + var clip = new AnimationClip(assetPreloadData); + if (clip.m_Legacy) + { + var iAnim = new ImportedKeyframedAnimation(); + iAnim.Name = clip.m_Name; + AnimationList.Add(iAnim); + iAnim.TrackList = new List(); + foreach (var m_RotationCurve in clip.m_RotationCurves) + { + var path = m_RotationCurve.path; + var boneName = path.Substring(path.LastIndexOf('/') + 1); + var track = iAnim.FindTrack(boneName); + if (track == null) + { + track = new ImportedAnimationKeyframedTrack(); + track.Name = boneName; + iAnim.TrackList.Add(track); + } + foreach (var m_Curve in m_RotationCurve.curve.m_Curve) + { + if (!track.Keyframes.TryGetValue(m_Curve.time, out var keyFrames)) + { + keyFrames = new ImportedAnimationKeyframe(); + track.Keyframes.Add(m_Curve.time, keyFrames); + } + keyFrames.Rotation = new ImportedKeyframe( + m_Curve.time, + new Quaternion(m_Curve.value.X, -m_Curve.value.Y, -m_Curve.value.Z, m_Curve.value.W), + new Quaternion(m_Curve.inSlope.X, -m_Curve.inSlope.Y, -m_Curve.inSlope.Z, m_Curve.inSlope.W), + new Quaternion(m_Curve.outSlope.X, -m_Curve.outSlope.Y, -m_Curve.outSlope.Z, m_Curve.outSlope.W)); + } + } + foreach (var m_PositionCurve in clip.m_PositionCurves) + { + var path = m_PositionCurve.path; + var boneName = path.Substring(path.LastIndexOf('/') + 1); + var track = iAnim.FindTrack(boneName); + if (track == null) + { + track = new ImportedAnimationKeyframedTrack(); + track.Name = boneName; + iAnim.TrackList.Add(track); + } + foreach (var m_Curve in m_PositionCurve.curve.m_Curve) + { + if (!track.Keyframes.TryGetValue(m_Curve.time, out var keyFrames)) + { + keyFrames = new ImportedAnimationKeyframe(); + track.Keyframes.Add(m_Curve.time, keyFrames); + } + keyFrames.Translation = new ImportedKeyframe( + m_Curve.time, + new Vector3(-m_Curve.value.X, m_Curve.value.Y, m_Curve.value.Z), + new Vector3(-m_Curve.inSlope.X, m_Curve.inSlope.Y, m_Curve.inSlope.Z), + new Vector3(-m_Curve.outSlope.X, m_Curve.outSlope.Y, m_Curve.outSlope.Z)); + } + } + foreach (var m_ScaleCurve in clip.m_ScaleCurves) + { + var path = m_ScaleCurve.path; + var boneName = path.Substring(path.LastIndexOf('/') + 1); + var track = iAnim.FindTrack(boneName); + if (track == null) + { + track = new ImportedAnimationKeyframedTrack(); + track.Name = boneName; + iAnim.TrackList.Add(track); + } + foreach (var m_Curve in m_ScaleCurve.curve.m_Curve) + { + if (!track.Keyframes.TryGetValue(m_Curve.time, out var keyFrames)) + { + keyFrames = new ImportedAnimationKeyframe(); + track.Keyframes.Add(m_Curve.time, keyFrames); + } + keyFrames.Scaling = new ImportedKeyframe( + m_Curve.time, + new Vector3(m_Curve.value.X, m_Curve.value.Y, m_Curve.value.Z), + new Vector3(m_Curve.inSlope.X, m_Curve.inSlope.Y, m_Curve.inSlope.Z), + new Vector3(m_Curve.outSlope.X, m_Curve.outSlope.Y, m_Curve.outSlope.Z)); + } + } + } + else + { + var iAnim = new ImportedSampledAnimation(); + iAnim.Name = clip.m_Name; + iAnim.SampleRate = clip.m_SampleRate; + AnimationList.Add(iAnim); + int numTracks = (clip.m_MuscleClip.m_Clip.m_ConstantClip.data.Length + (int)clip.m_MuscleClip.m_Clip.m_DenseClip.m_CurveCount + (int)clip.m_MuscleClip.m_Clip.m_StreamedClip.curveCount + 9) / 10; + iAnim.TrackList = new List(numTracks); + var streamedFrames = clip.m_MuscleClip.m_Clip.m_StreamedClip.ReadData(); + float[] streamedValues = new float[clip.m_MuscleClip.m_Clip.m_StreamedClip.curveCount]; + int numFrames = Math.Max(clip.m_MuscleClip.m_Clip.m_DenseClip.m_FrameCount, streamedFrames.Count - 2); + for (int frameIdx = 0; frameIdx < numFrames; frameIdx++) + { + if (1 + frameIdx < streamedFrames.Count) + { + for (int i = 0; i < streamedFrames[1 + frameIdx].keyList.Count; i++) + { + streamedValues[i] = streamedFrames[1 + frameIdx].keyList[i].value; + } + } + + int numStreamedCurves = 1 + frameIdx < streamedFrames.Count ? streamedFrames[1 + frameIdx].keyList.Count : 0; + int numCurves = numStreamedCurves + (int)clip.m_MuscleClip.m_Clip.m_DenseClip.m_CurveCount + clip.m_MuscleClip.m_Clip.m_ConstantClip.data.Length; + int streamOffset = numStreamedCurves - (int)clip.m_MuscleClip.m_Clip.m_StreamedClip.curveCount; + for (int curveIdx = 0; curveIdx < numCurves;) + { + GenericBinding binding; + float[] data; + int dataOffset; + if (1 + frameIdx < streamedFrames.Count && curveIdx < streamedFrames[1 + frameIdx].keyList.Count) + { + binding = clip.m_ClipBindingConstant.FindBinding(streamedFrames[1 + frameIdx].keyList[curveIdx].index); + data = streamedValues; + dataOffset = 0; + } + else if (curveIdx < numStreamedCurves + clip.m_MuscleClip.m_Clip.m_DenseClip.m_CurveCount) + { + binding = clip.m_ClipBindingConstant.FindBinding(curveIdx - streamOffset); + data = clip.m_MuscleClip.m_Clip.m_DenseClip.m_SampleArray; + dataOffset = numStreamedCurves - frameIdx * (int)clip.m_MuscleClip.m_Clip.m_DenseClip.m_CurveCount; + } + else + { + binding = clip.m_ClipBindingConstant.FindBinding(curveIdx - streamOffset); + data = clip.m_MuscleClip.m_Clip.m_ConstantClip.data; + dataOffset = numStreamedCurves + (int)clip.m_MuscleClip.m_Clip.m_DenseClip.m_CurveCount; + } + + if (binding.path == 0) + { + curveIdx++; + continue; + } + + string boneName = GetNameFromHashes(binding.path, binding.attribute); + ImportedAnimationSampledTrack track = iAnim.FindTrack(boneName); + if (track == null) + { + track = new ImportedAnimationSampledTrack(); + track.Name = boneName; + iAnim.TrackList.Add(track); + } + + try + { + switch (binding.attribute) + { + case 1: + if (track.Translations == null) + { + track.Translations = new Vector3?[numFrames]; + } + + track.Translations[frameIdx] = new Vector3 + ( + -data[curveIdx++ - dataOffset], + data[curveIdx++ - dataOffset], + data[curveIdx++ - dataOffset] + ); + break; + case 2: + if (track.Rotations == null) + { + track.Rotations = new Quaternion?[numFrames]; + } + + track.Rotations[frameIdx] = new Quaternion + ( + data[curveIdx++ - dataOffset], + -data[curveIdx++ - dataOffset], + -data[curveIdx++ - dataOffset], + data[curveIdx++ - dataOffset] + ); + break; + case 3: + if (track.Scalings == null) + { + track.Scalings = new Vector3?[numFrames]; + } + + track.Scalings[frameIdx] = new Vector3 + ( + data[curveIdx++ - dataOffset], + data[curveIdx++ - dataOffset], + data[curveIdx++ - dataOffset] + ); + break; + case 4: + if (track.Rotations == null) + { + track.Rotations = new Quaternion?[numFrames]; + } + + track.Rotations[frameIdx] = Fbx.EulerToQuaternion + ( + new Vector3 + ( + data[curveIdx++ - dataOffset], + -data[curveIdx++ - dataOffset], + -data[curveIdx++ - dataOffset] + ) + ); + break; + default: + if (track.Curve == null) + { + track.Curve = new float?[numFrames]; + } + + track.Curve[frameIdx] = data[curveIdx++ - dataOffset]; + break; + } + } + catch + { + //errors.Append(" ").Append(boneName).Append(" a=").Append(binding.attribute).Append(" ci=").Append(curveIdx).Append("/#=").Append(numCurves).Append(" of=").Append(dataOffset).Append(" f=").Append(frameIdx).Append("/#=").Append(numFrames).Append("\n"); + //TODO Display error + break; + } + } + } + } + } + } + + private string GetNameFromHashes(uint path, uint attribute) + { + var boneName = GetNameFromBonePathHashes(path); + if (string.IsNullOrEmpty(boneName)) + { + boneName = avatar?.FindBoneName(path); + } + if (string.IsNullOrEmpty(boneName)) + { + boneName = "unknown " + path; + } + if (attribute > 4) + { + if (morphChannelInfo.TryGetValue(attribute, out var morphChannel)) + { + return boneName + "." + morphChannel; + } + return boneName + ".unknown_morphChannel " + attribute; + } + return boneName; + } + + private string GetNameFromBonePathHashes(uint path) + { + if (bonePathHash.TryGetValue(path, out var boneName)) + boneName = boneName.Substring(boneName.LastIndexOf('/') + 1); + return boneName; + } + + private static string BlendShapeNameGroup(Mesh mesh, int index) + { + string name = mesh.m_Shapes.channels[index].name; + int dotPos = name.IndexOf('.'); + if (dotPos >= 0) + { + return name.Substring(0, dotPos); + } + return "Ungrouped"; + } + + private static string BlendShapeNameExtension(Mesh mesh, int index) + { + string name = mesh.m_Shapes.channels[index].name; + int dotPos = name.IndexOf('.'); + if (dotPos >= 0) + { + return name.Substring(dotPos + 1); + } + return name; + } + + private static ImportedVertex GetSourceVertex(List submeshList, int morphVertIndex) + { + for (int i = 0; i < submeshList.Count; i++) + { + List vertList = submeshList[i].VertexList; + if (morphVertIndex < vertList.Count) + { + return vertList[morphVertIndex]; + } + morphVertIndex -= vertList.Count; + } + return null; + } + + private void CreateBonePathHash(Transform m_Transform) + { + var name = GetTransformPath(m_Transform); + var crc = new SevenZip.CRC(); + var bytes = Encoding.UTF8.GetBytes(name); + crc.Update(bytes, 0, (uint)bytes.Length); + bonePathHash[crc.GetDigest()] = name; + int index; + while ((index = name.IndexOf("/", StringComparison.Ordinal)) >= 0) + { + name = name.Substring(index + 1); + crc = new SevenZip.CRC(); + bytes = Encoding.UTF8.GetBytes(name); + crc.Update(bytes, 0, (uint)bytes.Length); + bonePathHash[crc.GetDigest()] = name; + } + foreach (var pptr in m_Transform.m_Children) + { + if (assetsfileList.TryGetTransform(pptr, out var child)) + CreateBonePathHash(child); + } + } + } +} diff --git a/AssetStudio/StudioClasses/Studio.cs b/AssetStudio/StudioClasses/Studio.cs index 228494a..d7250a2 100644 --- a/AssetStudio/StudioClasses/Studio.cs +++ b/AssetStudio/StudioClasses/Studio.cs @@ -181,6 +181,19 @@ namespace AssetStudio exportable = true; break; } + case ClassIDReference.Animator: + { + exportable = true; + break; + } + case ClassIDReference.AnimationClip: + { + exportable = true; + var reader = asset.sourceFile.assetsFileReader; + reader.Position = asset.Offset; + asset.Text = reader.ReadAlignedString(); + break; + } } if (!exportable && displayAll) { @@ -271,6 +284,11 @@ namespace AssetStudio m_GameObject.m_SkinnedMeshRenderer = m_Component; break; } + case ClassIDReference.Animator: + { + asset.Text = m_GameObject.asset.Text;//TODO + break; + } } } } diff --git a/AssetStudio/app.config b/AssetStudio/app.config index 199fec6..c4f1921 100644 --- a/AssetStudio/app.config +++ b/AssetStudio/app.config @@ -61,6 +61,30 @@ False + + False + + + 0.25 + + + False + + + True + + + True + + + 10 + + + False + + + False + \ No newline at end of file diff --git a/AssetStudioFBX/AssemblyInfo.cpp b/AssetStudioFBX/AssemblyInfo.cpp new file mode 100644 index 0000000..75590de --- /dev/null +++ b/AssetStudioFBX/AssemblyInfo.cpp @@ -0,0 +1,20 @@ +using namespace System; +using namespace System::Reflection; +using namespace System::Runtime::CompilerServices; +using namespace System::Runtime::InteropServices; +using namespace System::Security::Permissions; + +[assembly:AssemblyTitleAttribute(L"AssetStudioFBX")]; +[assembly:AssemblyDescriptionAttribute(L"")]; +[assembly:AssemblyConfigurationAttribute(L"")]; +[assembly:AssemblyCompanyAttribute(L"")]; +[assembly:AssemblyProductAttribute(L"AssetStudioFBX")]; +[assembly:AssemblyCopyrightAttribute(L"Ȩ(c) 2018")]; +[assembly:AssemblyTrademarkAttribute(L"")]; +[assembly:AssemblyCultureAttribute(L"")]; + +[assembly:AssemblyVersionAttribute("1.0.*")]; + +[assembly:ComVisible(false)]; + +[assembly:CLSCompliantAttribute(true)]; \ No newline at end of file diff --git a/AssetStudioFBX/AssetStudioFBX-x86.vcxproj b/AssetStudioFBX/AssetStudioFBX-x86.vcxproj new file mode 100644 index 0000000..7a27328 --- /dev/null +++ b/AssetStudioFBX/AssetStudioFBX-x86.vcxproj @@ -0,0 +1,104 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + 15.0 + {276DE52E-3FFE-4C3D-9076-62BCB7A5B991} + v4.0 + ManagedCProj + AssetStudioFBX + 10.0.16299.0 + + + + DynamicLibrary + true + v140 + true + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + false + + + true + + + + Level3 + WIN32;FBXSDK_SHARED;NDEBUG;%(PreprocessorDefinitions) + D:\FBX SDK\2015.1\include;%(AdditionalIncludeDirectories) + + + libfbxsdk.lib;%(AdditionalDependencies) + D:\FBX SDK\2015.1\lib\vs2013\x86\release;%(AdditionalLibraryDirectories) + + + + + Level3 + Disabled + WIN32;FBXSDK_SHARED;_DEBUG;%(PreprocessorDefinitions) + D:\FBX SDK\2015.1\include;%(AdditionalIncludeDirectories) + + + libfbxsdk.lib;%(AdditionalDependencies) + D:\FBX SDK\2015.1\lib\vs2013\x86\release;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + ..\AssetStudio\Library\SharpDX.dll + + + ..\AssetStudio\Library\SharpDX.Mathematics.dll + + + + + + + {9131c403-7fe8-444d-9af5-5fe5df76ff24} + + + + + + \ No newline at end of file diff --git a/AssetStudioFBX/AssetStudioFBX.cpp b/AssetStudioFBX/AssetStudioFBX.cpp new file mode 100644 index 0000000..e21d2a0 --- /dev/null +++ b/AssetStudioFBX/AssetStudioFBX.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "AssetStudioFBX.h" + +namespace AssetStudio +{ + char* Fbx::StringToCharArray(String^ s) + { + return (char*)(void*)Marshal::StringToHGlobalAnsi(s); + } + + void Fbx::Init(FbxManager** pSdkManager, FbxScene** pScene) + { + *pSdkManager = FbxManager::Create(); + if (!pSdkManager) + { + throw gcnew Exception(gcnew String("Unable to create the FBX SDK manager")); + } + + FbxIOSettings* ios = FbxIOSettings::Create(*pSdkManager, IOSROOT); + (*pSdkManager)->SetIOSettings(ios); + + FbxString lPath = FbxGetApplicationDirectory(); +#if defined(FBXSDK_ENV_WIN) + FbxString lExtension = "dll"; +#elif defined(FBXSDK_ENV_MAC) + FbxString lExtension = "dylib"; +#elif defined(FBXSDK_ENV_LINUX) + FbxString lExtension = "so"; +#endif + (*pSdkManager)->LoadPluginsDirectory(lPath.Buffer(), lExtension.Buffer()); + + *pScene = FbxScene::Create(*pSdkManager, ""); + } + + Vector3 Fbx::QuaternionToEuler(Quaternion q) + { + FbxAMatrix lMatrixRot; + lMatrixRot.SetQ(FbxQuaternion(q.X, q.Y, q.Z, q.W)); + FbxVector4 lEuler = lMatrixRot.GetR(); + return Vector3((float)lEuler[0], (float)lEuler[1], (float)lEuler[2]); + } + + Quaternion Fbx::EulerToQuaternion(Vector3 v) + { + FbxAMatrix lMatrixRot; + lMatrixRot.SetR(FbxVector4(v.X, v.Y, v.Z)); + FbxQuaternion lQuaternion = lMatrixRot.GetQ(); + return Quaternion((float)lQuaternion[0], (float)lQuaternion[1], (float)lQuaternion[2], (float)lQuaternion[3]); + } +} \ No newline at end of file diff --git a/AssetStudioFBX/AssetStudioFBX.h b/AssetStudioFBX/AssetStudioFBX.h new file mode 100644 index 0000000..2227836 --- /dev/null +++ b/AssetStudioFBX/AssetStudioFBX.h @@ -0,0 +1,111 @@ +#pragma once + +#ifdef IOS_REF +#undef IOS_REF +#define IOS_REF (*(pSdkManager->GetIOSettings())) +#endif + +using namespace System; +using namespace System::Collections::Generic; +using namespace System::IO; +using namespace System::Runtime::InteropServices; +using namespace SharpDX; + +#define WITH_MARSHALLED_STRING(name,str,block)\ + { \ + char* name; \ + try \ + { \ + name = StringToCharArray(str); \ + block \ + } \ + finally \ + { \ + Marshal::FreeHGlobal((IntPtr)name); \ + } \ + } + +namespace AssetStudio { + + public ref class Fbx + { + public: + static Vector3 QuaternionToEuler(Quaternion q); + static Quaternion EulerToQuaternion(Vector3 v); + + ref class Exporter + { + public: + static void Export(String^ path, IImported^ imported, bool EulerFilter, float filterPrecision, String^ exportFormat, bool allFrames, bool allBones, bool skins, float boneSize, bool flatInbetween, bool compatibility); + static void ExportMorph(String^ path, IImported^ imported, String^ exportFormat, bool morphMask, bool flatInbetween, bool skins, float boneSize, bool compatibility); + + private: + HashSet^ frameNames; + HashSet^ meshNames; + bool EulerFilter; + float filterPrecision; + bool exportSkins; + bool embedMedia; + float boneSize; + + IImported^ imported; + + char* cDest; + char* cFormat; + FbxManager* pSdkManager; + FbxScene* pScene; + FbxExporter* pExporter; + FbxArray* pMaterials; + FbxArray* pTextures; + FbxArray* pMeshNodes; + + ~Exporter(); + !Exporter(); + void Fbx::Exporter::LinkTexture(ImportedMaterial^ mat, int attIndex, FbxFileTexture* pTexture, FbxProperty& prop); + void SetJointsNode(FbxNode* pNode, HashSet^ boneNames, bool allBones); + + Exporter(String^ path, IImported^ imported, String^ exportFormat, bool allFrames, bool allBones, bool skins, float boneSize, bool compatibility, bool normals); + HashSet^ SearchHierarchy(); + void SearchHierarchy(ImportedFrame^ frame, HashSet^ exportFrames); + void SetJointsFromImportedMeshes(bool allBones); + void ExportFrame(FbxNode* pParentNode, ImportedFrame^ frame); + void ExportMesh(FbxNode* pFrameNode, ImportedMesh^ meshList, bool normals); + FbxFileTexture* ExportTexture(ImportedTexture^ matTex, FbxMesh* pMesh); + void ExportAnimations(bool EulerFilter, float filterValue, bool flatInbetween); + void ExportKeyframedAnimation(ImportedKeyframedAnimation^ parser, FbxString& kTakeName, FbxAnimCurveFilterUnroll* EulerFilter, float filterPrecision, + FbxPropertyT& scale, FbxPropertyT& rotate, FbxPropertyT& translate, List^ pNotFound); + void ExportSampledAnimation(ImportedSampledAnimation^ parser, FbxString& kTakeName, FbxAnimCurveFilterUnroll* EulerFilter, float filterPrecision, bool flatInbetween, + FbxPropertyT& scale, FbxPropertyT& rotate, FbxPropertyT& translate, List^ pNotFound); + void ExportMorphs(IImported^ imported, bool morphMask, bool flatInbetween); + }; + + private: + ref class InterpolationHelper + { + private: + FbxScene * pScene; + FbxAnimLayer* pAnimLayer; + FbxAnimEvaluator* pAnimEvaluator; + + FbxAnimCurveDef::EInterpolationType interpolationMethod; + FbxAnimCurveFilterUnroll* lFilter; + float filterPrecision; + + FbxPropertyT* scale, *translate; + FbxPropertyT* rotate; + FbxAnimCurve* pScaleCurveX, *pScaleCurveY, *pScaleCurveZ, + *pRotateCurveX, *pRotateCurveY, *pRotateCurveZ, *pRotateCurveW, + *pTranslateCurveX, *pTranslateCurveY, *pTranslateCurveZ; + + array^ allCurves; + + public: + static const char* pScaleName = "Scale"; + static const char* pRotateName = "Rotate"; + static const char* pTranslateName = "Translate"; + }; + + static char* StringToCharArray(String^ s); + static void Init(FbxManager** pSdkManager, FbxScene** pScene); + }; +} diff --git a/AssetStudioFBX/AssetStudioFBX.rc b/AssetStudioFBX/AssetStudioFBX.rc new file mode 100644 index 0000000..1d2130d Binary files /dev/null and b/AssetStudioFBX/AssetStudioFBX.rc differ diff --git a/AssetStudioFBX/AssetStudioFBX.vcxproj b/AssetStudioFBX/AssetStudioFBX.vcxproj new file mode 100644 index 0000000..5113fa7 --- /dev/null +++ b/AssetStudioFBX/AssetStudioFBX.vcxproj @@ -0,0 +1,104 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {4F8EF5EF-732B-49CF-9EB3-B23E19AE6267} + v4.0 + ManagedCProj + AssetStudioFBX + 10.0.16299.0 + + + + DynamicLibrary + true + v140 + true + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + true + + + false + + + + Level3 + Disabled + FBXSDK_SHARED;_DEBUG;%(PreprocessorDefinitions) + D:\FBX SDK\2015.1\include;%(AdditionalIncludeDirectories) + + + libfbxsdk.lib;%(AdditionalDependencies) + D:\FBX SDK\2015.1\lib\vs2013\x64\release;%(AdditionalLibraryDirectories) + + + + + Level3 + FBXSDK_SHARED;NDEBUG;%(PreprocessorDefinitions) + D:\FBX SDK\2015.1\include;%(AdditionalIncludeDirectories) + + + libfbxsdk.lib;%(AdditionalDependencies) + D:\FBX SDK\2015.1\lib\vs2013\x64\release;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + ..\AssetStudio\Library\SharpDX.dll + + + ..\AssetStudio\Library\SharpDX.Mathematics.dll + + + + + + + {9131c403-7fe8-444d-9af5-5fe5df76ff24} + + + + + + \ No newline at end of file diff --git a/AssetStudioFBX/AssetStudioFBX.vcxproj.filters b/AssetStudioFBX/AssetStudioFBX.vcxproj.filters new file mode 100644 index 0000000..680f753 --- /dev/null +++ b/AssetStudioFBX/AssetStudioFBX.vcxproj.filters @@ -0,0 +1,32 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件 + + + + + 头文件 + + + \ No newline at end of file diff --git a/AssetStudioFBX/AssetStudioFBXExporter.cpp b/AssetStudioFBX/AssetStudioFBXExporter.cpp new file mode 100644 index 0000000..83d4e93 --- /dev/null +++ b/AssetStudioFBX/AssetStudioFBXExporter.cpp @@ -0,0 +1,82 @@ +#include +#include +#include "AssetStudioFBX.h" + +namespace AssetStudio +{ + Fbx::Exporter::~Exporter() + { + this->!Exporter(); + } + + Fbx::Exporter::!Exporter() + { + if (pMeshNodes != NULL) + { + delete pMeshNodes; + } + if (pMaterials != NULL) + { + delete pMaterials; + } + if (pTextures != NULL) + { + if (embedMedia) + { + for (int i = 0; i < pTextures->GetCount(); i++) + { + FbxFileTexture* tex = pTextures->GetAt(i); + File::Delete(gcnew String(tex->GetFileName())); + } + } + delete pTextures; + } + if (pExporter != NULL) + { + pExporter->Destroy(); + } + if (pScene != NULL) + { + pScene->Destroy(); + } + if (pSdkManager != NULL) + { + pSdkManager->Destroy(); + } + if (cFormat != NULL) + { + Marshal::FreeHGlobal((IntPtr)cFormat); + } + if (cDest != NULL) + { + Marshal::FreeHGlobal((IntPtr)cDest); + } + } + + void Fbx::Exporter::SetJointsNode(FbxNode* pNode, HashSet^ boneNames, bool allBones) + { + String^ nodeName = gcnew String(pNode->GetName()); + if (allBones || boneNames->Contains(nodeName)) + { + FbxSkeleton* pJoint = FbxSkeleton::Create(pSdkManager, ""); + pJoint->Size.Set((double)boneSize); + pJoint->SetSkeletonType(FbxSkeleton::eLimbNode); + pNode->SetNodeAttribute(pJoint); + } + else + { + FbxNull* pNull = FbxNull::Create(pSdkManager, ""); + if (pNode->GetChildCount() > 0) + { + pNull->Look.Set(FbxNull::eNone); + } + + pNode->SetNodeAttribute(pNull); + } + + for (int i = 0; i < pNode->GetChildCount(); i++) + { + SetJointsNode(pNode->GetChild(i), boneNames, allBones); + } + } +} \ No newline at end of file diff --git a/AssetStudioFBX/ImportedFBXExporter.cpp b/AssetStudioFBX/ImportedFBXExporter.cpp new file mode 100644 index 0000000..4a1d594 --- /dev/null +++ b/AssetStudioFBX/ImportedFBXExporter.cpp @@ -0,0 +1,1287 @@ +#include +#include +#include "AssetStudioFBX.h" + +namespace AssetStudio +{ + void Fbx::Exporter::Export(String^ path, IImported^ imported, bool EulerFilter, float filterPrecision, String^ exportFormat, bool allFrames, bool allBones, bool skins, float boneSize, bool flatInbetween, bool compatibility) + { + FileInfo^ file = gcnew FileInfo(path); + DirectoryInfo^ dir = file->Directory; + if (!dir->Exists) + { + dir->Create(); + } + String^ currentDir = Directory::GetCurrentDirectory(); + Directory::SetCurrentDirectory(dir->FullName); + path = Path::GetFileName(path); + + Exporter^ exporter = gcnew Exporter(path, imported, exportFormat, allFrames, allBones, skins, boneSize, compatibility, true); + exporter->ExportMorphs(imported, false, flatInbetween); + exporter->ExportAnimations(EulerFilter, filterPrecision, flatInbetween); + exporter->pExporter->Export(exporter->pScene); + + Directory::SetCurrentDirectory(currentDir); + } + + void Fbx::Exporter::ExportMorph(String^ path, IImported^ imported, String^ exportFormat, bool morphMask, bool flatInbetween, bool skins, float boneSize, bool compatibility) + { + FileInfo^ file = gcnew FileInfo(path); + DirectoryInfo^ dir = file->Directory; + if (!dir->Exists) + { + dir->Create(); + } + String^ currentDir = Directory::GetCurrentDirectory(); + Directory::SetCurrentDirectory(dir->FullName); + path = Path::GetFileName(path); + + Exporter^ exporter = gcnew Exporter(path, imported, exportFormat, false, true, skins, boneSize, compatibility, false); + exporter->ExportMorphs(imported, morphMask, flatInbetween); + exporter->pExporter->Export(exporter->pScene); + delete exporter; + + Directory::SetCurrentDirectory(currentDir); + } + + Fbx::Exporter::Exporter(String^ path, IImported^ imported, String^ exportFormat, bool allFrames, bool allBones, bool skins, float boneSize, bool compatibility, bool normals) + { + this->imported = imported; + exportSkins = skins; + this->boneSize = boneSize; + + cDest = NULL; + cFormat = NULL; + pSdkManager = NULL; + pScene = NULL; + pExporter = NULL; + pMaterials = NULL; + pTextures = NULL; + pMeshNodes = NULL; + + pin_ptr pSdkManagerPin = &pSdkManager; + pin_ptr pScenePin = &pScene; + Init(pSdkManagerPin, pScenePin); + + cDest = Fbx::StringToCharArray(path); + cFormat = Fbx::StringToCharArray(exportFormat); + pExporter = FbxExporter::Create(pScene, ""); + int lFormatIndex, lFormatCount = pSdkManager->GetIOPluginRegistry()->GetWriterFormatCount(); + for (lFormatIndex = 0; lFormatIndex < lFormatCount; lFormatIndex++) + { + FbxString lDesc = FbxString(pSdkManager->GetIOPluginRegistry()->GetWriterFormatDescription(lFormatIndex)); + if (lDesc.Find(cFormat) >= 0) + { + if (pSdkManager->GetIOPluginRegistry()->WriterIsFBX(lFormatIndex)) + { + if (lDesc.Find("binary") >= 0) + { + if (!compatibility || lDesc.Find("6.") >= 0) + { + break; + } + } + } + else + { + break; + } + } + } + + IOS_REF.SetBoolProp(EXP_FBX_MATERIAL, true); + IOS_REF.SetBoolProp(EXP_FBX_TEXTURE, true); + IOS_REF.SetBoolProp(EXP_FBX_EMBEDDED, false); + IOS_REF.SetBoolProp(EXP_FBX_SHAPE, true); + IOS_REF.SetBoolProp(EXP_FBX_GOBO, true); + IOS_REF.SetBoolProp(EXP_FBX_ANIMATION, true); + IOS_REF.SetBoolProp(EXP_FBX_GLOBAL_SETTINGS, true); + + FbxGlobalSettings& globalSettings = pScene->GetGlobalSettings(); + FbxTime::EMode pTimeMode = FbxTime::eFrames24; + globalSettings.SetTimeMode(pTimeMode); + + if (!pExporter->Initialize(cDest, lFormatIndex, pSdkManager->GetIOSettings())) + { + throw gcnew Exception(gcnew String("Failed to initialize FbxExporter: ") + gcnew String(pExporter->GetStatus().GetErrorString())); + } + + frameNames = nullptr; + if (!allFrames) + { + frameNames = SearchHierarchy(); + if (!frameNames) + { + return; + } + } + + pMeshNodes = imported->MeshList != nullptr ? new FbxArray(imported->MeshList->Count) : NULL; + ExportFrame(pScene->GetRootNode(), imported->FrameList[0]); + + if (imported->MeshList != nullptr) + { + SetJointsFromImportedMeshes(allBones); + + pMaterials = new FbxArray(); + pTextures = new FbxArray(); + pMaterials->Reserve(imported->MaterialList->Count); + pTextures->Reserve(imported->TextureList->Count); + + for (int i = 0; i < pMeshNodes->GetCount(); i++) + { + FbxNode* meshNode = pMeshNodes->GetAt(i); + String^ meshPath = gcnew String(meshNode->GetName()); + FbxNode* rootNode = meshNode; + while ((rootNode = rootNode->GetParent()) != pScene->GetRootNode()) + { + meshPath = gcnew String(rootNode->GetName()) + "/" + meshPath; + } + ImportedMesh^ mesh = ImportedHelpers::FindMesh(meshPath, imported->MeshList); + ExportMesh(meshNode, mesh, normals); + } + } + else + { + SetJointsNode(pScene->GetRootNode()->GetChild(0), nullptr, true); + } + } + + HashSet^ Fbx::Exporter::SearchHierarchy() + { + if (imported->MeshList == nullptr || imported->MeshList->Count == 0) + { + return nullptr; + } + HashSet^ exportFrames = gcnew HashSet(); + SearchHierarchy(imported->FrameList[0], exportFrames); + return exportFrames; + } + + void Fbx::Exporter::SearchHierarchy(ImportedFrame^ frame, HashSet^ exportFrames) + { + ImportedMesh^ meshListSome = ImportedHelpers::FindMesh(frame, imported->MeshList); + if (meshListSome != nullptr) + { + ImportedFrame^ parent = frame; + while (parent != nullptr) + { + exportFrames->Add(parent->Name); + parent = (ImportedFrame^)parent->Parent; + } + + List^ boneList = meshListSome->BoneList; + if (boneList != nullptr) + { + for (int i = 0; i < boneList->Count; i++) + { + if (!exportFrames->Contains(boneList[i]->Name)) + { + ImportedFrame^ boneParent = ImportedHelpers::FindFrame(boneList[i]->Name, imported->FrameList[0]); + while (boneParent != nullptr) + { + exportFrames->Add(boneParent->Name); + boneParent = (ImportedFrame^)boneParent->Parent; + } + } + } + } + } + + for (int i = 0; i < frame->Count; i++) + { + SearchHierarchy(frame[i], exportFrames); + } + } + + void Fbx::Exporter::SetJointsFromImportedMeshes(bool allBones) + { + if (!exportSkins) + { + return; + } + HashSet^ boneNames = gcnew HashSet(); + for (int i = 0; i < imported->MeshList->Count; i++) + { + ImportedMesh^ meshList = imported->MeshList[i]; + List^ boneList = meshList->BoneList; + if (boneList != nullptr) + { + for (int j = 0; j < boneList->Count; j++) + { + ImportedBone^ bone = boneList[j]; + boneNames->Add(bone->Name); + } + } + } + + SetJointsNode(pScene->GetRootNode()->GetChild(0), boneNames, allBones); + } + + void Fbx::Exporter::ExportFrame(FbxNode* pParentNode, ImportedFrame^ frame) + { + String^ frameName = frame->Name; + if ((frameNames == nullptr) || frameNames->Contains(frameName)) + { + FbxNode* pFrameNode = NULL; + char* pName = NULL; + try + { + pName = StringToCharArray(frameName); + pFrameNode = FbxNode::Create(pScene, pName); + } + finally + { + Marshal::FreeHGlobal((IntPtr)pName); + } + + Vector3 scale, translate; + Quaternion rotate; + frame->Matrix.Decompose(scale, rotate, translate); + Vector3 rotateVector = Fbx::QuaternionToEuler(rotate); + + pFrameNode->LclScaling.Set(FbxVector4(scale.X, scale.Y, scale.Z)); + pFrameNode->LclRotation.Set(FbxVector4(FbxDouble3(rotateVector.X, rotateVector.Y, rotateVector.Z))); + pFrameNode->LclTranslation.Set(FbxVector4(translate.X, translate.Y, translate.Z)); + pParentNode->AddChild(pFrameNode); + + if (imported->MeshList != nullptr && ImportedHelpers::FindMesh(frame, imported->MeshList) != nullptr) + { + pMeshNodes->Add(pFrameNode); + } + + for (int i = 0; i < frame->Count; i++) + { + ExportFrame(pFrameNode, frame[i]); + } + } + } + + void Fbx::Exporter::ExportMesh(FbxNode* pFrameNode, ImportedMesh^ meshList, bool normals) + { + int lastSlash = meshList->Name->LastIndexOf('/'); + String^ frameName = lastSlash < 0 ? meshList->Name : meshList->Name->Substring(lastSlash + 1); + List^ boneList = meshList->BoneList; + bool hasBones; + if (exportSkins && boneList != nullptr) + { + hasBones = boneList->Count > 0; + } + else + { + hasBones = false; + } + + FbxArray* pBoneNodeList = NULL; + try + { + if (hasBones) + { + pBoneNodeList = new FbxArray(); + pBoneNodeList->Reserve(boneList->Count); + for (int i = 0; i < boneList->Count; i++) + { + ImportedBone^ bone = boneList[i]; + String^ boneName = bone->Name; + char* pBoneName = NULL; + try + { + pBoneName = StringToCharArray(boneName); + FbxNode* foundNode = pScene->GetRootNode()->FindChild(pBoneName); + if (foundNode == NULL) + { + throw gcnew Exception(gcnew String("Couldn't find frame ") + boneName + gcnew String(" used by the bone")); + } + pBoneNodeList->Add(foundNode); + } + finally + { + Marshal::FreeHGlobal((IntPtr)pBoneName); + } + } + } + + for (int i = 0; i < meshList->SubmeshList->Count; i++) + { + char* pName = NULL; + FbxArray* pClusterArray = NULL; + try + { + pName = StringToCharArray(frameName + "_" + i); + FbxMesh* pMesh = FbxMesh::Create(pScene, ""); + + if (hasBones) + { + pClusterArray = new FbxArray(); + pClusterArray->Reserve(boneList->Count); + + for (int i = 0; i < boneList->Count; i++) + { + FbxNode* pNode = pBoneNodeList->GetAt(i); + FbxString lClusterName = pNode->GetNameOnly() + FbxString("Cluster"); + FbxCluster* pCluster = FbxCluster::Create(pSdkManager, lClusterName.Buffer()); + pCluster->SetLink(pNode); + pCluster->SetLinkMode(FbxCluster::eTotalOne); + pClusterArray->Add(pCluster); + } + } + + ImportedSubmesh^ meshObj = meshList->SubmeshList[i]; + List^ faceList = meshObj->FaceList; + List^ vertexList = meshObj->VertexList; + + pMesh->InitControlPoints(vertexList->Count); + FbxVector4* pControlPoints = pMesh->GetControlPoints(); + + FbxGeometryElementNormal* lGeometryElementNormal = NULL; + //if (normals) + { + lGeometryElementNormal = pMesh->GetElementNormal(); + if (!lGeometryElementNormal) + { + lGeometryElementNormal = pMesh->CreateElementNormal(); + } + lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByControlPoint); + lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eDirect); + } + + FbxGeometryElementUV* lGeometryElementUV = pMesh->GetElementUV(); + if (!lGeometryElementUV) + { + lGeometryElementUV = pMesh->CreateElementUV(""); + } + lGeometryElementUV->SetMappingMode(FbxGeometryElement::eByControlPoint); + lGeometryElementUV->SetReferenceMode(FbxGeometryElement::eDirect); + + FbxGeometryElementTangent* lGeometryElementTangent = NULL; + if (normals) + { + lGeometryElementTangent = pMesh->GetElementTangent(); + if (!lGeometryElementTangent) + { + lGeometryElementTangent = pMesh->CreateElementTangent(); + } + lGeometryElementTangent->SetMappingMode(FbxGeometryElement::eByControlPoint); + lGeometryElementTangent->SetReferenceMode(FbxGeometryElement::eDirect); + } + + bool vertexColours = vertexList->Count > 0 && dynamic_cast(vertexList[0]) != nullptr; + if (vertexColours) + { + FbxGeometryElementVertexColor* lGeometryElementVertexColor = pMesh->CreateElementVertexColor(); + lGeometryElementVertexColor->SetMappingMode(FbxGeometryElement::eByControlPoint); + lGeometryElementVertexColor->SetReferenceMode(FbxGeometryElement::eDirect); + for (int j = 0; j < vertexList->Count; j++) + { + ImportedVertexWithColour^ vert = (ImportedVertexWithColour^)vertexList[j]; + lGeometryElementVertexColor->GetDirectArray().Add(FbxColor(vert->Colour.Red, vert->Colour.Green, vert->Colour.Blue, vert->Colour.Alpha)); + } + } + + FbxNode* pMeshNode = FbxNode::Create(pScene, pName); + pMeshNode->SetNodeAttribute(pMesh); + pFrameNode->AddChild(pMeshNode); + + ImportedMaterial^ mat = ImportedHelpers::FindMaterial(meshObj->Material, imported->MaterialList); + if (mat != nullptr) + { + FbxGeometryElementMaterial* lGeometryElementMaterial = pMesh->GetElementMaterial(); + if (!lGeometryElementMaterial) + { + lGeometryElementMaterial = pMesh->CreateElementMaterial(); + } + lGeometryElementMaterial->SetMappingMode(FbxGeometryElement::eByPolygon); + lGeometryElementMaterial->SetReferenceMode(FbxGeometryElement::eIndexToDirect); + + char* pMatName = NULL; + try + { + pMatName = StringToCharArray(mat->Name); + int foundMat = -1; + for (int j = 0; j < pMaterials->GetCount(); j++) + { + FbxSurfacePhong* pMatTemp = pMaterials->GetAt(j); + if (strcmp(pMatTemp->GetName(), pMatName) == 0) + { + foundMat = j; + break; + } + } + + FbxSurfacePhong* pMat; + if (foundMat >= 0) + { + pMat = pMaterials->GetAt(foundMat); + } + else + { + FbxString lShadingName = "Phong"; + Color4 diffuse = mat->Diffuse; + Color4 ambient = mat->Ambient; + Color4 emissive = mat->Emissive; + Color4 specular = mat->Specular; + float specularPower = mat->Power; + pMat = FbxSurfacePhong::Create(pScene, pMatName); + pMat->Diffuse.Set(FbxDouble3(diffuse.Red, diffuse.Green, diffuse.Blue)); + pMat->DiffuseFactor.Set(FbxDouble(diffuse.Alpha)); + pMat->Ambient.Set(FbxDouble3(ambient.Red, ambient.Green, ambient.Blue)); + pMat->AmbientFactor.Set(FbxDouble(ambient.Alpha)); + pMat->Emissive.Set(FbxDouble3(emissive.Red, emissive.Green, emissive.Blue)); + pMat->EmissiveFactor.Set(FbxDouble(emissive.Alpha)); + pMat->Specular.Set(FbxDouble3(specular.Red, specular.Green, specular.Blue)); + pMat->SpecularFactor.Set(FbxDouble(specular.Alpha)); + pMat->Shininess.Set(specularPower); + pMat->ShadingModel.Set(lShadingName); + + foundMat = pMaterials->GetCount(); + pMaterials->Add(pMat); + } + pMeshNode->AddMaterial(pMat); + + bool hasTexture = false; + FbxFileTexture* pTextureDiffuse = ExportTexture(ImportedHelpers::FindTexture((String^)mat->Textures[0], imported->TextureList), pMesh); + if (pTextureDiffuse != NULL) + { + LinkTexture(mat, 0, pTextureDiffuse, pMat->Diffuse); + pMat->TransparentColor.ConnectSrcObject(pTextureDiffuse); + hasTexture = true; + } + + FbxFileTexture* pTextureAmbient = ExportTexture(ImportedHelpers::FindTexture((String^)mat->Textures[1], imported->TextureList), pMesh); + if (pTextureAmbient != NULL) + { + LinkTexture(mat, 1, pTextureAmbient, pMat->Ambient); + hasTexture = true; + } + + FbxFileTexture* pTextureEmissive = ExportTexture(ImportedHelpers::FindTexture((String^)mat->Textures[2], imported->TextureList), pMesh); + if (pTextureEmissive != NULL) + { + LinkTexture(mat, 2, pTextureEmissive, pMat->Emissive); + hasTexture = true; + } + + FbxFileTexture* pTextureSpecular = ExportTexture(ImportedHelpers::FindTexture((String^)mat->Textures[3], imported->TextureList), pMesh); + if (pTextureSpecular != NULL) + { + LinkTexture(mat, 3, pTextureSpecular, pMat->Specular); + hasTexture = true; + } + + if (mat->Textures->Length > 4) + { + FbxFileTexture* pTextureBump = ExportTexture(ImportedHelpers::FindTexture((String^)mat->Textures[4], imported->TextureList), pMesh); + if (pTextureBump != NULL) + { + LinkTexture(mat, 4, pTextureBump, pMat->Bump); + hasTexture = true; + } + } + + if (hasTexture) + { + pMeshNode->SetShadingMode(FbxNode::eTextureShading); + } + } + finally + { + Marshal::FreeHGlobal((IntPtr)pMatName); + } + } + + for (int j = 0; j < vertexList->Count; j++) + { + ImportedVertex^ vertex = vertexList[j]; + Vector3 coords = vertex->Position; + pControlPoints[j] = FbxVector4(coords.X, coords.Y, coords.Z, 0); + //if (normals) + { + Vector3 normal = vertex->Normal; + lGeometryElementNormal->GetDirectArray().Add(FbxVector4(normal.X, normal.Y, normal.Z, 0)); + } + array^ uv = vertex->UV; + lGeometryElementUV->GetDirectArray().Add(FbxVector2(uv[0], -uv[1])); + if (normals) + { + Vector4 tangent = vertex->Tangent; + lGeometryElementTangent->GetDirectArray().Add(FbxVector4(tangent.X, tangent.Y, tangent.Z, -tangent.W)); + } + + if (hasBones) + { + array^ boneIndices = vertex->BoneIndices; + array^ weights4 = vertex->Weights; + for (int k = 0; k < weights4->Length; k++) + { + if (boneIndices[k] < boneList->Count && weights4[k] > 0) + { + FbxCluster* pCluster = pClusterArray->GetAt(boneIndices[k]); + pCluster->AddControlPointIndex(j, weights4[k]); + } + } + } + } + + for (int j = 0; j < faceList->Count; j++) + { + ImportedFace^ face = faceList[j]; + unsigned short v1 = (unsigned short)face->VertexIndices[0]; + unsigned short v2 = (unsigned short)face->VertexIndices[1]; + unsigned short v3 = (unsigned short)face->VertexIndices[2]; + pMesh->BeginPolygon(false); + pMesh->AddPolygon(v1); + pMesh->AddPolygon(v2); + pMesh->AddPolygon(v3); + pMesh->EndPolygon(); + } + + if (hasBones) + { + FbxSkin* pSkin = FbxSkin::Create(pScene, ""); + for (int j = 0; j < boneList->Count; j++) + { + FbxCluster* pCluster = pClusterArray->GetAt(j); + if (pCluster->GetControlPointIndicesCount() > 0) + { + FbxNode* pBoneNode = pBoneNodeList->GetAt(j); + Matrix boneMatrix = boneList[j]->Matrix; + FbxAMatrix lBoneMatrix; + for (int m = 0; m < 4; m++) + { + for (int n = 0; n < 4; n++) + { + lBoneMatrix.mData[m][n] = boneMatrix[m, n]; + } + } + + FbxAMatrix lMeshMatrix = pMeshNode->EvaluateGlobalTransform(); + + pCluster->SetTransformMatrix(lMeshMatrix); + pCluster->SetTransformLinkMatrix(lMeshMatrix * lBoneMatrix.Inverse()); + + pSkin->AddCluster(pCluster); + } + } + + if (pSkin->GetClusterCount() > 0) + { + pMesh->AddDeformer(pSkin); + } + } + } + finally + { + if (pClusterArray != NULL) + { + delete pClusterArray; + } + Marshal::FreeHGlobal((IntPtr)pName); + } + } + } + finally + { + if (pBoneNodeList != NULL) + { + delete pBoneNodeList; + } + } + } + + FbxFileTexture* Fbx::Exporter::ExportTexture(ImportedTexture^ matTex, FbxMesh* pMesh) + { + FbxFileTexture* pTex = NULL; + + if (matTex != nullptr) + { + String^ matTexName = matTex->Name; + char* pTexName = NULL; + try + { + pTexName = StringToCharArray(matTexName); + int foundTex = -1; + for (int i = 0; i < pTextures->GetCount(); i++) + { + FbxFileTexture* pTexTemp = pTextures->GetAt(i); + if (strcmp(pTexTemp->GetName(), pTexName) == 0) + { + foundTex = i; + break; + } + } + + if (foundTex >= 0) + { + pTex = pTextures->GetAt(foundTex); + } + else + { + pTex = FbxFileTexture::Create(pScene, pTexName); + pTex->SetFileName(pTexName); + pTex->SetTextureUse(FbxTexture::eStandard); + pTex->SetMappingType(FbxTexture::eUV); + pTex->SetMaterialUse(FbxFileTexture::eModelMaterial); + pTex->SetSwapUV(false); + pTex->SetTranslation(0.0, 0.0); + pTex->SetScale(1.0, 1.0); + pTex->SetRotation(0.0, 0.0); + pTextures->Add(pTex); + + String^ path = Path::GetDirectoryName(gcnew String(pExporter->GetFileName().Buffer())); + if (path == String::Empty) + { + path = "."; + } + FileInfo^ file = gcnew FileInfo(path + Path::DirectorySeparatorChar + Path::GetFileName(matTex->Name)); + DirectoryInfo^ dir = file->Directory; + if (!dir->Exists) + { + dir->Create(); + } + BinaryWriter^ writer = gcnew BinaryWriter(file->Create()); + writer->Write(matTex->Data); + writer->Close(); + } + } + finally + { + Marshal::FreeHGlobal((IntPtr)pTexName); + } + } + + return pTex; + } + + void Fbx::Exporter::LinkTexture(ImportedMaterial^ mat, int attIndex, FbxFileTexture* pTexture, FbxProperty& prop) + { + if (mat->TexOffsets != nullptr) + { + pTexture->SetTranslation(mat->TexOffsets[attIndex].X, mat->TexOffsets[attIndex].Y); + } + if (mat->TexScales != nullptr) + { + pTexture->SetScale(mat->TexScales[attIndex].X, mat->TexScales[attIndex].Y); + } + prop.ConnectSrcObject(pTexture); + } + + void Fbx::Exporter::ExportAnimations(bool EulerFilter, float filterPrecision, bool flatInbetween) + { + List^ importedAnimationList = imported->AnimationList; + if (importedAnimationList == nullptr) + { + return; + } + + List^ pNotFound = gcnew List(); + + FbxPropertyT scale = FbxProperty::Create(pScene, FbxDouble3DT, InterpolationHelper::pScaleName); + FbxPropertyT rotate = FbxProperty::Create(pScene, FbxDouble4DT, InterpolationHelper::pRotateName); + FbxPropertyT translate = FbxProperty::Create(pScene, FbxDouble3DT, InterpolationHelper::pTranslateName); + + FbxAnimCurveFilterUnroll* lFilter = EulerFilter ? new FbxAnimCurveFilterUnroll() : NULL; + + for (int i = 0; i < importedAnimationList->Count; i++) + { + bool keyframed = dynamic_cast(importedAnimationList[i]) != nullptr; + if (keyframed) + { + ImportedKeyframedAnimation^ parser = (ImportedKeyframedAnimation^)importedAnimationList[i]; + FbxString kTakeName; + if (parser->Name) + { + WITH_MARSHALLED_STRING + ( + pClipName, + parser->Name, + kTakeName = FbxString(pClipName); + ); + } + else + { + kTakeName = FbxString("Take") + FbxString(i); + } + ExportKeyframedAnimation(parser, kTakeName, lFilter, filterPrecision, scale, rotate, translate, pNotFound); + } + else + { + ImportedSampledAnimation^ parser = (ImportedSampledAnimation^)importedAnimationList[i]; + FbxString kTakeName; + if (parser->Name) + { + WITH_MARSHALLED_STRING + ( + pClipName, + parser->Name, + kTakeName = FbxString(pClipName); + ); + } + else + { + kTakeName = FbxString("Take") + FbxString(i); + } + ExportSampledAnimation(parser, kTakeName, lFilter, filterPrecision, flatInbetween, scale, rotate, translate, pNotFound); + } + } + + if (pNotFound->Count > 0) + { + String^ pNotFoundString = gcnew String("Warning: Animations weren't exported for the following missing frames or morphs: "); + for (int i = 0; i < pNotFound->Count; i++) + { + pNotFoundString += pNotFound[i] + ", "; + } + //Report::ReportLog(pNotFoundString->Substring(0, pNotFoundString->Length - 2)); + } + } + + void Fbx::Exporter::ExportKeyframedAnimation(ImportedKeyframedAnimation^ parser, FbxString& kTakeName, FbxAnimCurveFilterUnroll* EulerFilter, float filterPrecision, + FbxPropertyT& scale, FbxPropertyT& rotate, FbxPropertyT& translate, List^ pNotFound) + { + List^ pAnimationList = parser->TrackList; + + char* lTakeName = kTakeName.Buffer(); + + FbxTime lTime; + FbxAnimStack* lAnimStack = FbxAnimStack::Create(pScene, lTakeName); + FbxAnimLayer* lAnimLayer = FbxAnimLayer::Create(pScene, "Base Layer"); + lAnimStack->AddMember(lAnimLayer); + + for (int j = 0; j < pAnimationList->Count; j++) + { + ImportedAnimationKeyframedTrack^ keyframeList = pAnimationList[j]; + String^ name = keyframeList->Name; + int dotPos = name->IndexOf('.'); + if (dotPos >= 0 && !ImportedHelpers::FindFrame(name, imported->FrameList[0])) + { + name = name->Substring(0, dotPos); + } + FbxNode* pNode = NULL; + char* pName = NULL; + try + { + pName = Fbx::StringToCharArray(name); + pNode = pScene->GetRootNode()->FindChild(pName); + } + finally + { + Marshal::FreeHGlobal((IntPtr)pName); + } + + if (pNode == NULL) + { + if (!pNotFound->Contains(name)) + { + pNotFound->Add(name); + } + } + else + { + FbxAnimCurve* lCurveSX = pNode->LclScaling.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true); + FbxAnimCurve* lCurveSY = pNode->LclScaling.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true); + FbxAnimCurve* lCurveSZ = pNode->LclScaling.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true); + FbxAnimCurve* lCurveRX = pNode->LclRotation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true); + FbxAnimCurve* lCurveRY = pNode->LclRotation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true); + FbxAnimCurve* lCurveRZ = pNode->LclRotation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true); + FbxAnimCurve* lCurveTX = pNode->LclTranslation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true); + FbxAnimCurve* lCurveTY = pNode->LclTranslation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true); + FbxAnimCurve* lCurveTZ = pNode->LclTranslation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true); + + lCurveSX->KeyModifyBegin(); + lCurveSY->KeyModifyBegin(); + lCurveSZ->KeyModifyBegin(); + lCurveRX->KeyModifyBegin(); + lCurveRY->KeyModifyBegin(); + lCurveRZ->KeyModifyBegin(); + lCurveTX->KeyModifyBegin(); + lCurveTY->KeyModifyBegin(); + lCurveTZ->KeyModifyBegin(); + + for each (auto keyframes in keyframeList->Keyframes) + { + lTime.SetSecondDouble(keyframes.Key); + + if (keyframes.Value->Scaling != nullptr) + { + lCurveSX->KeySet(lCurveSX->KeyAdd(lTime), lTime, keyframes.Value->Scaling->value.X); + lCurveSY->KeySet(lCurveSY->KeyAdd(lTime), lTime, keyframes.Value->Scaling->value.Y); + lCurveSZ->KeySet(lCurveSZ->KeyAdd(lTime), lTime, keyframes.Value->Scaling->value.Z); + } + if (keyframes.Value->Rotation != nullptr) + { + Vector3 rotation = Fbx::QuaternionToEuler(keyframes.Value->Rotation->value); + Vector3 inSlope = Fbx::QuaternionToEuler(keyframes.Value->Rotation->inSlope); + Vector3 outSlope = Fbx::QuaternionToEuler(keyframes.Value->Rotation->outSlope); + lCurveRX->KeySet(lCurveRX->KeyAdd(lTime), lTime, rotation.X); + lCurveRY->KeySet(lCurveRY->KeyAdd(lTime), lTime, rotation.Y); + lCurveRZ->KeySet(lCurveRZ->KeyAdd(lTime), lTime, rotation.Z); + } + if (keyframes.Value->Translation != nullptr) + { + lCurveTX->KeySet(lCurveTX->KeyAdd(lTime), lTime, keyframes.Value->Translation->value.X); + lCurveTY->KeySet(lCurveTY->KeyAdd(lTime), lTime, keyframes.Value->Translation->value.Y); + lCurveTZ->KeySet(lCurveTZ->KeyAdd(lTime), lTime, keyframes.Value->Translation->value.Z); + } + } + + lCurveSX->KeyModifyEnd(); + lCurveSY->KeyModifyEnd(); + lCurveSZ->KeyModifyEnd(); + lCurveRX->KeyModifyEnd(); + lCurveRY->KeyModifyEnd(); + lCurveRZ->KeyModifyEnd(); + lCurveTX->KeyModifyEnd(); + lCurveTY->KeyModifyEnd(); + lCurveTZ->KeyModifyEnd(); + + if (EulerFilter) + { + FbxAnimCurve* lCurve[3]; + lCurve[0] = lCurveRX; + lCurve[1] = lCurveRY; + lCurve[2] = lCurveRZ; + EulerFilter->Reset(); + EulerFilter->SetTestForPath(true); + EulerFilter->SetQualityTolerance(filterPrecision); + EulerFilter->Apply(lCurve, 3); + } + } + } + } + + void Fbx::Exporter::ExportSampledAnimation(ImportedSampledAnimation^ parser, FbxString& kTakeName, FbxAnimCurveFilterUnroll* EulerFilter, float filterPrecision, bool flatInbetween, + FbxPropertyT& scale, FbxPropertyT& rotate, FbxPropertyT& translate, List^ pNotFound) + { + List^ pAnimationList = parser->TrackList; + + char* lTakeName = kTakeName.Buffer(); + + FbxTime lTime; + FbxAnimStack* lAnimStack = FbxAnimStack::Create(pScene, lTakeName); + FbxAnimLayer* lAnimLayer = FbxAnimLayer::Create(pScene, "Base Layer"); + lAnimStack->AddMember(lAnimLayer); + + const double fps = 1.0 / parser->SampleRate; + + for (int j = 0; j < pAnimationList->Count; j++) + { + ImportedAnimationSampledTrack^ sampleList = pAnimationList[j]; + + int endAt; + if (sampleList->Scalings && sampleList->Scalings->Length > 0) + { + endAt = sampleList->Scalings->Length; + } + else if (sampleList->Rotations && sampleList->Rotations->Length > 0) + { + endAt = sampleList->Rotations->Length; + } + else if (sampleList->Translations && sampleList->Translations->Length > 0) + { + endAt = sampleList->Translations->Length; + } + else if (sampleList->Curve && sampleList->Curve->Length > 0) + { + endAt = sampleList->Curve->Length; + } + + String^ name = sampleList->Name; + int dotPos = name->IndexOf('.'); + if (dotPos >= 0 && !ImportedHelpers::FindFrame(name, imported->FrameList[0])) + { + name = name->Substring(0, dotPos); + } + FbxNode* pNode = NULL; + char* pName = NULL; + try + { + pName = Fbx::StringToCharArray(name); + pNode = pScene->GetRootNode()->FindChild(pName); + } + finally + { + Marshal::FreeHGlobal((IntPtr)pName); + } + + if (pNode == NULL) + { + if (!pNotFound->Contains(name)) + { + pNotFound->Add(name); + } + } + else + { + if (sampleList->Scalings) + { + FbxAnimCurve* lCurveSX = pNode->LclScaling.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true); + FbxAnimCurve* lCurveSY = pNode->LclScaling.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true); + FbxAnimCurve* lCurveSZ = pNode->LclScaling.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true); + lCurveSX->KeyModifyBegin(); + lCurveSY->KeyModifyBegin(); + lCurveSZ->KeyModifyBegin(); + for (int k = 0; k < endAt; k++) + { + if (!sampleList->Scalings[k].HasValue) + continue; + + lTime.SetSecondDouble(fps * k); + + lCurveSX->KeySet(lCurveSX->KeyAdd(lTime), lTime, sampleList->Scalings[k].Value.X); + lCurveSY->KeySet(lCurveSY->KeyAdd(lTime), lTime, sampleList->Scalings[k].Value.Y); + lCurveSZ->KeySet(lCurveSZ->KeyAdd(lTime), lTime, sampleList->Scalings[k].Value.Z); + } + lCurveSX->KeyModifyEnd(); + lCurveSY->KeyModifyEnd(); + lCurveSZ->KeyModifyEnd(); + } + + if (sampleList->Rotations) + { + FbxAnimCurve* lCurveRX = pNode->LclRotation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true); + FbxAnimCurve* lCurveRY = pNode->LclRotation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true); + FbxAnimCurve* lCurveRZ = pNode->LclRotation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true); + lCurveRX->KeyModifyBegin(); + lCurveRY->KeyModifyBegin(); + lCurveRZ->KeyModifyBegin(); + for (int k = 0; k < endAt; k++) + { + if (!sampleList->Rotations[k].HasValue) + continue; + + lTime.SetSecondDouble(fps * k); + + Vector3 rotation = Fbx::QuaternionToEuler(sampleList->Rotations[k].Value); + lCurveRX->KeySet(lCurveRX->KeyAdd(lTime), lTime, rotation.X); + lCurveRY->KeySet(lCurveRY->KeyAdd(lTime), lTime, rotation.Y); + lCurveRZ->KeySet(lCurveRZ->KeyAdd(lTime), lTime, rotation.Z); + } + lCurveRX->KeyModifyEnd(); + lCurveRY->KeyModifyEnd(); + lCurveRZ->KeyModifyEnd(); + + if (EulerFilter) + { + FbxAnimCurve* lCurve[3]; + lCurve[0] = lCurveRX; + lCurve[1] = lCurveRY; + lCurve[2] = lCurveRZ; + EulerFilter->Reset(); + EulerFilter->SetTestForPath(true); + EulerFilter->SetQualityTolerance(filterPrecision); + EulerFilter->Apply(lCurve, 3); + } + } + + if (sampleList->Translations) + { + FbxAnimCurve* lCurveTX = pNode->LclTranslation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true); + FbxAnimCurve* lCurveTY = pNode->LclTranslation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true); + FbxAnimCurve* lCurveTZ = pNode->LclTranslation.GetCurve(lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true); + lCurveTX->KeyModifyBegin(); + lCurveTY->KeyModifyBegin(); + lCurveTZ->KeyModifyBegin(); + for (int k = 0; k < endAt; k++) + { + if (!sampleList->Translations[k].HasValue) + continue; + + lTime.SetSecondDouble(fps * k); + + lCurveTX->KeySet(lCurveTX->KeyAdd(lTime), lTime, sampleList->Translations[k].Value.X); + lCurveTY->KeySet(lCurveTY->KeyAdd(lTime), lTime, sampleList->Translations[k].Value.Y); + lCurveTZ->KeySet(lCurveTZ->KeyAdd(lTime), lTime, sampleList->Translations[k].Value.Z); + } + lCurveTX->KeyModifyEnd(); + lCurveTY->KeyModifyEnd(); + lCurveTZ->KeyModifyEnd(); + } + + if (sampleList->Curve) + { + FbxNode* pMeshNode = pNode->GetChild(0); + FbxMesh* pMesh = pMeshNode ? pMeshNode->GetMesh() : NULL; + if (pMesh) + { + name = sampleList->Name->Substring(dotPos + 1); + int numBlendShapes = pMesh->GetDeformerCount(FbxDeformer::eBlendShape); + for (int bsIdx = 0; bsIdx < numBlendShapes; bsIdx++) + { + FbxBlendShape* lBlendShape = (FbxBlendShape*)pMesh->GetDeformer(bsIdx, FbxDeformer::eBlendShape); + int numChannels = lBlendShape->GetBlendShapeChannelCount(); + float flatMinStrength = 0, flatMaxStrength; + String^ shapeName = nullptr; + for (int chnIdx = 0; chnIdx < numChannels; chnIdx++) + { + FbxBlendShapeChannel* lChannel = lBlendShape->GetBlendShapeChannel(chnIdx); + String^ keyframeName; + if (!flatInbetween) + { + keyframeName = gcnew String(lChannel->GetName()); + } + else + { + shapeName = gcnew String(lChannel->GetTargetShape(0)->GetName()); + keyframeName = shapeName->Substring(0, shapeName->LastIndexOf("_")); + } + if (keyframeName == name) + { + FbxAnimCurve* lCurve = lChannel->DeformPercent.GetCurve(lAnimLayer, true); + if (flatInbetween) + { + FbxProperty weightProp; + WITH_MARSHALLED_STRING + ( + weightName, + shapeName + ".Weight", + weightProp = pMesh->FindProperty(weightName); + ); + if (weightProp.IsValid()) + { + flatMaxStrength = (float)weightProp.Get(); + } + else + { + flatMaxStrength = 100; + //Report::ReportLog("Error! Weight for flat Blend-Shape " + shapeName + " not found! Using a value of " + flatMaxStrength); + } + } + lCurve->KeyModifyBegin(); + for (int k = 0; k < endAt; k++) + { + if (!sampleList->Curve[k].HasValue) + { + continue; + } + + lTime.SetSecondDouble(fps * k); + + auto keySetIndex = lCurve->KeyAdd(lTime); + + if (!flatInbetween) + { + lCurve->KeySet(keySetIndex, lTime, sampleList->Curve[k].Value); + } + else + { + float val = sampleList->Curve[k].Value; + if (val >= flatMinStrength && val <= flatMaxStrength) + { + val = (val - flatMinStrength) * 100 / (flatMaxStrength - flatMinStrength); + } + else if (val < flatMinStrength) + { + val = 0; + } + else if (val > flatMaxStrength) + { + val = 100; + } + lCurve->KeySet(keySetIndex, lTime, val); + } + } + lCurve->KeyModifyEnd(); + if (!flatInbetween) + { + bsIdx = numBlendShapes; + break; + } + else + { + flatMinStrength = flatMaxStrength; + } + } + } + } + } + else + { + name = sampleList->Name; + if (!pNotFound->Contains(name)) + { + pNotFound->Add(name); + } + } + } + } + } + } + + void Fbx::Exporter::ExportMorphs(IImported^ imported, bool morphMask, bool flatInbetween) + { + if (imported->MeshList == nullptr) + { + return; + } + + for (int meshIdx = 0; meshIdx < imported->MeshList->Count; meshIdx++) + { + ImportedMesh^ meshList = imported->MeshList[meshIdx]; + FbxNode* pBaseNode = NULL; + for (int nodeIdx = 0; nodeIdx < pMeshNodes->GetCount(); nodeIdx++) + { + FbxNode* pMeshNode = pMeshNodes->GetAt(nodeIdx); + String^ framePath = gcnew String(pMeshNode->GetName()); + FbxNode* rootNode = pMeshNode; + while ((rootNode = rootNode->GetParent()) != pScene->GetRootNode()) + { + framePath = gcnew String(rootNode->GetName()) + "/" + framePath; + } + if (framePath == meshList->Name) + { + pBaseNode = pMeshNode; + break; + } + } + if (pBaseNode == NULL) + { + continue; + } + + for each (ImportedMorph^ morph in imported->MorphList) + { + if (morph->Name != meshList->Name) + { + continue; + } + + int meshVertexIndex = 0; + for (int meshObjIdx = 0; meshObjIdx < meshList->SubmeshList->Count; meshObjIdx++) + { + List^ vertList = meshList->SubmeshList[meshObjIdx]->VertexList; + FbxNode* pBaseMeshNode = pBaseNode->GetChild(meshObjIdx); + FbxMesh* pBaseMesh = pBaseMeshNode->GetMesh(); + + FbxBlendShape* lBlendShape; + WITH_MARSHALLED_STRING + ( + pShapeName, + morph->ClipName + (meshList->SubmeshList->Count > 1 ? "_" + meshObjIdx : String::Empty) /*+ "_BlendShape"*/, + lBlendShape = FbxBlendShape::Create(pScene, pShapeName); + ); + pBaseMesh->AddDeformer(lBlendShape); + List^ keyframes = morph->KeyframeList; + for (int i = 0; i < morph->Channels->Count; i++) + { + FbxBlendShapeChannel* lBlendShapeChannel; + if (!flatInbetween) + { + WITH_MARSHALLED_STRING + ( + pChannelName, + gcnew String(lBlendShape->GetName()) + "." + keyframes[morph->Channels[i]->Item2]->Name->Substring(0, keyframes[morph->Channels[i]->Item2]->Name->LastIndexOf("_")), + lBlendShapeChannel = FbxBlendShapeChannel::Create(pScene, pChannelName); + ); + lBlendShapeChannel->DeformPercent = morph->Channels[i]->Item1; + lBlendShape->AddBlendShapeChannel(lBlendShapeChannel); + } + + for (int frameIdx = 0; frameIdx < morph->Channels[i]->Item3; frameIdx++) + { + int shapeIdx = morph->Channels[i]->Item2 + frameIdx; + ImportedMorphKeyframe^ keyframe = keyframes[shapeIdx]; + + FbxShape* pShape; + if (!flatInbetween) + { + WITH_MARSHALLED_STRING + ( + pMorphShapeName, + keyframe->Name, + pShape = FbxShape::Create(pScene, pMorphShapeName); + ); + lBlendShapeChannel->AddTargetShape(pShape, keyframe->Weight); + } + else + { + lBlendShapeChannel = FbxBlendShapeChannel::Create(pScene, ""); + lBlendShapeChannel->DeformPercent = morph->Channels[i]->Item1; + lBlendShape->AddBlendShapeChannel(lBlendShapeChannel); + + WITH_MARSHALLED_STRING + ( + pMorphShapeName, + morph->ClipName + (meshList->SubmeshList->Count > 1 ? "_" + meshObjIdx : String::Empty) + "." + keyframe->Name, + pShape = FbxShape::Create(pScene, pMorphShapeName); + ); + lBlendShapeChannel->AddTargetShape(pShape, 100); + + FbxProperty weightProp; + WITH_MARSHALLED_STRING + ( + pWeightName, + gcnew String(pShape->GetName()) + ".Weight", + weightProp = FbxProperty::Create(pBaseMesh, FbxDoubleDT, pWeightName); + ); + weightProp.ModifyFlag(FbxPropertyFlags::eUserDefined, true); + weightProp.Set(keyframe->Weight); + } + + pShape->InitControlPoints(vertList->Count); + FbxVector4* pControlPoints = pShape->GetControlPoints(); + + for (int j = 0; j < vertList->Count; j++) + { + ImportedVertex^ vertex = vertList[j]; + Vector3 coords = vertex->Position; + pControlPoints[j] = FbxVector4(coords.X, coords.Y, coords.Z, 0); + } + List^ meshIndices = keyframe->MorphedVertexIndices; + for (int j = 0; j < meshIndices->Count; j++) + { + int controlPointIndex = meshIndices[j] - meshVertexIndex; + if (controlPointIndex >= 0 && controlPointIndex < vertList->Count) + { + Vector3 coords = keyframe->VertexList[j]->Position; + pControlPoints[controlPointIndex] = FbxVector4(coords.X, coords.Y, coords.Z, 0); + } + } + + if (flatInbetween && frameIdx > 0) + { + int shapeIdx = morph->Channels[i]->Item2 + frameIdx - 1; + ImportedMorphKeyframe^ keyframe = keyframes[shapeIdx]; + + List^ meshIndices = keyframe->MorphedVertexIndices; + for (int j = 0; j < meshIndices->Count; j++) + { + int controlPointIndex = meshIndices[j] - meshVertexIndex; + if (controlPointIndex >= 0 && controlPointIndex < vertList->Count) + { + Vector3 coords = keyframe->VertexList[j]->Position - vertList[controlPointIndex]->Position; + pControlPoints[controlPointIndex] -= FbxVector4(coords.X, coords.Y, coords.Z, 0); + } + } + } + + if (morphMask) + { + FbxGeometryElementVertexColor* lGeometryElementVertexColor = pBaseMesh->CreateElementVertexColor(); + lGeometryElementVertexColor->SetMappingMode(FbxGeometryElement::eByControlPoint); + lGeometryElementVertexColor->SetReferenceMode(FbxGeometryElement::eDirect); + WITH_MARSHALLED_STRING + ( + pColourLayerName, morph->KeyframeList[shapeIdx]->Name, + lGeometryElementVertexColor->SetName(pColourLayerName); + ); + for (int j = 0; j < vertList->Count; j++) + { + lGeometryElementVertexColor->GetDirectArray().Add(FbxColor(1, 1, 1)); + } + for (int j = 0; j < meshIndices->Count; j++) + { + int controlPointIndex = meshIndices[j] - meshVertexIndex; + if (controlPointIndex >= 0 && controlPointIndex < vertList->Count) + { + lGeometryElementVertexColor->GetDirectArray().SetAt(controlPointIndex, FbxColor(0, 0, 1)); + } + } + } + } + } + meshVertexIndex += meshList->SubmeshList[meshObjIdx]->VertexList->Count; + } + } + } + } +} diff --git a/AssetStudioUtility/AssetStudioUtility.csproj b/AssetStudioUtility/AssetStudioUtility.csproj new file mode 100644 index 0000000..2e04430 --- /dev/null +++ b/AssetStudioUtility/AssetStudioUtility.csproj @@ -0,0 +1,50 @@ + + + + + Debug + AnyCPU + {9131C403-7FE8-444D-9AF5-5FE5DF76FF24} + Library + Properties + AssetStudioUtility + AssetStudioUtility + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\AssetStudio\Library\SharpDX.Mathematics.dll + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AssetStudioUtility/Imported.cs b/AssetStudioUtility/Imported.cs new file mode 100644 index 0000000..7b479ca --- /dev/null +++ b/AssetStudioUtility/Imported.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.IO; +using SharpDX; + +namespace AssetStudio +{ + public interface IImported + { + List FrameList { get; } + List MeshList { get; } + List MaterialList { get; } + List TextureList { get; } + List AnimationList { get; } + List MorphList { get; } + } + + public class ImportedFrame : ObjChildren, IObjChild + { + public string Name { get; set; } + public Matrix Matrix { get; set; } + + public dynamic Parent { get; set; } + } + + public class ImportedMesh + { + public string Name { get; set; } + public List SubmeshList { get; set; } + public List BoneList { get; set; } + } + + public class ImportedSubmesh + { + public List VertexList { get; set; } + public List FaceList { get; set; } + public string Material { get; set; } + public int Index { get; set; } + public bool WorldCoords { get; set; } + public bool Visible { get; set; } + } + + public class ImportedVertex + { + public Vector3 Position { get; set; } + public float[] Weights { get; set; } + public byte[] BoneIndices { get; set; } + public Vector3 Normal { get; set; } + public float[] UV { get; set; } + public Vector4 Tangent { get; set; } + } + + public class ImportedVertexWithColour : ImportedVertex + { + public Color4 Colour { get; set; } + } + + public class ImportedFace + { + public int[] VertexIndices { get; set; } + } + + public class ImportedBone + { + public string Name { get; set; } + public Matrix Matrix { get; set; } + } + + public class ImportedMaterial + { + public string Name { get; set; } + public Color4 Diffuse { get; set; } + public Color4 Ambient { get; set; } + public Color4 Specular { get; set; } + public Color4 Emissive { get; set; } + public float Power { get; set; } + public string[] Textures { get; set; } + public Vector2[] TexOffsets { get; set; } + public Vector2[] TexScales { get; set; } + } + + public class ImportedTexture + { + public string Name { get; set; } + public byte[] Data { get; set; } + + public ImportedTexture(MemoryStream stream, string name) + { + Name = name; + Data = stream.ToArray(); + } + } + + public interface ImportedAnimation + { + } + + public abstract class ImportedAnimationTrackContainer : ImportedAnimation where TrackType : ImportedAnimationTrack + { + public List TrackList { get; set; } + + public TrackType FindTrack(string name) + { + return TrackList.Find(track => track.Name == name); + } + } + + public class ImportedKeyframedAnimation : ImportedAnimationTrackContainer + { + public string Name { get; set; } + } + + public class ImportedSampledAnimation : ImportedAnimationTrackContainer + { + public string Name { get; set; } + public float SampleRate { get; set; } + } + + public abstract class ImportedAnimationTrack + { + public string Name { get; set; } + } + + public class ImportedAnimationKeyframedTrack : ImportedAnimationTrack + { + public Dictionary Keyframes { get; set; } = new Dictionary(); + } + + public class ImportedKeyframe + { + public float time { get; set; } + public T value { get; set; } + public T inSlope { get; set; } + public T outSlope { get; set; } + + public ImportedKeyframe(float time, T value, T inSlope, T outSlope) + { + this.time = time; + this.value = value; + this.inSlope = inSlope; + this.outSlope = outSlope; + } + } + + public class ImportedAnimationKeyframe + { + public ImportedKeyframe Scaling { get; set; } + public ImportedKeyframe Rotation { get; set; } + public ImportedKeyframe Translation { get; set; } + } + + public class ImportedAnimationSampledTrack : ImportedAnimationTrack + { + public Vector3?[] Scalings; + public Quaternion?[] Rotations; + public Vector3?[] Translations; + public float?[] Curve; + } + + public class ImportedMorph + { + public string Name { get; set; } + public string ClipName { get; set; } + public List> Channels { get; set; } + public List KeyframeList { get; set; } + public List MorphedVertexIndices { get; set; } + } + + public class ImportedMorphKeyframe + { + public string Name { get; set; } + public List VertexList { get; set; } + public List MorphedVertexIndices { get; set; } + public float Weight { get; set; } + } + + public static class ImportedHelpers + { + public static ImportedFrame FindFrame(String name, ImportedFrame root) + { + ImportedFrame frame = root; + if ((frame != null) && (frame.Name == name)) + { + return frame; + } + + for (int i = 0; i < root.Count; i++) + { + if ((frame = FindFrame(name, root[i])) != null) + { + return frame; + } + } + + return null; + } + + public static ImportedMesh FindMesh(String frameName, List importedMeshList) + { + foreach (ImportedMesh mesh in importedMeshList) + { + if (mesh.Name == frameName) + { + return mesh; + } + } + + return null; + } + + public static ImportedMesh FindMesh(ImportedFrame frame, List importedMeshList) + { + string framePath = frame.Name; + ImportedFrame root = frame; + while (root.Parent != null) + { + root = root.Parent; + framePath = root.Name + "/" + framePath; + } + + foreach (ImportedMesh mesh in importedMeshList) + { + if (mesh.Name == framePath) + { + return mesh; + } + } + + return null; + } + + public static ImportedMaterial FindMaterial(String name, List importedMats) + { + foreach (ImportedMaterial mat in importedMats) + { + if (mat.Name == name) + { + return mat; + } + } + + return null; + } + + public static ImportedTexture FindTexture(string name, List importedTextureList) + { + if (string.IsNullOrEmpty(name)) + { + return null; + } + + foreach (ImportedTexture tex in importedTextureList) + { + if (tex.Name == name) + { + return tex; + } + } + + return null; + } + } +} diff --git a/AssetStudioUtility/ObjChildren.cs b/AssetStudioUtility/ObjChildren.cs new file mode 100644 index 0000000..1076f6b --- /dev/null +++ b/AssetStudioUtility/ObjChildren.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; + +namespace AssetStudio +{ + public interface IObjChild + { + dynamic Parent { get; set; } + } + + public abstract class ObjChildren : IEnumerable where T : IObjChild + { + protected List children; + + public T this[int i] + { + get { return (T)children[i]; } + } + + public int Count + { + get { return children.Count; } + } + + public void InitChildren(int count) + { + children = new List(count); + } + + public void AddChild(T obj) + { + children.Add(obj); + obj.Parent = this; + } + + public void InsertChild(int i, T obj) + { + children.Insert(i, obj); + obj.Parent = this; + } + + public void RemoveChild(T obj) + { + obj.Parent = null; + children.Remove(obj); + } + + public void RemoveChild(int i) + { + children[i].Parent = null; + children.RemoveAt(i); + } + + public int IndexOf(T obj) + { + return children.IndexOf(obj); + } + + public IEnumerator GetEnumerator() + { + return children.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/AssetStudioUtility/Properties/AssemblyInfo.cs b/AssetStudioUtility/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..762515f --- /dev/null +++ b/AssetStudioUtility/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("AssetStudioUtility")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AssetStudioUtility")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("9131c403-7fe8-444d-9af5-5fe5df76ff24")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")]