From 4c9aa8a86859dae050ea76a4776732373eb74e5c Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 31 Aug 2020 23:57:12 -0400 Subject: [PATCH 1/9] in-progress Models --- src/Lumina/Data/Files/MdlFile.cs | 168 ++++++ src/Lumina/Data/Parsing/Mdl/MdlStructs.cs | 502 ++++++++++++++++++ .../Extensions/BinaryReaderExtensions.cs | 13 + src/Lumina/Models/Model/Mesh.cs | 164 ++++++ src/Lumina/Models/Model/Model.cs | 254 +++++++++ src/Lumina/Models/Model/Shape.cs | 15 + src/Lumina/Models/Model/Submesh.cs | 51 ++ src/Lumina/Models/Model/Vertex.cs | 42 ++ 8 files changed, 1209 insertions(+) create mode 100644 src/Lumina/Data/Files/MdlFile.cs create mode 100644 src/Lumina/Data/Parsing/Mdl/MdlStructs.cs create mode 100644 src/Lumina/Models/Model/Mesh.cs create mode 100644 src/Lumina/Models/Model/Model.cs create mode 100644 src/Lumina/Models/Model/Shape.cs create mode 100644 src/Lumina/Models/Model/Submesh.cs create mode 100644 src/Lumina/Models/Model/Vertex.cs diff --git a/src/Lumina/Data/Files/MdlFile.cs b/src/Lumina/Data/Files/MdlFile.cs new file mode 100644 index 00000000..95c3bf5a --- /dev/null +++ b/src/Lumina/Data/Files/MdlFile.cs @@ -0,0 +1,168 @@ +using System; +using System.Diagnostics; +using Lumina.Data.Structs; +using Lumina.Extensions; +using static Lumina.Data.Parsing.Mdl.MdlStructs; + +namespace Lumina.Data.Files { + public class MdlFile : FileResource { + public ModelFileHeader FileHeader; + public VertexDeclarationStruct[] VertexDeclarations; + public ModelHeader ModelHeader; + public ElementIdStruct[] ElementIds; + public LodStruct[] Lods; + public ExtraLodStruct[] ExtraLods; + public MeshStruct[] Meshes; + public uint[] AttributeNameOffsets; + public SubmeshStruct[] Submeshes; + + public TerrainShadowMeshStruct[] TerrainShadowMeshes; + public TerrainShadowSubmeshStruct[] TerrainShadowSubmeshes; + + public uint[] MaterialNameOffsets; + public uint[] BoneNameOffsets; + public BoneTableStruct[] BoneTables; + public ShapeStruct[] Shapes; + public ShapeMeshStruct[] ShapeMeshes; + public ShapeValueStruct[] ShapeValues; + + public ushort[] SubmeshBoneMap; + public BoundingBoxStruct BoundingBoxes; + public BoundingBoxStruct ModelBoundingBoxes; + public BoundingBoxStruct WaterBoundingBoxes; + public BoundingBoxStruct VerticalFogBoundingBoxes; + public BoundingBoxStruct[] BoneBoundingBoxes; + + public ushort StringCount; + public byte[] Strings; + + public byte[][] VertexBuffers; + public byte[][] IndexBuffers; + + public override void LoadFile() + { + // We can ensure based on content-type that files are models + if( FileInfo.Type != FileType.Model ) + { + Console.WriteLine( $"Attempted to load {FilePath} of content type {FileInfo.Type} as a model, returning..." ); + return; + } + + FileHeader = ModelFileHeader.Read( Reader ); + + VertexDeclarations = new VertexDeclarationStruct[FileHeader.VertexDeclarationNum]; + for( int i = 0; i < FileHeader.VertexDeclarationNum; i++ ) VertexDeclarations[ i ] = VertexDeclarationStruct.Read( Reader ); + + StringCount = Reader.ReadUInt16(); + Reader.ReadUInt16(); + uint stringSize = Reader.ReadUInt32(); + Strings = Reader.ReadBytes( (int) stringSize ); + + ModelHeader = ModelHeader.Read( Reader ); + ElementIds = new ElementIdStruct[ModelHeader.ElementIdNum]; + Lods = new LodStruct[3]; + Meshes = new MeshStruct[ModelHeader.MeshNum]; + Submeshes = new SubmeshStruct[ModelHeader.SubmeshNum]; + TerrainShadowMeshes = new TerrainShadowMeshStruct[ModelHeader.TerrainShadowMeshNum]; + TerrainShadowSubmeshes = new TerrainShadowSubmeshStruct[ModelHeader.TerrainShadowSubmeshNum]; + BoneTables = new BoneTableStruct[ModelHeader.BoneTableNum]; + Shapes = new ShapeStruct[ModelHeader.ShapeNum]; + ShapeMeshes = new ShapeMeshStruct[ModelHeader.ShapeMeshNum]; + ShapeValues = new ShapeValueStruct[ModelHeader.ShapeValueNum]; + BoneBoundingBoxes = new BoundingBoxStruct[ModelHeader.BoneNum]; + + VertexBuffers = new byte[3][]; + IndexBuffers = new byte[3][]; + + for( int i = 0; i < ModelHeader.ElementIdNum; i++ ) ElementIds[ i ] = ElementIdStruct.Read( Reader ); + for( int i = 0; i < 3; i++ ) Lods[ i ] = LodStruct.Read( Reader ); + + if( ModelHeader.ExtraLodEnabled ) { + ExtraLods = new ExtraLodStruct[3]; + for( int i = 0; i < 3; i++ ) ExtraLods[ i ] = ExtraLodStruct.Read( Reader ); + } + + for( int i = 0; i < ModelHeader.MeshNum; i++ ) Meshes[ i ] = MeshStruct.Read( Reader ); + AttributeNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.AttributeNum ).ToArray(); + for( int i = 0; i < ModelHeader.SubmeshNum; i++ ) Submeshes[ i ] = SubmeshStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.TerrainShadowMeshNum; i++ ) TerrainShadowMeshes[ i ] = TerrainShadowMeshStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.TerrainShadowSubmeshNum; i++ ) TerrainShadowSubmeshes[ i ] = TerrainShadowSubmeshStruct.Read( Reader ); + + MaterialNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.MaterialNum ).ToArray(); + BoneNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.BoneNum ).ToArray(); + for( int i = 0; i < ModelHeader.BoneTableNum; i++ ) BoneTables[ i ] = BoneTableStruct.Read( Reader ); + + for( int i = 0; i < ModelHeader.ShapeNum; i++ ) Shapes[ i ] = ShapeStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.ShapeMeshNum; i++ ) ShapeMeshes[ i ] = ShapeMeshStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.ShapeValueNum; i++ ) ShapeValues[ i ] = ShapeValueStruct.Read( Reader ); + + uint submeshBoneMapSize = Reader.ReadUInt32(); + SubmeshBoneMap = Reader.ReadStructures< UInt16 >( (int) submeshBoneMapSize / 2 ).ToArray(); + + byte paddingAmount = Reader.ReadByte(); + Reader.Seek( Reader.BaseStream.Position + paddingAmount ); + + // Dunno what this first one is for? + BoundingBoxes = BoundingBoxStruct.Read( Reader ); + ModelBoundingBoxes = BoundingBoxStruct.Read( Reader ); + WaterBoundingBoxes = BoundingBoxStruct.Read( Reader ); + VerticalFogBoundingBoxes = BoundingBoxStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.BoneNum; i++ ) BoneBoundingBoxes[ i ] = BoundingBoxStruct.Read( Reader ); + + for( int i = 0; i < 3; i++ ) { + Reader.BaseStream.Position = FileHeader.VertexOffset[ i ]; + VertexBuffers[ i ] = Reader.ReadBytes( (int) FileHeader.VertexBufferSize[ i ] ); + + Reader.BaseStream.Position = FileHeader.IndexOffset[ i ]; + IndexBuffers[ i ] = Reader.ReadBytes( (int) FileHeader.IndexBufferSize[ i ] ); + } + } + } +} + +/* +// goes after file header, testing for something? +byte Stack[fileHeader.StackMemorySize] ; +byte Runtime[fileHeader.RuntimeMemorySize] ; + +local int i = 0; +for (i = 0; i < 3; i++) { + //FSeek(sizeof(Model_File_Header_Block) + fileHeader.VertexDataOffset[i]); + FSeek(fileHeader.VertexDataOffset[i]); + ByteArr vertBuffer(fileHeader.VertexBufferSize[i]) ; + + //FSeek(sizeof(Model_File_Header_Block) + fileHeader.IndexDataOffset[i]); + FSeek(fileHeader.IndexDataOffset[i]); + ByteArr indexBuffer(fileHeader.IndexBufferSize[i]) ; +}*/ + +/* +// header testing code +FSeek(fileHeader.VertexDataOffset[0]); +hfloat ignore[meshHeaders[0].VertexBufferOffset[1] / 2]; + +struct VertTest1 +{ + hfloat normal[4]; + byte tangent[4]; + hfloat texcoord[2]; +}; + +struct VertTest2 +{ + uint ny : 11; + uint nx : 11; + uint nz : 10; + byte tangent[4]; + hfloat texcoord[2]; +}; + +if (IsBigEndian()) { + VertTest2 test[20]; +} else { + VertTest1 test[20]; +} + +FSeek(fileHeader.IndexDataOffset[0]); +ushort indexBuffer[fileHeader.IndexBufferSize[0] / 2] ; +*/ \ No newline at end of file diff --git a/src/Lumina/Data/Parsing/Mdl/MdlStructs.cs b/src/Lumina/Data/Parsing/Mdl/MdlStructs.cs new file mode 100644 index 00000000..6d3dda38 --- /dev/null +++ b/src/Lumina/Data/Parsing/Mdl/MdlStructs.cs @@ -0,0 +1,502 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Lumina.Extensions; + +// Here is a catch-all comment telling you to check the template if you have access +namespace Lumina.Data.Parsing.Mdl +{ + public static class MdlStructs + { + public struct ModelFileHeader + { + public uint Version; + public uint StackSize; + public uint RuntimeSize; + public ushort VertexDeclarationNum; + public ushort MaterialNum; + public uint[] VertexOffset; + public uint[] IndexOffset; + public uint[] VertexBufferSize; + public uint[] IndexBufferSize; + public byte LodNum; + public bool EnableIndexBufferStreaming; + public bool EnableEdgeGeometry; + private byte Padding; + + public static ModelFileHeader Read( BinaryReader br ) + { + ModelFileHeader ret = new ModelFileHeader(); + ret.Version = br.ReadUInt32(); + ret.StackSize = br.ReadUInt32(); + ret.RuntimeSize = br.ReadUInt32(); + ret.VertexDeclarationNum = br.ReadUInt16(); + ret.MaterialNum = br.ReadUInt16(); + ret.VertexOffset = br.ReadStructures< UInt32 >( 3 ).ToArray(); + ret.IndexOffset = br.ReadStructures< UInt32 >( 3 ).ToArray(); + ret.VertexBufferSize = br.ReadStructures< UInt32 >( 3 ).ToArray(); + ret.IndexBufferSize = br.ReadStructures< UInt32 >( 3 ).ToArray(); + ret.LodNum = br.ReadByte(); + ret.EnableIndexBufferStreaming = br.ReadBoolean(); + ret.EnableEdgeGeometry = br.ReadBoolean(); + ret.Padding = br.ReadByte(); + if( ret.EnableEdgeGeometry ) + Console.WriteLine( "Win32 file with EdgeGeometry enabled?" ); + return ret; + } + } + + public struct VertexDeclarationStruct + { + // There are always 17, but stop when stream = -1 + public VertexElement[] VertexElements; + + public static VertexDeclarationStruct Read( BinaryReader br ) + { + VertexDeclarationStruct ret = new VertexDeclarationStruct(); + + var elems = new List< VertexElement >(); + + // Read the vertex elements that we need + var thisElem = VertexElement.Read( br ); + do + { + elems.Add( thisElem ); + thisElem = VertexElement.Read( br ); + } while( thisElem.Stream != 255 ); + + // Skip the number of bytes that we don't need to read + // We skip elems.Count * 9 because we had to read the invalid element + int toSeek = ( 17 * 8 ) - ( ( elems.Count + 1 ) * 8 ); + br.Seek( br.BaseStream.Position + toSeek ); + + ret.VertexElements = elems.ToArray(); + + return ret; + } + } + + public struct VertexElement + { + public byte Stream; + public byte Offset; + public byte Type; + public byte Usage; + public byte UsageIndex; // D3D9 remnant? + private byte[] Padding; + + public static VertexElement Read( BinaryReader br ) + { + VertexElement ret = new VertexElement(); + ret.Stream = br.ReadByte(); + ret.Offset = br.ReadByte(); + ret.Type = br.ReadByte(); + ret.Usage = br.ReadByte(); + ret.UsageIndex = br.ReadByte(); + ret.Padding = br.ReadBytes( 3 ); + return ret; + } + } + + public struct ModelHeader + { + // MeshHeader + public float Radius; + public ushort MeshNum; + public ushort AttributeNum; + public ushort SubmeshNum; + public ushort MaterialNum; + public ushort BoneNum; + public ushort BoneTableNum; + public ushort ShapeNum; + public ushort ShapeMeshNum; + public ushort ShapeValueNum; + public byte LodNum; + + private byte bitfield1; + + public bool DustOcclusionEnabled; + public bool SnowOcclusionEnabled; + public bool RainOcclusionEnabled; + public bool Unknown1; + public bool BGLightingReflectionEnabled; + public bool WavingAnimationDisabled; + public bool LightShadowDisabled; + public bool ShadowDisabled; + public ushort ElementIdNum; + public byte TerrainShadowMeshNum; + + private byte bitfield2; + + public bool Unknown2; + public bool BgUvScrollEnabled; + public bool EnableForceNonResident; + public bool ExtraLodEnabled; + public bool ShadowMaskEnabled; + public bool ForceLodRangeEnabled; + public bool EdgeGeometryEnabled; + public bool Unknown3; + + public float ModelClipOutDistance; + public float ShadowClipOutDistance; + public ushort Unknown4; + public ushort TerrainShadowSubmeshNum; + + private byte Unknown5; + + public byte BGChangeMaterialIndex; + public byte BGCrestChangeMaterialIndex; + public byte Unknown6; + public ushort Unknown7; + public ushort Unknown8; + public ushort Unknown9; + private byte[] Padding00; + + public static ModelHeader Read( BinaryReader br ) + { + ModelHeader ret = new ModelHeader(); + ret.Radius = br.ReadSingle(); + ret.MeshNum = br.ReadUInt16(); + ret.AttributeNum = br.ReadUInt16(); + ret.SubmeshNum = br.ReadUInt16(); + ret.MaterialNum = br.ReadUInt16(); + ret.BoneNum = br.ReadUInt16(); + ret.BoneTableNum = br.ReadUInt16(); + ret.ShapeNum = br.ReadUInt16(); + ret.ShapeMeshNum = br.ReadUInt16(); + ret.ShapeValueNum = br.ReadUInt16(); + ret.LodNum = br.ReadByte(); + + ret.bitfield1 = br.ReadByte(); + ret.DustOcclusionEnabled = ( ret.bitfield1 & 0x80 ) == 0x80; + ret.SnowOcclusionEnabled = ( ret.bitfield1 & 0x40 ) == 0x40; + ret.RainOcclusionEnabled = ( ret.bitfield1 & 0x20 ) == 0x20; + ret.Unknown1 = ( ret.bitfield1 & 0x10 ) == 0x10; + ret.BGLightingReflectionEnabled = ( ret.bitfield1 & 0x08 ) == 0x08; + ret.WavingAnimationDisabled = ( ret.bitfield1 & 0x04 ) == 0x04; + ret.LightShadowDisabled = ( ret.bitfield1 & 0x02 ) == 0x02; + ret.ShadowDisabled = ( ret.bitfield1 & 0x01 ) == 0x01; + + ret.ElementIdNum = br.ReadUInt16(); + ret.TerrainShadowMeshNum = br.ReadByte(); + + ret.bitfield2 = br.ReadByte(); + ret.Unknown2 = ( ret.bitfield2 & 0x80 ) == 0x80; + ret.BgUvScrollEnabled = ( ret.bitfield2 & 0x40 ) == 0x40; + ret.EnableForceNonResident = ( ret.bitfield2 & 0x20 ) == 0x20; + ret.ExtraLodEnabled = ( ret.bitfield2 & 0x10 ) == 0x10; + ret.ShadowMaskEnabled = ( ret.bitfield2 & 0x08 ) == 0x08; + ret.ForceLodRangeEnabled = ( ret.bitfield2 & 0x04 ) == 0x04; + ret.EdgeGeometryEnabled = ( ret.bitfield2 & 0x02 ) == 0x02; + ret.Unknown3 = ( ret.bitfield2 & 0x01 ) == 0x01; + + ret.ModelClipOutDistance = br.ReadSingle(); + ret.ShadowClipOutDistance = br.ReadSingle(); + ret.Unknown4 = br.ReadUInt16(); + ret.TerrainShadowSubmeshNum = br.ReadUInt16(); + ret.Unknown5 = br.ReadByte(); + ret.BGChangeMaterialIndex = br.ReadByte(); + ret.BGCrestChangeMaterialIndex = br.ReadByte(); + ret.Unknown6 = br.ReadByte(); + ret.Unknown7 = br.ReadUInt16(); + ret.Unknown8 = br.ReadUInt16(); + ret.Unknown9 = br.ReadUInt16(); + ret.Padding00 = br.ReadBytes( 6 ).ToArray(); + return ret; + } + } + + public struct ElementIdStruct + { + public uint ElementId; + public uint ParentBoneName; + public float[] Translate; + public float[] Rotate; + + public static ElementIdStruct Read( BinaryReader br ) + { + ElementIdStruct ret = new ElementIdStruct(); + ret.ElementId = br.ReadUInt32(); + ret.ParentBoneName = br.ReadUInt32(); + ret.Translate = br.ReadStructures< Single >( 3 ).ToArray(); + ret.Rotate = br.ReadStructures< Single >( 3 ).ToArray(); + return ret; + } + } + + public struct LodStruct + { + public ushort MeshIndex; + public ushort MeshNum; + public float ModelLodRange; + public float TextureLodRange; + public ushort WaterMeshIndex; + public ushort WaterMeshNum; + public ushort ShadowMeshIndex; + public ushort ShadowMeshNum; + public ushort TerrainShadowMeshIndex; + public ushort TerrainShadowMeshNum; + public ushort VerticalFogMeshIndex; + public ushort VerticalFogMeshNum; + // Yell at me if this ever exists on Win32 + public uint EdgeGeometrySize; + public uint EdgeGeometryDataOffset; + public uint PolygonNum; + public uint Unknown1; + public uint VertexBufferSize; + public uint IndexBufferSize; + public uint VertexDataOffset; + public uint IndexDataOffset; + + public static LodStruct Read( BinaryReader br ) + { + LodStruct ret = new LodStruct(); + ret.MeshIndex = br.ReadUInt16(); + ret.MeshNum = br.ReadUInt16(); + ret.ModelLodRange = br.ReadSingle(); + ret.TextureLodRange = br.ReadSingle(); + ret.WaterMeshIndex = br.ReadUInt16(); + ret.WaterMeshNum = br.ReadUInt16(); + ret.ShadowMeshIndex = br.ReadUInt16(); + ret.ShadowMeshNum = br.ReadUInt16(); + ret.TerrainShadowMeshIndex = br.ReadUInt16(); + ret.TerrainShadowMeshNum = br.ReadUInt16(); + ret.VerticalFogMeshIndex = br.ReadUInt16(); + ret.VerticalFogMeshNum = br.ReadUInt16(); + ret.EdgeGeometrySize = br.ReadUInt32(); + ret.EdgeGeometryDataOffset = br.ReadUInt32(); + ret.PolygonNum = br.ReadUInt32(); + ret.Unknown1 = br.ReadUInt32(); + ret.VertexBufferSize = br.ReadUInt32(); + ret.IndexBufferSize = br.ReadUInt32(); + ret.VertexDataOffset = br.ReadUInt32(); + ret.IndexDataOffset = br.ReadUInt32(); + return ret; + } + } + + public struct ExtraLodStruct + { + public ushort LightShaftMeshIndex; + public ushort LightShaftMeshNum; + public ushort GlassMeshIndex; + public ushort GlassMeshNum; + public ushort MaterialChangeMeshIndex; + public ushort MaterialChangeMeshNum; + public ushort CrestChangeMeshIndex; + public ushort CrestChangeMeshNum; + public ushort Unknown1; + public ushort Unknown2; + public ushort Unknown3; + public ushort Unknown4; + public ushort Unknown5; + public ushort Unknown6; + public ushort Unknown7; + public ushort Unknown8; + public ushort Unknown9; + public ushort Unknown10; + public ushort Unknown11; + public ushort Unknown12; + + public static ExtraLodStruct Read( BinaryReader br ) + { + ExtraLodStruct ret = new ExtraLodStruct(); + ret.LightShaftMeshIndex = br.ReadUInt16(); + ret.LightShaftMeshNum = br.ReadUInt16(); + ret.GlassMeshIndex = br.ReadUInt16(); + ret.GlassMeshNum = br.ReadUInt16(); + ret.MaterialChangeMeshIndex = br.ReadUInt16(); + ret.MaterialChangeMeshNum = br.ReadUInt16(); + ret.CrestChangeMeshIndex = br.ReadUInt16(); + ret.CrestChangeMeshNum = br.ReadUInt16(); + ret.Unknown1 = br.ReadUInt16(); + ret.Unknown2 = br.ReadUInt16(); + ret.Unknown3 = br.ReadUInt16(); + ret.Unknown4 = br.ReadUInt16(); + ret.Unknown5 = br.ReadUInt16(); + ret.Unknown6 = br.ReadUInt16(); + ret.Unknown7 = br.ReadUInt16(); + ret.Unknown8 = br.ReadUInt16(); + ret.Unknown9 = br.ReadUInt16(); + ret.Unknown10 = br.ReadUInt16(); + ret.Unknown11 = br.ReadUInt16(); + ret.Unknown12 = br.ReadUInt16(); + return ret; + } + } + + public struct MeshStruct + { + public ushort VertexNum; + private ushort padding; + public uint IndexNum; + public ushort MaterialIndex; + public ushort SubMeshIndex; + public ushort SubMeshNum; + public ushort BoneTableIndex; + public uint StartIndex; + public uint[] VertexBufferOffset; + public byte[] VertexBufferStride; + + public byte VertexStreamNum; + + public static MeshStruct Read( BinaryReader br ) + { + MeshStruct ret = new MeshStruct(); + ret.VertexNum = br.ReadUInt16(); + ret.padding = br.ReadUInt16(); + ret.IndexNum = br.ReadUInt32(); + ret.MaterialIndex = br.ReadUInt16(); + ret.SubMeshIndex = br.ReadUInt16(); + ret.SubMeshNum = br.ReadUInt16(); + ret.BoneTableIndex = br.ReadUInt16(); + ret.StartIndex = br.ReadUInt32(); + ret.VertexBufferOffset = br.ReadStructures< UInt32 >( 3 ).ToArray(); + ret.VertexBufferStride = br.ReadBytes( 3 ); + ret.VertexStreamNum = br.ReadByte(); + return ret; + } + } + + public struct SubmeshStruct + { + public uint IndexOffset; + public uint IndexNum; + public uint AttributeIndexMask; + public ushort BoneStartIndex; + public ushort BoneNum; + + public static SubmeshStruct Read( BinaryReader br ) + { + SubmeshStruct ret = new SubmeshStruct(); + ret.IndexOffset = br.ReadUInt32(); + ret.IndexNum = br.ReadUInt32(); + ret.AttributeIndexMask = br.ReadUInt32(); + ret.BoneStartIndex = br.ReadUInt16(); + ret.BoneNum = br.ReadUInt16(); + return ret; + } + } + + public struct TerrainShadowMeshStruct + { + public uint IndexNum; + public uint StartIndex; + public uint VertexBufferOffset; + public ushort VertexNum; + public ushort SubMeshIndex; + public ushort SubMeshNum; + public byte VertexBufferStride; + private byte Padding; + + public static TerrainShadowMeshStruct Read( BinaryReader br ) + { + TerrainShadowMeshStruct ret = new TerrainShadowMeshStruct(); + ret.IndexNum = br.ReadUInt32(); + ret.StartIndex = br.ReadUInt32(); + ret.VertexBufferOffset = br.ReadUInt32(); + ret.VertexNum = br.ReadUInt16(); + ret.SubMeshIndex = br.ReadUInt16(); + ret.SubMeshNum = br.ReadUInt16(); + ret.VertexBufferStride = br.ReadByte(); + ret.Padding = br.ReadByte(); + return ret; + } + } + + public struct TerrainShadowSubmeshStruct + { + public uint IndexOffset; + public uint IndexNum; + public ushort Unknown1; + public ushort Unknown2; + + public static TerrainShadowSubmeshStruct Read( BinaryReader br ) + { + TerrainShadowSubmeshStruct ret = new TerrainShadowSubmeshStruct(); + ret.IndexOffset = br.ReadUInt32(); + ret.IndexNum = br.ReadUInt32(); + ret.Unknown1 = br.ReadUInt16(); + ret.Unknown2 = br.ReadUInt16(); + return ret; + } + } + + public struct BoneTableStruct + { + public ushort[] BoneIndex; + public byte BoneNum; + private byte[] Padding; + + public static BoneTableStruct Read( BinaryReader br ) + { + BoneTableStruct ret = new BoneTableStruct(); + ret.BoneIndex = br.ReadStructures< UInt16 >( 64 ).ToArray(); + ret.BoneNum = br.ReadByte(); + ret.Padding = br.ReadBytes( 3 ); + return ret; + } + } + + public struct ShapeStruct + { + public uint StringOffset; + public ushort[] ShapeMeshStartIndex; + public ushort[] ShapeMeshNum; + + public static ShapeStruct Read( BinaryReader br ) + { + ShapeStruct ret = new ShapeStruct(); + ret.StringOffset = br.ReadUInt32(); + ret.ShapeMeshStartIndex = br.ReadStructures< UInt16 >( 3 ).ToArray(); + ret.ShapeMeshNum = br.ReadStructures< UInt16 >( 3 ).ToArray(); + return ret; + } + } + + public struct ShapeMeshStruct + { + public uint StartIndex; + public uint ShapeValueNum; + public uint ShapeValueOffset; + + public static ShapeMeshStruct Read( BinaryReader br ) + { + ShapeMeshStruct ret = new ShapeMeshStruct(); + ret.StartIndex = br.ReadUInt32(); + ret.ShapeValueNum = br.ReadUInt32(); + ret.ShapeValueOffset = br.ReadUInt32(); + return ret; + } + } + + public struct ShapeValueStruct + { + public ushort Offset; + public ushort Value; + + public static ShapeValueStruct Read( BinaryReader br ) + { + ShapeValueStruct ret = new ShapeValueStruct(); + ret.Offset = br.ReadUInt16(); + ret.Value = br.ReadUInt16(); + return ret; + } + } + + public struct BoundingBoxStruct + { + public float[] Min; + public float[] Max; + + public static BoundingBoxStruct Read( BinaryReader br ) + { + BoundingBoxStruct ret = new BoundingBoxStruct(); + ret.Min = br.ReadStructures< Single >( 4 ).ToArray(); + ret.Max = br.ReadStructures< Single >( 4 ).ToArray(); + return ret; + } + } + } +} \ No newline at end of file diff --git a/src/Lumina/Extensions/BinaryReaderExtensions.cs b/src/Lumina/Extensions/BinaryReaderExtensions.cs index 625a1d1b..8463c61f 100644 --- a/src/Lumina/Extensions/BinaryReaderExtensions.cs +++ b/src/Lumina/Extensions/BinaryReaderExtensions.cs @@ -92,6 +92,19 @@ public static string ReadStringOffsetData( this BinaryReader br, long offset ) return Encoding.UTF8.GetString( chars.ToArray(), 0, chars.Count ); } + + public static string ReadStringData( this BinaryReader br ) + { + var chars = new List< byte >(); + + byte current; + while( ( current = br.ReadByte() ) != 0 ) + { + chars.Add( current ); + } + + return Encoding.UTF8.GetString( chars.ToArray(), 0, chars.Count ); + } /// /// Seeks this BinaryReader's position to the given offset. Syntactic sugar. diff --git a/src/Lumina/Models/Model/Mesh.cs b/src/Lumina/Models/Model/Mesh.cs new file mode 100644 index 00000000..19c36b0b --- /dev/null +++ b/src/Lumina/Models/Model/Mesh.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Numerics; +using Lumina.Data.Parsing.Mdl; +using Lumina.Extensions; + +namespace Lumina.Models.Model { + public class Mesh { + + public enum MeshType + { + Main, + Water, + Shadow, + TerrainShadow, + VerticalFog, + LightShaft, + Glass, + MaterialChange, + CrestChange + } + + public Model Model { get; private set; } + public int MeshIndex { get; private set; } + public MeshType Type { get; private set; } + + public string[] Attributes { get; private set; } + + // TODO: build submeshes from the index data + // the information is available to build these, but it'd be nice + // to have access to Mesh objects with pre-read submesh meshes + // public Mesh[] SubmeshesAsMesh { get; private set; } + + public Submesh[] Submeshes { get; private set; } + + public ushort[] BoneTable { get; private set; } + + public Vertex[] Vertices; + public ushort[] Indices; + + // todo + // public Material Material { get; private set; } + + public Mesh( Model model, int index, MeshType type ) { + Model = model; + MeshIndex = index; + Type = type; + BuildMesh(); + } + + public void BuildMesh() { + ReadBoneTable(); + ReadIndices(); + ReadVertices(); + ReadSubmeshes(); + } + + private void ReadSubmeshes() + { + var currentMesh = Model.File.Meshes[ MeshIndex ]; + Submeshes = new Submesh[ currentMesh.SubMeshNum ]; + for( int i = 0; i < currentMesh.SubMeshNum; i++ ) + Submeshes[i] = new Submesh(Model, MeshIndex, i); + } + + //TODO is this necessary? + private void ReadBoneTable() + { + var currentMesh = Model.File.Meshes[ MeshIndex ]; + + // Copy over the bone table + int boneTableIndex = currentMesh.BoneTableIndex; + if (boneTableIndex != 255) + BoneTable = Model.File.BoneTables[ boneTableIndex ].BoneIndex; + } + + private void ReadIndices() + { + var currentMesh = Model.File.Meshes[ MeshIndex ]; + + // Read indices because they're easy + BinaryReader reader = new BinaryReader( new MemoryStream( Model.File.IndexBuffers[ (int) Model.Lod ] ) ); + reader.Seek( currentMesh.StartIndex * 2 ); + Indices = reader.ReadStructures< UInt16 >( (int) currentMesh.IndexNum ).ToArray(); + } + + private void ReadVertices() + { + MdlStructs.MeshStruct currentMesh = Model.File.Meshes[ MeshIndex ]; + + // Vertex reading stuff + MdlStructs.VertexDeclarationStruct currentDecl = Model.File.VertexDeclarations[ MeshIndex ]; + + // We have to go through these in order by offset with this implementation + // TODO is this slow? this sort's purpose is to avoid copying every vertex out + var orderedElements = currentDecl.VertexElements.ToList(); + orderedElements.Sort( ( e1, e2 ) => e1.Offset.CompareTo( e2.Offset ) ); + Vertices = new Vertex[currentMesh.VertexNum]; + + // Vertices may be defined across at most 3 streams defined by a Mesh's VertexDeclarations + var vertexStreamReader = new BinaryReader[3]; + for( int i = 0; i < 3; i++ ) { + vertexStreamReader[ i ] = new BinaryReader( new MemoryStream( Model.File.VertexBuffers[ (int) Model.Lod ] ) ); + vertexStreamReader[ i ].Seek( currentMesh.VertexBufferOffset[ i ] ); + } + + for( int i = 0; i < Vertices.Length; i++ ) { + Vertices[i] = new Vertex(); + + foreach( var element in orderedElements ) + SetElementField( ref Vertices[i], element, vertexStreamReader[element.Stream] ); + } + } + + private void SetElementField( ref Vertex v, MdlStructs.VertexElement element, BinaryReader br ) { + var type = (Vertex.VertexType) element.Type; + var usage = (Vertex.VertexUsage) element.Usage; + + object data = type switch { + Vertex.VertexType.Single3 => new Vector3( br.ReadSingle(), br.ReadSingle(), br.ReadSingle() ), + Vertex.VertexType.Single4 => new Vector4( br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle() ), + Vertex.VertexType.UInt => br.ReadBytes( 4 ), + Vertex.VertexType.ByteFloat4 => new Vector4( br.ReadByte() / 255f, br.ReadByte() / 255f, br.ReadByte() / 255f, br.ReadByte() / 255f ), + Vertex.VertexType.Half2 => new Vector2( br.ReadUInt16().Unpack(), br.ReadUInt16().Unpack() ), + Vertex.VertexType.Half4 => new Vector4( br.ReadUInt16().Unpack(), br.ReadUInt16().Unpack(), + br.ReadUInt16().Unpack(), br.ReadUInt16().Unpack() ), + _ => throw new ArgumentOutOfRangeException() + }; + + switch( usage ) { + case Vertex.VertexUsage.Position: v.Position = GetVector4(data); break; + case Vertex.VertexUsage.BlendWeights: v.BlendWeights = GetVector4(data); break; + case Vertex.VertexUsage.BlendIndices: v.BlendIndices = (byte[]) data; break; + case Vertex.VertexUsage.Normal: v.Normal = GetVector3(data); break; + case Vertex.VertexUsage.UV: v.UV = GetVector4(data); break; + case Vertex.VertexUsage.Tangent2: v.Tangent2 = GetVector4(data); break; + case Vertex.VertexUsage.Tangent1: v.Tangent1 = GetVector4(data); break; + case Vertex.VertexUsage.Color: v.Color = GetVector4(data); break; + default: throw new ArgumentOutOfRangeException(); + } + } + + private static Vector3 GetVector3( object value ) { + switch( value ) { + case Vector2 v2: return new Vector3( v2.X, v2.Y, 0 ); + case Vector3 v3: return v3; + case Vector4 v4: return new Vector3( v4.X, v4.Y, v4.Z ); + } + throw new ArgumentOutOfRangeException(); + } + + private static Vector4 GetVector4( object value ) { + switch( value ) { + case Vector2 v2: return new Vector4( v2.X, v2.Y, 0, 0 ); + case Vector3 v3: return new Vector4( v3.X, v3.Y, v3.Z, 1 ); + case Vector4 v4: return v4; + } + throw new ArgumentOutOfRangeException(); + } + } +} \ No newline at end of file diff --git a/src/Lumina/Models/Model/Model.cs b/src/Lumina/Models/Model/Model.cs new file mode 100644 index 00000000..03c269ea --- /dev/null +++ b/src/Lumina/Models/Model/Model.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using Lumina.Data.Files; +using Lumina.Data.Parsing.Mdl; +using Lumina.Extensions; + +namespace Lumina.Models.Model +{ + public class Model + { + public enum ModelLod + { + High, + Med, + Low + } + + public MdlFile File { get; private set; } + public ModelLod Lod { get; private set; } + public Mesh[] Meshes { get; private set; } + + public Dictionary Shapes { get; private set; } + + public Dictionary< int, string > StringOffsetToStringMap { get; private set; } + + public Model( MdlFile mdlFile, ModelLod lod = ModelLod.High ) + { + if( (uint) lod > mdlFile.FileHeader.LodNum ) + throw new ArgumentException( "The given model does not have the requested LoD.", nameof( lod ) ); + + File = mdlFile; + Lod = lod; + BuildModel(); + } + + public Model( Lumina lumina, string path, ModelLod lod = ModelLod.High ) + { + var mdlFile = lumina.GetFile< MdlFile >( path ); + File = mdlFile; + Lod = lod; + BuildModel(); + } + + private void BuildModel() + { + // Every valid MDL has VertexDeclarations + if( File.VertexDeclarations == null ) + { + Console.WriteLine( "BuildModel() called on an empty MdlFile." ); + return; + } + + ReadStrings(); + ReadMeshes(); + ReadShapes(); + } + + private void ReadShapes() + { + + } + + private void ReadStrings() + { + // If we save the offset and the string, we can access it later via the offset without reading + StringOffsetToStringMap = new Dictionary< int, string >(); + var br = new BinaryReader( new MemoryStream( File.Strings ) ); + for( int i = 0; i < File.StringCount; i++ ) + { + long startOffset = br.BaseStream.Position; + string tmp = br.ReadStringData(); + StringOffsetToStringMap[ (int) startOffset ] = tmp; + } + } + + private void ReadMeshes() + { + // We have to count how many of each mesh there are + // I think this is a constant order, and this is the correct order + MdlStructs.LodStruct CurrentLod = File.Lods[ (int) Lod ]; + + int totalMeshes = CurrentLod.MeshNum; + totalMeshes += CurrentLod.WaterMeshNum; + totalMeshes += CurrentLod.ShadowMeshNum; + totalMeshes += CurrentLod.TerrainShadowMeshNum; + totalMeshes += CurrentLod.VerticalFogMeshNum; + + if( File.ModelHeader.ExtraLodEnabled ) + { + MdlStructs.ExtraLodStruct ExtraLod = File.ExtraLods[ (int) Lod ]; + totalMeshes += ExtraLod.LightShaftMeshNum; + totalMeshes += ExtraLod.GlassMeshNum; + totalMeshes += ExtraLod.MaterialChangeMeshNum; + totalMeshes += ExtraLod.CrestChangeMeshNum; + } + + Meshes = new Mesh[totalMeshes]; + + for( int i = 0; i < Meshes.Length; i++ ) + { + Meshes[ i ] = new Mesh( this, i, GetMeshType( i ) ); + } + } + + private Mesh.MeshType GetMeshType( int index ) + { + // I could create arrays for the ranges for cleaner code, but all this is doing is checking ranges + + if( index > File.Lods[ (int) Lod ].MeshIndex && index < File.Lods[ (int) Lod ].MeshIndex + File.Lods[ (int) Lod ].MeshNum ) + return Mesh.MeshType.Main; + if( index > File.Lods[ (int) Lod ].WaterMeshIndex && index < File.Lods[ (int) Lod ].WaterMeshIndex + File.Lods[ (int) Lod ].WaterMeshNum ) + return Mesh.MeshType.Water; + if( index > File.Lods[ (int) Lod ].ShadowMeshIndex && index < File.Lods[ (int) Lod ].ShadowMeshIndex + File.Lods[ (int) Lod ].ShadowMeshNum ) + return Mesh.MeshType.Shadow; + if( index > File.Lods[ (int) Lod ].TerrainShadowMeshIndex && + index < File.Lods[ (int) Lod ].TerrainShadowMeshIndex + File.Lods[ (int) Lod ].TerrainShadowMeshNum ) + return Mesh.MeshType.TerrainShadow; + if( index > File.Lods[ (int) Lod ].VerticalFogMeshIndex && + index < File.Lods[ (int) Lod ].VerticalFogMeshIndex + File.Lods[ (int) Lod ].VerticalFogMeshNum ) + return Mesh.MeshType.VerticalFog; + + if( File.ModelHeader.ExtraLodEnabled ) + { + if( index > File.ExtraLods[ (int) Lod ].LightShaftMeshIndex && + index < File.ExtraLods[ (int) Lod ].LightShaftMeshIndex + File.ExtraLods[ (int) Lod ].LightShaftMeshNum ) + return Mesh.MeshType.LightShaft; + if( index > File.ExtraLods[ (int) Lod ].GlassMeshIndex && + index < File.ExtraLods[ (int) Lod ].GlassMeshIndex + File.ExtraLods[ (int) Lod ].GlassMeshNum ) + return Mesh.MeshType.Glass; + if( index > File.ExtraLods[ (int) Lod ].MaterialChangeMeshIndex && + index < File.ExtraLods[ (int) Lod ].MaterialChangeMeshIndex + File.ExtraLods[ (int) Lod ].MaterialChangeMeshNum ) + return Mesh.MeshType.MaterialChange; + if( index > File.ExtraLods[ (int) Lod ].CrestChangeMeshIndex && + index < File.ExtraLods[ (int) Lod ].CrestChangeMeshIndex + File.ExtraLods[ (int) Lod ].CrestChangeMeshNum ) + return Mesh.MeshType.CrestChange; + } + + return Mesh.MeshType.Main; + } + + public Mesh[] GetMeshesByType( Mesh.MeshType mp ) + { + return Meshes.Where( m => m.Type == mp ).ToArray(); + } + + /// + /// Writes an entire Model into a single Wavefront Object file. + /// This was meant for testing, but doesn't hurt to stay. + /// + /// A string containing the entire .obj file, including newlines. + public string GetObjectFileText() + { + string nl = Environment.NewLine; + + var vsList = new List< Vector3 >(); + var vcList = new List< Vector4 >(); + var vtList = new List< Vector4 >(); + var nmList = new List< Vector3 >(); + var inList = new List< Vector3 >(); + + int indexModifier = 1; + for( int i = 0; i < Meshes.Length; i++ ) + { + if( i != 0 ) + indexModifier += Meshes[i - 1].Vertices.Length; + foreach( var vert in Meshes[ i ].Vertices ) + { + Vector3 vs = new Vector3(); + Vector4 vc = new Vector4(); + Vector4 vt = new Vector4(); + Vector3 nm = new Vector3(); + + vs.X = vert.Position.Value.X; + vs.Y = vert.Position.Value.Y; + vs.Z = vert.Position.Value.Z; + + if( vert.Color == null ) + { + vc.X = 0; + vc.Y = 0; + vc.W = 0; + vc.Z = 0; + } + else + { + vc.X = vert.Color.Value.X; + vc.Y = vert.Color.Value.Y; + vc.W = vert.Color.Value.W; + vc.Z = vert.Color.Value.Z; + } + + if( vert.UV.HasValue ) + { + vt.X = vert.UV.Value.X; + vt.Y = -1 * vert.UV.Value.Y; + vt.W = ( vert.UV.Value.W == 0 ) ? vt.X : vert.UV.Value.W; + vt.Z = ( vert.UV.Value.Z == 0 ) ? vt.Y : vert.UV.Value.Z; + } + + if( vert.Normal.HasValue ) + { + nm.X = vert.Normal.Value.X; + nm.Y = vert.Normal.Value.Y; + nm.Z = vert.Normal.Value.Z; + } + + vsList.Add( vs ); + vcList.Add( vc ); + vtList.Add( vt ); + nmList.Add( nm ); + } + + for( int j = 0; j < Meshes[ i ].Indices.Length; j += 3 ) + { + Vector3 ind = new Vector3 + { + X = Meshes[ i ].Indices[ j + 0 ] + indexModifier, + Y = Meshes[ i ].Indices[ j + 1 ] + indexModifier, + Z = Meshes[ i ].Indices[ j + 2 ] + indexModifier + }; + inList.Add( ind ); + } + } + + var sb = new StringBuilder(); + foreach( var vert in vsList ) + sb.Append( $"v {(decimal) vert.X} {(decimal) vert.Y} {(decimal) vert.Z}{nl}" ); + + foreach( var color in vcList ) + sb.Append( $"vc {(decimal) color.W} {(decimal) color.X} {(decimal) color.Y} {(decimal) color.Z}{nl}" ); + + foreach( var texCoord in vtList ) + sb.Append( $"vt {(decimal) texCoord.X} {(decimal) texCoord.Y} {(decimal) texCoord.W} {(decimal) texCoord.Z}{nl}" ); + + foreach( var norm in nmList ) + sb.Append( $"vn {(decimal) norm.X} {(decimal) norm.Y} {(decimal) norm.Z}{nl}" ); + + foreach( var ind in inList ) + { + sb.Append( String.Format( "f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}{3}", + (ushort) ind.X, + (ushort) ind.Y, + (ushort) ind.Z, + nl ) ); + } + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Lumina/Models/Model/Shape.cs b/src/Lumina/Models/Model/Shape.cs new file mode 100644 index 00000000..6c538caf --- /dev/null +++ b/src/Lumina/Models/Model/Shape.cs @@ -0,0 +1,15 @@ +namespace Lumina.Models.Model +{ + public class Shape + { + public string ShapeName { get; private set; } + + public Shape(Model model, int shapeIndex) + { + var currentShape = model.File.Shapes[ shapeIndex ]; + ShapeName = model.StringOffsetToStringMap[ (int) currentShape.StringOffset ]; + + + } + } +} \ No newline at end of file diff --git a/src/Lumina/Models/Model/Submesh.cs b/src/Lumina/Models/Model/Submesh.cs new file mode 100644 index 00000000..5c771f5c --- /dev/null +++ b/src/Lumina/Models/Model/Submesh.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Lumina.Data.Parsing.Mdl; + +namespace Lumina.Models.Model +{ + public class Submesh + { + public uint IndexOffset; + public uint IndexNum; + public string[] Attributes; + public string[] Bones; + + public Submesh( Model model, int meshIndex, int subMeshIndex ) + { + var currentMesh = model.File.Meshes[ meshIndex ]; + int subMeshListIndex = currentMesh.SubMeshIndex + subMeshIndex; + var currentSubMesh = model.File.Submeshes[ subMeshListIndex ]; + + IndexOffset = currentSubMesh.IndexOffset; + IndexNum = currentSubMesh.IndexNum; + + // AttributeIndexMask is a bit-based index mask + // i.e. "5" is 0101 so it applies attrs 0 and 2 + var attributeList = new List< string >(); + for( int i = 0; i < 32; i++ ) + { + if( ( ( 1 << i ) & currentSubMesh.AttributeIndexMask ) > 0 ) + { + uint nameOffset = model.File.AttributeNameOffsets[ i ]; + attributeList.Add( model.StringOffsetToStringMap[ (int) nameOffset ] ); + } + } + + Attributes = attributeList.ToArray(); + + // I don't know what this is for + if( currentSubMesh.BoneStartIndex == 65535 ) return; + var affectedBoneTable = new List< string >(); + int boneEndIndex = currentSubMesh.BoneStartIndex + currentSubMesh.BoneNum; + for( int i = currentSubMesh.BoneStartIndex; i < boneEndIndex; i++ ) + { + var boneIndex = model.File.SubmeshBoneMap[ i ]; + var boneOffset = model.File.BoneNameOffsets[ boneIndex ]; + string boneName = model.StringOffsetToStringMap[ (int) boneOffset ]; + affectedBoneTable.Add( boneName ); + } + + Bones = affectedBoneTable.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Lumina/Models/Model/Vertex.cs b/src/Lumina/Models/Model/Vertex.cs new file mode 100644 index 00000000..f8585249 --- /dev/null +++ b/src/Lumina/Models/Model/Vertex.cs @@ -0,0 +1,42 @@ +using System.IO; +using System.Linq; +using System.Numerics; +using Lumina.Data.Files; +using Lumina.Data.Parsing.Mdl; + +namespace Lumina.Models.Model { + + public struct Vertex { + + public enum VertexType : byte { + Single3 = 2, + Single4 = 3, + UInt = 5, + ByteFloat4 = 8, + Half2 = 13, + Half4 = 14 + } + + public enum VertexUsage : byte { + Position = 0, + BlendWeights = 1, + BlendIndices = 2, + Normal = 3, + UV = 4, + Tangent2 = 5, + Tangent1 = 6, + Color = 7, + } + + public static int[] ElementSizes = {0, 0, 12, 16, 4, 4, 4, 8}; + + public Vector4? Position; + public Vector4? BlendWeights; + public byte[] BlendIndices; + public Vector3? Normal; + public Vector4? UV; + public Vector4? Color; + public Vector4? Tangent2; + public Vector4? Tangent1; + } +} \ No newline at end of file From 00aef2e03d31272b4b62778f8d6fc47ac685c94f Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 14 Feb 2021 03:47:57 -0500 Subject: [PATCH 2/9] num -> count globally, fix attribute bug --- src/Lumina/Data/Files/MdlFile.cs | 50 +++---- src/Lumina/Data/Parsing/Mdl/MdlStructs.cs | 156 +++++++++++----------- src/Lumina/Models/Model/Mesh.cs | 8 +- src/Lumina/Models/Model/Model.cs | 38 +++--- src/Lumina/Models/Model/Submesh.cs | 6 +- 5 files changed, 129 insertions(+), 129 deletions(-) diff --git a/src/Lumina/Data/Files/MdlFile.cs b/src/Lumina/Data/Files/MdlFile.cs index 95c3bf5a..2059ea69 100644 --- a/src/Lumina/Data/Files/MdlFile.cs +++ b/src/Lumina/Data/Files/MdlFile.cs @@ -50,8 +50,8 @@ public override void LoadFile() FileHeader = ModelFileHeader.Read( Reader ); - VertexDeclarations = new VertexDeclarationStruct[FileHeader.VertexDeclarationNum]; - for( int i = 0; i < FileHeader.VertexDeclarationNum; i++ ) VertexDeclarations[ i ] = VertexDeclarationStruct.Read( Reader ); + VertexDeclarations = new VertexDeclarationStruct[FileHeader.VertexDeclarationCount]; + for( int i = 0; i < FileHeader.VertexDeclarationCount; i++ ) VertexDeclarations[ i ] = VertexDeclarationStruct.Read( Reader ); StringCount = Reader.ReadUInt16(); Reader.ReadUInt16(); @@ -59,22 +59,22 @@ public override void LoadFile() Strings = Reader.ReadBytes( (int) stringSize ); ModelHeader = ModelHeader.Read( Reader ); - ElementIds = new ElementIdStruct[ModelHeader.ElementIdNum]; + ElementIds = new ElementIdStruct[ModelHeader.ElementIdCount]; Lods = new LodStruct[3]; - Meshes = new MeshStruct[ModelHeader.MeshNum]; - Submeshes = new SubmeshStruct[ModelHeader.SubmeshNum]; - TerrainShadowMeshes = new TerrainShadowMeshStruct[ModelHeader.TerrainShadowMeshNum]; - TerrainShadowSubmeshes = new TerrainShadowSubmeshStruct[ModelHeader.TerrainShadowSubmeshNum]; - BoneTables = new BoneTableStruct[ModelHeader.BoneTableNum]; - Shapes = new ShapeStruct[ModelHeader.ShapeNum]; - ShapeMeshes = new ShapeMeshStruct[ModelHeader.ShapeMeshNum]; - ShapeValues = new ShapeValueStruct[ModelHeader.ShapeValueNum]; - BoneBoundingBoxes = new BoundingBoxStruct[ModelHeader.BoneNum]; + Meshes = new MeshStruct[ModelHeader.MeshCount]; + Submeshes = new SubmeshStruct[ModelHeader.SubmeshCount]; + TerrainShadowMeshes = new TerrainShadowMeshStruct[ModelHeader.TerrainShadowMeshCount]; + TerrainShadowSubmeshes = new TerrainShadowSubmeshStruct[ModelHeader.TerrainShadowSubmeshCount]; + BoneTables = new BoneTableStruct[ModelHeader.BoneTableCount]; + Shapes = new ShapeStruct[ModelHeader.ShapeCount]; + ShapeMeshes = new ShapeMeshStruct[ModelHeader.ShapeMeshCount]; + ShapeValues = new ShapeValueStruct[ModelHeader.ShapeValueCount]; + BoneBoundingBoxes = new BoundingBoxStruct[ModelHeader.BoneCount]; VertexBuffers = new byte[3][]; IndexBuffers = new byte[3][]; - for( int i = 0; i < ModelHeader.ElementIdNum; i++ ) ElementIds[ i ] = ElementIdStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.ElementIdCount; i++ ) ElementIds[ i ] = ElementIdStruct.Read( Reader ); for( int i = 0; i < 3; i++ ) Lods[ i ] = LodStruct.Read( Reader ); if( ModelHeader.ExtraLodEnabled ) { @@ -82,19 +82,19 @@ public override void LoadFile() for( int i = 0; i < 3; i++ ) ExtraLods[ i ] = ExtraLodStruct.Read( Reader ); } - for( int i = 0; i < ModelHeader.MeshNum; i++ ) Meshes[ i ] = MeshStruct.Read( Reader ); - AttributeNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.AttributeNum ).ToArray(); - for( int i = 0; i < ModelHeader.SubmeshNum; i++ ) Submeshes[ i ] = SubmeshStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.TerrainShadowMeshNum; i++ ) TerrainShadowMeshes[ i ] = TerrainShadowMeshStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.TerrainShadowSubmeshNum; i++ ) TerrainShadowSubmeshes[ i ] = TerrainShadowSubmeshStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.MeshCount; i++ ) Meshes[ i ] = MeshStruct.Read( Reader ); + AttributeNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.AttributeCount ).ToArray(); + for( int i = 0; i < ModelHeader.SubmeshCount; i++ ) Submeshes[ i ] = SubmeshStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.TerrainShadowMeshCount; i++ ) TerrainShadowMeshes[ i ] = TerrainShadowMeshStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.TerrainShadowSubmeshCount; i++ ) TerrainShadowSubmeshes[ i ] = TerrainShadowSubmeshStruct.Read( Reader ); - MaterialNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.MaterialNum ).ToArray(); - BoneNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.BoneNum ).ToArray(); - for( int i = 0; i < ModelHeader.BoneTableNum; i++ ) BoneTables[ i ] = BoneTableStruct.Read( Reader ); + MaterialNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.MaterialCount ).ToArray(); + BoneNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.BoneCount ).ToArray(); + for( int i = 0; i < ModelHeader.BoneTableCount; i++ ) BoneTables[ i ] = BoneTableStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.ShapeNum; i++ ) Shapes[ i ] = ShapeStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.ShapeMeshNum; i++ ) ShapeMeshes[ i ] = ShapeMeshStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.ShapeValueNum; i++ ) ShapeValues[ i ] = ShapeValueStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.ShapeCount; i++ ) Shapes[ i ] = ShapeStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.ShapeMeshCount; i++ ) ShapeMeshes[ i ] = ShapeMeshStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.ShapeValueCount; i++ ) ShapeValues[ i ] = ShapeValueStruct.Read( Reader ); uint submeshBoneMapSize = Reader.ReadUInt32(); SubmeshBoneMap = Reader.ReadStructures< UInt16 >( (int) submeshBoneMapSize / 2 ).ToArray(); @@ -107,7 +107,7 @@ public override void LoadFile() ModelBoundingBoxes = BoundingBoxStruct.Read( Reader ); WaterBoundingBoxes = BoundingBoxStruct.Read( Reader ); VerticalFogBoundingBoxes = BoundingBoxStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.BoneNum; i++ ) BoneBoundingBoxes[ i ] = BoundingBoxStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.BoneCount; i++ ) BoneBoundingBoxes[ i ] = BoundingBoxStruct.Read( Reader ); for( int i = 0; i < 3; i++ ) { Reader.BaseStream.Position = FileHeader.VertexOffset[ i ]; diff --git a/src/Lumina/Data/Parsing/Mdl/MdlStructs.cs b/src/Lumina/Data/Parsing/Mdl/MdlStructs.cs index 6d3dda38..f5e48270 100644 --- a/src/Lumina/Data/Parsing/Mdl/MdlStructs.cs +++ b/src/Lumina/Data/Parsing/Mdl/MdlStructs.cs @@ -14,13 +14,13 @@ public struct ModelFileHeader public uint Version; public uint StackSize; public uint RuntimeSize; - public ushort VertexDeclarationNum; - public ushort MaterialNum; + public ushort VertexDeclarationCount; + public ushort MaterialCount; public uint[] VertexOffset; public uint[] IndexOffset; public uint[] VertexBufferSize; public uint[] IndexBufferSize; - public byte LodNum; + public byte LodCount; public bool EnableIndexBufferStreaming; public bool EnableEdgeGeometry; private byte Padding; @@ -31,13 +31,13 @@ public static ModelFileHeader Read( BinaryReader br ) ret.Version = br.ReadUInt32(); ret.StackSize = br.ReadUInt32(); ret.RuntimeSize = br.ReadUInt32(); - ret.VertexDeclarationNum = br.ReadUInt16(); - ret.MaterialNum = br.ReadUInt16(); + ret.VertexDeclarationCount = br.ReadUInt16(); + ret.MaterialCount = br.ReadUInt16(); ret.VertexOffset = br.ReadStructures< UInt32 >( 3 ).ToArray(); ret.IndexOffset = br.ReadStructures< UInt32 >( 3 ).ToArray(); ret.VertexBufferSize = br.ReadStructures< UInt32 >( 3 ).ToArray(); ret.IndexBufferSize = br.ReadStructures< UInt32 >( 3 ).ToArray(); - ret.LodNum = br.ReadByte(); + ret.LodCount = br.ReadByte(); ret.EnableIndexBufferStreaming = br.ReadBoolean(); ret.EnableEdgeGeometry = br.ReadBoolean(); ret.Padding = br.ReadByte(); @@ -103,16 +103,16 @@ public struct ModelHeader { // MeshHeader public float Radius; - public ushort MeshNum; - public ushort AttributeNum; - public ushort SubmeshNum; - public ushort MaterialNum; - public ushort BoneNum; - public ushort BoneTableNum; - public ushort ShapeNum; - public ushort ShapeMeshNum; - public ushort ShapeValueNum; - public byte LodNum; + public ushort MeshCount; + public ushort AttributeCount; + public ushort SubmeshCount; + public ushort MaterialCount; + public ushort BoneCount; + public ushort BoneTableCount; + public ushort ShapeCount; + public ushort ShapeMeshCount; + public ushort ShapeValueCount; + public byte LodCount; private byte bitfield1; @@ -124,8 +124,8 @@ public struct ModelHeader public bool WavingAnimationDisabled; public bool LightShadowDisabled; public bool ShadowDisabled; - public ushort ElementIdNum; - public byte TerrainShadowMeshNum; + public ushort ElementIdCount; + public byte TerrainShadowMeshCount; private byte bitfield2; @@ -141,7 +141,7 @@ public struct ModelHeader public float ModelClipOutDistance; public float ShadowClipOutDistance; public ushort Unknown4; - public ushort TerrainShadowSubmeshNum; + public ushort TerrainShadowSubmeshCount; private byte Unknown5; @@ -157,16 +157,16 @@ public static ModelHeader Read( BinaryReader br ) { ModelHeader ret = new ModelHeader(); ret.Radius = br.ReadSingle(); - ret.MeshNum = br.ReadUInt16(); - ret.AttributeNum = br.ReadUInt16(); - ret.SubmeshNum = br.ReadUInt16(); - ret.MaterialNum = br.ReadUInt16(); - ret.BoneNum = br.ReadUInt16(); - ret.BoneTableNum = br.ReadUInt16(); - ret.ShapeNum = br.ReadUInt16(); - ret.ShapeMeshNum = br.ReadUInt16(); - ret.ShapeValueNum = br.ReadUInt16(); - ret.LodNum = br.ReadByte(); + ret.MeshCount = br.ReadUInt16(); + ret.AttributeCount = br.ReadUInt16(); + ret.SubmeshCount = br.ReadUInt16(); + ret.MaterialCount = br.ReadUInt16(); + ret.BoneCount = br.ReadUInt16(); + ret.BoneTableCount = br.ReadUInt16(); + ret.ShapeCount = br.ReadUInt16(); + ret.ShapeMeshCount = br.ReadUInt16(); + ret.ShapeValueCount = br.ReadUInt16(); + ret.LodCount = br.ReadByte(); ret.bitfield1 = br.ReadByte(); ret.DustOcclusionEnabled = ( ret.bitfield1 & 0x80 ) == 0x80; @@ -178,8 +178,8 @@ public static ModelHeader Read( BinaryReader br ) ret.LightShadowDisabled = ( ret.bitfield1 & 0x02 ) == 0x02; ret.ShadowDisabled = ( ret.bitfield1 & 0x01 ) == 0x01; - ret.ElementIdNum = br.ReadUInt16(); - ret.TerrainShadowMeshNum = br.ReadByte(); + ret.ElementIdCount = br.ReadUInt16(); + ret.TerrainShadowMeshCount = br.ReadByte(); ret.bitfield2 = br.ReadByte(); ret.Unknown2 = ( ret.bitfield2 & 0x80 ) == 0x80; @@ -194,7 +194,7 @@ public static ModelHeader Read( BinaryReader br ) ret.ModelClipOutDistance = br.ReadSingle(); ret.ShadowClipOutDistance = br.ReadSingle(); ret.Unknown4 = br.ReadUInt16(); - ret.TerrainShadowSubmeshNum = br.ReadUInt16(); + ret.TerrainShadowSubmeshCount = br.ReadUInt16(); ret.Unknown5 = br.ReadByte(); ret.BGChangeMaterialIndex = br.ReadByte(); ret.BGCrestChangeMaterialIndex = br.ReadByte(); @@ -228,21 +228,21 @@ public static ElementIdStruct Read( BinaryReader br ) public struct LodStruct { public ushort MeshIndex; - public ushort MeshNum; + public ushort MeshCount; public float ModelLodRange; public float TextureLodRange; public ushort WaterMeshIndex; - public ushort WaterMeshNum; + public ushort WaterMeshCount; public ushort ShadowMeshIndex; - public ushort ShadowMeshNum; + public ushort ShadowMeshCount; public ushort TerrainShadowMeshIndex; - public ushort TerrainShadowMeshNum; + public ushort TerrainShadowMeshCount; public ushort VerticalFogMeshIndex; - public ushort VerticalFogMeshNum; + public ushort VerticalFogMeshCount; // Yell at me if this ever exists on Win32 public uint EdgeGeometrySize; public uint EdgeGeometryDataOffset; - public uint PolygonNum; + public uint PolygonCount; public uint Unknown1; public uint VertexBufferSize; public uint IndexBufferSize; @@ -253,20 +253,20 @@ public static LodStruct Read( BinaryReader br ) { LodStruct ret = new LodStruct(); ret.MeshIndex = br.ReadUInt16(); - ret.MeshNum = br.ReadUInt16(); + ret.MeshCount = br.ReadUInt16(); ret.ModelLodRange = br.ReadSingle(); ret.TextureLodRange = br.ReadSingle(); ret.WaterMeshIndex = br.ReadUInt16(); - ret.WaterMeshNum = br.ReadUInt16(); + ret.WaterMeshCount = br.ReadUInt16(); ret.ShadowMeshIndex = br.ReadUInt16(); - ret.ShadowMeshNum = br.ReadUInt16(); + ret.ShadowMeshCount = br.ReadUInt16(); ret.TerrainShadowMeshIndex = br.ReadUInt16(); - ret.TerrainShadowMeshNum = br.ReadUInt16(); + ret.TerrainShadowMeshCount = br.ReadUInt16(); ret.VerticalFogMeshIndex = br.ReadUInt16(); - ret.VerticalFogMeshNum = br.ReadUInt16(); + ret.VerticalFogMeshCount = br.ReadUInt16(); ret.EdgeGeometrySize = br.ReadUInt32(); ret.EdgeGeometryDataOffset = br.ReadUInt32(); - ret.PolygonNum = br.ReadUInt32(); + ret.PolygonCount = br.ReadUInt32(); ret.Unknown1 = br.ReadUInt32(); ret.VertexBufferSize = br.ReadUInt32(); ret.IndexBufferSize = br.ReadUInt32(); @@ -279,13 +279,13 @@ public static LodStruct Read( BinaryReader br ) public struct ExtraLodStruct { public ushort LightShaftMeshIndex; - public ushort LightShaftMeshNum; + public ushort LightShaftMeshCount; public ushort GlassMeshIndex; - public ushort GlassMeshNum; + public ushort GlassMeshCount; public ushort MaterialChangeMeshIndex; - public ushort MaterialChangeMeshNum; + public ushort MaterialChangeMeshCount; public ushort CrestChangeMeshIndex; - public ushort CrestChangeMeshNum; + public ushort CrestChangeMeshCount; public ushort Unknown1; public ushort Unknown2; public ushort Unknown3; @@ -303,13 +303,13 @@ public static ExtraLodStruct Read( BinaryReader br ) { ExtraLodStruct ret = new ExtraLodStruct(); ret.LightShaftMeshIndex = br.ReadUInt16(); - ret.LightShaftMeshNum = br.ReadUInt16(); + ret.LightShaftMeshCount = br.ReadUInt16(); ret.GlassMeshIndex = br.ReadUInt16(); - ret.GlassMeshNum = br.ReadUInt16(); + ret.GlassMeshCount = br.ReadUInt16(); ret.MaterialChangeMeshIndex = br.ReadUInt16(); - ret.MaterialChangeMeshNum = br.ReadUInt16(); + ret.MaterialChangeMeshCount = br.ReadUInt16(); ret.CrestChangeMeshIndex = br.ReadUInt16(); - ret.CrestChangeMeshNum = br.ReadUInt16(); + ret.CrestChangeMeshCount = br.ReadUInt16(); ret.Unknown1 = br.ReadUInt16(); ret.Unknown2 = br.ReadUInt16(); ret.Unknown3 = br.ReadUInt16(); @@ -328,33 +328,33 @@ public static ExtraLodStruct Read( BinaryReader br ) public struct MeshStruct { - public ushort VertexNum; + public ushort VertexCount; private ushort padding; - public uint IndexNum; + public uint IndexCount; public ushort MaterialIndex; public ushort SubMeshIndex; - public ushort SubMeshNum; + public ushort SubMeshCount; public ushort BoneTableIndex; public uint StartIndex; public uint[] VertexBufferOffset; public byte[] VertexBufferStride; - public byte VertexStreamNum; + public byte VertexStreamCount; public static MeshStruct Read( BinaryReader br ) { MeshStruct ret = new MeshStruct(); - ret.VertexNum = br.ReadUInt16(); + ret.VertexCount = br.ReadUInt16(); ret.padding = br.ReadUInt16(); - ret.IndexNum = br.ReadUInt32(); + ret.IndexCount = br.ReadUInt32(); ret.MaterialIndex = br.ReadUInt16(); ret.SubMeshIndex = br.ReadUInt16(); - ret.SubMeshNum = br.ReadUInt16(); + ret.SubMeshCount = br.ReadUInt16(); ret.BoneTableIndex = br.ReadUInt16(); ret.StartIndex = br.ReadUInt32(); ret.VertexBufferOffset = br.ReadStructures< UInt32 >( 3 ).ToArray(); ret.VertexBufferStride = br.ReadBytes( 3 ); - ret.VertexStreamNum = br.ReadByte(); + ret.VertexStreamCount = br.ReadByte(); return ret; } } @@ -362,43 +362,43 @@ public static MeshStruct Read( BinaryReader br ) public struct SubmeshStruct { public uint IndexOffset; - public uint IndexNum; + public uint IndexCount; public uint AttributeIndexMask; public ushort BoneStartIndex; - public ushort BoneNum; + public ushort BoneCount; public static SubmeshStruct Read( BinaryReader br ) { SubmeshStruct ret = new SubmeshStruct(); ret.IndexOffset = br.ReadUInt32(); - ret.IndexNum = br.ReadUInt32(); + ret.IndexCount = br.ReadUInt32(); ret.AttributeIndexMask = br.ReadUInt32(); ret.BoneStartIndex = br.ReadUInt16(); - ret.BoneNum = br.ReadUInt16(); + ret.BoneCount = br.ReadUInt16(); return ret; } } public struct TerrainShadowMeshStruct { - public uint IndexNum; + public uint IndexCount; public uint StartIndex; public uint VertexBufferOffset; - public ushort VertexNum; + public ushort VertexCount; public ushort SubMeshIndex; - public ushort SubMeshNum; + public ushort SubMeshCount; public byte VertexBufferStride; private byte Padding; public static TerrainShadowMeshStruct Read( BinaryReader br ) { TerrainShadowMeshStruct ret = new TerrainShadowMeshStruct(); - ret.IndexNum = br.ReadUInt32(); + ret.IndexCount = br.ReadUInt32(); ret.StartIndex = br.ReadUInt32(); ret.VertexBufferOffset = br.ReadUInt32(); - ret.VertexNum = br.ReadUInt16(); + ret.VertexCount = br.ReadUInt16(); ret.SubMeshIndex = br.ReadUInt16(); - ret.SubMeshNum = br.ReadUInt16(); + ret.SubMeshCount = br.ReadUInt16(); ret.VertexBufferStride = br.ReadByte(); ret.Padding = br.ReadByte(); return ret; @@ -408,7 +408,7 @@ public static TerrainShadowMeshStruct Read( BinaryReader br ) public struct TerrainShadowSubmeshStruct { public uint IndexOffset; - public uint IndexNum; + public uint IndexCount; public ushort Unknown1; public ushort Unknown2; @@ -416,7 +416,7 @@ public static TerrainShadowSubmeshStruct Read( BinaryReader br ) { TerrainShadowSubmeshStruct ret = new TerrainShadowSubmeshStruct(); ret.IndexOffset = br.ReadUInt32(); - ret.IndexNum = br.ReadUInt32(); + ret.IndexCount = br.ReadUInt32(); ret.Unknown1 = br.ReadUInt16(); ret.Unknown2 = br.ReadUInt16(); return ret; @@ -426,14 +426,14 @@ public static TerrainShadowSubmeshStruct Read( BinaryReader br ) public struct BoneTableStruct { public ushort[] BoneIndex; - public byte BoneNum; + public byte BoneCount; private byte[] Padding; public static BoneTableStruct Read( BinaryReader br ) { BoneTableStruct ret = new BoneTableStruct(); ret.BoneIndex = br.ReadStructures< UInt16 >( 64 ).ToArray(); - ret.BoneNum = br.ReadByte(); + ret.BoneCount = br.ReadByte(); ret.Padding = br.ReadBytes( 3 ); return ret; } @@ -443,14 +443,14 @@ public struct ShapeStruct { public uint StringOffset; public ushort[] ShapeMeshStartIndex; - public ushort[] ShapeMeshNum; + public ushort[] ShapeMeshCount; public static ShapeStruct Read( BinaryReader br ) { ShapeStruct ret = new ShapeStruct(); ret.StringOffset = br.ReadUInt32(); ret.ShapeMeshStartIndex = br.ReadStructures< UInt16 >( 3 ).ToArray(); - ret.ShapeMeshNum = br.ReadStructures< UInt16 >( 3 ).ToArray(); + ret.ShapeMeshCount = br.ReadStructures< UInt16 >( 3 ).ToArray(); return ret; } } @@ -458,14 +458,14 @@ public static ShapeStruct Read( BinaryReader br ) public struct ShapeMeshStruct { public uint StartIndex; - public uint ShapeValueNum; + public uint ShapeValueCount; public uint ShapeValueOffset; public static ShapeMeshStruct Read( BinaryReader br ) { ShapeMeshStruct ret = new ShapeMeshStruct(); ret.StartIndex = br.ReadUInt32(); - ret.ShapeValueNum = br.ReadUInt32(); + ret.ShapeValueCount = br.ReadUInt32(); ret.ShapeValueOffset = br.ReadUInt32(); return ret; } diff --git a/src/Lumina/Models/Model/Mesh.cs b/src/Lumina/Models/Model/Mesh.cs index 19c36b0b..4157cbf0 100644 --- a/src/Lumina/Models/Model/Mesh.cs +++ b/src/Lumina/Models/Model/Mesh.cs @@ -61,8 +61,8 @@ public void BuildMesh() { private void ReadSubmeshes() { var currentMesh = Model.File.Meshes[ MeshIndex ]; - Submeshes = new Submesh[ currentMesh.SubMeshNum ]; - for( int i = 0; i < currentMesh.SubMeshNum; i++ ) + Submeshes = new Submesh[ currentMesh.SubMeshCount ]; + for( int i = 0; i < currentMesh.SubMeshCount; i++ ) Submeshes[i] = new Submesh(Model, MeshIndex, i); } @@ -84,7 +84,7 @@ private void ReadIndices() // Read indices because they're easy BinaryReader reader = new BinaryReader( new MemoryStream( Model.File.IndexBuffers[ (int) Model.Lod ] ) ); reader.Seek( currentMesh.StartIndex * 2 ); - Indices = reader.ReadStructures< UInt16 >( (int) currentMesh.IndexNum ).ToArray(); + Indices = reader.ReadStructures< UInt16 >( (int) currentMesh.IndexCount ).ToArray(); } private void ReadVertices() @@ -98,7 +98,7 @@ private void ReadVertices() // TODO is this slow? this sort's purpose is to avoid copying every vertex out var orderedElements = currentDecl.VertexElements.ToList(); orderedElements.Sort( ( e1, e2 ) => e1.Offset.CompareTo( e2.Offset ) ); - Vertices = new Vertex[currentMesh.VertexNum]; + Vertices = new Vertex[currentMesh.VertexCount]; // Vertices may be defined across at most 3 streams defined by a Mesh's VertexDeclarations var vertexStreamReader = new BinaryReader[3]; diff --git a/src/Lumina/Models/Model/Model.cs b/src/Lumina/Models/Model/Model.cs index 03c269ea..2e884bbc 100644 --- a/src/Lumina/Models/Model/Model.cs +++ b/src/Lumina/Models/Model/Model.cs @@ -30,7 +30,7 @@ public enum ModelLod public Model( MdlFile mdlFile, ModelLod lod = ModelLod.High ) { - if( (uint) lod > mdlFile.FileHeader.LodNum ) + if( (uint) lod > mdlFile.FileHeader.LodCount ) throw new ArgumentException( "The given model does not have the requested LoD.", nameof( lod ) ); File = mdlFile; @@ -84,19 +84,19 @@ private void ReadMeshes() // I think this is a constant order, and this is the correct order MdlStructs.LodStruct CurrentLod = File.Lods[ (int) Lod ]; - int totalMeshes = CurrentLod.MeshNum; - totalMeshes += CurrentLod.WaterMeshNum; - totalMeshes += CurrentLod.ShadowMeshNum; - totalMeshes += CurrentLod.TerrainShadowMeshNum; - totalMeshes += CurrentLod.VerticalFogMeshNum; + int totalMeshes = CurrentLod.MeshCount; + totalMeshes += CurrentLod.WaterMeshCount; + totalMeshes += CurrentLod.ShadowMeshCount; + totalMeshes += CurrentLod.TerrainShadowMeshCount; + totalMeshes += CurrentLod.VerticalFogMeshCount; if( File.ModelHeader.ExtraLodEnabled ) { MdlStructs.ExtraLodStruct ExtraLod = File.ExtraLods[ (int) Lod ]; - totalMeshes += ExtraLod.LightShaftMeshNum; - totalMeshes += ExtraLod.GlassMeshNum; - totalMeshes += ExtraLod.MaterialChangeMeshNum; - totalMeshes += ExtraLod.CrestChangeMeshNum; + totalMeshes += ExtraLod.LightShaftMeshCount; + totalMeshes += ExtraLod.GlassMeshCount; + totalMeshes += ExtraLod.MaterialChangeMeshCount; + totalMeshes += ExtraLod.CrestChangeMeshCount; } Meshes = new Mesh[totalMeshes]; @@ -111,32 +111,32 @@ private Mesh.MeshType GetMeshType( int index ) { // I could create arrays for the ranges for cleaner code, but all this is doing is checking ranges - if( index > File.Lods[ (int) Lod ].MeshIndex && index < File.Lods[ (int) Lod ].MeshIndex + File.Lods[ (int) Lod ].MeshNum ) + if( index > File.Lods[ (int) Lod ].MeshIndex && index < File.Lods[ (int) Lod ].MeshIndex + File.Lods[ (int) Lod ].MeshCount ) return Mesh.MeshType.Main; - if( index > File.Lods[ (int) Lod ].WaterMeshIndex && index < File.Lods[ (int) Lod ].WaterMeshIndex + File.Lods[ (int) Lod ].WaterMeshNum ) + if( index > File.Lods[ (int) Lod ].WaterMeshIndex && index < File.Lods[ (int) Lod ].WaterMeshIndex + File.Lods[ (int) Lod ].WaterMeshCount ) return Mesh.MeshType.Water; - if( index > File.Lods[ (int) Lod ].ShadowMeshIndex && index < File.Lods[ (int) Lod ].ShadowMeshIndex + File.Lods[ (int) Lod ].ShadowMeshNum ) + if( index > File.Lods[ (int) Lod ].ShadowMeshIndex && index < File.Lods[ (int) Lod ].ShadowMeshIndex + File.Lods[ (int) Lod ].ShadowMeshCount ) return Mesh.MeshType.Shadow; if( index > File.Lods[ (int) Lod ].TerrainShadowMeshIndex && - index < File.Lods[ (int) Lod ].TerrainShadowMeshIndex + File.Lods[ (int) Lod ].TerrainShadowMeshNum ) + index < File.Lods[ (int) Lod ].TerrainShadowMeshIndex + File.Lods[ (int) Lod ].TerrainShadowMeshCount ) return Mesh.MeshType.TerrainShadow; if( index > File.Lods[ (int) Lod ].VerticalFogMeshIndex && - index < File.Lods[ (int) Lod ].VerticalFogMeshIndex + File.Lods[ (int) Lod ].VerticalFogMeshNum ) + index < File.Lods[ (int) Lod ].VerticalFogMeshIndex + File.Lods[ (int) Lod ].VerticalFogMeshCount ) return Mesh.MeshType.VerticalFog; if( File.ModelHeader.ExtraLodEnabled ) { if( index > File.ExtraLods[ (int) Lod ].LightShaftMeshIndex && - index < File.ExtraLods[ (int) Lod ].LightShaftMeshIndex + File.ExtraLods[ (int) Lod ].LightShaftMeshNum ) + index < File.ExtraLods[ (int) Lod ].LightShaftMeshIndex + File.ExtraLods[ (int) Lod ].LightShaftMeshCount ) return Mesh.MeshType.LightShaft; if( index > File.ExtraLods[ (int) Lod ].GlassMeshIndex && - index < File.ExtraLods[ (int) Lod ].GlassMeshIndex + File.ExtraLods[ (int) Lod ].GlassMeshNum ) + index < File.ExtraLods[ (int) Lod ].GlassMeshIndex + File.ExtraLods[ (int) Lod ].GlassMeshCount ) return Mesh.MeshType.Glass; if( index > File.ExtraLods[ (int) Lod ].MaterialChangeMeshIndex && - index < File.ExtraLods[ (int) Lod ].MaterialChangeMeshIndex + File.ExtraLods[ (int) Lod ].MaterialChangeMeshNum ) + index < File.ExtraLods[ (int) Lod ].MaterialChangeMeshIndex + File.ExtraLods[ (int) Lod ].MaterialChangeMeshCount ) return Mesh.MeshType.MaterialChange; if( index > File.ExtraLods[ (int) Lod ].CrestChangeMeshIndex && - index < File.ExtraLods[ (int) Lod ].CrestChangeMeshIndex + File.ExtraLods[ (int) Lod ].CrestChangeMeshNum ) + index < File.ExtraLods[ (int) Lod ].CrestChangeMeshIndex + File.ExtraLods[ (int) Lod ].CrestChangeMeshCount ) return Mesh.MeshType.CrestChange; } diff --git a/src/Lumina/Models/Model/Submesh.cs b/src/Lumina/Models/Model/Submesh.cs index 5c771f5c..4c7b1a1c 100644 --- a/src/Lumina/Models/Model/Submesh.cs +++ b/src/Lumina/Models/Model/Submesh.cs @@ -17,12 +17,12 @@ public Submesh( Model model, int meshIndex, int subMeshIndex ) var currentSubMesh = model.File.Submeshes[ subMeshListIndex ]; IndexOffset = currentSubMesh.IndexOffset; - IndexNum = currentSubMesh.IndexNum; + IndexNum = currentSubMesh.IndexCount; // AttributeIndexMask is a bit-based index mask // i.e. "5" is 0101 so it applies attrs 0 and 2 var attributeList = new List< string >(); - for( int i = 0; i < 32; i++ ) + for( int i = 0; i < model.File.ModelHeader.AttributeCount; i++ ) { if( ( ( 1 << i ) & currentSubMesh.AttributeIndexMask ) > 0 ) { @@ -36,7 +36,7 @@ public Submesh( Model model, int meshIndex, int subMeshIndex ) // I don't know what this is for if( currentSubMesh.BoneStartIndex == 65535 ) return; var affectedBoneTable = new List< string >(); - int boneEndIndex = currentSubMesh.BoneStartIndex + currentSubMesh.BoneNum; + int boneEndIndex = currentSubMesh.BoneStartIndex + currentSubMesh.BoneCount; for( int i = currentSubMesh.BoneStartIndex; i < boneEndIndex; i++ ) { var boneIndex = model.File.SubmeshBoneMap[ i ]; From ece6ebc929209d41beac6f7c96eae261ab5efb2b Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 18 Feb 2021 21:54:45 -0500 Subject: [PATCH 3/9] fix bone and mesh number bug --- src/Lumina/Data/Files/MdlFile.cs | 2 +- src/Lumina/Models/Model/Model.cs | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Lumina/Data/Files/MdlFile.cs b/src/Lumina/Data/Files/MdlFile.cs index 2059ea69..91a94d4b 100644 --- a/src/Lumina/Data/Files/MdlFile.cs +++ b/src/Lumina/Data/Files/MdlFile.cs @@ -84,8 +84,8 @@ public override void LoadFile() for( int i = 0; i < ModelHeader.MeshCount; i++ ) Meshes[ i ] = MeshStruct.Read( Reader ); AttributeNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.AttributeCount ).ToArray(); - for( int i = 0; i < ModelHeader.SubmeshCount; i++ ) Submeshes[ i ] = SubmeshStruct.Read( Reader ); for( int i = 0; i < ModelHeader.TerrainShadowMeshCount; i++ ) TerrainShadowMeshes[ i ] = TerrainShadowMeshStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.SubmeshCount; i++ ) Submeshes[ i ] = SubmeshStruct.Read( Reader ); for( int i = 0; i < ModelHeader.TerrainShadowSubmeshCount; i++ ) TerrainShadowSubmeshes[ i ] = TerrainShadowSubmeshStruct.Read( Reader ); MaterialNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.MaterialCount ).ToArray(); diff --git a/src/Lumina/Models/Model/Model.cs b/src/Lumina/Models/Model/Model.cs index 2e884bbc..c46a94dc 100644 --- a/src/Lumina/Models/Model/Model.cs +++ b/src/Lumina/Models/Model/Model.cs @@ -85,19 +85,19 @@ private void ReadMeshes() MdlStructs.LodStruct CurrentLod = File.Lods[ (int) Lod ]; int totalMeshes = CurrentLod.MeshCount; - totalMeshes += CurrentLod.WaterMeshCount; - totalMeshes += CurrentLod.ShadowMeshCount; - totalMeshes += CurrentLod.TerrainShadowMeshCount; - totalMeshes += CurrentLod.VerticalFogMeshCount; - - if( File.ModelHeader.ExtraLodEnabled ) - { - MdlStructs.ExtraLodStruct ExtraLod = File.ExtraLods[ (int) Lod ]; - totalMeshes += ExtraLod.LightShaftMeshCount; - totalMeshes += ExtraLod.GlassMeshCount; - totalMeshes += ExtraLod.MaterialChangeMeshCount; - totalMeshes += ExtraLod.CrestChangeMeshCount; - } + // totalMeshes += CurrentLod.WaterMeshCount; + // totalMeshes += CurrentLod.ShadowMeshCount; + // totalMeshes += CurrentLod.TerrainShadowMeshCount; + // totalMeshes += CurrentLod.VerticalFogMeshCount; + // + // if( File.ModelHeader.ExtraLodEnabled ) + // { + // MdlStructs.ExtraLodStruct ExtraLod = File.ExtraLods[ (int) Lod ]; + // totalMeshes += ExtraLod.LightShaftMeshCount; + // totalMeshes += ExtraLod.GlassMeshCount; + // totalMeshes += ExtraLod.MaterialChangeMeshCount; + // totalMeshes += ExtraLod.CrestChangeMeshCount; + // } Meshes = new Mesh[totalMeshes]; From 61252a73174dd06603382020634b00d3109afa23 Mon Sep 17 00:00:00 2001 From: Liam Date: Thu, 18 Feb 2021 23:31:46 -0500 Subject: [PATCH 4/9] do shapes eventually --- src/Lumina/Models/Model/Model.cs | 7 ++++++- src/Lumina/Models/Model/Shape.cs | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Lumina/Models/Model/Model.cs b/src/Lumina/Models/Model/Model.cs index c46a94dc..2f9320ad 100644 --- a/src/Lumina/Models/Model/Model.cs +++ b/src/Lumina/Models/Model/Model.cs @@ -62,7 +62,12 @@ private void BuildModel() private void ReadShapes() { - + Shapes = new Dictionary< string, Shape >(); + for( int i = 0; i < File.Shapes.Length; i++ ) + { + var shape = new Shape( this, i ); + Shapes[ shape.ShapeName ] = shape; + } } private void ReadStrings() diff --git a/src/Lumina/Models/Model/Shape.cs b/src/Lumina/Models/Model/Shape.cs index 6c538caf..4de03c6f 100644 --- a/src/Lumina/Models/Model/Shape.cs +++ b/src/Lumina/Models/Model/Shape.cs @@ -9,7 +9,8 @@ public Shape(Model model, int shapeIndex) var currentShape = model.File.Shapes[ shapeIndex ]; ShapeName = model.StringOffsetToStringMap[ (int) currentShape.StringOffset ]; - + // TODO: shape index modifier things + // someone else do this pls } } } \ No newline at end of file From bc6a47ce43e0b92df6c165c6854e811e357c7edb Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 4 Apr 2021 02:36:47 -0400 Subject: [PATCH 5/9] Refactored and more complete Model loading, down to Textures --- src/Lumina/Data/Files/MdlFile.cs | 150 ++++------ src/Lumina/Data/Files/MtrlFile.cs | 56 ++++ .../Data/Parsing/{Mdl => }/MdlStructs.cs | 113 +------- src/Lumina/Data/Parsing/MtrlStructs.cs | 107 ++++++++ src/Lumina/Data/Parsing/ShaderStructs.cs | 129 +++++++++ src/Lumina/Models/Materials/Material.cs | 230 ++++++++++++++++ src/Lumina/Models/Materials/Texture.cs | 126 +++++++++ src/Lumina/Models/Model/Model.cs | 259 ------------------ src/Lumina/Models/{Model => Models}/Mesh.cs | 83 ++++-- src/Lumina/Models/Models/Model.cs | 249 +++++++++++++++++ src/Lumina/Models/{Model => Models}/Shape.cs | 2 +- .../Models/{Model => Models}/Submesh.cs | 19 +- src/Lumina/Models/{Model => Models}/Vertex.cs | 6 +- 13 files changed, 1023 insertions(+), 506 deletions(-) create mode 100644 src/Lumina/Data/Files/MtrlFile.cs rename src/Lumina/Data/Parsing/{Mdl => }/MdlStructs.cs (75%) create mode 100644 src/Lumina/Data/Parsing/MtrlStructs.cs create mode 100644 src/Lumina/Data/Parsing/ShaderStructs.cs create mode 100644 src/Lumina/Models/Materials/Material.cs create mode 100644 src/Lumina/Models/Materials/Texture.cs delete mode 100644 src/Lumina/Models/Model/Model.cs rename src/Lumina/Models/{Model => Models}/Mesh.cs (71%) create mode 100644 src/Lumina/Models/Models/Model.cs rename src/Lumina/Models/{Model => Models}/Shape.cs (93%) rename src/Lumina/Models/{Model => Models}/Submesh.cs (79%) rename src/Lumina/Models/{Model => Models}/Vertex.cs (85%) diff --git a/src/Lumina/Data/Files/MdlFile.cs b/src/Lumina/Data/Files/MdlFile.cs index 91a94d4b..936eb652 100644 --- a/src/Lumina/Data/Files/MdlFile.cs +++ b/src/Lumina/Data/Files/MdlFile.cs @@ -1,37 +1,36 @@ using System; -using System.Diagnostics; +using Lumina.Data.Parsing; using Lumina.Data.Structs; using Lumina.Extensions; -using static Lumina.Data.Parsing.Mdl.MdlStructs; namespace Lumina.Data.Files { public class MdlFile : FileResource { - public ModelFileHeader FileHeader; - public VertexDeclarationStruct[] VertexDeclarations; - public ModelHeader ModelHeader; - public ElementIdStruct[] ElementIds; - public LodStruct[] Lods; - public ExtraLodStruct[] ExtraLods; - public MeshStruct[] Meshes; + public MdlStructs.ModelFileHeader FileHeader; + public MdlStructs.VertexDeclarationStruct[] VertexDeclarations; + public MdlStructs.ModelHeader ModelHeader; + public MdlStructs.ElementIdStruct[] ElementIds; + public MdlStructs.LodStruct[] Lods; + public MdlStructs.ExtraLodStruct[] ExtraLods; + public MdlStructs.MeshStruct[] Meshes; public uint[] AttributeNameOffsets; - public SubmeshStruct[] Submeshes; + public MdlStructs.SubmeshStruct[] Submeshes; - public TerrainShadowMeshStruct[] TerrainShadowMeshes; - public TerrainShadowSubmeshStruct[] TerrainShadowSubmeshes; + public MdlStructs.TerrainShadowMeshStruct[] TerrainShadowMeshes; + public MdlStructs.TerrainShadowSubmeshStruct[] TerrainShadowSubmeshes; public uint[] MaterialNameOffsets; public uint[] BoneNameOffsets; - public BoneTableStruct[] BoneTables; - public ShapeStruct[] Shapes; - public ShapeMeshStruct[] ShapeMeshes; - public ShapeValueStruct[] ShapeValues; + public MdlStructs.BoneTableStruct[] BoneTables; + public MdlStructs.ShapeStruct[] Shapes; + public MdlStructs.ShapeMeshStruct[] ShapeMeshes; + public MdlStructs.ShapeValueStruct[] ShapeValues; public ushort[] SubmeshBoneMap; - public BoundingBoxStruct BoundingBoxes; - public BoundingBoxStruct ModelBoundingBoxes; - public BoundingBoxStruct WaterBoundingBoxes; - public BoundingBoxStruct VerticalFogBoundingBoxes; - public BoundingBoxStruct[] BoneBoundingBoxes; + public MdlStructs.BoundingBoxStruct BoundingBoxes; + public MdlStructs.BoundingBoxStruct ModelBoundingBoxes; + public MdlStructs.BoundingBoxStruct WaterBoundingBoxes; + public MdlStructs.BoundingBoxStruct VerticalFogBoundingBoxes; + public MdlStructs.BoundingBoxStruct[] BoneBoundingBoxes; public ushort StringCount; public byte[] Strings; @@ -48,53 +47,45 @@ public override void LoadFile() return; } - FileHeader = ModelFileHeader.Read( Reader ); + FileHeader = MdlStructs.ModelFileHeader.Read( Reader ); - VertexDeclarations = new VertexDeclarationStruct[FileHeader.VertexDeclarationCount]; - for( int i = 0; i < FileHeader.VertexDeclarationCount; i++ ) VertexDeclarations[ i ] = VertexDeclarationStruct.Read( Reader ); + VertexDeclarations = new MdlStructs.VertexDeclarationStruct[FileHeader.VertexDeclarationCount]; + for( int i = 0; i < FileHeader.VertexDeclarationCount; i++ ) VertexDeclarations[ i ] = MdlStructs.VertexDeclarationStruct.Read( Reader ); StringCount = Reader.ReadUInt16(); Reader.ReadUInt16(); uint stringSize = Reader.ReadUInt32(); Strings = Reader.ReadBytes( (int) stringSize ); - ModelHeader = ModelHeader.Read( Reader ); - ElementIds = new ElementIdStruct[ModelHeader.ElementIdCount]; - Lods = new LodStruct[3]; - Meshes = new MeshStruct[ModelHeader.MeshCount]; - Submeshes = new SubmeshStruct[ModelHeader.SubmeshCount]; - TerrainShadowMeshes = new TerrainShadowMeshStruct[ModelHeader.TerrainShadowMeshCount]; - TerrainShadowSubmeshes = new TerrainShadowSubmeshStruct[ModelHeader.TerrainShadowSubmeshCount]; - BoneTables = new BoneTableStruct[ModelHeader.BoneTableCount]; - Shapes = new ShapeStruct[ModelHeader.ShapeCount]; - ShapeMeshes = new ShapeMeshStruct[ModelHeader.ShapeMeshCount]; - ShapeValues = new ShapeValueStruct[ModelHeader.ShapeValueCount]; - BoneBoundingBoxes = new BoundingBoxStruct[ModelHeader.BoneCount]; + ModelHeader = MdlStructs.ModelHeader.Read( Reader ); + ElementIds = new MdlStructs.ElementIdStruct[ModelHeader.ElementIdCount]; + Meshes = new MdlStructs.MeshStruct[ModelHeader.MeshCount]; + BoneTables = new MdlStructs.BoneTableStruct[ModelHeader.BoneTableCount]; + Shapes = new MdlStructs.ShapeStruct[ModelHeader.ShapeCount]; + BoneBoundingBoxes = new MdlStructs.BoundingBoxStruct[ModelHeader.BoneCount]; VertexBuffers = new byte[3][]; IndexBuffers = new byte[3][]; - for( int i = 0; i < ModelHeader.ElementIdCount; i++ ) ElementIds[ i ] = ElementIdStruct.Read( Reader ); - for( int i = 0; i < 3; i++ ) Lods[ i ] = LodStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.ElementIdCount; i++ ) ElementIds[ i ] = MdlStructs.ElementIdStruct.Read( Reader ); + Lods = Reader.ReadStructuresAsArray< MdlStructs.LodStruct >( 3 ); - if( ModelHeader.ExtraLodEnabled ) { - ExtraLods = new ExtraLodStruct[3]; - for( int i = 0; i < 3; i++ ) ExtraLods[ i ] = ExtraLodStruct.Read( Reader ); - } + if( ModelHeader.ExtraLodEnabled ) + ExtraLods = Reader.ReadStructuresAsArray< MdlStructs.ExtraLodStruct >( 3 ); - for( int i = 0; i < ModelHeader.MeshCount; i++ ) Meshes[ i ] = MeshStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.MeshCount; i++ ) Meshes[ i ] = MdlStructs.MeshStruct.Read( Reader ); AttributeNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.AttributeCount ).ToArray(); - for( int i = 0; i < ModelHeader.TerrainShadowMeshCount; i++ ) TerrainShadowMeshes[ i ] = TerrainShadowMeshStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.SubmeshCount; i++ ) Submeshes[ i ] = SubmeshStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.TerrainShadowSubmeshCount; i++ ) TerrainShadowSubmeshes[ i ] = TerrainShadowSubmeshStruct.Read( Reader ); + TerrainShadowMeshes = Reader.ReadStructuresAsArray< MdlStructs.TerrainShadowMeshStruct >( ModelHeader.TerrainShadowMeshCount ); + Submeshes = Reader.ReadStructuresAsArray< MdlStructs.SubmeshStruct >( ModelHeader.SubmeshCount ); + TerrainShadowSubmeshes = Reader.ReadStructuresAsArray< MdlStructs.TerrainShadowSubmeshStruct >( ModelHeader.TerrainShadowSubmeshCount ); MaterialNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.MaterialCount ).ToArray(); BoneNameOffsets = Reader.ReadStructures< UInt32 >( ModelHeader.BoneCount ).ToArray(); - for( int i = 0; i < ModelHeader.BoneTableCount; i++ ) BoneTables[ i ] = BoneTableStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.BoneTableCount; i++ ) BoneTables[ i ] = MdlStructs.BoneTableStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.ShapeCount; i++ ) Shapes[ i ] = ShapeStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.ShapeMeshCount; i++ ) ShapeMeshes[ i ] = ShapeMeshStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.ShapeValueCount; i++ ) ShapeValues[ i ] = ShapeValueStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.ShapeCount; i++ ) Shapes[ i ] = MdlStructs.ShapeStruct.Read( Reader ); + ShapeMeshes = Reader.ReadStructuresAsArray< MdlStructs.ShapeMeshStruct >( ModelHeader.ShapeMeshCount ); + ShapeValues = Reader.ReadStructuresAsArray< MdlStructs.ShapeValueStruct >( ModelHeader.ShapeValueCount ); uint submeshBoneMapSize = Reader.ReadUInt32(); SubmeshBoneMap = Reader.ReadStructures< UInt16 >( (int) submeshBoneMapSize / 2 ).ToArray(); @@ -103,11 +94,11 @@ public override void LoadFile() Reader.Seek( Reader.BaseStream.Position + paddingAmount ); // Dunno what this first one is for? - BoundingBoxes = BoundingBoxStruct.Read( Reader ); - ModelBoundingBoxes = BoundingBoxStruct.Read( Reader ); - WaterBoundingBoxes = BoundingBoxStruct.Read( Reader ); - VerticalFogBoundingBoxes = BoundingBoxStruct.Read( Reader ); - for( int i = 0; i < ModelHeader.BoneCount; i++ ) BoneBoundingBoxes[ i ] = BoundingBoxStruct.Read( Reader ); + BoundingBoxes = MdlStructs.BoundingBoxStruct.Read( Reader ); + ModelBoundingBoxes = MdlStructs.BoundingBoxStruct.Read( Reader ); + WaterBoundingBoxes = MdlStructs.BoundingBoxStruct.Read( Reader ); + VerticalFogBoundingBoxes = MdlStructs.BoundingBoxStruct.Read( Reader ); + for( int i = 0; i < ModelHeader.BoneCount; i++ ) BoneBoundingBoxes[ i ] = MdlStructs.BoundingBoxStruct.Read( Reader ); for( int i = 0; i < 3; i++ ) { Reader.BaseStream.Position = FileHeader.VertexOffset[ i ]; @@ -118,51 +109,4 @@ public override void LoadFile() } } } -} - -/* -// goes after file header, testing for something? -byte Stack[fileHeader.StackMemorySize] ; -byte Runtime[fileHeader.RuntimeMemorySize] ; - -local int i = 0; -for (i = 0; i < 3; i++) { - //FSeek(sizeof(Model_File_Header_Block) + fileHeader.VertexDataOffset[i]); - FSeek(fileHeader.VertexDataOffset[i]); - ByteArr vertBuffer(fileHeader.VertexBufferSize[i]) ; - - //FSeek(sizeof(Model_File_Header_Block) + fileHeader.IndexDataOffset[i]); - FSeek(fileHeader.IndexDataOffset[i]); - ByteArr indexBuffer(fileHeader.IndexBufferSize[i]) ; -}*/ - -/* -// header testing code -FSeek(fileHeader.VertexDataOffset[0]); -hfloat ignore[meshHeaders[0].VertexBufferOffset[1] / 2]; - -struct VertTest1 -{ - hfloat normal[4]; - byte tangent[4]; - hfloat texcoord[2]; -}; - -struct VertTest2 -{ - uint ny : 11; - uint nx : 11; - uint nz : 10; - byte tangent[4]; - hfloat texcoord[2]; -}; - -if (IsBigEndian()) { - VertTest2 test[20]; -} else { - VertTest1 test[20]; -} - -FSeek(fileHeader.IndexDataOffset[0]); -ushort indexBuffer[fileHeader.IndexBufferSize[0] / 2] ; -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/Lumina/Data/Files/MtrlFile.cs b/src/Lumina/Data/Files/MtrlFile.cs new file mode 100644 index 00000000..89fd756d --- /dev/null +++ b/src/Lumina/Data/Files/MtrlFile.cs @@ -0,0 +1,56 @@ +using System; +using Lumina.Data.Parsing; +using Lumina.Extensions; + +namespace Lumina.Data.Files +{ + public class MtrlFile : FileResource + { + public MaterialFileHeader FileHeader; + public MaterialHeader MaterialHeader; + public UvColorSet[] UvColorSets; + public TextureOffset[] TextureOffsets; + public int[] ColorSetOffsets; + + // Will have to double check this, this is from TexTools + public ColorSetInfo ColorSetInfo; + public ColorSetDyeInfo ColorSetDyeInfo; + + public ShaderKey[] ShaderKeys; + public Constant[] Constants; + public Sampler[] Samplers; + + public float[] ShaderValues; + public byte[] Strings; + + public override void LoadFile() + { + FileHeader = Reader.ReadStructure(); + TextureOffsets = Reader.ReadStructuresAsArray< TextureOffset >( FileHeader.TextureCount ); + + UvColorSets = Reader.ReadStructuresAsArray< UvColorSet >( FileHeader.UvSetCount ); + + ColorSetOffsets = Reader.ReadStructuresAsArray< Int32 >( FileHeader.ColorSetCount ); + + Strings = Reader.ReadBytes( FileHeader.StringTableSize ); + + // This seems to be a struct - do not know what it is + Reader.Seek(Reader.BaseStream.Position + FileHeader.AdditionalDataSize); + + if( FileHeader.DataSetSize > 0 ) + { + ColorSetInfo = Reader.ReadStructure< ColorSetInfo >(); + if (FileHeader.DataSetSize > 512) + ColorSetDyeInfo = Reader.ReadStructure< ColorSetDyeInfo >(); + } + + MaterialHeader = Reader.ReadStructure< MaterialHeader >(); + + ShaderKeys = Reader.ReadStructuresAsArray< ShaderKey >( MaterialHeader.ShaderKeyCount ); + Constants = Reader.ReadStructuresAsArray< Constant >( MaterialHeader.ConstantCount ); + Samplers = Reader.ReadStructuresAsArray< Sampler >( MaterialHeader.SamplerCount ); + + ShaderValues = Reader.ReadStructuresAsArray< float >( MaterialHeader.ShaderValueListSize / 4 ); + } + } +} \ No newline at end of file diff --git a/src/Lumina/Data/Parsing/Mdl/MdlStructs.cs b/src/Lumina/Data/Parsing/MdlStructs.cs similarity index 75% rename from src/Lumina/Data/Parsing/Mdl/MdlStructs.cs rename to src/Lumina/Data/Parsing/MdlStructs.cs index f5e48270..87e39d3e 100644 --- a/src/Lumina/Data/Parsing/Mdl/MdlStructs.cs +++ b/src/Lumina/Data/Parsing/MdlStructs.cs @@ -4,8 +4,7 @@ using System.Linq; using Lumina.Extensions; -// Here is a catch-all comment telling you to check the template if you have access -namespace Lumina.Data.Parsing.Mdl +namespace Lumina.Data.Parsing { public static class MdlStructs { @@ -68,7 +67,7 @@ public static VertexDeclarationStruct Read( BinaryReader br ) // Skip the number of bytes that we don't need to read // We skip elems.Count * 9 because we had to read the invalid element - int toSeek = ( 17 * 8 ) - ( ( elems.Count + 1 ) * 8 ); + int toSeek = 17 * 8 - ( elems.Count + 1 ) * 8; br.Seek( br.BaseStream.Position + toSeek ); ret.VertexElements = elems.ToArray(); @@ -248,32 +247,6 @@ public struct LodStruct public uint IndexBufferSize; public uint VertexDataOffset; public uint IndexDataOffset; - - public static LodStruct Read( BinaryReader br ) - { - LodStruct ret = new LodStruct(); - ret.MeshIndex = br.ReadUInt16(); - ret.MeshCount = br.ReadUInt16(); - ret.ModelLodRange = br.ReadSingle(); - ret.TextureLodRange = br.ReadSingle(); - ret.WaterMeshIndex = br.ReadUInt16(); - ret.WaterMeshCount = br.ReadUInt16(); - ret.ShadowMeshIndex = br.ReadUInt16(); - ret.ShadowMeshCount = br.ReadUInt16(); - ret.TerrainShadowMeshIndex = br.ReadUInt16(); - ret.TerrainShadowMeshCount = br.ReadUInt16(); - ret.VerticalFogMeshIndex = br.ReadUInt16(); - ret.VerticalFogMeshCount = br.ReadUInt16(); - ret.EdgeGeometrySize = br.ReadUInt32(); - ret.EdgeGeometryDataOffset = br.ReadUInt32(); - ret.PolygonCount = br.ReadUInt32(); - ret.Unknown1 = br.ReadUInt32(); - ret.VertexBufferSize = br.ReadUInt32(); - ret.IndexBufferSize = br.ReadUInt32(); - ret.VertexDataOffset = br.ReadUInt32(); - ret.IndexDataOffset = br.ReadUInt32(); - return ret; - } } public struct ExtraLodStruct @@ -298,38 +271,12 @@ public struct ExtraLodStruct public ushort Unknown10; public ushort Unknown11; public ushort Unknown12; - - public static ExtraLodStruct Read( BinaryReader br ) - { - ExtraLodStruct ret = new ExtraLodStruct(); - ret.LightShaftMeshIndex = br.ReadUInt16(); - ret.LightShaftMeshCount = br.ReadUInt16(); - ret.GlassMeshIndex = br.ReadUInt16(); - ret.GlassMeshCount = br.ReadUInt16(); - ret.MaterialChangeMeshIndex = br.ReadUInt16(); - ret.MaterialChangeMeshCount = br.ReadUInt16(); - ret.CrestChangeMeshIndex = br.ReadUInt16(); - ret.CrestChangeMeshCount = br.ReadUInt16(); - ret.Unknown1 = br.ReadUInt16(); - ret.Unknown2 = br.ReadUInt16(); - ret.Unknown3 = br.ReadUInt16(); - ret.Unknown4 = br.ReadUInt16(); - ret.Unknown5 = br.ReadUInt16(); - ret.Unknown6 = br.ReadUInt16(); - ret.Unknown7 = br.ReadUInt16(); - ret.Unknown8 = br.ReadUInt16(); - ret.Unknown9 = br.ReadUInt16(); - ret.Unknown10 = br.ReadUInt16(); - ret.Unknown11 = br.ReadUInt16(); - ret.Unknown12 = br.ReadUInt16(); - return ret; - } } public struct MeshStruct { public ushort VertexCount; - private ushort padding; + private ushort Padding; public uint IndexCount; public ushort MaterialIndex; public ushort SubMeshIndex; @@ -345,7 +292,7 @@ public static MeshStruct Read( BinaryReader br ) { MeshStruct ret = new MeshStruct(); ret.VertexCount = br.ReadUInt16(); - ret.padding = br.ReadUInt16(); + ret.Padding = br.ReadUInt16(); ret.IndexCount = br.ReadUInt32(); ret.MaterialIndex = br.ReadUInt16(); ret.SubMeshIndex = br.ReadUInt16(); @@ -366,17 +313,6 @@ public struct SubmeshStruct public uint AttributeIndexMask; public ushort BoneStartIndex; public ushort BoneCount; - - public static SubmeshStruct Read( BinaryReader br ) - { - SubmeshStruct ret = new SubmeshStruct(); - ret.IndexOffset = br.ReadUInt32(); - ret.IndexCount = br.ReadUInt32(); - ret.AttributeIndexMask = br.ReadUInt32(); - ret.BoneStartIndex = br.ReadUInt16(); - ret.BoneCount = br.ReadUInt16(); - return ret; - } } public struct TerrainShadowMeshStruct @@ -389,20 +325,6 @@ public struct TerrainShadowMeshStruct public ushort SubMeshCount; public byte VertexBufferStride; private byte Padding; - - public static TerrainShadowMeshStruct Read( BinaryReader br ) - { - TerrainShadowMeshStruct ret = new TerrainShadowMeshStruct(); - ret.IndexCount = br.ReadUInt32(); - ret.StartIndex = br.ReadUInt32(); - ret.VertexBufferOffset = br.ReadUInt32(); - ret.VertexCount = br.ReadUInt16(); - ret.SubMeshIndex = br.ReadUInt16(); - ret.SubMeshCount = br.ReadUInt16(); - ret.VertexBufferStride = br.ReadByte(); - ret.Padding = br.ReadByte(); - return ret; - } } public struct TerrainShadowSubmeshStruct @@ -411,16 +333,6 @@ public struct TerrainShadowSubmeshStruct public uint IndexCount; public ushort Unknown1; public ushort Unknown2; - - public static TerrainShadowSubmeshStruct Read( BinaryReader br ) - { - TerrainShadowSubmeshStruct ret = new TerrainShadowSubmeshStruct(); - ret.IndexOffset = br.ReadUInt32(); - ret.IndexCount = br.ReadUInt32(); - ret.Unknown1 = br.ReadUInt16(); - ret.Unknown2 = br.ReadUInt16(); - return ret; - } } public struct BoneTableStruct @@ -460,29 +372,12 @@ public struct ShapeMeshStruct public uint StartIndex; public uint ShapeValueCount; public uint ShapeValueOffset; - - public static ShapeMeshStruct Read( BinaryReader br ) - { - ShapeMeshStruct ret = new ShapeMeshStruct(); - ret.StartIndex = br.ReadUInt32(); - ret.ShapeValueCount = br.ReadUInt32(); - ret.ShapeValueOffset = br.ReadUInt32(); - return ret; - } } public struct ShapeValueStruct { public ushort Offset; public ushort Value; - - public static ShapeValueStruct Read( BinaryReader br ) - { - ShapeValueStruct ret = new ShapeValueStruct(); - ret.Offset = br.ReadUInt16(); - ret.Value = br.ReadUInt16(); - return ret; - } } public struct BoundingBoxStruct diff --git a/src/Lumina/Data/Parsing/MtrlStructs.cs b/src/Lumina/Data/Parsing/MtrlStructs.cs new file mode 100644 index 00000000..64ceef7c --- /dev/null +++ b/src/Lumina/Data/Parsing/MtrlStructs.cs @@ -0,0 +1,107 @@ +namespace Lumina.Data.Parsing +{ + /** + * These values are actually CRC values used by SE in order to + * coordinate mappings to shaders. Textures do not actually store + * whether they are diffuse, specular, etc. They store the shader + * that this texture is input for, in CRC form. + * + * That was my long way of explaining "these are linked manually." + */ + public enum TextureUsage : uint + { + Sampler = 0x88408C04, + Sampler0 = 0x213CB439, + Sampler1 = 0x563B84AF, + SamplerCatchlight = 0xFEA0F3D2, + SamplerColorMap0 = 0x1E6FEF9C, + SamplerColorMap1 = 0x6968DF0A, + SamplerDiffuse = 0x115306BE, + SamplerEnvMap = 0xF8D7957A, + SamplerMask = 0x8A4E82B6, + SamplerNormal = 0x0C5EC1F1, + SamplerNormalMap0 = 0xAAB4D9E9, + SamplerNormalMap1 = 0xDDB3E97F, + SamplerReflection = 0x87F6474D, + SamplerSpecular = 0x2B99E025, + SamplerSpecularMap0 = 0x1BBC2F12, + SamplerSpecularMap1 = 0x6CBB1F84, + SamplerWaveMap = 0xE6321AFC, + SamplerWaveletMap0 = 0x574E22D6, + SamplerWaveletMap1 = 0x20491240, + SamplerWhitecapMap = 0x95E1F64D + } + + public struct MaterialFileHeader + { + public uint Version; + public ushort FileSize; + public ushort DataSetSize; + public ushort StringTableSize; + public ushort ShaderPackageNameOffset; + public byte TextureCount; + public byte UvSetCount; + public byte ColorSetCount; + public byte AdditionalDataSize; + } + + public struct MaterialHeader + { + public ushort ShaderValueListSize; + public ushort ShaderKeyCount; + public ushort ConstantCount; + public ushort SamplerCount; + public ushort Unknown1; + public ushort Unknown2; + } + + public struct TextureOffset + { + public ushort Offset; + public ushort Flags; // This is an assumption; has always been 32768 (0x8000) + } + + public struct Constant + { + public uint ConstantId; + public ushort ValueOffset; + public ushort ValueSize; + } + + public struct SamplerState + { + // Seems to be a bitfield, but no idea what the + // values are for + public uint Unknown; + } + + public unsafe struct Sampler + { + public uint SamplerId; + public SamplerState SamplerState; + public byte TextureIndex; + private fixed byte Padding[3]; + } + + public struct ShaderKey + { + public uint Category; + public uint Value; + } + + public struct UvColorSet + { + public ushort NameOffset; + public ushort Index; + } + + public unsafe struct ColorSetInfo + { + public fixed ushort Data[256]; + } + + public unsafe struct ColorSetDyeInfo + { + public fixed ushort Data[16]; + } +} \ No newline at end of file diff --git a/src/Lumina/Data/Parsing/ShaderStructs.cs b/src/Lumina/Data/Parsing/ShaderStructs.cs new file mode 100644 index 00000000..784d9e7f --- /dev/null +++ b/src/Lumina/Data/Parsing/ShaderStructs.cs @@ -0,0 +1,129 @@ +using System.IO; +using Lumina.Extensions; + +namespace Lumina.Data.Parsing +{ + // Here is a catch-all comment telling you to check the template if you have access + public class ShaderStructs + { + struct ShaderHeader + { + public uint Magic; + public uint Version; + public uint Platform; + public uint FileSize; + public uint CodeStart; + public uint StringsStart; + + public static ShaderHeader Read(BinaryReader br) + { + ShaderHeader ret = new ShaderHeader(); + ret.Magic = br.ReadUInt32(); + ret.Version = br.ReadUInt32(); + ret.Platform = br.ReadUInt32(); + ret.FileSize = br.ReadUInt32(); + ret.CodeStart = br.ReadUInt32(); + ret.StringsStart = br.ReadUInt32(); + return ret; + } + } + + struct ShaderPackageHeader + { + public ShaderHeader ShaderHeader; + public uint VertexShaderCount; + public uint PixelShaderCount; + public uint MaterialBufferSize; + public uint MaterialElementCount; + public uint ConstantCount; + public uint SamplerCount; + public uint RwResourceCount; + public uint SystemKeyCount; + public uint SceneKeyCount; + public uint MaterialKeyCount; + public uint ShaderNodeCount; + public uint ReferenceNodeCount; + + public static ShaderPackageHeader Read(BinaryReader br) + { + ShaderPackageHeader ret = new ShaderPackageHeader(); + ret.ShaderHeader = br.ReadStructure< ShaderHeader >(); + ret.VertexShaderCount = br.ReadUInt32(); + ret.PixelShaderCount = br.ReadUInt32(); + ret.MaterialBufferSize = br.ReadUInt32(); + ret.MaterialElementCount = br.ReadUInt32(); + ret.ConstantCount = br.ReadUInt32(); + ret.SamplerCount = br.ReadUInt32(); + ret.RwResourceCount = br.ReadUInt32(); + ret.SystemKeyCount = br.ReadUInt32(); + ret.SceneKeyCount = br.ReadUInt32(); + ret.MaterialKeyCount = br.ReadUInt32(); + ret.ShaderNodeCount = br.ReadUInt32(); + ret.ReferenceNodeCount = br.ReadUInt32(); + return ret; + } + } + + struct ShaderParameter + { + public uint ShaderParameterKey; + public uint ParameterNameOffset; + public uint ParameterNameSize; + public ushort RegisterIndex; + public ushort RegisterCount; + } + + struct ShaderCode + { + public uint ShaderCodeOffset; + public uint ShaderCodeSize; + public ushort ConstantCount; + public ushort SamplerCount; + public ushort RwResourceCount; + public ushort Unknown2; + public ShaderParameter[] ShaderParameters; + + public static ShaderCode Read(BinaryReader br) + { + ShaderCode ret = new ShaderCode(); + ret.ShaderCodeOffset = br.ReadUInt32(); + ret.ShaderCodeSize = br.ReadUInt32(); + ret.ConstantCount = br.ReadUInt16(); + ret.SamplerCount = br.ReadUInt16(); + ret.RwResourceCount = br.ReadUInt16(); + ret.Unknown2 = br.ReadUInt16(); + ret.ShaderParameters = br.ReadStructuresAsArray(ret.ConstantCount + ret.SamplerCount + ret.RwResourceCount); + return ret; + } + } + + struct ShaderMaterial + { + public uint Material; + public ushort Offset; + public ushort Size; + } + + struct ShaderPass + { + public uint ShaderPassId; + public uint VertexShaderIndex; + public uint PixelShaderIndex; + } + + struct ShaderNode + { + public uint ShaderNodeKey; + public uint ShaderPassCount; + public byte[] ShaderPassIndices; + public uint[] ShaderKeys; + public ShaderPass[] ShaderPass; + } + + struct ShaderKeyValue + { + public uint Key; + public uint Value; + } + } +} \ No newline at end of file diff --git a/src/Lumina/Models/Materials/Material.cs b/src/Lumina/Models/Materials/Material.cs new file mode 100644 index 00000000..1359c8c7 --- /dev/null +++ b/src/Lumina/Models/Materials/Material.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Lumina.Data.Files; +using Lumina.Data.Parsing; +using Lumina.Extensions; +using Lumina.Models.Models; + +namespace Lumina.Models.Materials +{ + public class Material + { + /// + /// The path to this Material. May be relative or absolute. + /// + public string MaterialPath { get; private set; } + + /// + /// The resolved path to this Material. Guaranteed to not be initialized + /// in the case that MaterialPath.StartsWith("/") == true. + /// + public string ResolvedPath { get; private set; } + + /// + /// A convenience reference to the Model that instantiated this Material. + /// + public Model Parent { get; private set; } + + /// + /// The MtrlFile backing this Material. May not be initialized. + /// + public MtrlFile File { get; private set; } + + /// + /// The Textures for this Material. May not be initialized. + /// + public Texture[] Textures { get; private set; } + + /// + /// The shader package name used by this Material. + /// + public string ShaderPack { get; private set; } + + /// + /// The variant ID for this Material. This is specified by the caller + /// for relative paths and inferred for absolute paths or instantiation + /// via the MtrlFile constructor. + /// + public int VariantId { get; set; } + + private Dictionary< int, string > StringOffsetToStringMap; + + /// + /// Creates a new Material instance without resolving any game data. + /// + /// The path, relative or absolute, to this Material. + /// The variant ID for this material. This parameter is + /// ignored if an absolute path is provided; the variant ID will be inferred. + public Material( string path, int variantId = 1 ) + { + MaterialPath = path; + VariantId = !path.StartsWith( "/" ) ? GetVariantIdFromPath( path ) : variantId; + } + + /// + /// Creates a new Material instance without resolving any data. + /// This constructor is used by the class to + /// provide a reference to the model within the material. + /// + /// The Model this Material has been instantiated for. + /// The path, relative or absolute, to this Material. + /// The variant ID for this material. Default is 1. + public Material( Model parent, string path, int variantId = 1 ) + { + Parent = parent; + MaterialPath = path; + VariantId = variantId; + } + + /// + /// Creates a new Material instance using the provided MtrlFile. + /// Variant ID is inferred from the provided MtrlFile's path. + /// + /// The MtrlFile to back this Material. + public Material( MtrlFile file ) + { + File = file; + VariantId = GetVariantIdFromPath( file.FilePath ); + BuildMaterial(); + } + + /// + /// Creates a new Material instance using the provided path and + /// reference to game data. The Material will be built and then + /// updated with game data. + /// + /// A reference to game data access. + /// The path, relative or absolute, to this Material. + /// The variant ID for this material. Default is 1. + public Material( GameData data, string path, int variantId = 1 ) + { + MaterialPath = path; + BuildMaterial(); + Update( data ); + } + + /// + /// Update this Material using game data. If instantiated without a MtrlFile or + /// GameData, this method will retrieve the MtrlFile referenced by this Material and use + /// that file to update local fields. This method is not guaranteed to load the MtrlFile. + /// + /// A reference to game data access. + /// The existing Material instance, for method chaining. + public Material Update( GameData data ) + { + if( MaterialPath.StartsWith( "/" ) ) + { + ResolvedPath = ResolveRelativeMaterialPath( MaterialPath, VariantId ); + File = data.GetFile< MtrlFile >( ResolvedPath ); + } + else + { + File = data.GetFile< MtrlFile >( MaterialPath ); + } + + if( File != null ) + BuildMaterial(); + + return this; + } + + /// + /// Resolves a relative material path in the form /mt_c0101e0001_top_a.mtrl + /// into its full path, chara/equipment/e0001/material/v{variantId}/mt_c0101e0001_top_a.mtrl. + ///
This method will successfully resolve all known relative material paths. + ///
+ /// The relative path of the provided material. + /// The variant to use in material resolution. + /// The resolved, absolute path to the requested material, or null if unsuccessful. + public static string ResolveRelativeMaterialPath( string relativePath, int variantId ) + { + var id1 = char.Parse( relativePath.Substring( 4, 1 ) ); + var val1 = relativePath.Substring( 5, 4 ); + var id2 = char.Parse( relativePath.Substring( 9, 1 ) ); + var val2 = relativePath.Substring( 10, 4 ); + + return ( id1, id2 ) switch + { + ('c', 'a') => $"chara/accessory/a{val2}/material/v{variantId:D4}{relativePath}", + ('c', 'b') => $"chara/human/c{val1}/obj/body/b{val2}/material/v{variantId:D4}{relativePath}", + ('c', 'e') => $"chara/equipment/e{val2}/material/v{variantId:D4}{relativePath}", + ('c', 'f') => $"chara/human/c{val1}/obj/face/f{val2}/material{relativePath}", + ('c', 'h') => $"chara/human/c{val1}/obj/hair/h{val2}/material/v{variantId:D4}{relativePath}", + ('c', 't') => $"chara/human/c{val1}/obj/tail/t{val2}/material/v{variantId:D4}{relativePath}", + ('c', 'z') => $"chara/human/c{val1}/obj/zear/z{val2}/material{relativePath}", + ('d', 'e') => $"chara/demihuman/d{val1}/obj/equipment/e{val2}/material/v{variantId:D4}{relativePath}", + ('m', 'b') => $"chara/monster/m{val1}/obj/body/b{val2}/material/v{variantId:D4}{relativePath}", + ('w', 'b') => $"chara/weapon/w{val1}/obj/body/b{val2}/material/v{variantId:D4}{relativePath}", + (_, _) => null + }; + } + + /// + /// Parse the variant ID out of an existing absolute path to a .mtrl file. + /// + /// The absolute path to an existing .mtrl file. + /// The variant ID for the given .mtrl path. In case of error, 1. + public static int GetVariantIdFromPath(string matPath) { + var v = 1; + var vStart = matPath.IndexOf("/v", StringComparison.Ordinal ); + if( vStart == -1 ) + return v; + var vSub = matPath.Substring(vStart + 2, 4); + try + { + v = int.Parse( vSub ); + } + catch( FormatException ) { } + + return v; + } + + private void BuildMaterial() + { + ReadStrings(); + ReadTextures(); + + ShaderPack = StringOffsetToStringMap[ File.FileHeader.ShaderPackageNameOffset ]; + } + + private void ReadTextures() + { + Textures = new Texture[File.TextureOffsets.Length]; + + for( int i = 0; i < File.TextureOffsets.Length; i++ ) + { + TextureUsage raw = (TextureUsage) File.Samplers[ i ].SamplerId; + var texIndex = File.Samplers[ i ].TextureIndex; + var texOffset = File.TextureOffsets[ texIndex ].Offset; + var texPath = StringOffsetToStringMap[ texOffset ]; + Textures[ i ] = new Texture( this, raw, texPath ); + } + } + + private void ReadStrings() + { + StringOffsetToStringMap = new Dictionary< int, string >(); + var mr = new MemoryStream( File.Strings ); + var br = new BinaryReader( mr ); + + // They re-use offsets, so the number of offsets is not equal to the number of unique members + var uniqueTextureCount = File.TextureOffsets.Select( t => t.Offset ).Distinct().Count(); + var uniqueUvColorSetCount = File.UvColorSets.Select( t => t.NameOffset ).Distinct().Count(); + var uniqueColorOffsetCount = File.ColorSetOffsets.Select( t => t ).Distinct().Count(); + + // Add one for the shader package name at the end + var stringCount = uniqueTextureCount + uniqueUvColorSetCount + uniqueColorOffsetCount + 1; + for( int i = 0; i < stringCount; i++ ) + { + long startOffset = br.BaseStream.Position; + string tmp = br.ReadStringData(); + StringOffsetToStringMap[ (int) startOffset ] = tmp; + } + + br.Dispose(); + mr.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Lumina/Models/Materials/Texture.cs b/src/Lumina/Models/Materials/Texture.cs new file mode 100644 index 00000000..102020af --- /dev/null +++ b/src/Lumina/Models/Materials/Texture.cs @@ -0,0 +1,126 @@ +using System; +using Lumina.Data.Files; +using Lumina.Data.Parsing; + +namespace Lumina.Models.Materials +{ + public class Texture + { + /// + /// Less specific, and easier to use texture usage enumeration + /// See for a more complex enumeration. + /// + public enum Usage + { + Catchlight, + Diffuse, + Envmap, + Mask, + Normal, + Reflection, + Specular, + Wave, + Whitecap, + } + + /// + /// A convenience reference to the Material that instantiated this Texture. + /// + public Material Parent { get; private set; } + + /// + /// The raw shader ID this Texture is used as input for. + /// + public TextureUsage TextureUsageRaw { get; private set; } + + /// + /// The more user-friendly usage type of this Texture. + /// + public Usage TextureUsageSimple { get; private set; } + + /// + /// The path to this Texture. + /// + public string TexturePath { get; private set; } + + private bool _isLoaded = false; + private TexFile _tex; + + /// + /// Creates a new Texture instance without resolving any game data. + /// + /// The Material this Texture has been instantiated for. + /// The this Texture is input for. + /// The path to this Texture. + public Texture( Material parent, TextureUsage raw, string texturePath ) + { + Parent = parent; + TextureUsageRaw = raw; + TextureUsageSimple = GetUsage( raw ); + TexturePath = texturePath; + } + + /// + /// Creates a new Texture instance without resolving any game data. + /// + /// The this Texture is input for. + /// The path to this Texture. + public Texture( TextureUsage raw, string texturePath ) : this( null, raw, texturePath ) { } + + /// + /// Retrieve the TexFile referenced by this Texture, and store it + /// in this Texture for future use. + /// + /// A reference to game data access. + /// The TexFile referenced by this Texture if found, null otherwise. + public TexFile GetTexture(GameData data) + { + if( _isLoaded ) return _tex; + if( data == null ) return null; + + _tex = data.GetFile< TexFile >( TexturePath ); + _isLoaded = true; + + return _tex; + } + + /// + /// Retrieve the TexFile referenced by this Texture without storing it + /// or retrieving it from this Texture's local reference. + /// + /// A reference to game data access. + /// The TexFile referenced by this Texture if found, null otherwise. + public TexFile GetTextureNc(GameData data) + { + return data.GetFile< TexFile >( TexturePath ); + } + + private static Usage GetUsage(TextureUsage usage) + { + return usage switch + { + TextureUsage.Sampler => Usage.Diffuse, + TextureUsage.Sampler0 => Usage.Diffuse, + TextureUsage.Sampler1 => Usage.Diffuse, + TextureUsage.SamplerCatchlight => Usage.Catchlight, + TextureUsage.SamplerColorMap0 => Usage.Diffuse, + TextureUsage.SamplerColorMap1 => Usage.Diffuse, + TextureUsage.SamplerDiffuse => Usage.Diffuse, + TextureUsage.SamplerEnvMap => Usage.Envmap, + TextureUsage.SamplerMask => Usage.Mask, + TextureUsage.SamplerNormal => Usage.Normal, + TextureUsage.SamplerNormalMap0 => Usage.Normal, + TextureUsage.SamplerNormalMap1 => Usage.Normal, + TextureUsage.SamplerReflection => Usage.Reflection, + TextureUsage.SamplerSpecular => Usage.Specular, + TextureUsage.SamplerSpecularMap0 => Usage.Specular, + TextureUsage.SamplerSpecularMap1 => Usage.Specular, + TextureUsage.SamplerWaveMap => Usage.Wave, + TextureUsage.SamplerWaveletMap0 => Usage.Wave, + TextureUsage.SamplerWaveletMap1 => Usage.Wave, + TextureUsage.SamplerWhitecapMap => Usage.Whitecap, + _ => throw new ArgumentOutOfRangeException( nameof( usage ), usage, null ) + }; + } + } +} \ No newline at end of file diff --git a/src/Lumina/Models/Model/Model.cs b/src/Lumina/Models/Model/Model.cs deleted file mode 100644 index 2f9320ad..00000000 --- a/src/Lumina/Models/Model/Model.cs +++ /dev/null @@ -1,259 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Text; -using Lumina.Data.Files; -using Lumina.Data.Parsing.Mdl; -using Lumina.Extensions; - -namespace Lumina.Models.Model -{ - public class Model - { - public enum ModelLod - { - High, - Med, - Low - } - - public MdlFile File { get; private set; } - public ModelLod Lod { get; private set; } - public Mesh[] Meshes { get; private set; } - - public Dictionary Shapes { get; private set; } - - public Dictionary< int, string > StringOffsetToStringMap { get; private set; } - - public Model( MdlFile mdlFile, ModelLod lod = ModelLod.High ) - { - if( (uint) lod > mdlFile.FileHeader.LodCount ) - throw new ArgumentException( "The given model does not have the requested LoD.", nameof( lod ) ); - - File = mdlFile; - Lod = lod; - BuildModel(); - } - - public Model( Lumina lumina, string path, ModelLod lod = ModelLod.High ) - { - var mdlFile = lumina.GetFile< MdlFile >( path ); - File = mdlFile; - Lod = lod; - BuildModel(); - } - - private void BuildModel() - { - // Every valid MDL has VertexDeclarations - if( File.VertexDeclarations == null ) - { - Console.WriteLine( "BuildModel() called on an empty MdlFile." ); - return; - } - - ReadStrings(); - ReadMeshes(); - ReadShapes(); - } - - private void ReadShapes() - { - Shapes = new Dictionary< string, Shape >(); - for( int i = 0; i < File.Shapes.Length; i++ ) - { - var shape = new Shape( this, i ); - Shapes[ shape.ShapeName ] = shape; - } - } - - private void ReadStrings() - { - // If we save the offset and the string, we can access it later via the offset without reading - StringOffsetToStringMap = new Dictionary< int, string >(); - var br = new BinaryReader( new MemoryStream( File.Strings ) ); - for( int i = 0; i < File.StringCount; i++ ) - { - long startOffset = br.BaseStream.Position; - string tmp = br.ReadStringData(); - StringOffsetToStringMap[ (int) startOffset ] = tmp; - } - } - - private void ReadMeshes() - { - // We have to count how many of each mesh there are - // I think this is a constant order, and this is the correct order - MdlStructs.LodStruct CurrentLod = File.Lods[ (int) Lod ]; - - int totalMeshes = CurrentLod.MeshCount; - // totalMeshes += CurrentLod.WaterMeshCount; - // totalMeshes += CurrentLod.ShadowMeshCount; - // totalMeshes += CurrentLod.TerrainShadowMeshCount; - // totalMeshes += CurrentLod.VerticalFogMeshCount; - // - // if( File.ModelHeader.ExtraLodEnabled ) - // { - // MdlStructs.ExtraLodStruct ExtraLod = File.ExtraLods[ (int) Lod ]; - // totalMeshes += ExtraLod.LightShaftMeshCount; - // totalMeshes += ExtraLod.GlassMeshCount; - // totalMeshes += ExtraLod.MaterialChangeMeshCount; - // totalMeshes += ExtraLod.CrestChangeMeshCount; - // } - - Meshes = new Mesh[totalMeshes]; - - for( int i = 0; i < Meshes.Length; i++ ) - { - Meshes[ i ] = new Mesh( this, i, GetMeshType( i ) ); - } - } - - private Mesh.MeshType GetMeshType( int index ) - { - // I could create arrays for the ranges for cleaner code, but all this is doing is checking ranges - - if( index > File.Lods[ (int) Lod ].MeshIndex && index < File.Lods[ (int) Lod ].MeshIndex + File.Lods[ (int) Lod ].MeshCount ) - return Mesh.MeshType.Main; - if( index > File.Lods[ (int) Lod ].WaterMeshIndex && index < File.Lods[ (int) Lod ].WaterMeshIndex + File.Lods[ (int) Lod ].WaterMeshCount ) - return Mesh.MeshType.Water; - if( index > File.Lods[ (int) Lod ].ShadowMeshIndex && index < File.Lods[ (int) Lod ].ShadowMeshIndex + File.Lods[ (int) Lod ].ShadowMeshCount ) - return Mesh.MeshType.Shadow; - if( index > File.Lods[ (int) Lod ].TerrainShadowMeshIndex && - index < File.Lods[ (int) Lod ].TerrainShadowMeshIndex + File.Lods[ (int) Lod ].TerrainShadowMeshCount ) - return Mesh.MeshType.TerrainShadow; - if( index > File.Lods[ (int) Lod ].VerticalFogMeshIndex && - index < File.Lods[ (int) Lod ].VerticalFogMeshIndex + File.Lods[ (int) Lod ].VerticalFogMeshCount ) - return Mesh.MeshType.VerticalFog; - - if( File.ModelHeader.ExtraLodEnabled ) - { - if( index > File.ExtraLods[ (int) Lod ].LightShaftMeshIndex && - index < File.ExtraLods[ (int) Lod ].LightShaftMeshIndex + File.ExtraLods[ (int) Lod ].LightShaftMeshCount ) - return Mesh.MeshType.LightShaft; - if( index > File.ExtraLods[ (int) Lod ].GlassMeshIndex && - index < File.ExtraLods[ (int) Lod ].GlassMeshIndex + File.ExtraLods[ (int) Lod ].GlassMeshCount ) - return Mesh.MeshType.Glass; - if( index > File.ExtraLods[ (int) Lod ].MaterialChangeMeshIndex && - index < File.ExtraLods[ (int) Lod ].MaterialChangeMeshIndex + File.ExtraLods[ (int) Lod ].MaterialChangeMeshCount ) - return Mesh.MeshType.MaterialChange; - if( index > File.ExtraLods[ (int) Lod ].CrestChangeMeshIndex && - index < File.ExtraLods[ (int) Lod ].CrestChangeMeshIndex + File.ExtraLods[ (int) Lod ].CrestChangeMeshCount ) - return Mesh.MeshType.CrestChange; - } - - return Mesh.MeshType.Main; - } - - public Mesh[] GetMeshesByType( Mesh.MeshType mp ) - { - return Meshes.Where( m => m.Type == mp ).ToArray(); - } - - /// - /// Writes an entire Model into a single Wavefront Object file. - /// This was meant for testing, but doesn't hurt to stay. - /// - /// A string containing the entire .obj file, including newlines. - public string GetObjectFileText() - { - string nl = Environment.NewLine; - - var vsList = new List< Vector3 >(); - var vcList = new List< Vector4 >(); - var vtList = new List< Vector4 >(); - var nmList = new List< Vector3 >(); - var inList = new List< Vector3 >(); - - int indexModifier = 1; - for( int i = 0; i < Meshes.Length; i++ ) - { - if( i != 0 ) - indexModifier += Meshes[i - 1].Vertices.Length; - foreach( var vert in Meshes[ i ].Vertices ) - { - Vector3 vs = new Vector3(); - Vector4 vc = new Vector4(); - Vector4 vt = new Vector4(); - Vector3 nm = new Vector3(); - - vs.X = vert.Position.Value.X; - vs.Y = vert.Position.Value.Y; - vs.Z = vert.Position.Value.Z; - - if( vert.Color == null ) - { - vc.X = 0; - vc.Y = 0; - vc.W = 0; - vc.Z = 0; - } - else - { - vc.X = vert.Color.Value.X; - vc.Y = vert.Color.Value.Y; - vc.W = vert.Color.Value.W; - vc.Z = vert.Color.Value.Z; - } - - if( vert.UV.HasValue ) - { - vt.X = vert.UV.Value.X; - vt.Y = -1 * vert.UV.Value.Y; - vt.W = ( vert.UV.Value.W == 0 ) ? vt.X : vert.UV.Value.W; - vt.Z = ( vert.UV.Value.Z == 0 ) ? vt.Y : vert.UV.Value.Z; - } - - if( vert.Normal.HasValue ) - { - nm.X = vert.Normal.Value.X; - nm.Y = vert.Normal.Value.Y; - nm.Z = vert.Normal.Value.Z; - } - - vsList.Add( vs ); - vcList.Add( vc ); - vtList.Add( vt ); - nmList.Add( nm ); - } - - for( int j = 0; j < Meshes[ i ].Indices.Length; j += 3 ) - { - Vector3 ind = new Vector3 - { - X = Meshes[ i ].Indices[ j + 0 ] + indexModifier, - Y = Meshes[ i ].Indices[ j + 1 ] + indexModifier, - Z = Meshes[ i ].Indices[ j + 2 ] + indexModifier - }; - inList.Add( ind ); - } - } - - var sb = new StringBuilder(); - foreach( var vert in vsList ) - sb.Append( $"v {(decimal) vert.X} {(decimal) vert.Y} {(decimal) vert.Z}{nl}" ); - - foreach( var color in vcList ) - sb.Append( $"vc {(decimal) color.W} {(decimal) color.X} {(decimal) color.Y} {(decimal) color.Z}{nl}" ); - - foreach( var texCoord in vtList ) - sb.Append( $"vt {(decimal) texCoord.X} {(decimal) texCoord.Y} {(decimal) texCoord.W} {(decimal) texCoord.Z}{nl}" ); - - foreach( var norm in nmList ) - sb.Append( $"vn {(decimal) norm.X} {(decimal) norm.Y} {(decimal) norm.Z}{nl}" ); - - foreach( var ind in inList ) - { - sb.Append( String.Format( "f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}{3}", - (ushort) ind.X, - (ushort) ind.Y, - (ushort) ind.Z, - nl ) ); - } - return sb.ToString(); - } - } -} \ No newline at end of file diff --git a/src/Lumina/Models/Model/Mesh.cs b/src/Lumina/Models/Models/Mesh.cs similarity index 71% rename from src/Lumina/Models/Model/Mesh.cs rename to src/Lumina/Models/Models/Mesh.cs index 4157cbf0..ce771255 100644 --- a/src/Lumina/Models/Model/Mesh.cs +++ b/src/Lumina/Models/Models/Mesh.cs @@ -1,13 +1,12 @@ using System; -using System.Collections.Generic; -using System.Dynamic; using System.IO; using System.Linq; using System.Numerics; -using Lumina.Data.Parsing.Mdl; +using Lumina.Data.Parsing; using Lumina.Extensions; +using Lumina.Models.Materials; -namespace Lumina.Models.Model { +namespace Lumina.Models.Models { public class Mesh { public enum MeshType @@ -23,79 +22,107 @@ public enum MeshType CrestChange } - public Model Model { get; private set; } + /// + /// A convenience reference to the Model that instantiated this Mesh. + /// + public Model Parent { get; private set; } + + /// + /// The mesh index within the Model it belongs to. + /// public int MeshIndex { get; private set; } - public MeshType Type { get; private set; } + /// + /// The mesh type categories this mesh can be used for. + /// + public MeshType[] Types { get; private set; } + + /// + /// The list of mesh attributes applied to this mesh. + /// public string[] Attributes { get; private set; } // TODO: build submeshes from the index data // the information is available to build these, but it'd be nice // to have access to Mesh objects with pre-read submesh meshes - // public Mesh[] SubmeshesAsMesh { get; private set; } + // public Mesh SubmeshesAsMesh { get; private set; } + /// + /// The submeshes that make up this mesh. + /// public Submesh[] Submeshes { get; private set; } + /// + /// The bone remapping table for this mesh. + /// public ushort[] BoneTable { get; private set; } + /// + /// The array of all vertices for this mesh. + /// public Vertex[] Vertices; + + /// + /// The array of all indices for this mesh. + /// public ushort[] Indices; - - // todo - // public Material Material { get; private set; } - - public Mesh( Model model, int index, MeshType type ) { - Model = model; + + /// + /// A reference to the Material used by this mesh. + /// + public Material Material { get; private set; } + + public Mesh( Model model, int index, MeshType[] types ) { + Parent = model; MeshIndex = index; - Type = type; + Types = types; BuildMesh(); } - public void BuildMesh() { + private void BuildMesh() { ReadBoneTable(); ReadIndices(); ReadVertices(); ReadSubmeshes(); + + var matIndex = Parent.File.Meshes[ MeshIndex ].MaterialIndex; + Material = Parent.Materials[ matIndex ]; } private void ReadSubmeshes() { - var currentMesh = Model.File.Meshes[ MeshIndex ]; + var currentMesh = Parent.File.Meshes[ MeshIndex ]; Submeshes = new Submesh[ currentMesh.SubMeshCount ]; for( int i = 0; i < currentMesh.SubMeshCount; i++ ) - Submeshes[i] = new Submesh(Model, MeshIndex, i); + Submeshes[i] = new Submesh(Parent, MeshIndex, i); } //TODO is this necessary? private void ReadBoneTable() { - var currentMesh = Model.File.Meshes[ MeshIndex ]; + var currentMesh = Parent.File.Meshes[ MeshIndex ]; // Copy over the bone table int boneTableIndex = currentMesh.BoneTableIndex; if (boneTableIndex != 255) - BoneTable = Model.File.BoneTables[ boneTableIndex ].BoneIndex; + BoneTable = Parent.File.BoneTables[ boneTableIndex ].BoneIndex; } private void ReadIndices() { - var currentMesh = Model.File.Meshes[ MeshIndex ]; + var currentMesh = Parent.File.Meshes[ MeshIndex ]; - // Read indices because they're easy - BinaryReader reader = new BinaryReader( new MemoryStream( Model.File.IndexBuffers[ (int) Model.Lod ] ) ); + BinaryReader reader = new BinaryReader( new MemoryStream( Parent.File.IndexBuffers[ (int) Parent.Lod ] ) ); reader.Seek( currentMesh.StartIndex * 2 ); Indices = reader.ReadStructures< UInt16 >( (int) currentMesh.IndexCount ).ToArray(); } private void ReadVertices() { - MdlStructs.MeshStruct currentMesh = Model.File.Meshes[ MeshIndex ]; - - // Vertex reading stuff - MdlStructs.VertexDeclarationStruct currentDecl = Model.File.VertexDeclarations[ MeshIndex ]; + MdlStructs.MeshStruct currentMesh = Parent.File.Meshes[ MeshIndex ]; + MdlStructs.VertexDeclarationStruct currentDecl = Parent.File.VertexDeclarations[ MeshIndex ]; // We have to go through these in order by offset with this implementation - // TODO is this slow? this sort's purpose is to avoid copying every vertex out var orderedElements = currentDecl.VertexElements.ToList(); orderedElements.Sort( ( e1, e2 ) => e1.Offset.CompareTo( e2.Offset ) ); Vertices = new Vertex[currentMesh.VertexCount]; @@ -103,7 +130,7 @@ private void ReadVertices() // Vertices may be defined across at most 3 streams defined by a Mesh's VertexDeclarations var vertexStreamReader = new BinaryReader[3]; for( int i = 0; i < 3; i++ ) { - vertexStreamReader[ i ] = new BinaryReader( new MemoryStream( Model.File.VertexBuffers[ (int) Model.Lod ] ) ); + vertexStreamReader[ i ] = new BinaryReader( new MemoryStream( Parent.File.VertexBuffers[ (int) Parent.Lod ] ) ); vertexStreamReader[ i ].Seek( currentMesh.VertexBufferOffset[ i ] ); } diff --git a/src/Lumina/Models/Models/Model.cs b/src/Lumina/Models/Models/Model.cs new file mode 100644 index 00000000..e794f5d2 --- /dev/null +++ b/src/Lumina/Models/Models/Model.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Lumina.Data.Files; +using Lumina.Data.Parsing; +using Lumina.Extensions; +using Lumina.Models.Materials; + +namespace Lumina.Models.Models +{ + public class Model + { + /// + /// Model levels of details start at 0 for high, and + /// go up to indicate lowering levels of detail. + /// This means that models with only one level of detail will + /// only have a "high quality" model. + /// + public enum ModelLod + { + High, + Med, + Low + } + + public MdlFile File { get; private set; } + public ModelLod Lod { get; private set; } + public Mesh[] Meshes { get; private set; } + public Material[] Materials { get; private set; } + public Dictionary Shapes { get; private set; } + public Dictionary< int, string > StringOffsetToStringMap { get; private set; } + public int VariantId { get; private set; } + + /// + /// Creates a new Model instance using the provided MdlFile, level of detail, + /// and variant ID, without resolving any game data. + /// + /// The MdlFile to back this Model. + /// The quality of the loaded Model. + /// The variant ID of the Model to load. This will be used + /// to specify a Material variant, and may be used by the caller to limit attributes + /// depending on data from an ImcFile. + /// The specified MdlFile does not contain + /// the specified ModelLod. + public Model( MdlFile mdlFile, ModelLod lod = ModelLod.High, int variantId = 1) + { + if( (uint) lod > mdlFile.FileHeader.LodCount ) + throw new ArgumentException( "The given model does not have the requested LoD.", nameof( lod ) ); + + File = mdlFile; + Lod = lod; + VariantId = variantId; + BuildModel(); + } + + /// + /// Creates a new Model instance using the provided path, access to game data, + /// level of detail, and variant ID. The Model will be built and then updated + /// with game data. + /// + /// A reference to game data access. + /// The path to this Model. + /// The quality of the loaded Model. + /// The variant ID of the Model to load. This will be used + /// to specify a Material variant, and may be used by the caller to limit attributes + /// depending on data from an ImcFile. + /// The specified MdlFile does not contain + /// the specified ModelLod. + public Model( GameData data, string path, ModelLod lod = ModelLod.High, int variantId = 1 ) + { + var mdlFile = data.GetFile< MdlFile >( path ); + + if( (uint) lod > mdlFile.FileHeader.LodCount ) + throw new ArgumentException( "The given model does not have the requested LoD.", nameof( lod ) ); + + File = mdlFile; + Lod = lod; + VariantId = variantId; + BuildModel(); + Update(data); + } + + private void BuildModel() + { + // Every valid MDL has VertexDeclarations + if( File.VertexDeclarations == null ) + { + Console.WriteLine( "BuildModel() called on an empty MdlFile." ); + return; + } + + ReadStrings(); + ReadMaterials(); + ReadMeshes(); + ReadShapes(); + } + + /// + /// Update this Model using game data. The Model's references + /// to Materials will be resolved and the Material files fully + /// loaded to be used to resolve textures and other information. + /// + /// A reference to game data access. + /// The existing Model instance, for method chaining. + public Model Update(GameData data) + { + foreach (var mat in Materials) + mat.Update( data ); + return this; + } + + private void ReadMaterials() + { + Materials = new Material[File.FileHeader.MaterialCount]; + + for( int i = 0; i < File.FileHeader.MaterialCount; i++) + { + var pathOffset = File.MaterialNameOffsets[ i ]; + var path = StringOffsetToStringMap[ (int) pathOffset ]; + Materials[ i ] = new Material( this, path, VariantId ); + } + } + + private void ReadShapes() + { + Shapes = new Dictionary< string, Shape >(); + for( int i = 0; i < File.Shapes.Length; i++ ) + { + // We will need more info in the constructor here... eventually + var shape = new Shape( this, i ); + Shapes[ shape.ShapeName ] = shape; + } + } + + private void ReadStrings() + { + StringOffsetToStringMap = new Dictionary< int, string >(); + var mr = new MemoryStream( File.Strings ); + var br = new BinaryReader( mr ); + for( int i = 0; i < File.StringCount; i++ ) + { + long startOffset = br.BaseStream.Position; + string tmp = br.ReadStringData(); + StringOffsetToStringMap[ (int) startOffset ] = tmp; + } + br.Dispose(); + mr.Dispose(); + } + + private void ReadMeshes() + { + // My brain is fried + var ranges = new List< (int s, int e ) >(); + + MdlStructs.LodStruct currentLod = File.Lods[ (int) Lod ]; + ranges.Add( (currentLod.MeshIndex, currentLod.MeshIndex + currentLod.MeshCount) ); + ranges.Add( (currentLod.WaterMeshIndex, currentLod.WaterMeshIndex + currentLod.WaterMeshCount) ); + ranges.Add( (currentLod.ShadowMeshIndex, currentLod.ShadowMeshIndex + currentLod.ShadowMeshCount) ); + ranges.Add( (currentLod.TerrainShadowMeshIndex, currentLod.TerrainShadowMeshIndex + currentLod.TerrainShadowMeshCount) ); + ranges.Add( (currentLod.VerticalFogMeshIndex, currentLod.VerticalFogMeshIndex + currentLod.VerticalFogMeshCount) ); + + if( File.ModelHeader.ExtraLodEnabled ) + { + MdlStructs.ExtraLodStruct extraLod = File.ExtraLods[ (int) Lod ]; + ranges.Add( ( extraLod.LightShaftMeshIndex, extraLod.LightShaftMeshIndex + extraLod.LightShaftMeshCount ) ); + ranges.Add( ( extraLod.GlassMeshIndex, extraLod.GlassMeshIndex + extraLod.GlassMeshCount ) ); + ranges.Add( ( extraLod.MaterialChangeMeshIndex, extraLod.MaterialChangeMeshIndex + extraLod.MaterialChangeMeshCount ) ); + ranges.Add( ( extraLod.CrestChangeMeshIndex, extraLod.CrestChangeMeshIndex + extraLod.CrestChangeMeshCount ) ); + } + + int totalMeshes = 0; + bool atIndex = true; + while (atIndex) + { + atIndex = false; + foreach( var range in ranges ) + { + if( range.s <= totalMeshes && totalMeshes < range.e ) + { + atIndex = true; + totalMeshes++; + break; + } + } + } + + Meshes = new Mesh[totalMeshes]; + + for( int i = 0; i < Meshes.Length; i++ ) + { + Meshes[ i ] = new Mesh( this, i, GetMeshTypes( i ) ); + } + } + + private Mesh.MeshType[] GetMeshTypes( int index ) + { + // I could create arrays for the ranges for cleaner code, but all this is doing is checking ranges + var types = new List< Mesh.MeshType >(); + if( index >= File.Lods[ (int) Lod ].MeshIndex && + index < File.Lods[ (int) Lod ].MeshIndex + File.Lods[ (int) Lod ].MeshCount && + File.Lods[ (int) Lod ].MeshCount > 0) + types.Add( Mesh.MeshType.Main); + if( index >= File.Lods[ (int) Lod ].WaterMeshIndex && + index < File.Lods[ (int) Lod ].WaterMeshIndex + File.Lods[ (int) Lod ].WaterMeshCount && + File.Lods[ (int) Lod ].WaterMeshCount > 0) + types.Add( Mesh.MeshType.Water); + if( index >= File.Lods[ (int) Lod ].ShadowMeshIndex && + index < File.Lods[ (int) Lod ].ShadowMeshIndex + File.Lods[ (int) Lod ].ShadowMeshCount && + File.Lods[ (int) Lod ].ShadowMeshCount > 0) + types.Add( Mesh.MeshType.Shadow); + if( index >= File.Lods[ (int) Lod ].TerrainShadowMeshIndex && + index < File.Lods[ (int) Lod ].TerrainShadowMeshIndex + File.Lods[ (int) Lod ].TerrainShadowMeshCount && + File.Lods[ (int) Lod ].TerrainShadowMeshCount > 0) + types.Add( Mesh.MeshType.TerrainShadow); + if( index >= File.Lods[ (int) Lod ].VerticalFogMeshIndex && + index < File.Lods[ (int) Lod ].VerticalFogMeshIndex + File.Lods[ (int) Lod ].VerticalFogMeshCount && + File.Lods[ (int) Lod ].VerticalFogMeshCount > 0) + types.Add( Mesh.MeshType.VerticalFog); + + if( !File.ModelHeader.ExtraLodEnabled ) return types.ToArray(); + + if( index >= File.ExtraLods[ (int) Lod ].LightShaftMeshIndex && + index < File.ExtraLods[ (int) Lod ].LightShaftMeshIndex + File.ExtraLods[ (int) Lod ].LightShaftMeshCount && + File.ExtraLods[ (int) Lod ].LightShaftMeshCount > 0) + types.Add( Mesh.MeshType.LightShaft); + if( index >= File.ExtraLods[ (int) Lod ].GlassMeshIndex && + index < File.ExtraLods[ (int) Lod ].GlassMeshIndex + File.ExtraLods[ (int) Lod ].GlassMeshCount && + File.ExtraLods[ (int) Lod ].GlassMeshCount > 0) + types.Add( Mesh.MeshType.Glass); + if( index >= File.ExtraLods[ (int) Lod ].MaterialChangeMeshIndex && + index < File.ExtraLods[ (int) Lod ].MaterialChangeMeshIndex + File.ExtraLods[ (int) Lod ].MaterialChangeMeshCount && + File.ExtraLods[ (int) Lod ].MaterialChangeMeshCount > 0) + types.Add( Mesh.MeshType.MaterialChange); + if( index >= File.ExtraLods[ (int) Lod ].CrestChangeMeshIndex && + index < File.ExtraLods[ (int) Lod ].CrestChangeMeshIndex + File.ExtraLods[ (int) Lod ].CrestChangeMeshCount && + File.ExtraLods[ (int) Lod ].CrestChangeMeshCount > 0 ) + types.Add( Mesh.MeshType.CrestChange ); + + + return types.ToArray(); + } + + public Mesh[] GetMeshesByType( Mesh.MeshType mp ) + { + return Meshes.Where( m => m.Types.Contains(mp) ).ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Lumina/Models/Model/Shape.cs b/src/Lumina/Models/Models/Shape.cs similarity index 93% rename from src/Lumina/Models/Model/Shape.cs rename to src/Lumina/Models/Models/Shape.cs index 4de03c6f..8476930e 100644 --- a/src/Lumina/Models/Model/Shape.cs +++ b/src/Lumina/Models/Models/Shape.cs @@ -1,4 +1,4 @@ -namespace Lumina.Models.Model +namespace Lumina.Models.Models { public class Shape { diff --git a/src/Lumina/Models/Model/Submesh.cs b/src/Lumina/Models/Models/Submesh.cs similarity index 79% rename from src/Lumina/Models/Model/Submesh.cs rename to src/Lumina/Models/Models/Submesh.cs index 4c7b1a1c..642a1234 100644 --- a/src/Lumina/Models/Model/Submesh.cs +++ b/src/Lumina/Models/Models/Submesh.cs @@ -1,13 +1,28 @@ using System.Collections.Generic; -using Lumina.Data.Parsing.Mdl; +using Lumina.Data.Parsing; -namespace Lumina.Models.Model +namespace Lumina.Models.Models { public class Submesh { + /// + /// The offset to the index that this submesh begins. + /// public uint IndexOffset; + + /// + /// The number of indices present in this submesh. + /// public uint IndexNum; + + /// + /// The attributes that are enabled for this submesh. + /// public string[] Attributes; + + /// + /// The bones referenced by this submesh. + /// public string[] Bones; public Submesh( Model model, int meshIndex, int subMeshIndex ) diff --git a/src/Lumina/Models/Model/Vertex.cs b/src/Lumina/Models/Models/Vertex.cs similarity index 85% rename from src/Lumina/Models/Model/Vertex.cs rename to src/Lumina/Models/Models/Vertex.cs index f8585249..d82c5342 100644 --- a/src/Lumina/Models/Model/Vertex.cs +++ b/src/Lumina/Models/Models/Vertex.cs @@ -2,9 +2,9 @@ using System.Linq; using System.Numerics; using Lumina.Data.Files; -using Lumina.Data.Parsing.Mdl; +using Lumina.Data.Parsing; -namespace Lumina.Models.Model { +namespace Lumina.Models.Models { public struct Vertex { @@ -28,8 +28,6 @@ public enum VertexUsage : byte { Color = 7, } - public static int[] ElementSizes = {0, 0, 12, 16, 4, 4, 4, 8}; - public Vector4? Position; public Vector4? BlendWeights; public byte[] BlendIndices; From 0a98746e23687b98a8d8ce3cfa0c799ae77d1964 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 4 Apr 2021 02:37:00 -0400 Subject: [PATCH 6/9] Terrain files --- src/Lumina/Data/Files/TeraFile.cs | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/Lumina/Data/Files/TeraFile.cs diff --git a/src/Lumina/Data/Files/TeraFile.cs b/src/Lumina/Data/Files/TeraFile.cs new file mode 100644 index 00000000..23119505 --- /dev/null +++ b/src/Lumina/Data/Files/TeraFile.cs @@ -0,0 +1,48 @@ +using System.Numerics; +using Lumina.Extensions; + +namespace Lumina.Data.Files +{ + struct PlatePos + { + public short x; + public short y; + } + + public class TeraFile : FileResource + { + public uint Version; + public uint PlateCount; + public uint PlateSize; + public float ClipDistance; + public float Unknown; + public byte[] Padding; + + private PlatePos[] _positions; + + public override void LoadFile() + { + Version = Reader.ReadUInt32(); + PlateCount = Reader.ReadUInt32(); + PlateSize = Reader.ReadUInt32(); + ClipDistance = Reader.ReadSingle(); + Unknown = Reader.ReadSingle(); + Padding = Reader.ReadBytes( 32 ); + + _positions = Reader.ReadStructuresAsArray< PlatePos >( (int) PlateCount ); + } + + /// + /// Retrieve the X and Z coordinates of the specified plate index. Note that + /// the Y coordinate is unnecessary as bg plates each contain all necessary vertical + /// data in their respective plate. + /// + /// The index of the bg plate to obtain the coordinates for. + /// + public Vector2 GetPlatePosition( int plateIndex ) + { + var pos = _positions[ plateIndex ]; + return new Vector2( PlateSize * ( pos.x + 0.5f ), PlateSize * ( pos.y + 0.5f ) ); + } + } +} \ No newline at end of file From 79cf0b29ac2868f2b7a894729be380357bb484de Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 5 Apr 2021 00:55:06 -0400 Subject: [PATCH 7/9] Do not unnecessarily copy vertex and index buffers --- src/Lumina/Data/Files/MdlFile.cs | 14 ---- src/Lumina/Models/Models/Mesh.cs | 108 ++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 16 deletions(-) diff --git a/src/Lumina/Data/Files/MdlFile.cs b/src/Lumina/Data/Files/MdlFile.cs index 936eb652..1906d2e9 100644 --- a/src/Lumina/Data/Files/MdlFile.cs +++ b/src/Lumina/Data/Files/MdlFile.cs @@ -35,9 +35,6 @@ public class MdlFile : FileResource { public ushort StringCount; public byte[] Strings; - public byte[][] VertexBuffers; - public byte[][] IndexBuffers; - public override void LoadFile() { // We can ensure based on content-type that files are models @@ -64,9 +61,6 @@ public override void LoadFile() Shapes = new MdlStructs.ShapeStruct[ModelHeader.ShapeCount]; BoneBoundingBoxes = new MdlStructs.BoundingBoxStruct[ModelHeader.BoneCount]; - VertexBuffers = new byte[3][]; - IndexBuffers = new byte[3][]; - for( int i = 0; i < ModelHeader.ElementIdCount; i++ ) ElementIds[ i ] = MdlStructs.ElementIdStruct.Read( Reader ); Lods = Reader.ReadStructuresAsArray< MdlStructs.LodStruct >( 3 ); @@ -99,14 +93,6 @@ public override void LoadFile() WaterBoundingBoxes = MdlStructs.BoundingBoxStruct.Read( Reader ); VerticalFogBoundingBoxes = MdlStructs.BoundingBoxStruct.Read( Reader ); for( int i = 0; i < ModelHeader.BoneCount; i++ ) BoneBoundingBoxes[ i ] = MdlStructs.BoundingBoxStruct.Read( Reader ); - - for( int i = 0; i < 3; i++ ) { - Reader.BaseStream.Position = FileHeader.VertexOffset[ i ]; - VertexBuffers[ i ] = Reader.ReadBytes( (int) FileHeader.VertexBufferSize[ i ] ); - - Reader.BaseStream.Position = FileHeader.IndexOffset[ i ]; - IndexBuffers[ i ] = Reader.ReadBytes( (int) FileHeader.IndexBufferSize[ i ] ); - } } } } \ No newline at end of file diff --git a/src/Lumina/Models/Models/Mesh.cs b/src/Lumina/Models/Models/Mesh.cs index ce771255..8cc56e53 100644 --- a/src/Lumina/Models/Models/Mesh.cs +++ b/src/Lumina/Models/Models/Mesh.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; +using System.Text; using Lumina.Data.Parsing; using Lumina.Extensions; using Lumina.Models.Materials; @@ -112,7 +114,9 @@ private void ReadIndices() { var currentMesh = Parent.File.Meshes[ MeshIndex ]; - BinaryReader reader = new BinaryReader( new MemoryStream( Parent.File.IndexBuffers[ (int) Parent.Lod ] ) ); + BinaryReader reader = new BinaryReader( new MemoryStream( Parent.File.Data, + (int) Parent.File.FileHeader.IndexOffset[ (int) Parent.Lod ], + (int) Parent.File.FileHeader.IndexBufferSize[ (int) Parent.Lod ] ) ); reader.Seek( currentMesh.StartIndex * 2 ); Indices = reader.ReadStructures< UInt16 >( (int) currentMesh.IndexCount ).ToArray(); } @@ -130,7 +134,9 @@ private void ReadVertices() // Vertices may be defined across at most 3 streams defined by a Mesh's VertexDeclarations var vertexStreamReader = new BinaryReader[3]; for( int i = 0; i < 3; i++ ) { - vertexStreamReader[ i ] = new BinaryReader( new MemoryStream( Parent.File.VertexBuffers[ (int) Parent.Lod ] ) ); + vertexStreamReader[ i ] = new BinaryReader( new MemoryStream( Parent.File.Data, + (int) Parent.File.FileHeader.VertexOffset[ (int) Parent.Lod ], + (int) Parent.File.FileHeader.VertexBufferSize[ (int) Parent.Lod ] ) ); vertexStreamReader[ i ].Seek( currentMesh.VertexBufferOffset[ i ] ); } @@ -187,5 +193,103 @@ private static Vector4 GetVector4( object value ) { } throw new ArgumentOutOfRangeException(); } + + /// + /// Writes a Mesh into a single Wavefront Object file. This is not recommended + /// for general use as the .obj format has very limited information, and does not + /// include vertex colors, materials, or textures. + /// + /// A string containing the entire .obj file, including newlines. + public string GetObjectFileText() + { + string nl = Environment.NewLine; + + var vsList = new List< Vector3 >(); + var vcList = new List< Vector4 >(); + var vtList = new List< Vector4 >(); + var nmList = new List< Vector3 >(); + var inList = new List< Vector3 >(); + + foreach( var vert in Vertices ) + { + Vector3 vs = new Vector3(); + Vector4 vc = new Vector4(); + Vector4 vt = new Vector4(); + Vector3 nm = new Vector3(); + + vs.X = vert.Position.Value.X; + vs.Y = vert.Position.Value.Y; + vs.Z = vert.Position.Value.Z; + + if( vert.Color == null ) + { + vc.X = 0; + vc.Y = 0; + vc.W = 0; + vc.Z = 0; + } + else + { + vc.X = vert.Color.Value.X; + vc.Y = vert.Color.Value.Y; + vc.W = vert.Color.Value.W; + vc.Z = vert.Color.Value.Z; + } + + if( vert.UV.HasValue ) + { + vt.X = vert.UV.Value.X; + vt.Y = -1 * vert.UV.Value.Y; + vt.W = ( vert.UV.Value.W == 0 ) ? vt.X : vert.UV.Value.W; + vt.Z = ( vert.UV.Value.Z == 0 ) ? vt.Y : vert.UV.Value.Z; + } + + if( vert.Normal.HasValue ) + { + nm.X = vert.Normal.Value.X; + nm.Y = vert.Normal.Value.Y; + nm.Z = vert.Normal.Value.Z; + } + + vsList.Add( vs ); + vcList.Add( vc ); + vtList.Add( vt ); + nmList.Add( nm ); + } + + for( int j = 0; j < Indices.Length; j += 3 ) + { + Vector3 ind = new Vector3 + { + X = Indices[ j + 0 ] + 1, + Y = Indices[ j + 1 ] + 1, + Z = Indices[ j + 2 ] + 1 + }; + inList.Add( ind ); + } + + var sb = new StringBuilder(); + foreach( var vert in vsList ) + sb.Append( $"v {(decimal) vert.X} {(decimal) vert.Y} {(decimal) vert.Z}{nl}" ); + + foreach( var color in vcList ) + sb.Append( $"vc {(decimal) color.W} {(decimal) color.X} {(decimal) color.Y} {(decimal) color.Z}{nl}" ); + + foreach( var texCoord in vtList ) + sb.Append( $"vt {(decimal) texCoord.X} {(decimal) texCoord.Y} {(decimal) texCoord.W} {(decimal) texCoord.Z}{nl}" ); + + foreach( var norm in nmList ) + sb.Append( $"vn {(decimal) norm.X} {(decimal) norm.Y} {(decimal) norm.Z}{nl}" ); + + foreach( var ind in inList ) + { + sb.Append( String.Format( "f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}{3}", + (ushort) ind.X, + (ushort) ind.Y, + (ushort) ind.Z, + nl ) ); + } + return sb.ToString(); + } } } \ No newline at end of file From 715494105711efdb3b4116b23b37af1c147be1ba Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 5 Apr 2021 01:02:10 -0400 Subject: [PATCH 8/9] omit shader stuff for now, not done --- src/Lumina/Data/Parsing/ShaderStructs.cs | 129 ----------------------- 1 file changed, 129 deletions(-) delete mode 100644 src/Lumina/Data/Parsing/ShaderStructs.cs diff --git a/src/Lumina/Data/Parsing/ShaderStructs.cs b/src/Lumina/Data/Parsing/ShaderStructs.cs deleted file mode 100644 index 784d9e7f..00000000 --- a/src/Lumina/Data/Parsing/ShaderStructs.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.IO; -using Lumina.Extensions; - -namespace Lumina.Data.Parsing -{ - // Here is a catch-all comment telling you to check the template if you have access - public class ShaderStructs - { - struct ShaderHeader - { - public uint Magic; - public uint Version; - public uint Platform; - public uint FileSize; - public uint CodeStart; - public uint StringsStart; - - public static ShaderHeader Read(BinaryReader br) - { - ShaderHeader ret = new ShaderHeader(); - ret.Magic = br.ReadUInt32(); - ret.Version = br.ReadUInt32(); - ret.Platform = br.ReadUInt32(); - ret.FileSize = br.ReadUInt32(); - ret.CodeStart = br.ReadUInt32(); - ret.StringsStart = br.ReadUInt32(); - return ret; - } - } - - struct ShaderPackageHeader - { - public ShaderHeader ShaderHeader; - public uint VertexShaderCount; - public uint PixelShaderCount; - public uint MaterialBufferSize; - public uint MaterialElementCount; - public uint ConstantCount; - public uint SamplerCount; - public uint RwResourceCount; - public uint SystemKeyCount; - public uint SceneKeyCount; - public uint MaterialKeyCount; - public uint ShaderNodeCount; - public uint ReferenceNodeCount; - - public static ShaderPackageHeader Read(BinaryReader br) - { - ShaderPackageHeader ret = new ShaderPackageHeader(); - ret.ShaderHeader = br.ReadStructure< ShaderHeader >(); - ret.VertexShaderCount = br.ReadUInt32(); - ret.PixelShaderCount = br.ReadUInt32(); - ret.MaterialBufferSize = br.ReadUInt32(); - ret.MaterialElementCount = br.ReadUInt32(); - ret.ConstantCount = br.ReadUInt32(); - ret.SamplerCount = br.ReadUInt32(); - ret.RwResourceCount = br.ReadUInt32(); - ret.SystemKeyCount = br.ReadUInt32(); - ret.SceneKeyCount = br.ReadUInt32(); - ret.MaterialKeyCount = br.ReadUInt32(); - ret.ShaderNodeCount = br.ReadUInt32(); - ret.ReferenceNodeCount = br.ReadUInt32(); - return ret; - } - } - - struct ShaderParameter - { - public uint ShaderParameterKey; - public uint ParameterNameOffset; - public uint ParameterNameSize; - public ushort RegisterIndex; - public ushort RegisterCount; - } - - struct ShaderCode - { - public uint ShaderCodeOffset; - public uint ShaderCodeSize; - public ushort ConstantCount; - public ushort SamplerCount; - public ushort RwResourceCount; - public ushort Unknown2; - public ShaderParameter[] ShaderParameters; - - public static ShaderCode Read(BinaryReader br) - { - ShaderCode ret = new ShaderCode(); - ret.ShaderCodeOffset = br.ReadUInt32(); - ret.ShaderCodeSize = br.ReadUInt32(); - ret.ConstantCount = br.ReadUInt16(); - ret.SamplerCount = br.ReadUInt16(); - ret.RwResourceCount = br.ReadUInt16(); - ret.Unknown2 = br.ReadUInt16(); - ret.ShaderParameters = br.ReadStructuresAsArray(ret.ConstantCount + ret.SamplerCount + ret.RwResourceCount); - return ret; - } - } - - struct ShaderMaterial - { - public uint Material; - public ushort Offset; - public ushort Size; - } - - struct ShaderPass - { - public uint ShaderPassId; - public uint VertexShaderIndex; - public uint PixelShaderIndex; - } - - struct ShaderNode - { - public uint ShaderNodeKey; - public uint ShaderPassCount; - public byte[] ShaderPassIndices; - public uint[] ShaderKeys; - public ShaderPass[] ShaderPass; - } - - struct ShaderKeyValue - { - public uint Key; - public uint Value; - } - } -} \ No newline at end of file From 34635bab946874639c5e14f62a777abf339c98d5 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 5 Apr 2021 01:49:28 -0400 Subject: [PATCH 9/9] Minor refactors --- src/Lumina/Data/Files/MdlFile.cs | 2 +- src/Lumina/Data/Parsing/MdlStructs.cs | 144 +++++++++--------------- src/Lumina/Data/Parsing/MtrlStructs.cs | 9 +- src/Lumina/Models/Materials/Material.cs | 4 +- 4 files changed, 57 insertions(+), 102 deletions(-) diff --git a/src/Lumina/Data/Files/MdlFile.cs b/src/Lumina/Data/Files/MdlFile.cs index 1906d2e9..84df29c9 100644 --- a/src/Lumina/Data/Files/MdlFile.cs +++ b/src/Lumina/Data/Files/MdlFile.cs @@ -54,7 +54,7 @@ public override void LoadFile() uint stringSize = Reader.ReadUInt32(); Strings = Reader.ReadBytes( (int) stringSize ); - ModelHeader = MdlStructs.ModelHeader.Read( Reader ); + ModelHeader = Reader.ReadStructure(); ElementIds = new MdlStructs.ElementIdStruct[ModelHeader.ElementIdCount]; Meshes = new MdlStructs.MeshStruct[ModelHeader.MeshCount]; BoneTables = new MdlStructs.BoneTableStruct[ModelHeader.BoneTableCount]; diff --git a/src/Lumina/Data/Parsing/MdlStructs.cs b/src/Lumina/Data/Parsing/MdlStructs.cs index 87e39d3e..afa84322 100644 --- a/src/Lumina/Data/Parsing/MdlStructs.cs +++ b/src/Lumina/Data/Parsing/MdlStructs.cs @@ -8,6 +8,32 @@ namespace Lumina.Data.Parsing { public static class MdlStructs { + [Flags] + public enum ModelFlags1 : byte + { + DustOcclusionEnabled = 0x80, + SnowOcclusionEnabled = 0x40, + RainOcclusionEnabled = 0x20, + Unknown1 = 0x10, + LightingReflectionEnabled = 0x08, + WavingAnimationDisabled = 0x04, + LightShadowDisabled = 0x02, + ShadowDisabled = 0x01, + } + + [Flags] + public enum ModelFlags2 : byte + { + Unknown2 = 0x80, + BgUvScrollEnabled = 0x40, + EnableForceNonResident = 0x20, + ExtraLodEnabled = 0x10, + ShadowMaskEnabled = 0x08, + ForceLodRangeEnabled = 0x04, + EdgeGeometryEnabled = 0x02, + Unknown3 = 0x01 + } + public struct ModelFileHeader { public uint Version; @@ -58,11 +84,11 @@ public static VertexDeclarationStruct Read( BinaryReader br ) var elems = new List< VertexElement >(); // Read the vertex elements that we need - var thisElem = VertexElement.Read( br ); + var thisElem = br.ReadStructure(); do { elems.Add( thisElem ); - thisElem = VertexElement.Read( br ); + thisElem = br.ReadStructure(); } while( thisElem.Stream != 255 ); // Skip the number of bytes that we don't need to read @@ -76,29 +102,17 @@ public static VertexDeclarationStruct Read( BinaryReader br ) } } - public struct VertexElement + public unsafe struct VertexElement { public byte Stream; public byte Offset; public byte Type; public byte Usage; public byte UsageIndex; // D3D9 remnant? - private byte[] Padding; - - public static VertexElement Read( BinaryReader br ) - { - VertexElement ret = new VertexElement(); - ret.Stream = br.ReadByte(); - ret.Offset = br.ReadByte(); - ret.Type = br.ReadByte(); - ret.Usage = br.ReadByte(); - ret.UsageIndex = br.ReadByte(); - ret.Padding = br.ReadBytes( 3 ); - return ret; - } + private fixed byte Padding[3]; } - public struct ModelHeader + public unsafe struct ModelHeader { // MeshHeader public float Radius; @@ -113,29 +127,30 @@ public struct ModelHeader public ushort ShapeValueCount; public byte LodCount; - private byte bitfield1; + private ModelFlags1 Flags1; + + public bool DustOcclusionEnabled => Flags1.HasFlag(ModelFlags1.DustOcclusionEnabled); + public bool SnowOcclusionEnabled => Flags1.HasFlag(ModelFlags1.SnowOcclusionEnabled); + public bool RainOcclusionEnabled => Flags1.HasFlag(ModelFlags1.RainOcclusionEnabled); + public bool Unknown1 => Flags1.HasFlag(ModelFlags1.Unknown1); + public bool BgLightingReflectionEnabled => Flags1.HasFlag(ModelFlags1.LightingReflectionEnabled); + public bool WavingAnimationDisabled => Flags1.HasFlag(ModelFlags1.WavingAnimationDisabled); + public bool LightShadowDisabled => Flags1.HasFlag(ModelFlags1.LightShadowDisabled); + public bool ShadowDisabled => Flags1.HasFlag(ModelFlags1.ShadowDisabled); - public bool DustOcclusionEnabled; - public bool SnowOcclusionEnabled; - public bool RainOcclusionEnabled; - public bool Unknown1; - public bool BGLightingReflectionEnabled; - public bool WavingAnimationDisabled; - public bool LightShadowDisabled; - public bool ShadowDisabled; public ushort ElementIdCount; public byte TerrainShadowMeshCount; - - private byte bitfield2; - - public bool Unknown2; - public bool BgUvScrollEnabled; - public bool EnableForceNonResident; - public bool ExtraLodEnabled; - public bool ShadowMaskEnabled; - public bool ForceLodRangeEnabled; - public bool EdgeGeometryEnabled; - public bool Unknown3; + + private ModelFlags2 Flags2; + + public bool Unknown2 => Flags2.HasFlag(ModelFlags2.Unknown2); + public bool BgUvScrollEnabled => Flags2.HasFlag(ModelFlags2.BgUvScrollEnabled); + public bool EnableForceNonResident => Flags2.HasFlag(ModelFlags2.EnableForceNonResident); + public bool ExtraLodEnabled => Flags2.HasFlag(ModelFlags2.ExtraLodEnabled); + public bool ShadowMaskEnabled => Flags2.HasFlag(ModelFlags2.ShadowMaskEnabled); + public bool ForceLodRangeEnabled => Flags2.HasFlag(ModelFlags2.ForceLodRangeEnabled); + public bool EdgeGeometryEnabled => Flags2.HasFlag(ModelFlags2.EdgeGeometryEnabled); + public bool Unknown3 => Flags2.HasFlag(ModelFlags2.Unknown3); public float ModelClipOutDistance; public float ShadowClipOutDistance; @@ -150,60 +165,7 @@ public struct ModelHeader public ushort Unknown7; public ushort Unknown8; public ushort Unknown9; - private byte[] Padding00; - - public static ModelHeader Read( BinaryReader br ) - { - ModelHeader ret = new ModelHeader(); - ret.Radius = br.ReadSingle(); - ret.MeshCount = br.ReadUInt16(); - ret.AttributeCount = br.ReadUInt16(); - ret.SubmeshCount = br.ReadUInt16(); - ret.MaterialCount = br.ReadUInt16(); - ret.BoneCount = br.ReadUInt16(); - ret.BoneTableCount = br.ReadUInt16(); - ret.ShapeCount = br.ReadUInt16(); - ret.ShapeMeshCount = br.ReadUInt16(); - ret.ShapeValueCount = br.ReadUInt16(); - ret.LodCount = br.ReadByte(); - - ret.bitfield1 = br.ReadByte(); - ret.DustOcclusionEnabled = ( ret.bitfield1 & 0x80 ) == 0x80; - ret.SnowOcclusionEnabled = ( ret.bitfield1 & 0x40 ) == 0x40; - ret.RainOcclusionEnabled = ( ret.bitfield1 & 0x20 ) == 0x20; - ret.Unknown1 = ( ret.bitfield1 & 0x10 ) == 0x10; - ret.BGLightingReflectionEnabled = ( ret.bitfield1 & 0x08 ) == 0x08; - ret.WavingAnimationDisabled = ( ret.bitfield1 & 0x04 ) == 0x04; - ret.LightShadowDisabled = ( ret.bitfield1 & 0x02 ) == 0x02; - ret.ShadowDisabled = ( ret.bitfield1 & 0x01 ) == 0x01; - - ret.ElementIdCount = br.ReadUInt16(); - ret.TerrainShadowMeshCount = br.ReadByte(); - - ret.bitfield2 = br.ReadByte(); - ret.Unknown2 = ( ret.bitfield2 & 0x80 ) == 0x80; - ret.BgUvScrollEnabled = ( ret.bitfield2 & 0x40 ) == 0x40; - ret.EnableForceNonResident = ( ret.bitfield2 & 0x20 ) == 0x20; - ret.ExtraLodEnabled = ( ret.bitfield2 & 0x10 ) == 0x10; - ret.ShadowMaskEnabled = ( ret.bitfield2 & 0x08 ) == 0x08; - ret.ForceLodRangeEnabled = ( ret.bitfield2 & 0x04 ) == 0x04; - ret.EdgeGeometryEnabled = ( ret.bitfield2 & 0x02 ) == 0x02; - ret.Unknown3 = ( ret.bitfield2 & 0x01 ) == 0x01; - - ret.ModelClipOutDistance = br.ReadSingle(); - ret.ShadowClipOutDistance = br.ReadSingle(); - ret.Unknown4 = br.ReadUInt16(); - ret.TerrainShadowSubmeshCount = br.ReadUInt16(); - ret.Unknown5 = br.ReadByte(); - ret.BGChangeMaterialIndex = br.ReadByte(); - ret.BGCrestChangeMaterialIndex = br.ReadByte(); - ret.Unknown6 = br.ReadByte(); - ret.Unknown7 = br.ReadUInt16(); - ret.Unknown8 = br.ReadUInt16(); - ret.Unknown9 = br.ReadUInt16(); - ret.Padding00 = br.ReadBytes( 6 ).ToArray(); - return ret; - } + private fixed byte Padding[6]; } public struct ElementIdStruct diff --git a/src/Lumina/Data/Parsing/MtrlStructs.cs b/src/Lumina/Data/Parsing/MtrlStructs.cs index 64ceef7c..d566c037 100644 --- a/src/Lumina/Data/Parsing/MtrlStructs.cs +++ b/src/Lumina/Data/Parsing/MtrlStructs.cs @@ -68,17 +68,10 @@ public struct Constant public ushort ValueSize; } - public struct SamplerState - { - // Seems to be a bitfield, but no idea what the - // values are for - public uint Unknown; - } - public unsafe struct Sampler { public uint SamplerId; - public SamplerState SamplerState; + public uint Flags; // Bitfield; values unknown public byte TextureIndex; private fixed byte Padding[3]; } diff --git a/src/Lumina/Models/Materials/Material.cs b/src/Lumina/Models/Materials/Material.cs index 1359c8c7..8477f53c 100644 --- a/src/Lumina/Models/Materials/Material.cs +++ b/src/Lumina/Models/Materials/Material.cs @@ -140,9 +140,9 @@ public Material Update( GameData data ) /// The resolved, absolute path to the requested material, or null if unsuccessful. public static string ResolveRelativeMaterialPath( string relativePath, int variantId ) { - var id1 = char.Parse( relativePath.Substring( 4, 1 ) ); + var id1 = relativePath[4]; var val1 = relativePath.Substring( 5, 4 ); - var id2 = char.Parse( relativePath.Substring( 9, 1 ) ); + var id2 = relativePath[9]; var val2 = relativePath.Substring( 10, 4 ); return ( id1, id2 ) switch