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