diff --git a/.gitignore b/.gitignore index 5d79d730..9d13503b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ obj/ *GL_EditorFramework-master *SuperBMD-master *SuperBMD-link -*Assimp *.resources BrawlboxHelper/BrawlHelperTest2.cs BrawlboxHelper/BrawlHelperTest2.zip diff --git a/Switch_Toolbox_Library/FileFormats/Assimp/AssimpSaver.cs b/Switch_Toolbox_Library/FileFormats/Assimp/AssimpSaver.cs new file mode 100644 index 00000000..96f4f979 --- /dev/null +++ b/Switch_Toolbox_Library/FileFormats/Assimp/AssimpSaver.cs @@ -0,0 +1,652 @@ +using System; +using System.Drawing; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using Assimp; +using OpenTK; +using Toolbox.Library.Rendering; +using System.Windows.Forms; +using Toolbox.Library.Animations; +using Toolbox.Library.Forms; + +namespace Toolbox.Library +{ + public class AssimpSaver + { + private List ExtractedTextures = new List(); + + public List BoneNames = new List(); + + STProgressBar progressBar; + + public void SaveFromModel(STGenericModel model, string FileName, List Textures, STSkeleton skeleton = null, List NodeArray = null) + { + SaveFromModel(model.Objects.ToList(), model.Materials.ToList(), FileName, Textures, skeleton, NodeArray); + } + + public void SaveFromModel(List Meshes, List Materials, string FileName, List Textures, STSkeleton skeleton = null, List NodeArray = null) + { + ExtractedTextures.Clear(); + + Scene scene = new Scene(); + scene.RootNode = new Node("RootNode"); + + progressBar = new STProgressBar(); + progressBar.Task = "Exporting Skeleton..."; + progressBar.Value = 0; + progressBar.StartPosition = FormStartPosition.CenterScreen; + progressBar.Show(); + progressBar.Refresh(); + + SaveSkeleton(skeleton, scene.RootNode); + SaveMaterials(scene, Materials, FileName, Textures); + + progressBar.Task = "Exporting Meshes..."; + progressBar.Value = 50; + + SaveMeshes(scene, Meshes, skeleton, FileName, NodeArray); + + progressBar.Task = "Saving File..."; + progressBar.Value = 80; + + SaveScene(FileName, scene, Meshes); + + progressBar.Value = 100; + progressBar.Close(); + progressBar.Dispose(); + } + + private void SaveScene(string FileName, Scene scene, List Meshes) + { + using (var v = new AssimpContext()) + { + string ext = System.IO.Path.GetExtension(FileName); + + string formatID = "collada"; + if (ext == ".obj") + formatID = "obj"; + if (ext == ".3ds") + formatID = "3ds"; + if (ext == ".dae") + formatID = "collada"; + if (ext == ".ply") + formatID = "ply"; + + bool ExportSuccessScene = v.ExportFile(scene, FileName, formatID, PostProcessSteps.FlipUVs); + if (ExportSuccessScene) + { + if (ext == ".dae") + WriteExtraSkinningInfo(FileName, scene, Meshes); + + MessageBox.Show($"Exported {FileName} Successfuly!"); + } + else + MessageBox.Show($"Failed to export {FileName}!"); + } + + } + + private void SaveMeshes(Scene scene, List Meshes, STSkeleton skeleton, string FileName, List NodeArray) + { + int MeshIndex = 0; + foreach (var obj in Meshes) + { + var mesh = SaveMesh((STGenericObject)obj, scene, MeshIndex++, skeleton, NodeArray); + scene.Meshes.Add(mesh); + } + Node geomNode = new Node(Path.GetFileNameWithoutExtension(FileName), scene.RootNode); + + for (int ob = 0; ob < scene.MeshCount; ob++) + { + geomNode.MeshIndices.Add(ob); + + // if (!scene.Meshes[ob].HasBones) + } + + scene.RootNode.Children.Add(geomNode); + } + + private Mesh SaveMesh(STGenericObject genericObj, Scene scene, int index, STSkeleton skeleton, List NodeArray) + { + //Assimp is weird so use mesh_# for the name. We'll change it back after save + Mesh mesh = new Mesh($"mesh_{ index }", PrimitiveType.Triangle); + + if (genericObj.MaterialIndex < scene.MaterialCount && genericObj.MaterialIndex > 0) + mesh.MaterialIndex = genericObj.MaterialIndex; + else + mesh.MaterialIndex = 0; + + List textureCoords0 = new List(); + List textureCoords1 = new List(); + List textureCoords2 = new List(); + List vertexColors = new List(); + + int vertexID = 0; + foreach (Vertex v in genericObj.vertices) + { + mesh.Vertices.Add(new Vector3D(v.pos.X, v.pos.Y, v.pos.Z)); + mesh.Normals.Add(new Vector3D(v.nrm.X, v.nrm.Y, v.nrm.Z)); + textureCoords0.Add(new Vector3D(v.uv0.X, v.uv0.Y, 0)); + textureCoords1.Add(new Vector3D(v.uv1.X, v.uv1.Y, 0)); + textureCoords2.Add(new Vector3D(v.uv2.X, v.uv2.Y, 0)); + vertexColors.Add(new Color4D(v.col.X, v.col.Y, v.col.Z, v.col.W)); + + if (skeleton != null) + { + for (int j = 0; j < v.boneIds.Count; j++) + { + if (j < genericObj.VertexSkinCount) + { + STBone STbone = null; + if (NodeArray != null) + { + //Get the bone via the node array and bone index from the vertex + STbone = skeleton.bones[NodeArray[v.boneIds[j]]]; + } + else + STbone = skeleton.bones[v.boneIds[j]]; + + //Find the index of a bone. If it doesn't exist then we add it + int boneInd = mesh.Bones.FindIndex(x => x.Name == STbone.Text); + + if (boneInd == -1) + { + var matrices = Toolbox.Library.IO.MatrixExenstion.CalculateInverseMatrix(STbone); + + //Set the inverse matrix + Matrix4x4 transform = matrices.inverse.FromNumerics(); + + //Create a new assimp bone + Bone bone = new Bone(); + bone.Name = STbone.Text; + bone.OffsetMatrix = STbone.invert.ToMatrix4x4(); + mesh.Bones.Add(bone); + BoneNames.Add(bone.Name); + + boneInd = mesh.Bones.IndexOf(bone); //Set the index of the bone for the vertex weight + } + + int MinWeightAmount = 0; + + //Check if the max amount of weights is higher than the current bone id + if (v.boneWeights.Count > j && v.boneWeights[j] > MinWeightAmount) + { + if (v.boneWeights[j] <= 1) + mesh.Bones[boneInd].VertexWeights.Add(new VertexWeight(vertexID, v.boneWeights[j])); + else + mesh.Bones[boneInd].VertexWeights.Add(new VertexWeight(vertexID, 1)); + } + else if (v.boneWeights.Count == 0 || v.boneWeights[j] > MinWeightAmount) + mesh.Bones[boneInd].VertexWeights.Add(new VertexWeight(vertexID, 1)); + } + } + } + + + vertexID++; + } + + if (genericObj.lodMeshes.Count != 0) + { + List faces = genericObj.lodMeshes[genericObj.DisplayLODIndex].faces; + for (int f = 0; f < faces.Count; f++) + mesh.Faces.Add(new Face(new int[] { faces[f++], faces[f++], faces[f] })); + } + if (genericObj.PolygonGroups.Count != 0) + { + for (int p = 0; p < genericObj.PolygonGroups.Count; p++) + { + var polygonGroup = genericObj.PolygonGroups[p]; + for (int f = 0; f < polygonGroup.faces.Count; f++) + if (f < polygonGroup.faces.Count - 2) + mesh.Faces.Add(new Face(new int[] { polygonGroup.faces[f++], polygonGroup.faces[f++], polygonGroup.faces[f] })); + } + } + + mesh.TextureCoordinateChannels.SetValue(textureCoords0, 0); + mesh.TextureCoordinateChannels.SetValue(textureCoords1, 1); + mesh.TextureCoordinateChannels.SetValue(textureCoords2, 2); + mesh.VertexColorChannels.SetValue(vertexColors, 0); + + return mesh; + } + + //Extra skin data based on https://github.com/Sage-of-Mirrors/SuperBMD/blob/ce1061e9b5f57de112f1d12f6459b938594664a0/SuperBMDLib/source/Model.cs#L193 + //Todo this doesn't quite work yet + //Need to adjust all mesh name IDs so they are correct + private void WriteExtraSkinningInfo(string FileName, Scene outScene, List Meshes) + { + StreamWriter test = new StreamWriter(FileName + ".tmp"); + StreamReader dae = File.OpenText(FileName); + + int geomIndex = 0; + while (!dae.EndOfStream) + { + string line = dae.ReadLine(); + + /* if (line == " ") + { + AddControllerLibrary(outScene, test); + test.WriteLine(line); + test.Flush(); + } + else if (line.Contains("", $" sid=\"{ name }\" type=\"JOINT\">"); + test.WriteLine(jointLine); + test.Flush(); + } + else if (line.Contains("")) + { + foreach (Mesh mesh in outScene.Meshes) + { + test.WriteLine($" "); + + test.WriteLine($" "); + test.WriteLine(" #skeleton_root"); + test.WriteLine(" "); + test.WriteLine(" "); + test.WriteLine($" "); + test.WriteLine(" "); + test.WriteLine(" "); + test.WriteLine(" "); + + test.WriteLine(" "); + test.Flush(); + } + + test.WriteLine(line); + test.Flush(); + }*/ + if (line.Contains(" "); + test.Flush(); + + geomIndex++; + } + else + { + test.WriteLine(line); + test.Flush(); + } + + /* else if (line.Contains("", ""); + test.WriteLine(matLine); + test.Flush(); + }*/ + + } + + test.Close(); + dae.Close(); + + File.Copy(FileName + ".tmp", FileName, true); + File.Delete(FileName + ".tmp"); + } + + private void AddControllerLibrary(Scene scene, StreamWriter writer) + { + writer.WriteLine(" "); + + for (int i = 0; i < scene.MeshCount; i++) + { + Mesh curMesh = scene.Meshes[i]; + curMesh.Name = curMesh.Name.Replace('_', '-'); + + writer.WriteLine($" "); + + writer.WriteLine($" "); + + WriteBindShapeMatrixToStream(writer); + WriteJointNameArrayToStream(curMesh, writer); + WriteInverseBindMatricesToStream(curMesh, writer); + WriteSkinWeightsToStream(curMesh, writer); + + writer.WriteLine(" "); + + writer.WriteLine($" "); + writer.WriteLine($" "); + + writer.WriteLine(" "); + writer.Flush(); + + WriteVertexWeightsToStream(curMesh, writer); + + writer.WriteLine(" "); + + writer.WriteLine(" "); + writer.Flush(); + } + + writer.WriteLine(" "); + writer.Flush(); + } + + private void WriteJointNameArrayToStream(Mesh mesh, StreamWriter writer) + { + writer.WriteLine($" "); + writer.WriteLine($" "); + + writer.Write(" "); + foreach (Bone bone in mesh.Bones) + { + writer.Write($"{ bone.Name }"); + if (bone != mesh.Bones.Last()) + writer.Write(' '); + else + writer.Write('\n'); + + writer.Flush(); + } + + writer.WriteLine(" "); + writer.Flush(); + + writer.WriteLine(" "); + writer.WriteLine($" "); + writer.WriteLine(" "); + writer.WriteLine(" "); + writer.WriteLine(" "); + writer.WriteLine(" "); + writer.Flush(); + } + + private void WriteInverseBindMatricesToStream(Mesh mesh, StreamWriter writer) + { + writer.WriteLine($" "); + writer.WriteLine($" "); + + foreach (Bone bone in mesh.Bones) + { + Matrix4x4 ibm = bone.OffsetMatrix; + ibm.Transpose(); + + writer.WriteLine($" {ibm.A1.ToString("F")} {ibm.A2.ToString("F")} {ibm.A3.ToString("F")} {ibm.A4.ToString("F")}"); + writer.WriteLine($" {ibm.B1.ToString("F")} {ibm.B2.ToString("F")} {ibm.B3.ToString("F")} {ibm.B4.ToString("F")}"); + writer.WriteLine($" {ibm.C1.ToString("F")} {ibm.C2.ToString("F")} {ibm.C3.ToString("F")} {ibm.C4.ToString("F")}"); + writer.WriteLine($" {ibm.D1.ToString("F")} {ibm.D2.ToString("F")} {ibm.D3.ToString("F")} {ibm.D4.ToString("F")}"); + + if (bone != mesh.Bones.Last()) + writer.WriteLine(""); + } + + writer.WriteLine(" "); + writer.Flush(); + + writer.WriteLine(" "); + writer.WriteLine($" "); + writer.WriteLine(" "); + writer.WriteLine(" "); + writer.WriteLine(" "); + writer.WriteLine(" "); + writer.Flush(); + } + + private void WriteSkinWeightsToStream(Mesh mesh, StreamWriter writer) + { + int totalWeightCount = 0; + + foreach (Bone bone in mesh.Bones) + { + totalWeightCount += bone.VertexWeightCount; + } + + writer.WriteLine($" "); + writer.WriteLine($" "); + writer.Write(" "); + + foreach (Bone bone in mesh.Bones) + { + foreach (VertexWeight weight in bone.VertexWeights) + { + writer.Write($"{ weight.Weight } "); + } + + if (bone == mesh.Bones.Last()) + writer.WriteLine(); + } + + writer.WriteLine(" "); + writer.Flush(); + + writer.WriteLine(" "); + writer.WriteLine($" "); + writer.WriteLine(" "); + writer.WriteLine(" "); + writer.WriteLine(" "); + writer.WriteLine(" "); + writer.Flush(); + } + + private class RiggedWeight + { + public List Weights { get; private set; } + public List BoneIndices { get; private set; } + + public int WeightCount { get; private set; } + + public RiggedWeight() + { + Weights = new List(); + BoneIndices = new List(); + } + + public void AddWeight(float weight, int boneIndex) + { + Weights.Add(weight); + BoneIndices.Add(boneIndex); + WeightCount++; + } + } + + private void WriteVertexWeightsToStream(Mesh mesh, StreamWriter writer) + { + List weights = new List(); + Dictionary vertIDWeights = new Dictionary(); + + foreach (Bone bone in mesh.Bones) + { + foreach (VertexWeight weight in bone.VertexWeights) + { + weights.Add(weight.Weight); + + if (!vertIDWeights.ContainsKey(weight.VertexID)) + vertIDWeights.Add(weight.VertexID, new RiggedWeight()); + + vertIDWeights[weight.VertexID].AddWeight(weight.Weight, mesh.Bones.IndexOf(bone)); + } + } + + writer.WriteLine($" "); + + writer.WriteLine($" "); + writer.WriteLine($" "); + + writer.WriteLine(" "); + + writer.Write(" "); + for (int i = 0; i < vertIDWeights.Count; i++) + writer.Write($"{ vertIDWeights[i].WeightCount } "); + + writer.WriteLine("\n "); + + writer.WriteLine(" "); + writer.Write(" "); + + for (int i = 0; i < vertIDWeights.Count; i++) + { + RiggedWeight curWeight = vertIDWeights[i]; + + for (int j = 0; j < curWeight.WeightCount; j++) + { + writer.Write($"{ curWeight.BoneIndices[j] } { weights.IndexOf(curWeight.Weights[j]) } "); + } + } + + writer.WriteLine("\n "); + + writer.WriteLine($" "); + } + + private void WriteBindShapeMatrixToStream(StreamWriter writer) + { + writer.WriteLine(" "); + + writer.WriteLine(" 1 0 0 0"); + writer.WriteLine(" 0 1 0 0"); + writer.WriteLine(" 0 0 1 0"); + writer.WriteLine(" 0 0 0 1"); + + writer.WriteLine(" "); + writer.Flush(); + } + + private void SaveMaterials(Scene scene, List Materials, string FileName, List Textures) + { + string TextureExtension = ".png"; + string TexturePath = System.IO.Path.GetDirectoryName(FileName); + + for (int i = 0; i < Textures.Count; i++) + { + string path = System.IO.Path.Combine(TexturePath, Textures[i].Text + TextureExtension); + + if (!ExtractedTextures.Contains(path)) + { + ExtractedTextures.Add(path); + + progressBar.Task = $"Exporting Texture {Textures[i].Text}"; + progressBar.Value = ((i * 100) / Textures.Count); + progressBar.Refresh(); + + var bitmap = Textures[i].GetBitmap(); + bitmap.Save(path); + bitmap.Dispose(); + + GC.Collect(); + } + } + + if (Materials.Count == 0) + { + Material material = new Material(); + material.Name = "New Material"; + scene.Materials.Add(material); + return; + } + + foreach (var mat in Materials) + { + var genericMat = (STGenericMaterial)mat; + + Material material = new Material(); + material.Name = genericMat.Text; + + foreach (var tex in genericMat.TextureMaps) + { + int index = Textures.FindIndex(r => r.Text.Equals(tex.Name)); + + string path = System.IO.Path.Combine(TexturePath, tex.Name + TextureExtension); + + if (!File.Exists(path)) + continue; + + TextureSlot slot2 = new TextureSlot(path, ConvertToAssimpTextureType(tex.Type), 0, TextureMapping.FromUV, + 0, 1.0f, Assimp.TextureOperation.Add, ConvertToAssimpWrapType(tex.WrapModeS), ConvertToAssimpWrapType(tex.WrapModeT), 0); + + material.AddMaterialTexture(ref slot2); + } + scene.Materials.Add(material); + } + + } + + private static Assimp.TextureWrapMode ConvertToAssimpWrapType(STTextureWrapMode type) + { + switch (type) + { + case STTextureWrapMode.Repeat: return TextureWrapMode.Wrap; + case STTextureWrapMode.Mirror: return TextureWrapMode.Mirror; + case STTextureWrapMode.Clamp: return TextureWrapMode.Clamp; + default: + return TextureWrapMode.Wrap; + } + } + + private static Assimp.TextureType ConvertToAssimpTextureType(STGenericMatTexture.TextureType type) + { + switch (type) + { + case STGenericMatTexture.TextureType.Diffuse: return TextureType.Diffuse; + case STGenericMatTexture.TextureType.AO: return TextureType.Ambient; + case STGenericMatTexture.TextureType.Normal: return TextureType.Normals; + case STGenericMatTexture.TextureType.Light: return TextureType.Lightmap; + case STGenericMatTexture.TextureType.Emission: return TextureType.Emissive; + case STGenericMatTexture.TextureType.Specular: return TextureType.Specular; + default: + return TextureType.Unknown; + } + } + + public void SaveFromObject(STGenericObject genericObject, string FileName) + { + Scene scene = new Scene(); + scene.RootNode = new Node("Root"); + + var mesh = SaveMesh(genericObject, scene, 0, null, null); + mesh.MaterialIndex = 0; + scene.Meshes.Add(mesh); + + Material material = new Material(); + material.Name = "NewMaterial"; + scene.Materials.Add(material); + + SaveScene(FileName, scene, new List() { genericObject }); + } + + private void SaveSkeleton(STSkeleton skeleton, Node parentNode) + { + Node root = new Node("skeleton_root"); + parentNode.Children.Add(root); + + Console.WriteLine($"bones {skeleton.bones.Count}"); + + if (skeleton.bones.Count > 0) + { + foreach (var bone in skeleton.bones) + { + //Get each root bone and find children + if (bone.parentIndex == -1) + { + Node boneNode = new Node(bone.Text); + boneNode.Transform = AssimpHelper.GetBoneMatrix(bone); + root.Children.Add(boneNode); + + foreach (STBone child in bone.GetChildren()) + SaveBones(boneNode, child, skeleton); + } + } + } + } + private void SaveBones(Node parentBone, STBone bone, STSkeleton skeleton) + { + Node boneNode = new Node(bone.Text); + parentBone.Children.Add(boneNode); + + boneNode.Transform = AssimpHelper.GetBoneMatrix(bone); + + foreach (STBone child in bone.GetChildren()) + SaveBones(boneNode, child, skeleton); + } + } +} diff --git a/Switch_Toolbox_Library/Toolbox_Library.csproj b/Switch_Toolbox_Library/Toolbox_Library.csproj index ac134125..a5b069d8 100644 --- a/Switch_Toolbox_Library/Toolbox_Library.csproj +++ b/Switch_Toolbox_Library/Toolbox_Library.csproj @@ -247,7 +247,7 @@ - +