Editor/Conversion/Writers/VmatWriter.cs

Writer for .vmat material files used by the editor. It builds a string representing a VMAT with supplied texture paths and PBR parameters, and can write it to disk with UTF-8 no BOM.

File Access
using System.IO;
using System.Text;

namespace GmodAssetImporter;

public sealed class VmatWriteRequest
{
	public string ColorTextureAssetPath; // materials/.../name_color.png
	public string NormalTextureAssetPath;
	public string SelfIllumTextureAssetPath;
	public bool Translucent;
	public bool AlphaTest;
	public float Metalness = 0f;
	public float Roughness = 0.5f;
}

public static class VmatWriter
{
	static readonly Encoding Utf8NoBom = new UTF8Encoding( encoderShouldEmitUTF8Identifier: false );

	public static string Build( VmatWriteRequest req )
	{
		var color = Escape( req.ColorTextureAssetPath ?? "materials/default/default_color.tga" );
		var normal = Escape( req.NormalTextureAssetPath ?? "materials/default/default_normal.tga" );
		var roughDefault = "materials/default/default_rough.tga";
		var aoDefault = "materials/default/default_ao.tga";

		var sb = new StringBuilder();
		sb.AppendLine( "// THIS FILE IS AUTO-GENERATED" );
		sb.AppendLine();
		sb.AppendLine( "Layer0" );
		sb.AppendLine( "{" );
		sb.AppendLine( "\tshader \"shaders/complex.shader\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- PBR ----" );
		sb.AppendLine( "\tF_SPECULAR 1" );
		if ( req.Translucent )
			sb.AppendLine( "\tF_TRANSLUCENT 1" );
		if ( req.AlphaTest )
			sb.AppendLine( "\tF_ALPHA_TEST 1" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Ambient Occlusion ----" );
		sb.AppendLine( "\tg_flAmbientOcclusionDirectDiffuse \"0.000\"" );
		sb.AppendLine( "\tg_flAmbientOcclusionDirectSpecular \"0.000\"" );
		sb.AppendLine( $"\tTextureAmbientOcclusion \"{aoDefault}\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Color ----" );
		sb.AppendLine( "\tg_flModelTintAmount \"1.000\"" );
		sb.AppendLine( "\tg_vColorTint \"[1.000000 1.000000 1.000000 0.000000]\"" );
		sb.AppendLine( $"\tTextureColor \"{color}\"" );
		sb.AppendLine( $"\tTextureNormal \"{normal}\"" );
		sb.AppendLine( $"\tTextureRoughness \"{roughDefault}\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Fade ----" );
		sb.AppendLine( "\tg_flFadeExponent \"1.000\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Fog ----" );
		sb.AppendLine( "\tg_bFogEnabled \"1\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Metalness ----" );
		sb.AppendLine( $"\tg_flMetalness \"{req.Metalness:0.000}\"" );
		sb.AppendLine();
		sb.AppendLine( "\t//---- Roughness ----" );
		sb.AppendLine( $"\tg_flRoughnessScaleFactor \"{req.Roughness:0.000}\"" );

		if ( !string.IsNullOrWhiteSpace( req.SelfIllumTextureAssetPath ) )
		{
			sb.AppendLine();
			sb.AppendLine( "\t//---- Self Illum ----" );
			sb.AppendLine( "\tF_SELF_ILLUM 1" );
			sb.AppendLine( $"\tTextureSelfIllumMask \"{Escape( req.SelfIllumTextureAssetPath )}\"" );
		}

		sb.AppendLine();
		sb.AppendLine( "\t//---- Texture Coordinates ----" );
		sb.AppendLine( "\tg_vTexCoordOffset \"[0.000 0.000]\"" );
		sb.AppendLine( "\tg_vTexCoordScale \"[1.000 1.000]\"" );
		sb.AppendLine( "\tg_vTexCoordScrollSpeed \"[0.000 0.000]\"" );
		sb.AppendLine( "}" );
		return sb.ToString();
	}

	public static void Write( string absolutePath, VmatWriteRequest req )
	{
		Directory.CreateDirectory( Path.GetDirectoryName( absolutePath )! );
		File.WriteAllText( absolutePath, Build( req ), Utf8NoBom );
	}

	static string Escape( string path ) => PathResolve.NormalizeSlashes( path ?? "" );
}