Importer for glTF/GLB geometry that walks the scene node tree, accumulates world transforms, bakes mesh primitives into world space, and produces a RigMesh with positions, normals, UVs, triangles, per-triangle tags and material indices.
using System.Numerics;
using AutoRig.Mesh;
namespace AutoRig.Formats.Gltf;
// 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>
/// Extracts a combined <see cref="RigMesh"/> from a glTF/GLB document: walks the node
/// tree accumulating world transforms, bakes each mesh-carrying node's primitives into
/// world space, and tags triangles with the node name.
/// </summary>
public static class GltfMeshImporter
{
/// <exception cref="FormatException">Malformed file, external .bin buffers, or no
/// triangle geometry.</exception>
public static RigMesh Import( byte[] data, string sourceName )
{
var document = GltfDocument.Parse( data );
var outPositions = new List<Vector3>();
var outNormals = new List<Vector3>();
var outUvs = new List<Vector2>();
var outTriangles = new List<int>();
var outTriangleTags = new List<int>();
var outTriangleMaterials = new List<int>();
var tags = new List<string>();
var anyNormal = false;
var anyUv = false;
// Depth-first from roots, accumulating world = local * parentWorld (row vectors).
var stack = new Stack<(int Node, Matrix4x4 ParentWorld)>();
for ( var i = document.Nodes.Count - 1; i >= 0; i-- )
if ( document.Nodes[i].Parent < 0 )
stack.Push( (i, Matrix4x4.Identity) );
while ( stack.Count > 0 )
{
var (nodeIndex, parentWorld) = stack.Pop();
var node = document.Nodes[nodeIndex];
var world = node.LocalMatrix * parentWorld;
if ( node.Mesh >= 0 && node.Mesh < document.Meshes.Count )
{
var mesh = document.Meshes[node.Mesh];
var tagName = node.Name ?? mesh.Name ?? $"node{nodeIndex}";
var tag = tags.IndexOf( tagName );
if ( tag < 0 ) { tag = tags.Count; tags.Add( tagName ); }
// Normals need the inverse-transpose of the world's 3x3 part.
Matrix4x4.Invert( world, out var inverse );
var normalMatrix = Matrix4x4.Transpose( inverse );
foreach ( var primitive in mesh.Primitives )
{
var baseVertex = outPositions.Count;
var vertexCount = primitive.Positions.Length / 3;
for ( var v = 0; v < vertexCount; v++ )
{
var p = new Vector3(
primitive.Positions[v * 3],
primitive.Positions[v * 3 + 1],
primitive.Positions[v * 3 + 2] );
outPositions.Add( Vector3.Transform( p, world ) );
if ( primitive.Normals is { } normals )
{
var n = new Vector3( normals[v * 3], normals[v * 3 + 1], normals[v * 3 + 2] );
var transformed = Vector3.TransformNormal( n, normalMatrix );
var length = transformed.Length();
outNormals.Add( length > 1e-8f ? transformed / length : Vector3.UnitZ );
anyNormal = true;
}
else
{
outNormals.Add( Vector3.UnitZ );
}
if ( primitive.Uvs is { } uvs )
{
outUvs.Add( new Vector2( uvs[v * 2], uvs[v * 2 + 1] ) );
anyUv = true;
}
else
{
outUvs.Add( Vector2.Zero );
}
}
var material = primitive.Material >= 0
&& primitive.Material < document.Materials.Count ? primitive.Material : -1;
for ( var i = 0; i < primitive.Indices.Length; i += 3 )
{
outTriangles.Add( baseVertex + primitive.Indices[i] );
outTriangles.Add( baseVertex + primitive.Indices[i + 1] );
outTriangles.Add( baseVertex + primitive.Indices[i + 2] );
outTriangleTags.Add( tag );
outTriangleMaterials.Add( material );
}
}
}
foreach ( var child in node.Children )
stack.Push( (child, world) );
}
if ( outTriangles.Count == 0 )
throw new FormatException( $"glTF: '{sourceName}' contains no triangle geometry." );
var result = new RigMesh
{
SourceName = sourceName,
Positions = [ .. outPositions ],
Normals = anyNormal ? [ .. outNormals ] : [],
Uvs = anyUv ? [ .. outUvs ] : [],
Triangles = [ .. outTriangles ],
TriangleTags = [ .. outTriangleTags ],
Tags = [ .. tags ],
Materials = document.Materials.Select( m => new RigMaterial
{
Name = m.Name,
BaseColorImage = m.BaseColorImage,
Tint = m.BaseColorFactor,
} ).ToArray(),
TriangleMaterials = [ .. outTriangleMaterials ],
};
result.Validate();
return result;
}
}