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")]