Editor utility that writes SMD files for meshes and skeletal animations. It outputs nodes, skeleton bind pose, triangles and per-frame animation bone transforms to disk in Valve SMD text format.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace GmodAssetImporter;
public static class SmdWriter
{
static readonly Encoding Utf8NoBom = new UTF8Encoding( false );
public static void WriteMesh( string path, List<BoneData> bones, MeshExport mesh )
{
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
using var sw = new StreamWriter( path, false, Utf8NoBom );
sw.WriteLine( "version 1" );
WriteNodes( sw, bones );
WriteSkeletonBind( sw, bones );
sw.WriteLine( "triangles" );
foreach ( var tri in mesh.Triangles )
{
sw.WriteLine( tri.MaterialName );
WriteVert( sw, mesh.Vertices[tri.A] );
WriteVert( sw, mesh.Vertices[tri.B] );
WriteVert( sw, mesh.Vertices[tri.C] );
}
sw.WriteLine( "end" );
}
/// <summary>
/// Write a simple single-frame (or multi-frame) skeletal animation SMD using per-frame bone transforms.
/// </summary>
public static void WriteAnimation( string path, List<BoneData> bones, List<List<BoneData>> frames, float /*fps*/ _ )
{
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
using var sw = new StreamWriter( path, false, Utf8NoBom );
sw.WriteLine( "version 1" );
WriteNodes( sw, bones );
sw.WriteLine( "skeleton" );
for ( var f = 0; f < frames.Count; f++ )
{
sw.WriteLine( $"time {f}" );
var frame = frames[f];
for ( var i = 0; i < bones.Count; i++ )
{
var b = i < frame.Count ? frame[i] : bones[i];
sw.WriteLine( string.Format( CultureInfo.InvariantCulture,
"{0} {1:0.######} {2:0.######} {3:0.######} {4:0.######} {5:0.######} {6:0.######}",
i, b.Position.X, b.Position.Y, b.Position.Z, b.Rotation.X, b.Rotation.Y, b.Rotation.Z ) );
}
}
sw.WriteLine( "end" );
}
static void WriteNodes( StreamWriter sw, List<BoneData> bones )
{
sw.WriteLine( "nodes" );
for ( var i = 0; i < bones.Count; i++ )
{
var parent = bones[i].Parent;
sw.WriteLine( $"{i} \"{Escape( bones[i].Name )}\" {parent}" );
}
sw.WriteLine( "end" );
}
static void WriteSkeletonBind( StreamWriter sw, List<BoneData> bones )
{
sw.WriteLine( "skeleton" );
sw.WriteLine( "time 0" );
for ( var i = 0; i < bones.Count; i++ )
{
var b = bones[i];
sw.WriteLine( string.Format( CultureInfo.InvariantCulture,
"{0} {1:0.######} {2:0.######} {3:0.######} {4:0.######} {5:0.######} {6:0.######}",
i, b.Position.X, b.Position.Y, b.Position.Z, b.Rotation.X, b.Rotation.Y, b.Rotation.Z ) );
}
sw.WriteLine( "end" );
}
static void WriteVert( StreamWriter sw, MeshVertex v )
{
var links = Math.Min( 4, v.Weights.Count );
var parent = links > 0 ? v.Weights[0].Bone : 0;
var sb = new StringBuilder();
sb.Append( string.Format( CultureInfo.InvariantCulture,
"{0} {1:0.######} {2:0.######} {3:0.######} {4:0.######} {5:0.######} {6:0.######} {7:0.######} {8:0.######} {9}",
parent,
v.Position.X, v.Position.Y, v.Position.Z,
v.Normal.X, v.Normal.Y, v.Normal.Z,
v.Uv.X, v.Uv.Y,
links ) );
for ( var i = 0; i < links; i++ )
{
sb.Append( ' ' );
sb.Append( v.Weights[i].Bone );
sb.Append( ' ' );
sb.Append( v.Weights[i].Weight.ToString( "0.######", CultureInfo.InvariantCulture ) );
}
sw.WriteLine( sb.ToString() );
}
static string Escape( string s ) => (s ?? "").Replace( "\"", "" );
}