phase(N.4) Tasks 22+23: WbDrawDispatcher + surface metadata side-table
WbDrawDispatcher draws all entities through WB's ObjectRenderData (VAO/VBO per GfxObj, per-batch IBO) using acdream's TextureCache for texture resolution. Two-pass rendering (opaque+ClipMap, then translucent) matching the existing InstancedMeshRenderer pattern. Per-entity single-instance drawing for N.4 simplicity — true instancing grouping deferred to N.6. Atlas-tier entities: mesh from WB, texture from TextureCache via batch SurfaceId. Per-instance-tier entities: AnimatedEntityState drives part overrides + hidden-parts, palette/surface overrides resolve through TextureCache's composite-key caches. Side-table population (Task 23 folded in): WbMeshAdapter now takes DatCollection and populates AcSurfaceMetadataTable on first IncrementRefCount per GfxObj. The side-table provides TranslucencyKind (critical for ClipMap alpha-test on vegetation) plus Luminosity, Diffuse, SurfOpacity, NeedsUvRepeat, DisableFog for sky-pass and lighting. GameWindow wiring: when WbFoundationFlag is enabled, WbDrawDispatcher draws everything and InstancedMeshRenderer is skipped. Flag-off path is unchanged. Matrix composition: restPose * animOverride * entityWorld, matching the spec. Three MatrixCompositionTests verify the contract. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5b4fd4b61d
commit
01cff4144f
5 changed files with 507 additions and 8 deletions
|
|
@ -0,0 +1,64 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
|
||||
namespace AcDream.Core.Tests.Rendering.Wb;
|
||||
|
||||
public sealed class MatrixCompositionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Compose_EntityAnimRest_ProducesExpectedWorldMatrix()
|
||||
{
|
||||
var entityWorld = Matrix4x4.CreateTranslation(100, 200, 300);
|
||||
var animOverride = Matrix4x4.CreateRotationZ(MathF.PI / 4);
|
||||
var restPose = Matrix4x4.CreateTranslation(1, 0, 0);
|
||||
|
||||
var result = WbDrawDispatcher.ComposePartWorldMatrix(entityWorld, animOverride, restPose);
|
||||
|
||||
var expected = restPose * animOverride * entityWorld;
|
||||
AssertMatrixEqual(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compose_IdentityAnim_EqualsRestTimesEntity()
|
||||
{
|
||||
var entityWorld = Matrix4x4.CreateFromQuaternion(
|
||||
Quaternion.CreateFromYawPitchRoll(0.5f, 0, 0)) *
|
||||
Matrix4x4.CreateTranslation(10, 20, 30);
|
||||
var restPose = Matrix4x4.CreateTranslation(0.5f, -0.3f, 0.1f);
|
||||
|
||||
var result = WbDrawDispatcher.ComposePartWorldMatrix(
|
||||
entityWorld, Matrix4x4.Identity, restPose);
|
||||
|
||||
var expected = restPose * entityWorld;
|
||||
AssertMatrixEqual(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compose_AllIdentity_ReturnsIdentity()
|
||||
{
|
||||
var result = WbDrawDispatcher.ComposePartWorldMatrix(
|
||||
Matrix4x4.Identity, Matrix4x4.Identity, Matrix4x4.Identity);
|
||||
|
||||
AssertMatrixEqual(Matrix4x4.Identity, result);
|
||||
}
|
||||
|
||||
private static void AssertMatrixEqual(Matrix4x4 expected, Matrix4x4 actual, float eps = 1e-5f)
|
||||
{
|
||||
Assert.Equal(expected.M11, actual.M11, eps);
|
||||
Assert.Equal(expected.M12, actual.M12, eps);
|
||||
Assert.Equal(expected.M13, actual.M13, eps);
|
||||
Assert.Equal(expected.M14, actual.M14, eps);
|
||||
Assert.Equal(expected.M21, actual.M21, eps);
|
||||
Assert.Equal(expected.M22, actual.M22, eps);
|
||||
Assert.Equal(expected.M23, actual.M23, eps);
|
||||
Assert.Equal(expected.M24, actual.M24, eps);
|
||||
Assert.Equal(expected.M31, actual.M31, eps);
|
||||
Assert.Equal(expected.M32, actual.M32, eps);
|
||||
Assert.Equal(expected.M33, actual.M33, eps);
|
||||
Assert.Equal(expected.M34, actual.M34, eps);
|
||||
Assert.Equal(expected.M41, actual.M41, eps);
|
||||
Assert.Equal(expected.M42, actual.M42, eps);
|
||||
Assert.Equal(expected.M43, actual.M43, eps);
|
||||
Assert.Equal(expected.M44, actual.M44, eps);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ public sealed class WbMeshAdapterTests
|
|||
// We can't pass a real GL (no context in tests), so we verify only the
|
||||
// null-GL guard. The real pipeline is tested via integration.
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new WbMeshAdapter(gl: null!, datDir: "some/path", logger: NullLogger<WbMeshAdapter>.Instance));
|
||||
new WbMeshAdapter(gl: null!, datDir: "some/path", dats: null!, logger: NullLogger<WbMeshAdapter>.Instance));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue