diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..299804d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,28 @@ +name: build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install nuget dependencies + run: nuget restore SuperBMD.sln + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.1 + - name: Build + run: msbuild SuperBMD.sln /t:Build /property:Configuration=Release + - name: Upload build + uses: actions/upload-artifact@v4 + with: + name: SuperBMD + path: SuperBMD\bin\Release\* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..dda3a5e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "GameFormatReader"] + path = GameFormatReader + url = https://github.com/LagoLunatic/GameFormatReader.git + branch = master diff --git a/GameFormatReader b/GameFormatReader new file mode 160000 index 0000000..62a940a --- /dev/null +++ b/GameFormatReader @@ -0,0 +1 @@ +Subproject commit 62a940a902b603ff0ce858a3b8732a1785051739 diff --git a/SuperBMD.sln b/SuperBMD.sln index 9346c16..ae606c7 100644 --- a/SuperBMD.sln +++ b/SuperBMD.sln @@ -1,12 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.16 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30002.166 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperBMDLib", "SuperBMDLib\SuperBMDLib.csproj", "{828BE5E5-9E98-46C9-B63E-D2D03322A825}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperBMD", "SuperBMD\SuperBMD.csproj", "{7349EE4E-A17C-4715-A5B4-07533D1DC701}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFormatReader", "GameFormatReader\GameFormatReader\GameFormatReader.csproj", "{AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +43,18 @@ Global {7349EE4E-A17C-4715-A5B4-07533D1DC701}.Release|x64.Build.0 = Release|Any CPU {7349EE4E-A17C-4715-A5B4-07533D1DC701}.Release|x86.ActiveCfg = Release|Any CPU {7349EE4E-A17C-4715-A5B4-07533D1DC701}.Release|x86.Build.0 = Release|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Debug|x64.ActiveCfg = Debug|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Debug|x64.Build.0 = Debug|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Debug|x86.ActiveCfg = Debug|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Debug|x86.Build.0 = Debug|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Release|Any CPU.Build.0 = Release|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Release|x64.ActiveCfg = Release|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Release|x64.Build.0 = Release|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Release|x86.ActiveCfg = Release|Any CPU + {AFCE536D-92FF-4EE5-8536-731D2CD5FBCA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SuperBMD/App.config b/SuperBMD/App.config index 731f6de..ecdcf8a 100644 --- a/SuperBMD/App.config +++ b/SuperBMD/App.config @@ -1,6 +1,6 @@ - + - + - \ No newline at end of file + diff --git a/SuperBMD/Program.cs b/SuperBMD/Program.cs index ff7b00e..315bbdf 100644 --- a/SuperBMD/Program.cs +++ b/SuperBMD/Program.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Threading; using SuperBMDLib; namespace SuperBMDLib @@ -8,8 +9,8 @@ class Program { static void Main(string[] args) { - // Prevents floats being written to the .dae with commas instead of periods on European systems. - CultureInfo.CurrentCulture = new CultureInfo("", false); + // Prevents issues with reading/writing floats on European systems. + Thread.CurrentThread.CurrentCulture = new CultureInfo("", false); if (args.Length == 0 || args[0] == "-h" || args[0] == "--help") { diff --git a/SuperBMD/SuperBMD.csproj b/SuperBMD/SuperBMD.csproj index 21550c1..9371f79 100644 --- a/SuperBMD/SuperBMD.csproj +++ b/SuperBMD/SuperBMD.csproj @@ -8,11 +8,12 @@ Exe SuperBMD SuperBMD - v4.6.1 + v4.7.2 512 true + AnyCPU @@ -34,8 +35,8 @@ 4 - - ..\packages\AssimpNet.3.3.2\lib\net45\AssimpNet.dll + + ..\packages\AssimpNet.4.1.0\lib\net40\AssimpNet.dll @@ -61,11 +62,11 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/SuperBMD/packages.config b/SuperBMD/packages.config index 6f77ea1..0ed1446 100644 --- a/SuperBMD/packages.config +++ b/SuperBMD/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/SuperBMDLib/OpenTK.dll.config b/SuperBMDLib/OpenTK.dll.config deleted file mode 100644 index 7098d39..0000000 --- a/SuperBMDLib/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SuperBMDLib/Properties/Resources.Designer.cs b/SuperBMDLib/Properties/Resources.Designer.cs index 5cc82e2..8d96003 100644 --- a/SuperBMDLib/Properties/Resources.Designer.cs +++ b/SuperBMDLib/Properties/Resources.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace SuperBMDLib.Properties { +namespace SuperBMD.Properties { using System; @@ -19,10 +19,10 @@ namespace SuperBMDLib.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { + public class Resources { private static global::System.Resources.ResourceManager resourceMan; @@ -36,7 +36,7 @@ internal Resources() { /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SuperBMD.Properties.Resources", typeof(Resources).Assembly); @@ -51,7 +51,7 @@ internal Resources() { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +63,7 @@ internal Resources() { /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// - internal static System.Drawing.Bitmap default_checker { + public static System.Drawing.Bitmap default_checker { get { object obj = ResourceManager.GetObject("default_checker", resourceCulture); return ((System.Drawing.Bitmap)(obj)); diff --git a/SuperBMDLib/SuperBMDLib.csproj b/SuperBMDLib/SuperBMDLib.csproj index bd4315f..e7199df 100644 --- a/SuperBMDLib/SuperBMDLib.csproj +++ b/SuperBMDLib/SuperBMDLib.csproj @@ -8,7 +8,7 @@ Library SuperBMD SuperBMDLib - v4.6.1 + v4.7.2 512 true @@ -28,6 +28,7 @@ false false true + AnyCPU @@ -52,28 +53,49 @@ - - ..\packages\AssimpNet.3.3.2\lib\net45\AssimpNet.dll + + $(SolutionDir)\packages\AssimpNet.4.1.0\lib\net40\AssimpNet.dll - - False - lib\EndianBinaryStreams.dll + + ..\..\packages\DynamicData.6.16.6\lib\net461\DynamicData.dll - - ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + + $(SolutionDir)\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll - ..\packages\OpenTK.3.0.1\lib\net20\OpenTK.dll + False + $(SolutionDir)\lib\OpenTK.dll + + + ..\..\packages\Splat.9.6.1\lib\net461\Splat.dll + + ..\..\packages\System.Reactive.4.4.1\lib\net46\System.Reactive.dll + + + ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + ..\..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + + + + + ..\..\packages\TgaLib.1.0.2\lib\net472\TgaLib.dll + + @@ -203,10 +225,14 @@ - - + + + {afce536d-92ff-4ee5-8536-731d2cd5fbca} + GameFormatReader + + False @@ -222,17 +248,17 @@ - ResXFileCodeGenerator + PublicResXFileCodeGenerator Resources.Designer.cs - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + \ No newline at end of file diff --git a/SuperBMDLib/lib/EndianBinaryStreams.dll b/SuperBMDLib/lib/EndianBinaryStreams.dll deleted file mode 100644 index 2850aaa..0000000 Binary files a/SuperBMDLib/lib/EndianBinaryStreams.dll and /dev/null differ diff --git a/SuperBMDLib/packages.config b/SuperBMDLib/packages.config index 4f13856..930ea2e 100644 --- a/SuperBMDLib/packages.config +++ b/SuperBMDLib/packages.config @@ -1,7 +1,12 @@  - - - - + + + + + + + + + \ No newline at end of file diff --git a/SuperBMDLib/source/Arguments.cs b/SuperBMDLib/source/Arguments.cs index 563eb86..808ee00 100644 --- a/SuperBMDLib/source/Arguments.cs +++ b/SuperBMDLib/source/Arguments.cs @@ -15,6 +15,7 @@ public struct Arguments public string tristrip_mode; public bool rotate_model; public bool output_bdl; + public bool generate_map_materials; /// /// Initializes a new Arguments instance from the arguments passed in to SuperBMD. @@ -29,6 +30,7 @@ public Arguments(string[] args) tristrip_mode = "static"; rotate_model = false; output_bdl = false; + generate_map_materials = false; for (int i = 0; i < args.Length; i++) { @@ -82,6 +84,9 @@ public Arguments(string[] args) case "--bdl": output_bdl = true; break; + case "-glm": + generate_map_materials = true; + break; default: throw new Exception($"Unknown parameter \"{ args[i] }\""); } diff --git a/SuperBMDLib/source/BMD/INF1.cs b/SuperBMDLib/source/BMD/INF1.cs index 0224167..7cbd0b8 100644 --- a/SuperBMDLib/source/BMD/INF1.cs +++ b/SuperBMDLib/source/BMD/INF1.cs @@ -169,6 +169,7 @@ public void FillScene(Scene scene, List flatSkeleton, bool useSkel SceneNode lastNode = Root; Node curAssRoot = new Node(flatSkeleton[0].Name, root); + curAssRoot.Transform = flatSkeleton[0].TransformationMatrix.ToMatrix4x4(); Node lastAssNode = curAssRoot; root.Children.Add(curAssRoot); diff --git a/SuperBMDLib/source/BMD/MAT3.cs b/SuperBMDLib/source/BMD/MAT3.cs index 19135f9..ffa0cc8 100644 --- a/SuperBMDLib/source/BMD/MAT3.cs +++ b/SuperBMDLib/source/BMD/MAT3.cs @@ -9,6 +9,7 @@ using System.IO; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using System.Text.RegularExpressions; namespace SuperBMDLib.BMD { @@ -323,7 +324,7 @@ private void LoadInitData(EndianBinaryReader reader, int matindex) if (ambColorIndex != -1) mat.AmbientColors[1] = m_AmbientColorBlock[ambColorIndex]; - for (int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) { int lightIndex = reader.ReadInt16(); if ((lightIndex == -1) || (lightIndex > m_LightingColorBlock.Count) || (m_LightingColorBlock.Count == 0)) @@ -394,7 +395,7 @@ private void LoadInitData(EndianBinaryReader reader, int matindex) for (int i = 0; i < 16; i++) { - mat.ColorSels[i] = (KonstColorSel)reader.ReadByte(); + mat.ColorSels[i] = (KonstColorSel)reader.ReadByte(); } for (int i = 0; i < 16; i++) @@ -466,12 +467,46 @@ public MAT3(Assimp.Scene scene, TEX1 textures, SHP1 shapes, Arguments args) if (args.materials_path != "") LoadFromJson(scene, textures, shapes, args.materials_path); + else if (args.generate_map_materials) + GenerateMapMaterials(scene, textures, shapes); else LoadFromScene(scene, textures, shapes); FillMaterialDataBlocks(); } + public int GetMaterialIndexFromMaterialName(string matName) + { + for (int i = 0; i < m_Materials.Count; i++) + { + if (matName == m_MaterialNames[i]) + { + return i; + } + } + + return -1; + } + + public int GetMaterialIndexFromSanitizedMaterialName(string sanitizedMatName) + { + List allSanitizedMatNames = new List(); + foreach (string materialName in m_MaterialNames) + { + allSanitizedMatNames.Add(materialName.Replace("(", "_").Replace(")", "_")); + } + + for (int i = 0; i < m_Materials.Count; i++) + { + if (sanitizedMatName == allSanitizedMatNames[i]) + { + return i; + } + } + + return -1; + } + private void LoadFromJson(Assimp.Scene scene, TEX1 textures, SHP1 shapes, string json_path) { JsonSerializer serial = new JsonSerializer(); @@ -511,44 +546,86 @@ private void LoadFromJson(Assimp.Scene scene, TEX1 textures, SHP1 shapes, string for (int i = 0; i < scene.MeshCount; i++) { Assimp.Material meshMat = scene.Materials[scene.Meshes[i].MaterialIndex]; - string test = meshMat.Name.Replace("-material", ""); - List materialNamesWithoutParentheses = new List(); - foreach (string materialName in m_MaterialNames) - { - materialNamesWithoutParentheses.Add(materialName.Replace("(", "_").Replace(")", "_")); - } + int matIndex = GetMaterialIndexFromMaterialName(meshMat.Name); - while (!materialNamesWithoutParentheses.Contains(test)) + if (matIndex == -1) { - if (test.Length <= 1) + // None of material names in materials.json are an exact match. + // Try checking if this is a legacy model from an older version of SuperBMD where the material names were sanitized. + Regex reg = new Regex("^m(\\d+)([^\"]+)"); + Match match = reg.Match(meshMat.Name); + if (match.Success) { - throw new Exception($"Mesh \"{scene.Meshes[i].Name}\" has a material named \"{meshMat.Name.Replace("-material", "")}\" which was not found in materials.json."); + int tmpMatIndex = Int32.Parse(match.Groups[1].Value); + string tmpSanitizedMatName = match.Groups[2].Value; + if (tmpMatIndex >= 0 && tmpMatIndex < m_Materials.Count) + { + string possibleMatName = m_Materials[tmpMatIndex].Name; + if (tmpSanitizedMatName == possibleMatName.Replace("(", "_").Replace(")", "_")) + { + matIndex = tmpMatIndex; + } + } } - test = test.Substring(1); } - for (int j = 0; j < m_Materials.Count; j++) + if (matIndex == -1) { - if (test == materialNamesWithoutParentheses[j]) - { - scene.Meshes[i].MaterialIndex = j; - break; - } + // The material name isn't exported by a new or old version of SuperBMD. + // In this case it might be exported by new SuperBMD, but then sanitized by Blender 2.79 (e.g. "ear(5)" -> "ear_5_"). + matIndex = GetMaterialIndexFromSanitizedMaterialName(meshMat.Name); + } + + if (matIndex == -1) + { + throw new Exception($"Mesh \"{scene.Meshes[i].Name}\" has a material named \"{meshMat.Name}\" which was not found in materials.json."); } + scene.Meshes[i].MaterialIndex = matIndex; + //m_RemapIndices[i] = scene.Meshes[i].MaterialIndex; } } + private void GenerateMapMaterials(Assimp.Scene scene, TEX1 textures, SHP1 shapes) + { + for (int mat_index = 0; mat_index < scene.MaterialCount; mat_index++) + { + var mesh = scene.Meshes.Find(m => m.MaterialIndex == mat_index); + int meshIndex = scene.Meshes.IndexOf(mesh); + + Assimp.Material meshMat = scene.Materials[mesh.MaterialIndex]; + Materials.Material bmdMaterial = new Material(); + + bool hasVtxColor0 = shapes.Shapes[meshIndex].AttributeData.CheckAttribute(GXVertexAttribute.Color0); + int texIndex = -1; + string texName = null; + if (meshMat.HasTextureDiffuse) + { + texName = Path.GetFileNameWithoutExtension(meshMat.TextureDiffuse.FilePath); + texIndex = textures.Textures.IndexOf(textures[texName]); + } + + bmdMaterial.SetUpTevForMaps(meshMat.HasTextureDiffuse, hasVtxColor0, texIndex, texName, meshMat.Opacity); + + m_Materials.Add(bmdMaterial); + m_RemapIndices.Add(mat_index); + m_MaterialNames.Add(meshMat.Name); + } + } + private void LoadFromScene(Assimp.Scene scene, TEX1 textures, SHP1 shapes) { - for (int i = 0; i < scene.MeshCount; i++) + for (int mat_index = 0; mat_index < scene.MaterialCount; mat_index++) { - Assimp.Material meshMat = scene.Materials[scene.Meshes[i].MaterialIndex]; + var mesh = scene.Meshes.Find(m => m.MaterialIndex == mat_index); + int meshIndex = scene.Meshes.IndexOf(mesh); + + Assimp.Material meshMat = scene.Materials[mesh.MaterialIndex]; Materials.Material bmdMaterial = new Material(); - bool hasVtxColor0 = shapes.Shapes[i].AttributeData.CheckAttribute(GXVertexAttribute.Color0); + bool hasVtxColor0 = shapes.Shapes[meshIndex].AttributeData.CheckAttribute(GXVertexAttribute.Color0); int texIndex = -1; string texName = null; if (meshMat.HasTextureDiffuse) @@ -560,7 +637,7 @@ private void LoadFromScene(Assimp.Scene scene, TEX1 textures, SHP1 shapes) bmdMaterial.SetUpTev(meshMat.HasTextureDiffuse, hasVtxColor0, texIndex, texName); m_Materials.Add(bmdMaterial); - m_RemapIndices.Add(i); + m_RemapIndices.Add(mat_index); m_MaterialNames.Add(meshMat.Name); } } @@ -776,9 +853,9 @@ public void FillScene(Assimp.Scene scene, TEX1 textures, string fileDir) { int texIndex = mat.TextureIndices[0]; //texIndex = m_TexRemapBlock[texIndex]; - string texPath = Path.Combine(fileDir, textures[texIndex].Name + ".png"); + string texFilename = textures[texIndex].Name + ".png"; - Assimp.TextureSlot tex = new Assimp.TextureSlot(texPath, Assimp.TextureType.Diffuse, 0, + Assimp.TextureSlot tex = new Assimp.TextureSlot(texFilename, Assimp.TextureType.Diffuse, 0, Assimp.TextureMapping.FromUV, 0, 1.0f, Assimp.TextureOperation.Add, textures[texIndex].WrapS.ToAssImpWrapMode(), textures[texIndex].WrapT.ToAssImpWrapMode(), 0); diff --git a/SuperBMDLib/source/BMD/TEX1.cs b/SuperBMDLib/source/BMD/TEX1.cs index 664aa44..4d93fb2 100644 --- a/SuperBMDLib/source/BMD/TEX1.cs +++ b/SuperBMDLib/source/BMD/TEX1.cs @@ -156,6 +156,12 @@ public void Write(EndianBinaryWriter writer) foreach (BinaryTextureImage img in Textures) { + var firstImgWithName = Textures.Find(x => x.Name == img.Name); + if (img.Format != firstImgWithName.Format) + throw new Exception($"Texture \"{ img.Name }\" has multiple headers with conflicting image formats."); + if (img.NeedsPalettes() && img.PaletteFormat != firstImgWithName.PaletteFormat) + throw new Exception($"Texture \"{ img.Name }\" has multiple headers with conflicting palette formats."); + if (image_palette_Data.ContainsKey(img.Name)) { img.PaletteCount = (ushort)image_palette_Data[img.Name].Item2.Length; diff --git a/SuperBMDLib/source/Materials/BinaryTextureImage.cs b/SuperBMDLib/source/Materials/BinaryTextureImage.cs index 7b81135..b5bbe80 100644 --- a/SuperBMDLib/source/Materials/BinaryTextureImage.cs +++ b/SuperBMDLib/source/Materials/BinaryTextureImage.cs @@ -249,8 +249,8 @@ public void Load(Assimp.TextureSlot texture, string modelDirectory) if (File.Exists(texPath)) { - texData = new Bitmap(texture.FilePath); - Name = Path.GetFileNameWithoutExtension(texture.FilePath); + texData = new Bitmap(texPath); + Name = Path.GetFileNameWithoutExtension(texPath); } else { @@ -261,7 +261,7 @@ public void Load(Assimp.TextureSlot texture, string modelDirectory) if (!File.Exists(texPath)) { Console.WriteLine($"Cannot find texture { fileName }. Using a checkboard texture instead..."); - texData = new Bitmap(SuperBMDLib.Properties.Resources.default_checker); + texData = new Bitmap(SuperBMD.Properties.Resources.default_checker); Name = Path.GetFileNameWithoutExtension(texPath); } else @@ -416,6 +416,17 @@ public void WriteHeader(EndianBinaryWriter writer) writer.Write((int)0); } + public bool NeedsPalettes() + { + if (Format == TextureFormats.C4) + return true; + if (Format == TextureFormats.C8) + return true; + if (Format == TextureFormats.C14X2) + return true; + return false; + } + #region Decoding private static byte[] DecodeData(EndianBinaryReader stream, uint width, uint height, TextureFormats format, Palette imagePalette, PaletteFormats paletteFormat) { diff --git a/SuperBMDLib/source/Materials/Enums/J3DAttenuationFn.cs b/SuperBMDLib/source/Materials/Enums/J3DAttenuationFn.cs index b155ede..674b6dd 100644 --- a/SuperBMDLib/source/Materials/Enums/J3DAttenuationFn.cs +++ b/SuperBMDLib/source/Materials/Enums/J3DAttenuationFn.cs @@ -8,9 +8,9 @@ namespace SuperBMDLib.Materials.Enums { public enum J3DAttenuationFn { - None_0 = 0, - Spec = 1, - None_2 = 2, - Spot = 3 + None_0 = 0, // No attenuation + Spec = 1, // Point light attenuation + None_2 = 2, // Directional light attenuation + Spot = 3 // Spot light attenuation } } diff --git a/SuperBMDLib/source/Materials/Material.cs b/SuperBMDLib/source/Materials/Material.cs index 20328a2..05eda2b 100644 --- a/SuperBMDLib/source/Materials/Material.cs +++ b/SuperBMDLib/source/Materials/Material.cs @@ -140,6 +140,8 @@ public void SetUpTev(bool hasTexture, bool hasVtxColor, int texIndex, string tex AddTevOrder(TexCoordId.TexCoord0, TexMapId.TexMap0, GXColorChannelId.ColorNull); AddTexIndex(texIndex); + TextureNames[0] = texName; + // Texture + Vertex Color if (hasVtxColor) { @@ -184,6 +186,199 @@ public void SetUpTev(bool hasTexture, bool hasVtxColor, int texIndex, string tex AddTevStage(stageParams); } + public void SetUpTevForMaps(bool hasTexture, bool hasVtxColor, int texIndex, string texName, float opacity) + { + // We need both vertex colors and textures to do map lighting. + if (!hasVtxColor || !hasTexture) + { + SetUpTev(hasTexture, hasVtxColor, texIndex, texName); + return; + } + + bool IsTranslucent = opacity < 1.0f; + + Flag = 1; + if (IsTranslucent) + { + Flag = 4; + } + + ChannelControls[0] = null; + AddChannelControl(J3DColorChannelId.Color0, false, ColorSrc.Vertex, LightId.None, DiffuseFn.Clamp, J3DAttenuationFn.Spec, ColorSrc.Register); + AddChannelControl(J3DColorChannelId.Alpha0, false, ColorSrc.Vertex, LightId.None, DiffuseFn.Clamp, J3DAttenuationFn.Spec, ColorSrc.Register); + + AddChannelControl(J3DColorChannelId.Color1, true, ColorSrc.Register, LightId.None, DiffuseFn.Signed, J3DAttenuationFn.None_0, ColorSrc.Register); + AddChannelControl(J3DColorChannelId.Alpha1, false, ColorSrc.Register, LightId.None, DiffuseFn.Clamp, J3DAttenuationFn.None_2, ColorSrc.Register); + + AddChannelControl(J3DColorChannelId.Color1, true, ColorSrc.Register, LightId.None, DiffuseFn.Signed, J3DAttenuationFn.None_0, ColorSrc.Register); + AddChannelControl(J3DColorChannelId.Alpha1, false, ColorSrc.Register, LightId.None, DiffuseFn.Clamp, J3DAttenuationFn.None_2, ColorSrc.Register); + + ColorChannelControlsCount = 1; + TevStageParameters first_stage = new TevStageParameters + { + ColorInA = CombineColorInput.C0, + ColorInB = CombineColorInput.Konst, + ColorInC = CombineColorInput.RasColor, + ColorInD = CombineColorInput.Zero, + + ColorOp = TevOp.Add, + ColorBias = TevBias.Zero, + ColorScale = TevScale.Scale_1, + ColorClamp = true, + ColorRegId = TevRegisterId.TevPrev, + + AlphaInA = CombineAlphaInput.Zero, + AlphaInB = CombineAlphaInput.TexAlpha, + AlphaInC = CombineAlphaInput.RasAlpha, + AlphaInD = CombineAlphaInput.Zero, + + AlphaOp = TevOp.Add, + AlphaBias = TevBias.Zero, + AlphaScale = TevScale.Scale_1, + AlphaClamp = true, + AlphaRegId = TevRegisterId.TevPrev, + }; + + TevStageParameters second_stage = new TevStageParameters + { + ColorInA = CombineColorInput.Zero, + ColorInB = CombineColorInput.TexColor, + ColorInC = CombineColorInput.ColorPrev, + ColorInD = CombineColorInput.Zero, + + ColorOp = TevOp.Add, + ColorBias = TevBias.Zero, + ColorScale = TevScale.Scale_1, + ColorClamp = true, + ColorRegId = TevRegisterId.TevPrev, + + AlphaInA = CombineAlphaInput.Zero, + AlphaInB = CombineAlphaInput.Konst, + AlphaInC = CombineAlphaInput.AlphaPrev, + AlphaInD = CombineAlphaInput.Zero, + + AlphaOp = TevOp.Add, + AlphaBias = TevBias.Zero, + AlphaScale = TevScale.Scale_1, + AlphaClamp = true, + AlphaRegId = TevRegisterId.TevPrev, + }; + + TevStageParameters third_stage = new TevStageParameters + { + ColorInA = CombineColorInput.Zero, + ColorInB = CombineColorInput.Zero, + ColorInC = CombineColorInput.Zero, + ColorInD = CombineColorInput.ColorPrev, + + ColorOp = TevOp.Add, + ColorBias = TevBias.Zero, + ColorScale = TevScale.Scale_1, + ColorClamp = true, + ColorRegId = TevRegisterId.TevPrev, + + AlphaInA = CombineAlphaInput.Zero, + AlphaInB = CombineAlphaInput.Konst, + AlphaInC = CombineAlphaInput.AlphaPrev, + AlphaInD = CombineAlphaInput.Zero, + + AlphaOp = TevOp.Add, + AlphaBias = TevBias.Zero, + AlphaScale = TevScale.Scale_1, + AlphaClamp = true, + AlphaRegId = TevRegisterId.TevPrev, + }; + + AddTevStage(first_stage); + AddTevStage(second_stage); + AddTevStage(third_stage); + + TevOrders[0] = null; + AddTevOrder(TexCoordId.TexCoord0, TexMapId.TexMap0, GXColorChannelId.Color0A0); + AddTevOrder(TexCoordId.TexCoord0, TexMapId.TexMap0, GXColorChannelId.ColorNull); + AddTevOrder(TexCoordId.TexCoord0, TexMapId.TexMap0, GXColorChannelId.ColorNull); + + AddTexGen(TexGenType.Matrix2x4, TexGenSrc.Tex0, Enums.TexMatrix.Identity); + AddTexMatrix(TexGenType.Matrix2x4, 0, new Vector3(0.5f, 0.5f, 0.5f), OpenTK.Vector2.One, 0, OpenTK.Vector2.Zero, OpenTK.Matrix4.Identity); + AddTexIndex(texIndex); + + TextureNames[0] = texName; + + SwapModes[0] = new TevSwapMode(0, 0); + SwapModes[1] = new TevSwapMode(0, 0); + SwapModes[2] = new TevSwapMode(0, 0); + + SwapTables[0] = new TevSwapModeTable(0, 1, 2, 3); + SwapTables[1] = new TevSwapModeTable(0, 1, 2, 3); + SwapTables[2] = new TevSwapModeTable(0, 1, 2, 3); + + ColorSels[0] = KonstColorSel.KCSel_K0; + ColorSels[1] = KonstColorSel.KCSel_K0; + ColorSels[2] = KonstColorSel.KCSel_K0; + + AlphaSels[0] = KonstAlphaSel.KASel_K0_A; + AlphaSels[1] = KonstAlphaSel.KASel_K3_A; + AlphaSels[2] = KonstAlphaSel.KASel_K2_A; + + TevColors[0] = new Color(0, 0, 0, 1); + TevColors[1] = new Color(1, 1, 1, 1); + TevColors[2] = new Color(1, 1, 1, 1); + TevColors[3] = new Color(0, 0, 0, 0); + + KonstColors[0] = new Color(1, 1, 1, 1); + KonstColors[1] = new Color(1, 1, 1, 1); + KonstColors[2] = new Color(1, 1, 1, opacity); + KonstColors[3] = new Color(1, 1, 1, 1); + + MaterialColors[0] = new Color(0.8f, 0.8f, 0.8f, 1.0f); + MaterialColors[1] = new Color(0.8f, 0.8f, 0.8f, 1.0f); + + AmbientColors[0] = new Color(0.196078435f, 0.196078435f, 0.196078435f, 0.196078435f); + AmbientColors[1] = new Color(0, 0, 0, 0); + + ZMode = new ZMode(true, CompareType.LEqual, !IsTranslucent); + + if (IsTranslucent) + { + BMode = new BlendMode(Enums.BlendMode.Blend, BlendModeControl.SrcAlpha, BlendModeControl.InverseSrcAlpha, LogicOp.Copy); + AlphCompare = new AlphaCompare(CompareType.Always, 0, AlphaOp.Or, CompareType.Always, 0); + } + else + { + BMode = new BlendMode(Enums.BlendMode.Blend, BlendModeControl.SrcAlpha, BlendModeControl.InverseSrcAlpha, LogicOp.NoOp); + AlphCompare = new AlphaCompare(CompareType.GEqual, 128, AlphaOp.And, CompareType.LEqual, 255); + } + + CullMode = CullMode.Back; + ZCompLoc = false; + Dither = true; + + FogInfo = new Fog() + { + Type = 2, + Enable = false, + Center = 320, + StartZ = 10000.0f, + EndZ = 20000.0f, + NearZ = 5.0f, + FarZ = 50000.0f, + Color = new Color(1, 1, 1, 1), + RangeAdjustmentTable = new float[] + { + 1.0f, + 1.00390625f, + 1.01171875f, + 1.0234375f, + 1.03515625f, + 1.05078125f, + 1.0703125f, + 1.08984375f, + 1.11328125f, + 1.140625f + } + }; + } + public void AddChannelControl(J3DColorChannelId id, bool enable, ColorSrc MatSrcColor, LightId litId, DiffuseFn diffuse, J3DAttenuationFn atten, ColorSrc ambSrcColor) { ChannelControl control = new ChannelControl diff --git a/SuperBMDLib/source/Materials/Mdl/BPCommand.cs b/SuperBMDLib/source/Materials/Mdl/BPCommand.cs index bec02fa..0a9a2a2 100644 --- a/SuperBMDLib/source/Materials/Mdl/BPCommand.cs +++ b/SuperBMDLib/source/Materials/Mdl/BPCommand.cs @@ -26,6 +26,8 @@ public BPCommand(BPRegister register, int value) /// Number of bits to modify public void SetBits(int value, int offset, int size) { + if (value < 0 || value >= (1 << size)) + throw new Exception($"Cannot insert value {value} with shift {offset} and size {size} into BP register 0x{((int)Register).ToString("X2")}: Not enough bits."); Value = Value & (int)(~((0xFFFFFFFF >> (32 - size)) << offset) & 0xFFFFFFFF) | (value << offset); } diff --git a/SuperBMDLib/source/Materials/Mdl/MdlEntry.cs b/SuperBMDLib/source/Materials/Mdl/MdlEntry.cs index d15fe5c..580d7e9 100644 --- a/SuperBMDLib/source/Materials/Mdl/MdlEntry.cs +++ b/SuperBMDLib/source/Materials/Mdl/MdlEntry.cs @@ -466,7 +466,7 @@ private void GenerateScaleCommands(Material mat) continue; } - XFCommand posMatricesCommand = new XFCommand((XFRegister)(0x0078 + i * 12)); + XFCommand posMatricesCommand = new XFCommand(XFRegister.SETTEXMTX0 + i * 12); int scaleX = BitConverter.ToInt32(BitConverter.GetBytes(matrix.Scale.X), 0); int scaleY = BitConverter.ToInt32(BitConverter.GetBytes(matrix.Scale.Y), 0); @@ -523,31 +523,31 @@ private void GenerateTexGenXFCommands(Material mat) case TexGenType.Bump5: case TexGenType.Bump6: case TexGenType.Bump7: - texGenArg.SetBits(1, 4, 2); + texGenArg.SetBits(1, 4, 3); texGenArg.SetBits(texgen.Source - TexGenSrc.Tex0, 12, 3); texGenArg.SetBits(texgen.Type - TexGenType.Bump0, 15, 3); - texGenArg.SetBits(5, 7, 3); + texGenArg.SetBits(5, 7, 5); break; case TexGenType.SRTG: - texGenArg.SetBits(2, 7, 3); + texGenArg.SetBits(2, 7, 5); if (texgen.Source == TexGenSrc.Color0) { - texGenArg.SetBits(2, 4, 2); + texGenArg.SetBits(2, 4, 3); } else if (texgen.Source == TexGenSrc.Color1) { - texGenArg.SetBits(3, 4, 2); + texGenArg.SetBits(3, 4, 3); } break; default: - texGenArg.SetBits(0, 4, 2); + texGenArg.SetBits(0, 4, 3); if (texgen.Source == TexGenSrc.Position || texgen.Source == TexGenSrc.Normal) { - texGenArg.SetBits((int)texgen.Source, 7, 3); + texGenArg.SetBits((int)texgen.Source, 7, 5); } else { - texGenArg.SetBits((int)texgen.Source + 1, 7, 3); + texGenArg.SetBits((int)texgen.Source + 1, 7, 5); } break; } @@ -640,7 +640,7 @@ private void GenerateChannelControlCommands(Material mat) chanControlArg.SetBits((int)chanCtrl.MaterialSrcColor, 0, 1); chanControlArg.SetFlag(chanCtrl.Enable, 1); - chanControlArg.SetBits((int)chanCtrl.LitMask, 2, 4); + chanControlArg.SetBits((int)chanCtrl.LitMask & 0x0F, 2, 4); chanControlArg.SetBits((int)chanCtrl.AmbientSrcColor, 6, 1); if (chanCtrl.AttenuationFunction == J3DAttenuationFn.None_0) diff --git a/SuperBMDLib/source/Materials/Mdl/XFCommand.cs b/SuperBMDLib/source/Materials/Mdl/XFCommand.cs index d62909a..ca24c1a 100644 --- a/SuperBMDLib/source/Materials/Mdl/XFCommand.cs +++ b/SuperBMDLib/source/Materials/Mdl/XFCommand.cs @@ -51,6 +51,8 @@ public XFCommandArgument(int value) /// Number of bits to modify public void SetBits(int value, int offset, int size) { + if (value < 0 || value >= (1<> (32 - size)) << offset) & 0xFFFFFFFF) | (value << offset); } diff --git a/SuperBMDLib/source/Materials/Mdl/XFRegisters.cs b/SuperBMDLib/source/Materials/Mdl/XFRegisters.cs index e9ae6a0..9b0c9fa 100644 --- a/SuperBMDLib/source/Materials/Mdl/XFRegisters.cs +++ b/SuperBMDLib/source/Materials/Mdl/XFRegisters.cs @@ -8,6 +8,17 @@ namespace SuperBMDLib.Materials.Mdl { public enum XFRegister : int { + SETTEXMTX0 = 0x0078, + SETTEXMTX1 = 0x0084, + SETTEXMTX2 = 0x0090, + SETTEXMTX3 = 0x009C, + SETTEXMTX4 = 0x00A8, + SETTEXMTX5 = 0x00B4, + SETTEXMTX6 = 0x00C0, + SETTEXMTX7 = 0x00CC, + SETTEXMTX8 = 0x00D8, + SETTEXMTX9 = 0x00E4, + SETNUMCHAN = 0x1009, SETCHAN0_AMBCOLOR = 0x100A, SETCHAN0_MATCOLOR = 0x100C, diff --git a/SuperBMDLib/source/Model.cs b/SuperBMDLib/source/Model.cs index 24fda01..44b001c 100644 --- a/SuperBMDLib/source/Model.cs +++ b/SuperBMDLib/source/Model.cs @@ -53,7 +53,36 @@ public static Model Load(Arguments args) // effectively disabling tri stripping postprocess = Assimp.PostProcessSteps.Triangulate; } - Assimp.Scene aiScene = cont.ImportFile(args.input_path, postprocess); + + // Manually fix some aspects of the .dae file that Assimp.NET cannot properly import. + StreamReader dae = File.OpenText(args.input_path); + StreamWriter fixed_dae = new StreamWriter(args.input_path + ".tmp"); + + while (!dae.EndOfStream) + { + string line = dae.ReadLine(); + + if (line == " ") + { + // Alpha component of a vertex color. + // Skip writing this, since the old version of Assimp used by Assimp.NET cannot read it properly. + // Instead of reading the alpha channel, it sets the red channel to the alpha value, and then sets the alpha channel to 1. + // Assimp issue: https://github.com/assimp/assimp/issues/1417 + } + else + { + fixed_dae.WriteLine(line); + fixed_dae.Flush(); + } + } + + fixed_dae.Close(); + dae.Close(); + + Assimp.Scene aiScene = cont.ImportFile(args.input_path + ".tmp", postprocess); + + // Delete the temporary fixed file after it has been imported. + File.Delete(args.input_path + ".tmp"); output = new Model(aiScene, args); } @@ -89,7 +118,6 @@ public Model(EndianBinaryReader reader, Arguments args) SkipMDL3(reader); Textures = new TEX1(reader, (int)reader.BaseStream.Position); Materials.SetTextureNames(Textures); - Materials.DumpMaterials(Path.GetDirectoryName(args.input_path)); foreach (Geometry.Shape shape in Shapes.Shapes) packetCount += shape.Packets.Count; @@ -108,9 +136,14 @@ private void SkipMDL3(EndianBinaryReader reader) public Model(Scene scene, Arguments args) { - EnsureOneMaterialPerMesh(scene); + EnsureOneMaterialPerMeshAndOneMeshPerMaterial(scene); SortMeshesByObjectNames(scene); + if (args.rotate_model) + { + RotateModel(scene); + } + VertexData = new VTX1(scene); Joints = new JNT1(scene, VertexData); Textures = new TEX1(scene, args); @@ -199,6 +232,7 @@ public void ExportAssImp(string fileName, string modelType, ExportSettings setti Shapes.FillScene(outScene, VertexData.Attributes, Joints.FlatSkeleton, SkinningEnvelopes.InverseBindMatrices); Scenegraph.FillScene(outScene, Joints.FlatSkeleton, settings.UseSkeletonRoot); Scenegraph.CorrectMaterialIndices(outScene, Materials); + Materials.DumpMaterials(outDir); Textures.DumpTextures(outDir); @@ -233,20 +267,44 @@ public void ExportAssImp(string fileName, string modelType, ExportSettings setti } else if (line.Contains("$"); + Match match = reg.Match(line); - if (Joints.FlatSkeleton.Exists(x => x.Name == name)) + if (match.Success) { - string jointLine = line.Replace(">", $" sid=\"{ name }\" type=\"JOINT\">"); - test.WriteLine(jointLine); - test.Flush(); + string indentation = match.Groups[1].Value; + string joint_name = match.Groups[2].Value; + if (Joints.FlatSkeleton.Exists(x => x.Name == joint_name)) + { + string jointLine = indentation + $""; + test.WriteLine(jointLine); + } else + { + test.WriteLine(line); + } + } else + { + test.WriteLine(line); + } + test.Flush(); + } + else if (line.Contains("$"); + Match match = reg.Match(line); + if (match.Success) + { + string mat_name_sanitized = match.Groups[1].Value; + int mat_index = Materials.GetMaterialIndexFromSanitizedMaterialName(mat_name_sanitized); + string mat_name = Materials.m_Materials[mat_index].Name; + string matLine = $" "; + test.WriteLine(matLine); } else { test.WriteLine(line); - test.Flush(); } + test.Flush(); } else if (line.Contains("")) { @@ -258,7 +316,7 @@ public void ExportAssImp(string fileName, string modelType, ExportSettings setti test.WriteLine(" #skeleton_root"); test.WriteLine(" "); test.WriteLine(" "); - test.WriteLine($" "); + test.WriteLine($" "); test.WriteLine(" "); test.WriteLine(" "); test.WriteLine(" "); @@ -270,12 +328,6 @@ public void ExportAssImp(string fileName, string modelType, ExportSettings setti test.WriteLine(line); test.Flush(); } - else if (line.Contains("", ""); - test.WriteLine(matLine); - test.Flush(); - } else { test.WriteLine(line); @@ -488,12 +540,22 @@ private void WriteVertexWeightsToStream(Mesh mesh, StreamWriter writer) private void RemoveDuplicateVertices(Mesh mesh) { - // Calculate which vertices are duplicates (based on their position, texture coordinates, and normals). - List>> uniqueVertInfos = new List>>(); + // Calculate which vertices are duplicates (based on their position, texture coordinates, normals, and color). + List< + Tuple, List> + > uniqueVertInfos = new List< + Tuple, List> + >(); int[] replaceVertexIDs = new int[mesh.Vertices.Count]; bool[] vertexIsUnique = new bool[mesh.Vertices.Count]; for (var origVertexID = 0; origVertexID < mesh.Vertices.Count; origVertexID++) { + var colorsForVert = new List(); + for (var i = 0; i < mesh.VertexColorChannelCount; i++) + { + colorsForVert.Add(mesh.VertexColorChannels[i][origVertexID]); + } + var coordsForVert = new List(); for (var i = 0; i < mesh.TextureCoordinateChannelCount; i++) { @@ -509,14 +571,18 @@ private void RemoveDuplicateVertices(Mesh mesh) normal = null; } - var vertInfo = new Tuple>(mesh.Vertices[origVertexID], normal, coordsForVert); + var vertInfo = new Tuple< + Vector3D, Vector3D?, List, List + >(mesh.Vertices[origVertexID], normal, coordsForVert, colorsForVert); // Determine if this vertex is a duplicate of a previously encountered vertex or not and if it is keep track of the new index var duplicateVertexIndex = -1; for (var i = 0; i < uniqueVertInfos.Count; i++) { - Tuple> otherVertInfo = uniqueVertInfos[i]; - if (CheckVertInfosAreDuplicates(vertInfo.Item1, vertInfo.Item2, vertInfo.Item3, otherVertInfo.Item1, otherVertInfo.Item2, otherVertInfo.Item3)) + Tuple, List> otherVertInfo = uniqueVertInfos[i]; + if (CheckVertInfosAreDuplicates( + vertInfo.Item1, vertInfo.Item2, vertInfo.Item3, vertInfo.Item4, + otherVertInfo.Item1, otherVertInfo.Item2, otherVertInfo.Item3, otherVertInfo.Item4)) { duplicateVertexIndex = i; break; @@ -536,16 +602,21 @@ private void RemoveDuplicateVertices(Mesh mesh) } } - // Remove duplicate vertices, normals, and texture coordinates. + // Remove duplicate vertices, normals, texture coordinates, and colors. mesh.Vertices.Clear(); mesh.Normals.Clear(); - // Need to preserve the channel count since it gets set to 0 when clearing all the channels + // Need to preserve the channel counts since they gets set to 0 when clearing all the channels int origTexCoordChannelCount = mesh.TextureCoordinateChannelCount; for (var i = 0; i < origTexCoordChannelCount; i++) { mesh.TextureCoordinateChannels[i].Clear(); } - foreach (Tuple> vertInfo in uniqueVertInfos) + int origColorChannelCount = mesh.VertexColorChannelCount; + for (var i = 0; i < origColorChannelCount; i++) + { + mesh.VertexColorChannels[i].Clear(); + } + foreach (Tuple, List> vertInfo in uniqueVertInfos) { mesh.Vertices.Add(vertInfo.Item1); if (vertInfo.Item2 != null) @@ -557,6 +628,11 @@ private void RemoveDuplicateVertices(Mesh mesh) var coord = vertInfo.Item3[i]; mesh.TextureCoordinateChannels[i].Add(coord); } + for (var i = 0; i < origColorChannelCount; i++) + { + var color = vertInfo.Item4[i]; + mesh.VertexColorChannels[i].Add(color); + } } // Update vertex indices for the faces. @@ -587,7 +663,8 @@ private void RemoveDuplicateVertices(Mesh mesh) } } - private bool CheckVertInfosAreDuplicates(Vector3D vert1, Vector3D? norm1, List vert1TexCoords, Vector3D vert2, Vector3D? norm2, List vert2TexCoords) + private bool CheckVertInfosAreDuplicates(Vector3D vert1, Vector3D? norm1, List vert1TexCoords, List vert1Colors, + Vector3D vert2, Vector3D? norm2, List vert2TexCoords, List vert2Colors) { if (vert1 != vert2) { @@ -610,6 +687,15 @@ private bool CheckVertInfosAreDuplicates(Vector3D vert1, Vector3D? norm1, List meshNames, ref } } - private void EnsureOneMaterialPerMesh(Scene scene) + private void EnsureOneMaterialPerMeshAndOneMeshPerMaterial(Scene scene) { + List usedMaterialIndexes = new List(); foreach (Mesh mesh1 in scene.Meshes) { + if (mesh1.Faces.Any(x => x.Indices.Count < 3)) + { + // Loose vertex/edge. These are handled weirdly by Assimp and put in separate meshes. + // We don't want a misleading error message here, so skip it in this function, and raise a different error elsewhere. + continue; + } + foreach (Mesh mesh2 in scene.Meshes) { if (mesh1.Name == mesh2.Name && mesh1.MaterialIndex != mesh2.MaterialIndex) @@ -680,6 +774,59 @@ private void EnsureOneMaterialPerMesh(Scene scene) throw new Exception($"Mesh \"{mesh1.Name}\" has more than one material assigned to it. Currently only one material per mesh is supported."); } } + + if (usedMaterialIndexes.Contains(mesh1.MaterialIndex)) + { + throw new Exception($"Material \"{scene.Materials[mesh1.MaterialIndex].Name}\" is used by more than one mesh. Each mesh must have a unique material. Try merging meshes that share a material."); + } + usedMaterialIndexes.Add(mesh1.MaterialIndex); + } + } + + private void RotateModel(Scene scene) + { + Assimp.Node root = null; + for (int i = 0; i < scene.RootNode.ChildCount; i++) + { + if (scene.RootNode.Children[i].Name.ToLowerInvariant() == "skeleton_root") + { + if (scene.RootNode.Children[i].ChildCount == 0) + { + throw new System.Exception("skeleton_root has no children! If you are making a rigged model, make sure skeleton_root contains the root of your skeleton."); + } + root = scene.RootNode.Children[i].Children[0]; + break; + } + } + + Matrix4x4 rotate = Matrix4x4.FromRotationX((float)(-(1 / 2.0) * Math.PI)); + Matrix4x4 rotateinv = rotate; + rotateinv.Inverse(); + + + foreach (Mesh mesh in scene.Meshes) + { + if (root != null) + { + foreach (Assimp.Bone bone in mesh.Bones) + { + bone.OffsetMatrix = rotateinv * bone.OffsetMatrix; + } + } + + for (int i = 0; i < mesh.VertexCount; i++) + { + Vector3D vertex = mesh.Vertices[i]; + vertex.Set(vertex.X, vertex.Z, -vertex.Y); + mesh.Vertices[i] = vertex; + } + for (int i = 0; i < mesh.Normals.Count; i++) + { + Vector3D norm = mesh.Normals[i]; + norm.Set(norm.X, norm.Z, -norm.Y); + + mesh.Normals[i] = norm; + } } } } diff --git a/lib/OpenTK.GLControl.dll b/lib/OpenTK.GLControl.dll new file mode 100644 index 0000000..0f8821b Binary files /dev/null and b/lib/OpenTK.GLControl.dll differ diff --git a/lib/OpenTK.dll b/lib/OpenTK.dll new file mode 100644 index 0000000..da13e8b Binary files /dev/null and b/lib/OpenTK.dll differ