Files
sbox-public/engine/Sandbox.Engine/Resources/Model/Model.Save.cs
2026-05-21 00:18:26 +00:00

122 lines
3.2 KiB
C#

using NativeEngine;
using Sandbox.Resources;
namespace Sandbox;
partial class Model
{
public byte[] SaveToVmdl()
{
var writer = new VmdlWriter( this );
return writer.Write();
}
public async Task<byte[]> SaveToVmdlAsync()
{
return await Task.Run( SaveToVmdl );
}
}
sealed class VmdlWriter
{
const ushort RESOURCE_VERSION = 1;
const uint RESOURCE_BLOCK_ID_MDAT = 0x5441444D; // 'MDAT'
const uint RESOURCE_BLOCK_ID_MBUF = 0x4655424D; // 'MBUF'
const uint RESOURCE_BLOCK_ID_MRPH = 0x4850524D; // 'MRPH'
const uint RESOURCE_BLOCK_ID_CTRL = 0x4C525443; // 'CTRL'
const uint RESOURCE_BLOCK_ID_ASEQ = 0x51455341; // 'ASEQ'
const uint RESOURCE_BLOCK_ID_AGRP = 0x50524741; // 'AGRP'
const uint RESOURCE_BLOCK_ID_ANIM = 0x4D494E41; // 'ANIM'
const uint RESOURCE_BLOCK_ID_PHYS = 0x53594850; // 'PHYS'
readonly IModel _native;
readonly ResourceWriter _resource;
public VmdlWriter( Model model )
{
_native = model.native;
_resource = new ResourceWriter
{
ResourceVersion = RESOURCE_VERSION
};
}
public byte[] Write()
{
var physBlock = WritePHYSBlock();
WriteCTRLBlock( physBlock );
WriteDATABlock();
return _resource.ToArray();
}
void WriteCTRLBlock( int physBlock )
{
var ctrl = KeyValues3.Create();
try
{
var embeddedMeshes = ctrl.FindOrCreateMember( "embedded_meshes" );
embeddedMeshes.SetToEmptyArray();
var meshCount = _native.GetNumMeshes();
for ( int i = 0; i < meshCount; i++ ) WriteEmbeddedMesh( embeddedMeshes, i );
if ( physBlock >= 0 )
{
var embeddedPhysics = ctrl.FindOrCreateMember( "embedded_physics" );
embeddedPhysics.SetToEmptyTable();
embeddedPhysics.SetMemberInt( "phys_data_block", physBlock );
}
using var buffer = ctrl.Save();
_resource.RegisterAdditionalBlock( RESOURCE_BLOCK_ID_CTRL, buffer.ToArray() );
}
finally
{
ctrl.DeleteThis();
}
}
void WriteEmbeddedMesh( KeyValues3 array, int meshIndex )
{
var vbibBlock = WriteVBIBBlock( meshIndex );
var mdatBlock = WriteMDATBlock( meshIndex );
var mesh = array.ArrayAddToTail();
mesh.SetToEmptyTable();
mesh.SetMemberString( "name", $"mesh#{meshIndex}" );
mesh.SetMemberInt( "mesh_index", meshIndex );
mesh.SetMemberInt( "data_block", mdatBlock );
mesh.SetMemberInt( "vbib_block", vbibBlock );
}
int WriteVBIBBlock( int meshIndex )
{
using var buffer = MeshGlue.SerializeMeshBuffers( _native, meshIndex );
return buffer.IsNull || buffer.TellMaxPut() <= 0 ? -1 : _resource.RegisterAdditionalBlock( RESOURCE_BLOCK_ID_MBUF, buffer.ToArray() );
}
int WriteMDATBlock( int meshIndex )
{
using var buffer = MeshGlue.SerializeMeshData( _native, meshIndex );
return buffer.IsNull || buffer.TellMaxPut() <= 0 ? -1 : _resource.RegisterAdditionalBlock( RESOURCE_BLOCK_ID_MDAT, buffer.ToArray() );
}
void WriteDATABlock()
{
using var buffer = MeshGlue.SerializeModelData( _native );
if ( buffer.IsNull || buffer.TellMaxPut() <= 0 ) return;
_resource.SetDataBlock( buffer.ToArray() );
}
int WritePHYSBlock()
{
using var buffer = MeshGlue.SerializePhysicsData( _native );
if ( buffer.IsNull || buffer.TellMaxPut() <= 0 ) return -1;
return _resource.RegisterAdditionalBlock( RESOURCE_BLOCK_ID_PHYS, buffer.ToArray() );
}
}