acdream/tests/AcDream.App.Tests/Rendering/Walk/WalkScreenClipTests.cs
Erik 11ca527fb9 feat(render) Campaign FW1: port the view machinery (xformStart, polyClipFinish, copy_view)
WalkScreenClip ports PrimD3DRender::xformStart @0x0059b990 (homogeneous
viewport coords, y-flip, no divide) and ACRender::polyClipFinish
@0x006b6d00 (w>=cdstW plane then last-to-first edge passes, inside =
side<=0 homogeneous 2D cross, reverse-scan passes with original-winding
restore, <3 early-outs). WalkViews ports the view_type/portal_view_type
data model and Render::copy_view @0x0054dfc0 exactly: in-place divide,
the keep/last/stl/second pruning bookkeeping with all three closing
wrap checks, <3 reject leaving dest untouched, cap 31, pool-base reset
at view_count==0, retail fabs on copy, and edge planes
N=normalize(cross(ray[k+1],ray[k])), d=-dot(N,eye) behind an
IWalkRayCaster seam. Thirteen new tests; Walk namespace 85/85.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 09:50:03 +02:00

94 lines
3.2 KiB
C#

using System.Numerics;
using AcDream.App.Rendering.Walk;
namespace AcDream.App.Tests.Rendering.Walk;
public sealed class WalkScreenClipTests
{
private const float W = 640f, H = 480f;
// The root full-viewport quad in retail's vertex order (0,H)(W,H)(W,0)(0,0).
private static readonly Vector2[] RootQuad =
[
new(0, H), new(W, H), new(W, 0), new(0, 0),
];
private static WalkScreenPoint Pt(float x, float y, float w = 1f)
=> new(x * w, y * w, 0f, w); // homogeneous: screen * w
[Fact]
public void Transform_maps_clip_center_to_screen_center_with_y_flip()
{
Matrix4x4 identity = Matrix4x4.Identity;
WalkScreenPoint center = WalkScreenClip.TransformToScreen(
Vector3.Zero, identity, W, H);
Assert.Equal(W / 2, center.X);
Assert.Equal(H / 2, center.Y);
Assert.Equal(1f, center.W);
// Clip y = +1 (top of clip space) lands at screen y = 0 (top-left origin).
WalkScreenPoint top = WalkScreenClip.TransformToScreen(
new Vector3(0, 1, 0), identity, W, H);
Assert.Equal(0f, top.Y);
}
[Fact]
public void Fully_inside_polygon_survives_unchanged_with_original_winding()
{
Span<WalkScreenPoint> tri = [Pt(100, 100), Pt(300, 120), Pt(200, 300)];
Span<WalkScreenPoint> outPts = stackalloc WalkScreenPoint[16];
int n = WalkScreenClip.ClipAgainstView(tri, RootQuad, outPts);
Assert.Equal(3, n);
Assert.Equal(100f, outPts[0].X);
Assert.Equal(300f, outPts[1].X);
Assert.Equal(200f, outPts[2].X);
}
[Fact]
public void Polygon_straddling_the_left_edge_is_clipped_at_x_zero()
{
Span<WalkScreenPoint> tri = [Pt(-100, 100), Pt(100, 100), Pt(100, 300)];
Span<WalkScreenPoint> outPts = stackalloc WalkScreenPoint[16];
int n = WalkScreenClip.ClipAgainstView(tri, RootQuad, outPts);
Assert.True(n >= 3);
for (int i = 0; i < n; i++)
Assert.True(outPts[i].X / outPts[i].W >= -0.001f, $"vertex {i} left of x=0");
// Something was actually cut (an intersection vertex exists at x≈0).
bool touchesEdge = false;
for (int i = 0; i < n; i++)
if (MathF.Abs(outPts[i].X / outPts[i].W) < 0.001f) touchesEdge = true;
Assert.True(touchesEdge);
}
[Fact]
public void Polygon_fully_outside_one_edge_returns_zero()
{
Span<WalkScreenPoint> tri = [Pt(-300, 100), Pt(-100, 100), Pt(-200, 300)];
Span<WalkScreenPoint> outPts = stackalloc WalkScreenPoint[16];
Assert.Equal(0, WalkScreenClip.ClipAgainstView(tri, RootQuad, outPts));
}
[Fact]
public void W_plane_clips_points_behind_the_eye()
{
// One vertex behind the eye (w < cdstW); survivors get intersections
// at w == cdstW rather than dropping the polygon.
Span<WalkScreenPoint> tri =
[
Pt(100, 100), Pt(300, 100), new WalkScreenPoint(200, 200, 0, -0.5f),
];
Span<WalkScreenPoint> outPts = stackalloc WalkScreenPoint[16];
int n = WalkScreenClip.ClipAgainstView(tri, RootQuad, outPts);
Assert.True(n >= 3);
for (int i = 0; i < n; i++)
Assert.True(outPts[i].W >= WalkScreenClip.MinW - 1e-6f);
}
}