Prototype animation export

This commit is contained in:
Perfare
2018-04-07 07:51:33 +08:00
parent 4bcbdbc57d
commit 0b111d5f79
33 changed files with 3700 additions and 45 deletions

View File

@ -167,6 +167,7 @@
<Compile Include="StudioClasses\ClassStruct.cs" />
<Compile Include="StudioClasses\FBXExporter.cs" />
<Compile Include="StudioClasses\BuildTarget.cs" />
<Compile Include="StudioClasses\ModelConverter.cs" />
<Compile Include="StudioClasses\ShaderResource.Designer.cs">
<DependentUpon>ShaderResource.resx</DependentUpon>
<AutoGen>True</AutoGen>
@ -264,6 +265,16 @@
<ItemGroup>
<None Include="Resources\preview.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AssetStudioFBX\AssetStudioFBX-x86.vcxproj">
<Project>{276de52e-3ffe-4c3d-9076-62bcb7a5b991}</Project>
<Name>AssetStudioFBX-x86</Name>
</ProjectReference>
<ProjectReference Include="..\AssetStudioUtility\AssetStudioUtility.csproj">
<Project>{9131c403-7fe8-444d-9af5-5fe5df76ff24}</Project>
<Name>AssetStudioUtility</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>xcopy /y "$(ProjectDir)Library" "$(TargetDir)"

View File

@ -205,6 +205,7 @@
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StudioClasses\AssetsFile.cs" />
<Compile Include="StudioClasses\ModelConverter.cs" />
<Compile Include="StudioClasses\WebFile.cs" />
<Compile Include="AssetStudioForm.cs">
<SubType>Form</SubType>
@ -264,6 +265,16 @@
<ItemGroup>
<None Include="Resources\preview.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AssetStudioFBX\AssetStudioFBX.vcxproj">
<Project>{4f8ef5ef-732b-49cf-9eb3-b23e19ae6267}</Project>
<Name>AssetStudioFBX</Name>
</ProjectReference>
<ProjectReference Include="..\AssetStudioUtility\AssetStudioUtility.csproj">
<Project>{9131c403-7fe8-444d-9af5-5fe5df76ff24}</Project>
<Name>AssetStudioUtility</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>xcopy /y "$(ProjectDir)Library" "$(TargetDir)"

View File

@ -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;
}
}

View File

@ -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<AssetPreloadData> animationList = new List<AssetPreloadData>();
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();
});
}
}
}
}
}

View File

@ -8,6 +8,7 @@ namespace AssetStudio
{
public class GameObject : TreeNode
{
public AssetPreloadData asset;
public List<PPtr> m_Components = new List<PPtr>();
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();

View File

@ -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<BlendShapeVertex> vertices { get; set; }
public List<MeshBlendShape> shapes { get; set; }
public List<MeshBlendShapeChannel> channels { get; set; }
public List<float> fullWeights { get; set; }
public BlendShapeData(EndianBinaryReader reader)
{
int numVerts = reader.ReadInt32();
vertices = new List<BlendShapeVertex>(numVerts);
for (int i = 0; i < numVerts; i++)
{
vertices.Add(new BlendShapeVertex(reader));
}
int numShapes = reader.ReadInt32();
shapes = new List<MeshBlendShape>(numShapes);
for (int i = 0; i < numShapes; i++)
{
shapes.Add(new MeshBlendShape(reader));
}
int numChannels = reader.ReadInt32();
channels = new List<MeshBlendShapeChannel>(numChannels);
for (int i = 0; i < numChannels; i++)
{
channels.Add(new MeshBlendShapeChannel(reader));
}
int numWeights = reader.ReadInt32();
fullWeights = new List<float>(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();
}

View File

@ -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;

View File

@ -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<float> 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<float>(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();
}
}*/
}
}
}

View File

@ -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;
}
}

View File

@ -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();

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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;
}
}
}
}

View File

@ -56,5 +56,29 @@
<Setting Name="displayOriginalName" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="EulerFilter" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="filterPrecision" Type="System.Decimal" Scope="User">
<Value Profile="(Default)">0.25</Value>
</Setting>
<Setting Name="allFrames" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="allBones" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="skins" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="boneSize" Type="System.Decimal" Scope="User">
<Value Profile="(Default)">10</Value>
</Setting>
<Setting Name="flatInbetween" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="compatibility" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@ -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<AssetPreloadData> 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;
}
}
}

View File

@ -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<ImportedFrame> FrameList { get; protected set; } = new List<ImportedFrame>();
public List<ImportedMesh> MeshList { get; protected set; } = new List<ImportedMesh>();
public List<ImportedMaterial> MaterialList { get; protected set; } = new List<ImportedMaterial>();
public List<ImportedTexture> TextureList { get; protected set; } = new List<ImportedTexture>();
public List<ImportedAnimation> AnimationList { get; protected set; } = new List<ImportedAnimation>();
public List<ImportedMorph> MorphList { get; protected set; } = new List<ImportedMorph>();
private Avatar avatar;
private Dictionary<uint, string> morphChannelInfo = new Dictionary<uint, string>();
private HashSet<AssetPreloadData> animationClipHashSet = new HashSet<AssetPreloadData>();
private Dictionary<uint, string> bonePathHash = new Dictionary<uint, string>();
public ModelConverter(Animator m_Animator)
{
InitWithAnimator(m_Animator);
CollectAnimationClip(m_Animator);
ConvertAnimations();
}
public ModelConverter(Animator m_Animator, List<AssetPreloadData> 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<ImportedFrame>();
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<ImportedSubmesh>(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<ImportedVertex>((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<ImportedFace>(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<ImportedBone>(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<Tuple<float, int, int>>(mesh.m_Shapes.channels.Count);
morph.KeyframeList = new List<ImportedMorphKeyframe>(mesh.m_Shapes.shapes.Count);
lastGroup = group;
}
morph.Channels.Add(new Tuple<float, int, int>(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<ImportedVertex>((int)mesh.m_Shapes.shapes[shapeIdx].vertexCount);
keyframe.MorphedVertexIndices = new List<ushort>((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<ImportedAnimationKeyframedTrack>();
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<Quaternion>(
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<Vector3>(
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<Vector3>(
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<ImportedAnimationSampledTrack>(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<ImportedSubmesh> submeshList, int morphVertIndex)
{
for (int i = 0; i < submeshList.Count; i++)
{
List<ImportedVertex> 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);
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -61,6 +61,30 @@
<setting name="displayOriginalName" serializeAs="String">
<value>False</value>
</setting>
<setting name="EulerFilter" serializeAs="String">
<value>False</value>
</setting>
<setting name="filterPrecision" serializeAs="String">
<value>0.25</value>
</setting>
<setting name="allFrames" serializeAs="String">
<value>False</value>
</setting>
<setting name="allBones" serializeAs="String">
<value>True</value>
</setting>
<setting name="skins" serializeAs="String">
<value>True</value>
</setting>
<setting name="boneSize" serializeAs="String">
<value>10</value>
</setting>
<setting name="flatInbetween" serializeAs="String">
<value>False</value>
</setting>
<setting name="compatibility" serializeAs="String">
<value>False</value>
</setting>
</AssetStudio.Properties.Settings>
</userSettings>
</configuration>