Defines RigMaterial and RigMesh types representing a triangle mesh and its source materials. RigMesh stores positions, normals, uvs, triangle indices, per-triangle tags and material indices, and provides helpers for bounds, surface area and validation of structural invariants.
using System.Numerics;
namespace AutoRig.Mesh;
// s&box compat: the engine defines Vector2/Vector3 in the GLOBAL namespace, which
// shadows using-directive imports - alias explicitly to System.Numerics.
using Vector2 = System.Numerics.Vector2;
using Vector3 = System.Numerics.Vector3;
/// <summary>
/// A triangle mesh as loaded from a source file: indexed positions with optional
/// normals/UVs, and a per-triangle tag pointing at the source node/group/material
/// name it came from (used later for part naming and classification hints).
/// </summary>
/// <summary>A source material: name plus its base-color image (png/jpg bytes as
/// stored in the file) and/or a flat tint. Carried through to export.</summary>
public sealed class RigMaterial
{
public string Name = "";
/// <summary>Encoded base-color image bytes (png/jpg) or null when untextured.</summary>
public byte[]? BaseColorImage;
/// <summary>Image path as referenced by the source file (relative), for textures
/// stored BESIDE the model instead of embedded — the editor layer resolves it
/// (Code/ does no file IO).</summary>
public string? SourceImagePath;
/// <summary>Flat base color factor (multiplies the texture; white = none).</summary>
public Vector3 Tint = new( 1f, 1f, 1f );
}
public sealed class RigMesh
{
public string SourceName = "";
public Vector3[] Positions = [];
public Vector3[] Normals = [];
public Vector2[] Uvs = [];
public int[] Triangles = [];
public int[] TriangleTags = [];
public string[] Tags = [];
/// <summary>Source materials (empty when the file had none / importer skips them).</summary>
public RigMaterial[] Materials = [];
/// <summary>Per-triangle material index into <see cref="Materials"/>, -1 = none.
/// Empty array = legacy/no material data.</summary>
public int[] TriangleMaterials = [];
public int TriangleCount => Triangles.Length / 3;
public Aabb3 ComputeBounds() => Aabb3.FromPoints( Positions );
public float SurfaceArea()
{
double area = 0;
for ( var t = 0; t < Triangles.Length; t += 3 )
{
var a = Positions[Triangles[t]];
var b = Positions[Triangles[t + 1]];
var c = Positions[Triangles[t + 2]];
area += Vector3.Cross( b - a, c - a ).Length() * 0.5f;
}
return (float)area;
}
/// <summary>Checks structural invariants.</summary>
/// <exception cref="FormatException">On any malformed field.</exception>
public void Validate()
{
if ( Triangles.Length % 3 != 0 )
throw new FormatException( $"Triangle index count {Triangles.Length} is not a multiple of 3." );
if ( Normals.Length != 0 && Normals.Length != Positions.Length )
throw new FormatException( $"Normal count {Normals.Length} does not match position count {Positions.Length}." );
if ( Uvs.Length != 0 && Uvs.Length != Positions.Length )
throw new FormatException( $"UV count {Uvs.Length} does not match position count {Positions.Length}." );
if ( TriangleTags.Length != TriangleCount )
throw new FormatException( $"Triangle tag count {TriangleTags.Length} does not match triangle count {TriangleCount}." );
if ( TriangleMaterials.Length != 0 && TriangleMaterials.Length != TriangleCount )
throw new FormatException(
$"Triangle material count {TriangleMaterials.Length} does not match triangle count {TriangleCount}." );
foreach ( var m in TriangleMaterials )
if ( m < -1 || m >= Materials.Length )
throw new FormatException( $"Triangle material index {m} out of range (have {Materials.Length})." );
foreach ( var i in Triangles )
if ( i < 0 || i >= Positions.Length )
throw new FormatException( $"Triangle index {i} out of range (0..{Positions.Length - 1})." );
foreach ( var tag in TriangleTags )
if ( tag < 0 || tag >= Tags.Length )
throw new FormatException( $"Triangle tag {tag} out of range (0..{Tags.Length - 1})." );
foreach ( var p in Positions )
if ( !float.IsFinite( p.X ) || !float.IsFinite( p.Y ) || !float.IsFinite( p.Z ) )
throw new FormatException( "Mesh contains a non-finite vertex position." );
}
}