Support Mega Man 11 and MHGU (Switch) model/texture loading.

This commit is contained in:
KillzXGaming 2022-10-08 15:48:40 -04:00
parent 38bd5f6bca
commit 02273a7501
13 changed files with 21645 additions and 0 deletions

View file

@ -0,0 +1,59 @@
using System.Text;
namespace CafeLibrary.M2
{
class CRC32Hash
{
private static uint[] Table = new uint[256];
private static bool IsInitialized = false;
public static void Initialize()
{
uint Polynomial = 0xedb88320;
for (uint Value, i = 0; i < Table.Length; i++)
{
Value = i;
for (int j = 8; j > 0; --j)
{
Value = (Value >> 1) ^ (Polynomial * (Value & 1));
}
Table[i] = Value;
}
IsInitialized = true;
}
public static uint Hash(byte[] Data)
{
if (!IsInitialized) Initialize();
uint CRC = 0xffffffff;
for (int i = 0; i < Data.Length; i++)
{
CRC = (CRC >> 8) ^ Table[(CRC & 0xff) ^ Data[i]];
}
return CRC;
}
public static uint Hash(string Text)
{
return Hash(Encoding.ASCII.GetBytes(Text));
}
public static uint HashNegated(byte[] Data)
{
return ~Hash(Data);
}
public static uint HashNegated(string Text)
{
return ~Hash(Text);
}
}
}

View file

@ -0,0 +1,66 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Toolbox.Library.Security.Cryptography;
namespace CafeLibrary.M2
{
public class MT_Globals
{
private static Dictionary<uint, Shader.AttributeGroup> attGroups;
public static Dictionary<uint, Shader.AttributeGroup> AttributeGroups
{
get
{
if (attGroups == null)
Load();
return attGroups;
}
}
public static void Load()
{
attGroups = new Dictionary<uint, Shader.AttributeGroup>();
foreach (var file in Directory.GetFiles(Path.Combine("Lib", "MTVertexFormats")))
{
LoadPresets(file);
}
}
static void LoadPresets(string filePath)
{
var shader = JsonConvert.DeserializeObject<Shader>(File.ReadAllText(filePath));
foreach (var item in shader.AttributeGroups)
if (!attGroups.ContainsKey(item.Key))
attGroups.Add(item.Key, item.Value);
}
public static uint Hash(string Text) {
return CRC32Hash.Hash(Text);
}
public static uint jamcrc(string name)
{
var crc = Crc32.Compute(name);
return (crc ^ 0xffffffff) & 0x7fffffff;
}
public static uint mfxcrc(string name, uint index)
{
var crc = Crc32.Compute(name);
return (((crc ^ 0xffffffff) & 0x7fffffff) << 12) + index;
}
public static uint crc32(string name)
{
var crc = Crc32.Compute(name);
return crc;
}
}
}

View file

@ -0,0 +1,277 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Toolbox;
using System.Windows.Forms;
using Toolbox.Library;
using Toolbox.Library.IO;
using Toolbox.Library.Forms;
using Toolbox.Library.Rendering;
using OpenTK;
using CafeLibrary.M2;
using System.IO;
namespace FirstPlugin
{
public class MT_Model : TreeNodeFile, IFileFormat, IExportableModel
{
public FileType FileType { get; set; } = FileType.Model;
public bool CanSave { get; set; }
public string[] Description { get; set; } = new string[] { "MT Model" };
public string[] Extension { get; set; } = new string[] { "*.mod" };
public string FileName { get; set; }
public string FilePath { get; set; }
public IFileInfo IFileInfo { get; set; }
public bool Identify(System.IO.Stream stream)
{
using (var reader = new FileReader(stream, true)) {
return reader.CheckSignature(3, "MOD");
}
}
public Type[] Types
{
get
{
List<Type> types = new List<Type>();
return types.ToArray();
}
}
//Check for the viewport in the object editor
//This is attached to it to load multiple file formats within the object editor to the viewer
Viewport viewport
{
get
{
var editor = LibraryGUI.GetObjectEditor();
return editor.GetViewport();
}
set
{
var editor = LibraryGUI.GetObjectEditor();
editor.LoadViewport(value);
}
}
bool DrawablesLoaded = false;
public override void OnClick(TreeView treeView)
{
//Make sure opengl is enabled
if (Runtime.UseOpenGL)
{
//Open the viewport
if (viewport == null)
{
viewport = new Viewport(ObjectEditor.GetDrawableContainers());
viewport.Dock = DockStyle.Fill;
}
//Make sure to load the drawables only once so set it to true!
if (!DrawablesLoaded)
{
ObjectEditor.AddContainer(DrawableContainer);
DrawablesLoaded = true;
}
//Reload which drawable to display
viewport.ReloadDrawables(DrawableContainer);
LibraryGUI.LoadEditor(viewport);
viewport.Text = Text;
}
}
public IEnumerable<STGenericObject> ExportableMeshes => Model.Objects;
public IEnumerable<STGenericMaterial> ExportableMaterials => Model.Materials;
public IEnumerable<STGenericTexture> ExportableTextures => TextureList;
public STSkeleton ExportableSkeleton => Model.GenericSkeleton;
public List<MT_TEX> TextureList { get; set; } = new List<MT_TEX>();
public MT_Renderer Renderer;
public DrawableContainer DrawableContainer = new DrawableContainer();
public Model ModelData { get; set; }
public STGenericModel Model;
public void Load(System.IO.Stream stream)
{
Text = FileName;
Renderer = new MT_Renderer();
ModelData = new Model(stream);
ModelData.LoadMaterials(this.FilePath.Replace(".mod", ".mrl"));
ModelData.LoadTextures(Path.GetDirectoryName(this.FilePath));
TextureList = ModelData.Textures;
TreeNode meshFolder = new TreeNode("Meshes");
Nodes.Add(meshFolder);
TreeNode texFolder = new STTextureFolder("Textures");
Nodes.Add(texFolder);
TreeNode skeletonFolder = new STTextureFolder("Skeleton");
Nodes.Add(skeletonFolder);
foreach (var tex in ModelData.Textures)
texFolder.Nodes.Add(tex);
Model = ToGeneric();
foreach (GenericRenderedObject mesh in Model.Objects)
{
Renderer.Meshes.Add(mesh);
meshFolder.Nodes.Add(mesh);
}
Renderer.Skeleton = Model.GenericSkeleton;
foreach (var bone in Model.GenericSkeleton.bones)
{
if (bone.Parent == null)
skeletonFolder.Nodes.Add(bone);
}
foreach (var tex in TextureList)
Renderer.Textures.Add(tex);
DrawableContainer = new DrawableContainer();
DrawableContainer.Name = FileName;
DrawableContainer.Drawables.Add(Renderer);
DrawableContainer.Drawables.Add(Model.GenericSkeleton);
}
public void Unload()
{
}
public void Save(System.IO.Stream stream)
{
}
public STGenericModel ToGeneric()
{
var model = new STGenericModel();
model.Name = this.FileName;
model.GenericSkeleton = new STSkeleton();
foreach (var bn in ModelData.Bones)
{
model.GenericSkeleton.bones.Add(new STBone(model.GenericSkeleton)
{
Text = $"Bone_{bn.ID}",
Position = bn.Position,
Rotation = bn.LocalMatrix.ExtractRotation(),
Scale = bn.LocalMatrix.ExtractScale(),
parentIndex = bn.ParentIndex,
});
}
model.GenericSkeleton.reset();
model.GenericSkeleton.update();
List<STGenericMaterial> materials = new List<STGenericMaterial>();
for (int i = 0; i < ModelData.MaterialNames.Length; i++)
{
STGenericMaterial mat = new STGenericMaterial();
mat.Text = ModelData.MaterialNames[i];
var hash = MT_Globals.Hash(mat.Text);
if (ModelData.MaterialList != null && ModelData.MaterialList.Materials.ContainsKey(hash))
{
var m = ModelData.MaterialList.Materials[hash];
if (m.DiffuseMap != null)
{
string tex = Path.GetFileName(m.DiffuseMap.Name);
mat.TextureMaps.Add(new STGenericMatTexture()
{
Type = STGenericMatTexture.TextureType.Diffuse,
Name = tex,
});
}
if (m.NormalMap != null)
{
string tex = Path.GetFileName(m.NormalMap.Name);
mat.TextureMaps.Add(new STGenericMatTexture()
{
Type = STGenericMatTexture.TextureType.Normal,
Name = tex,
});
}
if (m.SpecularMap != null)
{
string tex = Path.GetFileName(m.SpecularMap.Name);
mat.TextureMaps.Add(new STGenericMatTexture()
{
Type = STGenericMatTexture.TextureType.Specular,
Name = tex,
});
}
}
materials.Add(mat);
}
model.Materials = materials;
List<STGenericObject> meshes = new List<STGenericObject>();
foreach (var mesh in ModelData.Meshes)
{
if (mesh.Vertices.Length == 0)
continue;
var attributeGroup = MT_Globals.AttributeGroups[mesh.VertexFormatHash];
GenericRenderedObject genericMesh = new GenericRenderedObject();
meshes.Add(genericMesh);
genericMesh.ImageKey = "mesh";
genericMesh.SelectedImageKey = "mesh";
genericMesh.Text = $"Mesh_{meshes.Count - 1}";
foreach (var vert in mesh.Vertices)
{
var genericVertex = new Vertex()
{
pos = new Vector3(vert.Position.X, vert.Position.Y, vert.Position.Z),
nrm = new Vector3(vert.Normal.X, vert.Normal.Y, vert.Normal.Z),
col = new Vector4(vert.Color.X, vert.Color.Y, vert.Color.Z, vert.Color.W),
};
if (vert.TexCoords?.Length > 0) genericVertex.uv0 = vert.TexCoords[0];
if (vert.TexCoords?.Length > 1) genericVertex.uv1 = vert.TexCoords[1];
if (vert.TexCoords?.Length > 2) genericVertex.uv2 = vert.TexCoords[2];
foreach (var boneID in vert.BoneIndices)
{
genericVertex.boneIds.Add(boneID);
}
foreach (var boneW in vert.BoneWeights)
genericVertex.boneWeights.Add(boneW);
genericMesh.vertices.Add(genericVertex);
}
STGenericPolygonGroup poly = new STGenericPolygonGroup();
poly.PrimativeType = STPrimitiveType.Triangles;
genericMesh.PolygonGroups.Add(poly);
poly.Material = materials[(int)mesh.MaterialID];
foreach (var ind in mesh.Indices)
poly.faces.Add(ind);
genericMesh.CalculateNormals();
}
model.Objects = meshes.ToList();
return model;
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Toolbox.Library.Rendering;
using GL_EditorFramework.GL_Core;
using GL_EditorFramework.Interfaces;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using Toolbox.Library;
using FirstPlugin;
namespace FirstPlugin
{
public class MT_Renderer : GenericModelRenderer
{
public override void OnRender(GLControl control)
{
}
public override void SetRenderData(STGenericMaterial mat, ShaderProgram shader, STGenericObject m)
{
shader.SetBoolToInt("NoSkinning", Skeleton.bones.Count == 0);
}
}
}

View file

@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using Toolbox;
using System.Windows.Forms;
using Toolbox.Library;
using Toolbox.Library.Forms;
using Toolbox.Library.IO;
namespace FirstPlugin
{
public class MT_TEX : STGenericTexture, IFileFormat, IContextMenuNode
{
public FileType FileType { get; set; } = FileType.Image;
public bool CanSave { get; set; }
public string[] Description { get; set; } = new string[] { "TEX (3DS)" };
public string[] Extension { get; set; } = new string[] { "*.tex" };
public string FileName { get; set; }
public string FilePath { get; set; }
public IFileInfo IFileInfo { get; set; }
public bool Identify(System.IO.Stream stream)
{
using (var reader = new FileReader(stream, true)) {
return reader.CheckSignature(3, "TEX");
}
}
public Type[] Types
{
get
{
List<Type> types = new List<Type>();
return types.ToArray();
}
}
public override bool CanEdit { get; set; } = false;
public override TEX_FORMAT[] SupportedFormats
{
get
{
return new TEX_FORMAT[]
{
TEX_FORMAT.B5G6R5_UNORM,
TEX_FORMAT.R8G8_UNORM,
TEX_FORMAT.B5G5R5A1_UNORM,
TEX_FORMAT.B4G4R4A4_UNORM,
TEX_FORMAT.LA8,
TEX_FORMAT.HIL08,
TEX_FORMAT.L8,
TEX_FORMAT.A8_UNORM,
TEX_FORMAT.LA4,
TEX_FORMAT.A4,
TEX_FORMAT.ETC1_UNORM,
TEX_FORMAT.ETC1_A4,
};
}
}
public byte[] ImageData;
public override void OnClick(TreeView treeView)
{
UpdateEditor();
}
private void UpdateEditor()
{
ImageEditorBase editor = (ImageEditorBase)LibraryGUI.GetActiveContent(typeof(ImageEditorBase));
if (editor == null)
{
editor = new ImageEditorBase();
editor.Dock = DockStyle.Fill;
LibraryGUI.LoadEditor(editor);
}
editor.LoadProperties(GenericProperties);
editor.LoadImage(this);
}
public ToolStripItem[] GetContextMenuItems()
{
List<ToolStripItem> Items = new List<ToolStripItem>();
Items.Add(new ToolStripMenuItem("Save", null, SaveAction, Keys.Control | Keys.S));
Items.AddRange(base.GetContextMenuItems());
return Items.ToArray();
}
private void SaveAction(object sender, EventArgs args)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = Utils.GetAllFilters(this);
sfd.FileName = FileName;
if (sfd.ShowDialog() == DialogResult.OK)
{
STFileSaver.SaveFileFormat(this, sfd.FileName);
}
}
public void FillEditor(UserControl control)
{
Properties prop = new Properties();
prop.Width = Width;
prop.Height = Height;
prop.Depth = Depth;
prop.MipCount = MipCount;
prop.ArrayCount = ArrayCount;
prop.ImageSize = (uint)ImageData.Length;
prop.Format = Format;
((ImageEditorBase)control).LoadImage(this);
((ImageEditorBase)control).LoadProperties(prop);
}
public void Load(System.IO.Stream stream)
{
PlatformSwizzle = PlatformSwizzle.Platform_Switch;
CanSave = true;
CanReplace = true;
Text = FileName;
this.ImageKey = "texture";
this.SelectedImageKey = "texture";
using (var reader = new FileReader(stream))
{
reader.ReadInt32(); //magic
int block1 = reader.ReadInt32();
uint block2 = reader.ReadUInt32();
uint block3 = reader.ReadUInt32();
int Version = (block1 >> 0) & 0xfff;
int Shift = (block1 >> 24) & 0xf;
Width = (block2 >> 6) & 0x1fff;
Height = (block2 >> 19) & 0x1fff;
uint format = (block3 >> 8) & 0xff;
uint Aspect = (block3 >> 16) & 0x1fff;
MipCount = (block2 & 0x3F);
uint imageSize = reader.ReadUInt32();
reader.ReadUInt32s((int)MipCount); //mip offsets
ImageData = reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position));
Format = FormatList[format];
}
}
public void Save(System.IO.Stream stream)
{
using (var writer = new FileWriter(stream, true))
{
}
}
public override void Replace(string FileName)
{
}
public override void SetImageData(System.Drawing.Bitmap bitmap, int ArrayLevel)
{
}
public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0, int DepthLevel = 0)
{
return TegraX1Swizzle.GetImageData(this, ImageData, ArrayLevel, MipLevel, DepthLevel);
}
Dictionary<uint, TEX_FORMAT> FormatList = new Dictionary<uint, TEX_FORMAT>()
{
{ 7, TEX_FORMAT.R8G8B8A8_UNORM_SRGB },
{ 19, TEX_FORMAT.BC1_UNORM_SRGB },
{ 20, TEX_FORMAT.BC2_UNORM_SRGB },
{ 23, TEX_FORMAT.BC3_UNORM_SRGB },
{ 25, TEX_FORMAT.BC4_UNORM },
{ 31, TEX_FORMAT.BC5_UNORM },
{ 33, TEX_FORMAT.BC1_UNORM },
{ 39, TEX_FORMAT.BC3_UNORM },
{ 42, TEX_FORMAT.BC3_UNORM },
{ 48, TEX_FORMAT.BC7_UNORM },
};
}
}

View file

@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using Toolbox.Library;
using Toolbox.Library.IO;
namespace CafeLibrary.M2
{
public class MaterialList
{
public uint Version { get; set; }
public Dictionary<uint, Material> Materials = new Dictionary<uint, Material>();
public List<Texture> Textures = new List<Texture>();
public uint ShaderHash { get; set; }
private bool _hasUint64Offsets;
public MaterialList(Stream stream, bool hasUint64Offsets)
{
_hasUint64Offsets = hasUint64Offsets;
using (var reader = new FileReader(stream))
{
Read(reader);
}
}
public void Read(FileReader reader)
{
reader.ReadUInt32(); //version
Version = reader.ReadUInt32();
uint materialCount = reader.ReadUInt32();
uint textureCount = reader.ReadUInt32();
ShaderHash = ReadUint(reader);
uint textureOffset = ReadUint(reader);
uint materialOffset = ReadUint(reader);
reader.SeekBegin(textureOffset);
for (int i = 0; i < textureCount; i++)
{
Texture tex = new Texture();
tex.Flags = ReadUint(reader);
ReadUint(reader); //0
ReadUint(reader); //0
long pos = reader.Position;
tex.Name = reader.ReadZeroTerminatedString();
reader.SeekBegin(pos + 64);
Textures.Add(tex);
}
reader.SeekBegin(materialOffset);
for (int i = 0; i < materialCount; i++)
{
Material material = new Material();
uint id = ReadUint(reader);
material.NameHash = reader.ReadUInt32();
uint paramSize = reader.ReadUInt32();
material.ShaderHash = reader.ReadUInt32();
uint skinningHash = reader.ReadUInt32();
reader.ReadUInt16();
reader.ReadByte();
reader.ReadBytes(9);
reader.ReadByte();
reader.ReadBytes(19);
uint paramOffset = ReadUint(reader);
ReadUint(reader);
Materials.Add(material.NameHash, material);
using (reader.TemporarySeek(paramOffset, SeekOrigin.Begin))
{
if (_hasUint64Offsets)
{
ulong[] parameters = reader.ReadUInt64s((int)paramSize / 8);
for (int j = 0; j < parameters.Length; j++)
{
if (parameters[j].ToString().StartsWith("34397848")) //tAlbedoMap
material.DiffuseMap = this.Textures[(int)parameters[j - 1] - 1];
if (parameters[j].ToString().StartsWith("577110")) //tNormalMap
material.NormalMap = this.Textures[(int)parameters[j - 1] - 1];
if (parameters[j].ToString().StartsWith("24862")) //tSpecularMap
material.SpecularMap = this.Textures[(int)parameters[j - 1] - 1];
}
}
else
{
uint[] parameters = reader.ReadUInt32s((int)paramSize / 4);
for (int j = 0; j < parameters.Length; j++)
{
if (parameters[j].ToString().StartsWith("34397848")) //tAlbedoMap
material.DiffuseMap = this.Textures[(int)parameters[j - 1] - 1];
if (parameters[j].ToString().StartsWith("5771109")) //tNormalMap
material.NormalMap = this.Textures[(int)parameters[j - 1] - 1];
if (parameters[j].ToString().StartsWith("2486240")) //tSpecularMap
material.SpecularMap = this.Textures[(int)parameters[j - 1] - 1];
}
}
}
}
}
private uint ReadUint(FileReader reader)
{
if (_hasUint64Offsets)
{
var v = (uint)reader.ReadInt32();
reader.ReadInt32();
return v;
}
return reader.ReadUInt32();
}
}
public class Material
{
public uint NameHash;
public uint ShaderHash;
public Texture DiffuseMap;
public Texture NormalMap;
public Texture SpecularMap;
}
public class Texture
{
public uint Flags;
public string Name;
}
}

View file

@ -0,0 +1,484 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using OpenTK;
using Toolbox.Library;
using Toolbox.Library.IO;
using FirstPlugin;
namespace CafeLibrary.M2
{
public class Model
{
public ushort Version { get; set; }
public string[] MaterialNames { get; set; }
public MaterialList MaterialList { get; set; }
public List<Mesh> Meshes { get; set; } = new List<Mesh>();
public List<Bone> Bones { get; set; } = new List<Bone>();
public List<MT_TEX> Textures { get; set; } = new List<MT_TEX>();
public List<Bounding> Boundings { get; set; } = new List<Bounding>();
public Vector3 BoundingCenter { get; set; }
public float BoundingScale { get; set; }
public Vector3 Min { get; set; }
public Vector3 Max { get; set; }
public Model(Stream stream)
{
using (var reader = new MTFileReader(stream, this)) {
Read(reader);
}
}
public void LoadMaterials(string filePath)
{
if (!File.Exists(filePath))
return;
MaterialList = new MaterialList(File.OpenRead(filePath), Version == 211);
}
public void LoadTextures(string folder)
{
if (MaterialList == null)
return;
foreach (var tex in MaterialList.Textures)
{
string filePath = Path.Combine(folder, Path.GetFileName(tex.Name) + ".tex");
if (!File.Exists(filePath))
continue;
MT_TEX texture = new MT_TEX();
texture.FileName = Path.GetFileNameWithoutExtension(filePath);
texture.Load(File.OpenRead(filePath));
Textures.Add(texture);
}
}
public void Read(MTFileReader reader)
{
reader.ReadUInt32(); //magic
Version = reader.ReadUInt16();
ushort boneCount = reader.ReadUInt16();
ushort meshCount = reader.ReadUInt16();
ushort matCount = reader.ReadUInt16();
uint totalVertexCount = reader.ReadUInt32();
uint totalIndexCount = reader.ReadUInt32();
uint totalTrisCount = reader.ReadUInt32();
uint vertexBufferSize = reader.ReadUInt32();
reader.ReadUInt32(); //padding
uint numBoundings = reader.ReadUInt32();
if (Version == 211 || Version == 230)
reader.ReadUInt32(); //padding
uint bonesOffset = reader.ReadOffset();
uint boundingOffset = reader.ReadOffset();
uint materialNamesOffset = reader.ReadOffset();
uint meshListOffset = reader.ReadOffset();
uint vertexBufferOffset = reader.ReadOffset();
uint indexBufferOffset = reader.ReadOffset();
uint unkOffset = reader.ReadOffset();
BoundingCenter = reader.ReadVec3();
BoundingScale = reader.ReadSingle();
Min = reader.ReadVec3();
reader.ReadSingle();
Max = reader.ReadVec3();
reader.ReadSingle();
reader.ReadUInt32s(5); //unknown
reader.SeekBegin(materialNamesOffset);
MaterialNames = new string[matCount];
for (int i = 0; i < matCount; i++)
MaterialNames[i] = reader.ReadString(128, true);
byte[] boneIDs = new byte[boneCount];
reader.SeekBegin(bonesOffset);
for (int i = 0; i < boneCount; i++)
{
Bone bone = new Bone();
bone.ID = reader.ReadByte();
bone.ParentIndex = reader.ReadSByte();
bone.MirrorIndex = reader.ReadSByte();
bone.Unk = reader.ReadSByte();
bone.ChildDistance = reader.ReadSingle();
bone.ParentDistance = reader.ReadSingle();
bone.Position = new Vector3(
reader.ReadSingle(),
reader.ReadSingle(),
reader.ReadSingle());
Bones.Add(bone);
boneIDs[i] = bone.ID;
}
//Local space
for (int i = 0; i < boneCount; i++)
Bones[i].LocalMatrix = reader.ReadMatrix4();
//World space
for (int i = 0; i < boneCount; i++)
Bones[i].WorldMatrix = reader.ReadMatrix4();
//Bone ID property to bone index
byte[] remapTable = Version == 137 ? new byte[0x200] : new byte[0x100];
remapTable = reader.ReadBytes(remapTable.Length);
Dictionary<int, int> boneIndices = new Dictionary<int, int>();
for (int i = 0; i < boneCount; i++)
{
if (Bones[i].ID < 255)
{
boneIDs[i] = (byte)remapTable[Bones[i].ID];
boneIndices.Add(boneIDs[i], i);
Console.WriteLine($"boneIDs {Bones[i].ID} -> {boneIDs[i]}");
}
}
//Boundings after
reader.SeekBegin(boundingOffset);
for (int i = 0; i < numBoundings; i++)
{
Bounding bnd = new Bounding();
bnd.ID = reader.ReadUInt32();
bnd.Boundings = reader.ReadSingles(7);
Boundings.Add(bnd);
}
//Scaling as vertex data is scaled down for optmization
Vector3 scale = Max - Min;
if (this.Version >= 190 && Bones.Count > 0)
scale = Bones[0].WorldMatrix.ExtractScale();
//Mesh data
reader.SeekBegin(meshListOffset);
for (int i = 0; i < meshCount; i++)
{
Mesh mesh = new Mesh();
mesh.Read(reader, this, vertexBufferOffset, indexBufferOffset);
Meshes.Add(mesh);
}
var mat = Matrix4.CreateScale(scale);
foreach (var mesh in Meshes)
{
for (int i = 0; i < mesh.Vertices.Length; i++)
{
//Vertex positions use 16 SNorm format which is optmized to be smaller. Scale and offset by bounding size
mesh.Vertices[i].Position = Min + Vector3.TransformPosition(mesh.Vertices[i].Position, mat);
var vertex = mesh.Vertices[i];
if (vertex.BoneIndices.Count == 0)
continue;
//Adjustments to single weights
if (vertex.BoneIndices.Count > 0 && vertex.BoneWeights.Count == 0)
vertex.BoneWeights.Add(1.0f);
//Adjustments to non matching weights as these get optmized
if (vertex.BoneIndices.Count != vertex.BoneWeights.Count)
{
var diff = vertex.BoneIndices.Count - vertex.BoneWeights.Count;
if (diff > 0)
{
var totalToFill = 1.0f - vertex.BoneWeights.Sum(x => x);
if (totalToFill < 0)
throw new Exception();
for (int j = 0; j < diff; j++)
vertex.BoneWeights.Add(totalToFill / diff);
}
}
for (int j = 0; j < vertex.BoneIndices.Count; j++)
{
var boneID = (int)vertex.BoneIndices[j];
var boneW = vertex.BoneWeights[j];
if (boneW == 0)
continue;
Console.WriteLine($"boneID {boneID} boneW {boneW}");
}
mesh.Vertices[i] = vertex;
}
}
Console.WriteLine();
}
public class Bounding
{
public uint ID;
public float[] Boundings = new float[7];
}
}
public class MTFileReader : FileReader
{
private Model Model;
public MTFileReader(Stream stream, Model model) : base(stream)
{
Model = model;
}
public uint ReadOffset()
{
if (Model.Version == 211)
return (uint)ReadUInt64();
else
return ReadUInt32();
}
}
public class Bone
{
public Vector3 Position;
public Matrix4 WorldMatrix;
public Matrix4 LocalMatrix;
public float ParentDistance;
public float ChildDistance;
public byte ID;
public sbyte ParentIndex;
public sbyte Unk;
public sbyte MirrorIndex;
}
public class Mesh
{
public byte Order;
public uint BoundingBoxID;
public uint MaterialID;
public sbyte LOD;
public byte BoundingStartID;
public ushort BatchID;
public ushort[] Indices;
public uint VertexFormatHash;
public Vertex[] Vertices = new Vertex[0];
public class Vertex
{
public Vector3 Position;
public Vector3 Normal;
public Vector2[] TexCoords = new Vector2[1];
public Vector4 Color = Vector4.One;
public List<float> BoneWeights = new List<float>();
public List<int> BoneIndices = new List<int>();
}
public void Read(FileReader reader, Model model, uint vertexBufferOffset, uint indexBufferOffset)
{
reader.ReadUInt16(); //flags
ushort vertexCount = reader.ReadUInt16();
uint meshFlags = reader.ReadUInt32();
reader.ReadByte();
Order = reader.ReadByte();
byte stride = reader.ReadByte();
reader.ReadByte();
uint vertexID = reader.ReadUInt32();
uint vertexDataOffset = reader.ReadUInt32();
VertexFormatHash = reader.ReadUInt32();
uint indexID = reader.ReadUInt32();
uint indexCount = reader.ReadUInt32();
uint indexDataOffset = reader.ReadUInt32();
byte unk = reader.ReadByte();
BoundingStartID = reader.ReadByte();
BatchID = reader.ReadUInt16();
reader.ReadUInt32();
reader.ReadUInt32();
BoundingBoxID = (meshFlags >> 0) & 0xfff;
MaterialID = (meshFlags >> 12) & 0xfff;
LOD = (sbyte)(meshFlags >> 24);
if (model.Version == 211)
{
reader.ReadUInt32();
reader.ReadUInt32();
}
using (reader.TemporarySeek(vertexBufferOffset + vertexDataOffset + (vertexID * stride), SeekOrigin.Begin)) {
ParseVertexBuffer(reader, VertexFormatHash, vertexCount, stride);
}
using (reader.TemporarySeek(indexBufferOffset + indexDataOffset + (indexID * 2), SeekOrigin.Begin)) {
if (model.Version == 212 || model.Version == 211)
ReadTriStrips(reader, indexCount, vertexID);
else
ReadTris(reader, indexCount, vertexID);
}
}
private void ParseVertexBuffer(FileReader reader, uint formatHash, ushort count, byte stride)
{
if (!MT_Globals.AttributeGroups.ContainsKey(formatHash))
{
// throw new Exception($"Unsupported attribute layout! {formatHash}");
return;
}
Vertices = new Vertex[count];
var attributeGroup = MT_Globals.AttributeGroups[formatHash];
long basePos = reader.Position;
Console.WriteLine($"ATTRIBUTE {attributeGroup.Name}");
for (int i = 0; i < count; i++)
{
Vertices[i] = new Vertex();
var maxUVSet = attributeGroup.Attributes.Where(x => x.Name.ToLower() == "texcoord").Sum(x => x.Set);
Vertices[i].TexCoords = new Vector2[maxUVSet + 1];
foreach (var att in attributeGroup.Attributes)
{
reader.SeekBegin((uint)basePos + (i * stride) + att.Offset);
switch (att.Name.ToLower())
{
case "position":
Vertices[i].Position.X = Parse(reader, att.Format);
Vertices[i].Position.Y = Parse(reader, att.Format);
if (att.ElementCount > 2)
Vertices[i].Position.Z = Parse(reader, att.Format);
break;
case "normal":
Vertices[i].Normal.X = Parse(reader, att.Format);
Vertices[i].Normal.Y = Parse(reader, att.Format);
Vertices[i].Normal.Z = Parse(reader, att.Format);
Vertices[i].Normal = Vertices[i].Normal.Normalized();
break;
case "texcoord":
Vector2 uv = new Vector2();
uv.X = Parse(reader, att.Format);
uv.Y = Parse(reader, att.Format);
Vertices[i].TexCoords[att.Set] = uv;
break;
case "vertexcolor":
Vertices[i].Color.X = Parse(reader, att.Format);
Vertices[i].Color.Y = Parse(reader, att.Format);
Vertices[i].Color.Z = Parse(reader, att.Format);
if (att.ElementCount == 4) //Alpha
Vertices[i].Color.W = Parse(reader, att.Format);
break;
case "vertexalpha":
Vertices[i].Color.W = Parse(reader, att.Format);
break;
case "weight":
for (int j = 0; j < att.ElementCount; j++)
Vertices[i].BoneWeights.Add(Parse(reader, att.Format));
break;
case "joint":
for (int j = 0; j < att.ElementCount; j++)
Vertices[i].BoneIndices.Add((int)Parse(reader, att.Format));
break;
}
}
}
}
private float Parse(FileReader reader, Shader.AttributeGroup.AttributeFormat format)
{
switch (format)
{
case Shader.AttributeGroup.AttributeFormat.Float: return reader.ReadSingle();
case Shader.AttributeGroup.AttributeFormat.HalfFloat: return reader.ReadHalfSingle();
case Shader.AttributeGroup.AttributeFormat.UShort: return reader.ReadUInt16();
case Shader.AttributeGroup.AttributeFormat.Short: return reader.ReadInt16();
case Shader.AttributeGroup.AttributeFormat.S16N: return reader.ReadInt16() * (1f / short.MaxValue);
case Shader.AttributeGroup.AttributeFormat.U16N: return reader.ReadUInt16() * (1f / ushort.MaxValue);
case Shader.AttributeGroup.AttributeFormat.Sbyte: return reader.ReadSByte();
case Shader.AttributeGroup.AttributeFormat.Byte: return reader.ReadByte();
case Shader.AttributeGroup.AttributeFormat.S8N: return reader.ReadSByte() * (1f / sbyte.MaxValue);
case Shader.AttributeGroup.AttributeFormat.U8N: return reader.ReadByte() * (1f / byte.MaxValue);
case (Shader.AttributeGroup.AttributeFormat)11: return (reader.ReadByte() - 127) * 0.0078125f;
case Shader.AttributeGroup.AttributeFormat.RGB: return reader.ReadByte();
case Shader.AttributeGroup.AttributeFormat.RGBA: return reader.ReadByte();
}
return 0;
}
private void ReadTris(FileReader reader, uint indexCount, uint vertexID)
{
Indices = new ushort[indexCount / 3];
for (int Index = 0; Index < Indices.Length; Index++)
Indices[Index] = (ushort)(reader.ReadInt16() - vertexID); //Make sure index is relative rather than one big buffer
}
private void ReadTriStrips(FileReader reader, uint indexCount, uint vertexID)
{
List<ushort> indices = new List<ushort>();
int startDirection = 1;
//Start with triangle (f1, f2, f3)
ushort f1 = reader.ReadUInt16();
ushort f2 = reader.ReadUInt16();
int faceDirection = startDirection;
int p = 2;
ushort f3;
do
{
f3 = reader.ReadUInt16();
p++;
if (f3 == ushort.MaxValue) //Value after 0xFFFF is triangle (f1, f2, f3)
{
f1 = reader.ReadUInt16();
f2 = reader.ReadUInt16();
faceDirection = startDirection;
p += 2;
}
else
{
//Direction switches each strip
faceDirection *= -1;
//Skip single points
if ((f1 != f2) && (f2 != f3) && (f3 != f1))
{
if (faceDirection > 0) //Determine strip face orientation
{
indices.Add(f3);
indices.Add(f2);
indices.Add(f1);
}
else
{
indices.Add(f2);
indices.Add(f3);
indices.Add(f1);
}
}
//Remap indices for next strip
f1 = f2;
f2 = f3;
}
} while (p < indexCount);
//Shift indices as indices in .mod index one global vertex table
for (int i = 0; i < indices.Count; i++)
indices[i] = (ushort)(indices[i] - vertexID);
Indices = indices.ToArray();
}
}
}

View file

@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Newtonsoft.Json;
using Toolbox.Library;
using Toolbox.Library.IO;
using Toolbox.Library.Security.Cryptography;
namespace CafeLibrary.M2
{
public class Shader
{
public Dictionary<uint, AttributeGroup> AttributeGroups { get; set; } = new Dictionary<uint, AttributeGroup>();
public Shader() { }
public Shader(Stream stream)
{
using (var reader = new FileReader(stream)) {
Read(reader);
}
}
public void Read(FileReader reader)
{
reader.ReadUInt32(); //MFX
reader.Seek(8); //unks
uint descCount = reader.ReadUInt32();
uint stringTableOffset = reader.ReadUInt32();
reader.ReadUInt32();
long pos = reader.Position;
for (int i = 0; i < descCount - 1; i++)
{
reader.SeekBegin(pos + (i * 4));
uint ofs = reader.ReadUInt32();
if (ofs == 0)
continue;
reader.SeekBegin(ofs);
Descriptor desc = new Descriptor();
desc.Name = ReadName(reader, stringTableOffset);
desc.Type = ReadName(reader, stringTableOffset);
ushort DescType = reader.ReadUInt16();
ushort MapLength = reader.ReadUInt16(); //Actual length is value / 2? Not sure
ushort MapIndex = reader.ReadUInt16();
ushort DescIndex = reader.ReadUInt16();
reader.ReadUInt32();
uint MapAddress = reader.ReadUInt32(); //Not sure what this address actually points to
var crc = MT_Globals.crc32(desc.Name);
var jcrc = MT_Globals.jamcrc(desc.Name);
var mcrc = MT_Globals.mfxcrc(desc.Name, DescIndex);
if (desc.Type == "__InputLayout")
{
Console.WriteLine($"attribute {desc.Name} DescIndex {DescIndex}");
AttributeGroups.Add(mcrc, new AttributeGroup(reader, stringTableOffset)
{
Name = desc.Name,
});
}
}
File.WriteAllText("Shader.json", JsonConvert.SerializeObject(this, Formatting.Indented));
}
private static string ReadName(FileReader reader, uint stringTableOffset)
{
uint ofs = reader.ReadUInt32();
using (reader.TemporarySeek(stringTableOffset + ofs, SeekOrigin.Begin)) {
return reader.ReadZeroTerminatedString();
}
}
class Descriptor
{
public string Name;
public string Type;
}
public class AttributeGroup
{
public string Name;
public List<Attribute> Attributes { get; set; } = new List<Attribute>();
public AttributeGroup() { }
public AttributeGroup(FileReader reader, uint stringTableOffset)
{
ushort AttrCount = reader.ReadUInt16();
byte Stride = reader.ReadByte();
reader.ReadByte();
reader.ReadUInt32();
Stride *= 4;
for (int j = 0; j < AttrCount; j++)
{
string name = ReadName(reader, stringTableOffset);
uint Format = reader.ReadUInt32();
uint AttrIndex = (Format >> 0) & 0x3f; //Used when attribute is repeated usually
uint AttrFormat = (Format >> 6) & 0x1f; //See AttributeFormat enumerator
uint AttrElems = (Format >> 11) & 0x7F; //2 = 2D, 3 = 3D, 4 = 4D, ...
uint AttrOffset = (Format >> 24) & 0xff; //In Word Count (32-bits)
var instancing = (Format >> 31) & 0x1;
//Color
if (AttrFormat == 0xe || AttrFormat == 0xb || AttrFormat == 0xc)
AttrElems = 4;
AttrOffset *= 4;
this.Attributes.Add(new Attribute()
{
Name = name,
Format = (AttributeFormat)AttrFormat,
ElementCount = AttrElems,
Offset = AttrOffset,
Set = AttrIndex,
});
}
}
public class Attribute
{
public string Name { get; set; }
public AttributeFormat Format { get; set; }
public uint Offset { get; set; }
public uint ElementCount { get; set; }
public uint Set { get; set; }
}
public enum AttributeFormat
{
Float = 1,
HalfFloat = 2,
UShort = 3,
Short = 4,
S16N = 5,
U16N = 6,
Sbyte = 7,
Byte = 8,
S8N = 9,
U8N = 10,
RGB = 13,
RGBA = 14
}
}
}
}

View file

@ -322,6 +322,14 @@
<Compile Include="FileFormats\MarioParty\HSF.cs" />
<Compile Include="FileFormats\Message\MSYT.cs" />
<Compile Include="FileFormats\MKAGPDX\LM2_ARCADE_Model.cs" />
<Compile Include="FileFormats\MT\CRC32Hash.cs" />
<Compile Include="FileFormats\MT\Model\MaterialList.cs" />
<Compile Include="FileFormats\MT\Model\Model.cs" />
<Compile Include="FileFormats\MT\Model\Shader.cs" />
<Compile Include="FileFormats\MT\MT_Globals.cs" />
<Compile Include="FileFormats\MT\MT_Renderer.cs" />
<Compile Include="FileFormats\MT\MT_Model.cs" />
<Compile Include="FileFormats\MT\MT_TEX.cs" />
<Compile Include="FileFormats\NKN.cs" />
<Compile Include="FileFormats\NLG\LM2\LM2_Material.cs" />
<Compile Include="FileFormats\NLG\LM3\LM3_ChunkTable.cs" />

View file

@ -341,6 +341,8 @@ namespace FirstPlugin
{
List<Type> Formats = new List<Type>();
Formats.Add(typeof(BFRES));
Formats.Add(typeof(MT_TEX));
Formats.Add(typeof(MT_Model));
Formats.Add(typeof(BCSV));
Formats.Add(typeof(TVOL));
Formats.Add(typeof(BTI));

View file

@ -17,6 +17,7 @@ namespace Toolbox.Library.Rendering
public virtual float PreviewScale { get; set; } = 1.0f;
public static List<ITextureContainer> TextureContainers = new List<ITextureContainer>();
public List<STGenericTexture> Textures = new List<STGenericTexture>();
public List<GenericRenderedObject> Meshes = new List<GenericRenderedObject>();
public STSkeleton Skeleton = new STSkeleton();
@ -333,6 +334,15 @@ namespace Toolbox.Library.Rendering
}
}
for (int i = 0; i < Textures?.Count; i++)
{
if (activeTex == Textures[i].Text)
{
BindGLTexture(tex, shader, Textures[i]);
return tex.textureUnit + 1;
}
}
return tex.textureUnit + 1;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff