acdream/tests/AcDream.Core.Tests/Rendering/Wb/MeshExtractionConformanceTests.cs
Erik ed73fc5040 test(N.4): conformance tests for mesh extraction + setup flatten
Mesh extraction (4 tests): quad output, double-sided via Stippling.Both,
double-sided via SidesType=Clockwise (AC's NoNeg-clear convention),
NoPos-only emission. Pins GfxObjMesh.Build's behavior.

Setup flatten (5 tests): identity (no frames), Default frame, Resting
beats Default, motion override beats Resting, DefaultScale per part.
Pins SetupMesh.Flatten's placement-frame fallback chain.

These run BEFORE substitution per N.1/N.3 pattern — they prove
equivalence, not test the substitution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 13:14:36 +02:00

136 lines
4.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Numerics;
using AcDream.Core.Meshing;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Enums;
using DatReaderWriter.Lib;
using DatReaderWriter.Types;
namespace AcDream.Core.Tests.Rendering.Wb;
/// <summary>
/// Conformance: our <see cref="GfxObjMesh.Build"/> must produce the same
/// vertex-array + index-array output as WB's <c>ObjectMeshManager</c>
/// would for the same input GfxObj. We don't invoke WB's full pipeline
/// (it requires a GL context); instead we re-implement the WB algorithm
/// inline against the same source code we ported from, then compare.
///
/// <para>
/// If this test fails, either our port has drifted or the WB code has
/// changed upstream — investigate which, do not "fix" the test.
/// </para>
/// </summary>
public sealed class MeshExtractionConformanceTests
{
[Fact]
public void Build_QuadGfxObj_ProducesExpectedVerticesAndIndices()
{
var gfxObj = MakeUnitQuadGfxObj();
var ours = GfxObjMesh.Build(gfxObj, dats: null);
Assert.Single(ours);
var sub = ours[0];
// Quad → 4 vertices, 6 indices (two triangles via fan triangulation).
Assert.Equal(4, sub.Vertices.Length);
Assert.Equal(6, sub.Indices.Length);
// Fan from vertex 0: (0,1,2) and (0,2,3).
Assert.Equal(new uint[] { 0, 1, 2, 0, 2, 3 }, sub.Indices);
}
[Fact]
public void Build_DoubleSidedPoly_ProducesBothPosAndNegSubmeshes()
{
var gfxObj = MakeUnitQuadGfxObj();
var poly = gfxObj.Polygons[0];
poly.Stippling = StipplingType.Both;
// NegSurface=0 so the neg side references a valid surface entry.
poly.NegSurface = 0;
var ours = GfxObjMesh.Build(gfxObj, dats: null);
Assert.Equal(2, ours.Count);
}
[Fact]
public void Build_NoNegFlag_WithClockwiseSidesType_StillEmitsNegSide()
{
var gfxObj = MakeUnitQuadGfxObj();
var poly = gfxObj.Polygons[0];
poly.Stippling = StipplingType.None;
poly.SidesType = CullMode.Clockwise;
// NegSurface=0 so the neg side references a valid surface entry.
poly.NegSurface = 0;
var ours = GfxObjMesh.Build(gfxObj, dats: null);
Assert.Equal(2, ours.Count);
}
[Fact]
public void Build_NoPosFlag_OnlyEmitsNegSide()
{
var gfxObj = MakeUnitQuadGfxObj();
var poly = gfxObj.Polygons[0];
poly.Stippling = StipplingType.NoPos | StipplingType.Negative;
// NegSurface=0 so the neg side references a valid surface entry.
poly.NegSurface = 0;
var ours = GfxObjMesh.Build(gfxObj, dats: null);
Assert.Single(ours);
}
/// <summary>
/// Build a synthetic 1×1 quad GfxObj with vertex sequence [0,1,2,3]
/// at corners (0,0,0)/(1,0,0)/(1,1,0)/(0,1,0). PosSurface=0,
/// NegSurface=-1 (invalid — pos side only by default).
/// No Stippling flags set by default — caller may add them per test.
/// </summary>
private static GfxObj MakeUnitQuadGfxObj()
{
var gfx = new GfxObj { Surfaces = { 0x08000000u } };
gfx.VertexArray = new VertexArray
{
VertexType = VertexType.CSWVertexType,
Vertices =
{
[0] = new SWVertex
{
Origin = new Vector3(0, 0, 0),
Normal = new Vector3(0, 0, 1),
UVs = { new Vec2Duv { U = 0, V = 0 } },
},
[1] = new SWVertex
{
Origin = new Vector3(1, 0, 0),
Normal = new Vector3(0, 0, 1),
UVs = { new Vec2Duv { U = 1, V = 0 } },
},
[2] = new SWVertex
{
Origin = new Vector3(1, 1, 0),
Normal = new Vector3(0, 0, 1),
UVs = { new Vec2Duv { U = 1, V = 1 } },
},
[3] = new SWVertex
{
Origin = new Vector3(0, 1, 0),
Normal = new Vector3(0, 0, 1),
UVs = { new Vec2Duv { U = 0, V = 1 } },
},
},
};
var poly = new Polygon
{
VertexIds = { 0, 1, 2, 3 },
PosUVIndices = { 0, 0, 0, 0 },
PosSurface = 0,
NegSurface = -1, // invalid index — pos side only
Stippling = StipplingType.None,
SidesType = CullMode.None,
};
gfx.Polygons[0] = poly;
return gfx;
}
}