Utility class for reading binary data used by the importer. It reads null-terminated strings, reads a string at an offset relative to a base, reads 3D vector and quaternion values, and converts a quaternion to Euler angles in degrees using Source-style ordering (pitch, yaw, roll).
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace GmodAssetImporter;
static class BinaryUtil
{
public static string ReadNullTerminated( BinaryReader br, int max )
{
var bytes = br.ReadBytes( max );
var len = Array.IndexOf( bytes, (byte)0 );
if ( len < 0 ) len = bytes.Length;
return Encoding.ASCII.GetString( bytes, 0, len );
}
public static string ReadStringAt( BinaryReader br, long baseOffset, int relativeIndex )
{
if ( relativeIndex == 0 ) return "";
var pos = br.BaseStream.Position;
br.BaseStream.Position = baseOffset + relativeIndex;
var chars = new List<byte>();
while ( true )
{
var b = br.ReadByte();
if ( b == 0 ) break;
chars.Add( b );
if ( chars.Count > 512 ) break;
}
br.BaseStream.Position = pos;
return Encoding.ASCII.GetString( chars.ToArray() );
}
public static Vec3 ReadVec3( BinaryReader br ) => new( br.ReadSingle(), br.ReadSingle(), br.ReadSingle() );
public static Quat ReadQuat( BinaryReader br ) => new( br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle() );
public static float[] QuaternionToEulerDegrees( Quat q )
{
// Source-style Euler (pitch, yaw, roll) approximate for SMD
var sinrCosp = 2 * (q.W * q.X + q.Y * q.Z);
var cosrCosp = 1 - 2 * (q.X * q.X + q.Y * q.Y);
var roll = MathF.Atan2( sinrCosp, cosrCosp );
var sinp = 2 * (q.W * q.Y - q.Z * q.X);
var pitch = MathF.Abs( sinp ) >= 1 ? MathF.CopySign( MathF.PI / 2, sinp ) : MathF.Asin( sinp );
var sinyCosp = 2 * (q.W * q.Z + q.X * q.Y);
var cosyCosp = 1 - 2 * (q.Y * q.Y + q.Z * q.Z);
var yaw = MathF.Atan2( sinyCosp, cosyCosp );
const float r2d = 180f / MathF.PI;
return new[] { pitch * r2d, yaw * r2d, roll * r2d };
}
}