feat(render): Phase A8.F — ViewPolygon + CellView clip-region data model
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
bb903bc157
commit
406307e8ee
2 changed files with 123 additions and 0 deletions
71
src/AcDream.App/Rendering/PortalView.cs
Normal file
71
src/AcDream.App/Rendering/PortalView.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
// PortalView.cs
|
||||||
|
//
|
||||||
|
// Phase A8.F: GL-free 2D screen-space (NDC) clip-region data model.
|
||||||
|
// Mirrors retail view_poly (acclient.h:32465) and view_type (acclient.h:32338):
|
||||||
|
// a cell's clip region is a SET of convex polygons in normalized device coords.
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace AcDream.App.Rendering;
|
||||||
|
|
||||||
|
/// <summary>One convex polygon in NDC screen space (xy in [-1,1]), plus its bounding rect.</summary>
|
||||||
|
public readonly struct ViewPolygon
|
||||||
|
{
|
||||||
|
public readonly Vector2[] Vertices;
|
||||||
|
public readonly float MinX, MinY, MaxX, MaxY;
|
||||||
|
|
||||||
|
public ViewPolygon(Vector2[] vertices)
|
||||||
|
{
|
||||||
|
Vertices = vertices;
|
||||||
|
if (vertices is null || vertices.Length < 3)
|
||||||
|
{
|
||||||
|
MinX = MinY = MaxX = MaxY = 0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float minX = float.MaxValue, minY = float.MaxValue, maxX = float.MinValue, maxY = float.MinValue;
|
||||||
|
foreach (var v in vertices)
|
||||||
|
{
|
||||||
|
if (v.X < minX) minX = v.X;
|
||||||
|
if (v.X > maxX) maxX = v.X;
|
||||||
|
if (v.Y < minY) minY = v.Y;
|
||||||
|
if (v.Y > maxY) maxY = v.Y;
|
||||||
|
}
|
||||||
|
MinX = minX; MinY = minY; MaxX = maxX; MaxY = maxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEmpty => Vertices is null || Vertices.Length < 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A cell's accumulated clip region: a set of convex view polygons + the union bounding rect.</summary>
|
||||||
|
public sealed class CellView
|
||||||
|
{
|
||||||
|
public readonly List<ViewPolygon> Polygons = new();
|
||||||
|
public float MinX { get; private set; } = float.MaxValue;
|
||||||
|
public float MinY { get; private set; } = float.MaxValue;
|
||||||
|
public float MaxX { get; private set; } = float.MinValue;
|
||||||
|
public float MaxY { get; private set; } = float.MinValue;
|
||||||
|
|
||||||
|
public bool IsEmpty => Polygons.Count == 0;
|
||||||
|
|
||||||
|
/// <summary>A region covering the entire NDC viewport — the camera cell's seed region
|
||||||
|
/// (mirrors retail PView::DrawInside copy_view(..., 4) at decomp:433814).</summary>
|
||||||
|
public static CellView FullScreen()
|
||||||
|
{
|
||||||
|
var v = new CellView();
|
||||||
|
v.Add(new ViewPolygon(new[]
|
||||||
|
{
|
||||||
|
new Vector2(-1f, -1f), new Vector2(1f, -1f), new Vector2(1f, 1f), new Vector2(-1f, 1f),
|
||||||
|
}));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(ViewPolygon p)
|
||||||
|
{
|
||||||
|
if (p.IsEmpty) return;
|
||||||
|
Polygons.Add(p);
|
||||||
|
if (p.MinX < MinX) MinX = p.MinX;
|
||||||
|
if (p.MinY < MinY) MinY = p.MinY;
|
||||||
|
if (p.MaxX > MaxX) MaxX = p.MaxX;
|
||||||
|
if (p.MaxY > MaxY) MaxY = p.MaxY;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
tests/AcDream.App.Tests/Rendering/PortalViewTests.cs
Normal file
52
tests/AcDream.App.Tests/Rendering/PortalViewTests.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using AcDream.App.Rendering;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AcDream.App.Tests.Rendering;
|
||||||
|
|
||||||
|
public class PortalViewTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ViewPolygon_ComputesBoundingRect()
|
||||||
|
{
|
||||||
|
var p = new ViewPolygon(new[]
|
||||||
|
{
|
||||||
|
new Vector2(-0.5f, -0.25f), new Vector2(0.5f, -0.25f), new Vector2(0.0f, 0.75f),
|
||||||
|
});
|
||||||
|
Assert.Equal(-0.5f, p.MinX, 5);
|
||||||
|
Assert.Equal(0.5f, p.MaxX, 5);
|
||||||
|
Assert.Equal(-0.25f, p.MinY, 5);
|
||||||
|
Assert.Equal(0.75f, p.MaxY, 5);
|
||||||
|
Assert.False(p.IsEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ViewPolygon_FewerThanThreeVerts_IsEmpty()
|
||||||
|
{
|
||||||
|
Assert.True(new ViewPolygon(new[] { new Vector2(0, 0), new Vector2(1, 0) }).IsEmpty);
|
||||||
|
Assert.True(new ViewPolygon(System.Array.Empty<Vector2>()).IsEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CellView_FullScreen_CoversNdc()
|
||||||
|
{
|
||||||
|
var v = CellView.FullScreen();
|
||||||
|
Assert.False(v.IsEmpty);
|
||||||
|
Assert.Equal(-1f, v.MinX, 5);
|
||||||
|
Assert.Equal(1f, v.MaxX, 5);
|
||||||
|
Assert.Equal(-1f, v.MinY, 5);
|
||||||
|
Assert.Equal(1f, v.MaxY, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CellView_Add_GrowsUnionBoundsAndIsEmptyTracks()
|
||||||
|
{
|
||||||
|
var v = new CellView();
|
||||||
|
Assert.True(v.IsEmpty);
|
||||||
|
v.Add(new ViewPolygon(new[] { new Vector2(0, 0), new Vector2(0.2f, 0), new Vector2(0, 0.2f) }));
|
||||||
|
v.Add(new ViewPolygon(new[] { new Vector2(-0.3f, -0.3f), new Vector2(-0.1f, -0.3f), new Vector2(-0.1f, -0.1f) }));
|
||||||
|
Assert.False(v.IsEmpty);
|
||||||
|
Assert.Equal(-0.3f, v.MinX, 5);
|
||||||
|
Assert.Equal(0.2f, v.MaxX, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue