Penumbra/Penumbra.GameData/Files/MdlFile.Write.cs

317 lines
No EOL
10 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Lumina.Data.Parsing;
namespace Penumbra.GameData.Files;
public partial class MdlFile
{
private static uint Write( BinaryWriter w, string s, long basePos )
{
var currentPos = w.BaseStream.Position;
w.Write( Encoding.UTF8.GetBytes( s ) );
w.Write( ( byte )0 );
return ( uint )( currentPos - basePos );
}
private List< uint > WriteStrings( BinaryWriter w )
{
var startPos = ( int )w.BaseStream.Position;
var basePos = startPos + 8;
var count = ( ushort )( Attributes.Length + Bones.Length + Materials.Length + Shapes.Length );
w.Write( count );
w.Seek( basePos, SeekOrigin.Begin );
var ret = Attributes.Concat( Bones )
.Concat( Materials )
.Concat( Shapes.Select( s => s.ShapeName ) )
.Select( attribute => Write( w, attribute, basePos ) ).ToList();
w.Write( ( ushort )0 ); // Seems to always have two additional null-bytes, not padding.
var size = ( int )w.BaseStream.Position - basePos;
w.Seek( startPos + 4, SeekOrigin.Begin );
w.Write( ( uint )size );
w.Seek( basePos + size, SeekOrigin.Begin );
return ret;
}
private void WriteModelFileHeader( BinaryWriter w, uint runtimeSize )
{
w.Write( Version );
w.Write( StackSize );
w.Write( runtimeSize );
w.Write( ( ushort )VertexDeclarations.Length );
w.Write( ( ushort )Materials.Length );
w.Write( VertexOffset[ 0 ] > 0 ? VertexOffset[ 0 ] + runtimeSize : 0u );
w.Write( VertexOffset[ 1 ] > 0 ? VertexOffset[ 1 ] + runtimeSize : 0u );
w.Write( VertexOffset[ 2 ] > 0 ? VertexOffset[ 2 ] + runtimeSize : 0u );
w.Write( IndexOffset[ 0 ] > 0 ? IndexOffset[ 0 ] + runtimeSize : 0u );
w.Write( IndexOffset[ 1 ] > 0 ? IndexOffset[ 1 ] + runtimeSize : 0u );
w.Write( IndexOffset[ 2 ] > 0 ? IndexOffset[ 2 ] + runtimeSize : 0u );
w.Write( VertexBufferSize[ 0 ] );
w.Write( VertexBufferSize[ 1 ] );
w.Write( VertexBufferSize[ 2 ] );
w.Write( IndexBufferSize[ 0 ] );
w.Write( IndexBufferSize[ 1 ] );
w.Write( IndexBufferSize[ 2 ] );
w.Write( LodCount );
w.Write( EnableIndexBufferStreaming );
w.Write( EnableEdgeGeometry );
w.Write( ( byte )0 ); // Padding
}
private void WriteModelHeader( BinaryWriter w )
{
w.Write( Radius );
w.Write( ( ushort )Meshes.Length );
w.Write( ( ushort )Attributes.Length );
w.Write( ( ushort )SubMeshes.Length );
w.Write( ( ushort )Materials.Length );
w.Write( ( ushort )Bones.Length );
w.Write( ( ushort )BoneTables.Length );
w.Write( ( ushort )Shapes.Length );
w.Write( ( ushort )ShapeMeshes.Length );
w.Write( ( ushort )ShapeValues.Length );
w.Write( LodCount );
w.Write( ( byte )Flags1 );
w.Write( ( ushort )ElementIds.Length );
w.Write( ( byte )TerrainShadowMeshes.Length );
w.Write( ( byte )Flags2 );
w.Write( ModelClipOutDistance );
w.Write( ShadowClipOutDistance );
w.Write( Unknown4 );
w.Write( ( ushort )TerrainShadowSubMeshes.Length );
w.Write( Unknown5 );
w.Write( BgChangeMaterialIndex );
w.Write( BgCrestChangeMaterialIndex );
w.Write( Unknown6 );
w.Write( Unknown7 );
w.Write( Unknown8 );
w.Write( Unknown9 );
w.Write( ( uint )0 ); // 6 byte padding
w.Write( ( ushort )0 );
}
private static void Write( BinaryWriter w, in MdlStructs.VertexElement vertex )
{
w.Write( vertex.Stream );
w.Write( vertex.Offset );
w.Write( vertex.Type );
w.Write( vertex.Usage );
w.Write( vertex.UsageIndex );
w.Write( ( ushort )0 ); // 3 byte padding
w.Write( ( byte )0 );
}
private static void Write( BinaryWriter w, in MdlStructs.VertexDeclarationStruct vertexDecl )
{
foreach( var vertex in vertexDecl.VertexElements )
{
Write( w, vertex );
}
Write( w, new MdlStructs.VertexElement() { Stream = 255 } );
w.Seek( ( int )( NumVertices - 1 - vertexDecl.VertexElements.Length ) * 8, SeekOrigin.Current );
}
private static void Write( BinaryWriter w, in MdlStructs.ElementIdStruct elementId )
{
w.Write( elementId.ElementId );
w.Write( elementId.ParentBoneName );
w.Write( elementId.Translate[ 0 ] );
w.Write( elementId.Translate[ 1 ] );
w.Write( elementId.Translate[ 2 ] );
w.Write( elementId.Rotate[ 0 ] );
w.Write( elementId.Rotate[ 1 ] );
w.Write( elementId.Rotate[ 2 ] );
}
private static unsafe void Write< T >( BinaryWriter w, in T data ) where T : unmanaged
{
fixed( T* ptr = &data )
{
var bytePtr = ( byte* )ptr;
var size = sizeof( T );
var span = new ReadOnlySpan< byte >( bytePtr, size );
w.Write( span );
}
}
private static void Write( BinaryWriter w, MdlStructs.MeshStruct mesh )
{
w.Write( mesh.VertexCount );
w.Write( ( ushort )0 ); // padding
w.Write( mesh.IndexCount );
w.Write( mesh.MaterialIndex );
w.Write( mesh.SubMeshIndex );
w.Write( mesh.SubMeshCount );
w.Write( mesh.BoneTableIndex );
w.Write( mesh.StartIndex );
w.Write( mesh.VertexBufferOffset[ 0 ] );
w.Write( mesh.VertexBufferOffset[ 1 ] );
w.Write( mesh.VertexBufferOffset[ 2 ] );
w.Write( mesh.VertexBufferStride[ 0 ] );
w.Write( mesh.VertexBufferStride[ 1 ] );
w.Write( mesh.VertexBufferStride[ 2 ] );
w.Write( mesh.VertexStreamCount );
}
private static void Write( BinaryWriter w, MdlStructs.BoneTableStruct bone )
{
foreach( var index in bone.BoneIndex )
{
w.Write( index );
}
w.Write( bone.BoneCount );
w.Write( ( ushort )0 ); // 3 bytes padding
w.Write( ( byte )0 );
}
private void Write( BinaryWriter w, int shapeIdx, IReadOnlyList< uint > offsets )
{
var shape = Shapes[ shapeIdx ];
var offset = offsets[ Attributes.Length + Bones.Length + Materials.Length + shapeIdx ];
w.Write( offset );
w.Write( shape.ShapeMeshStartIndex[ 0 ] );
w.Write( shape.ShapeMeshStartIndex[ 1 ] );
w.Write( shape.ShapeMeshStartIndex[ 2 ] );
w.Write( shape.ShapeMeshCount[ 0 ] );
w.Write( shape.ShapeMeshCount[ 1 ] );
w.Write( shape.ShapeMeshCount[ 2 ] );
}
private static void Write( BinaryWriter w, MdlStructs.BoundingBoxStruct box )
{
w.Write( box.Min[ 0 ] );
w.Write( box.Min[ 1 ] );
w.Write( box.Min[ 2 ] );
w.Write( box.Min[ 3 ] );
w.Write( box.Max[ 0 ] );
w.Write( box.Max[ 1 ] );
w.Write( box.Max[ 2 ] );
w.Write( box.Max[ 3 ] );
}
public byte[] Write()
{
using var stream = new MemoryStream();
using( var w = new BinaryWriter( stream ) )
{
// Skip and write this later when we actually know it.
w.Seek( ( int )FileHeaderSize, SeekOrigin.Begin );
foreach( var vertexDecl in VertexDeclarations )
{
Write( w, vertexDecl );
}
var offsets = WriteStrings( w );
WriteModelHeader( w );
foreach( var elementId in ElementIds )
{
Write( w, elementId );
}
foreach( var lod in Lods )
{
Write( w, lod );
}
if( Flags2.HasFlag( MdlStructs.ModelFlags2.ExtraLodEnabled ) )
{
foreach( var extraLod in ExtraLods )
{
Write( w, extraLod );
}
}
foreach( var mesh in Meshes )
{
Write( w, mesh );
}
for( var i = 0; i < Attributes.Length; ++i )
{
w.Write( offsets[ i ] );
}
foreach( var terrainShadowMesh in TerrainShadowMeshes )
{
Write( w, terrainShadowMesh );
}
foreach( var subMesh in SubMeshes )
{
Write( w, subMesh );
}
foreach( var terrainShadowSubMesh in TerrainShadowSubMeshes )
{
Write( w, terrainShadowSubMesh );
}
for( var i = 0; i < Materials.Length; ++i )
{
w.Write( offsets[ Attributes.Length + Bones.Length + i ] );
}
for( var i = 0; i < Bones.Length; ++i )
{
w.Write( offsets[ Attributes.Length + i ] );
}
foreach( var boneTable in BoneTables )
{
Write( w, boneTable );
}
for( var i = 0; i < Shapes.Length; ++i )
{
Write( w, i, offsets );
}
foreach( var shapeMesh in ShapeMeshes )
{
Write( w, shapeMesh );
}
foreach( var shapeValue in ShapeValues )
{
Write( w, shapeValue );
}
w.Write( SubMeshBoneMap.Length * 2 );
foreach( var bone in SubMeshBoneMap )
{
w.Write( bone );
}
w.Write( ( byte )0 ); // number of padding bytes, which is 0 for us.
Write( w, BoundingBoxes );
Write( w, ModelBoundingBoxes );
Write( w, WaterBoundingBoxes );
Write( w, VerticalFogBoundingBoxes );
foreach( var box in BoneBoundingBoxes )
{
Write( w, box );
}
var totalSize = w.BaseStream.Position;
var runtimeSize = ( uint )( totalSize - StackSize - FileHeaderSize );
w.Write( RemainingData );
// Write header data.
w.Seek( 0, SeekOrigin.Begin );
WriteModelFileHeader( w, runtimeSize );
}
return stream.ToArray();
}
}