123 lines
4.6 KiB
C#
123 lines
4.6 KiB
C#
using System.Globalization;
|
|
using System.Numerics;
|
|
using AcDream.App.Rendering.Walk;
|
|
|
|
namespace AcDream.App.Tests.Rendering.Walk;
|
|
|
|
public sealed class WalkReplayProjectionTests
|
|
{
|
|
private const string OhRoot =
|
|
"docs/research/2026-09-01-overhaul/oh-capture";
|
|
|
|
[Fact]
|
|
public void Captured_matrix_screen_ray_screen_round_trips_without_legacy_half_pixel()
|
|
{
|
|
WalkOracleFrame captured = Assert.Single(WalkOracleTrace.Load(
|
|
OhRoot,
|
|
"cathedral-stair-arch-e229.combined-r1"));
|
|
WalkOraclePose pose = Assert.IsType<WalkOraclePose>(captured.ReplayPose);
|
|
var context = new WalkTraceReplayContext(
|
|
pose,
|
|
new Dictionary<uint, WalkCell>());
|
|
Vector2[] points =
|
|
[
|
|
new(512f, 360f),
|
|
new(0f, 0f),
|
|
new(1024f, 0f),
|
|
new(1024f, 720f),
|
|
new(0f, 720f),
|
|
new(127.25f, 83.75f),
|
|
new(817.5f, 611.125f),
|
|
];
|
|
|
|
foreach (Vector2 point in points)
|
|
{
|
|
Vector3 ray = context.Rays.RayThrough(point.X, point.Y);
|
|
Vector2 roundTrip = context.ProjectWorldToScreen(
|
|
pose.Origin + ray * 100f);
|
|
float error = Vector2.Distance(point, roundTrip);
|
|
Assert.True(
|
|
error <= 0.001f,
|
|
string.Create(
|
|
CultureInfo.InvariantCulture,
|
|
$"matrix round-trip error {error:R} at {point}"));
|
|
}
|
|
|
|
Vector3 legacyCenterRay = LegacyRayThrough(pose, 512f, 360f);
|
|
Vector2 legacyCenter = context.ProjectWorldToScreen(
|
|
pose.Origin + legacyCenterRay * 100f);
|
|
Assert.InRange(MathF.Abs(legacyCenter.X - 512f), 0.45f, 0.55f);
|
|
Assert.InRange(MathF.Abs(legacyCenter.Y - 360f), 0.45f, 0.55f);
|
|
}
|
|
|
|
[Fact]
|
|
public void Configured_projection_is_bound_to_captured_newmethod_matrix_words()
|
|
{
|
|
string[] lines = File.ReadAllLines(FixturePath(
|
|
"cathedral-stair-arch-e229.projection-mode"));
|
|
int newMethod = Array.FindIndex(lines, line => line.Trim() == "NEWMETHOD");
|
|
Assert.True(newMethod >= 0);
|
|
Assert.Matches(@"\s00000001\s*$", lines[newMethod + 1]);
|
|
int heading = Array.FindIndex(lines, line => line.Trim() == "VIEW_TO_CLIP");
|
|
Assert.True(heading >= 0);
|
|
uint[] captured = lines.Skip(heading + 1)
|
|
.Take(4)
|
|
.SelectMany(ParseDumpRow)
|
|
.ToArray();
|
|
|
|
Matrix4x4 configured = WalkTraceReplayContext.CapturedViewToClip();
|
|
uint[] actual =
|
|
[
|
|
Bits(configured.M11), Bits(configured.M12), Bits(configured.M13), Bits(configured.M14),
|
|
Bits(configured.M21), Bits(configured.M22), Bits(configured.M23), Bits(configured.M24),
|
|
Bits(configured.M31), Bits(configured.M32), Bits(configured.M33), Bits(configured.M34),
|
|
Bits(configured.M41), Bits(configured.M42), Bits(configured.M43), Bits(configured.M44),
|
|
];
|
|
|
|
Assert.Equal(captured, actual);
|
|
}
|
|
|
|
private static Vector3 LegacyRayThrough(
|
|
WalkOraclePose pose,
|
|
float screenX,
|
|
float screenY)
|
|
{
|
|
var rotation = new Quaternion(pose.Q1, pose.Q2, pose.Q3, pose.Q0);
|
|
Vector3 right = Vector3.Transform(Vector3.UnitX, rotation);
|
|
Vector3 forward = Vector3.Transform(Vector3.UnitY, rotation);
|
|
Vector3 up = Vector3.Transform(Vector3.UnitZ, rotation);
|
|
float u = screenX * 0.00025f - 0.127875f;
|
|
float w = screenY * 0.00025f - 0.089875f;
|
|
return right * u + forward * 0.1330766976f - up * w;
|
|
}
|
|
|
|
private static IEnumerable<uint> ParseDumpRow(string line)
|
|
{
|
|
string[] fields = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
|
Assert.Equal(5, fields.Length);
|
|
return fields.Skip(1).Select(value => uint.Parse(
|
|
value,
|
|
NumberStyles.HexNumber,
|
|
CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
private static uint Bits(float value) =>
|
|
BitConverter.SingleToUInt32Bits(value);
|
|
|
|
private static string FixturePath(string fixtureName) => Path.Combine(
|
|
FindRepositoryRoot(),
|
|
Path.Combine(OhRoot.Split('/')),
|
|
fixtureName + ".log");
|
|
|
|
private static string FindRepositoryRoot()
|
|
{
|
|
DirectoryInfo? directory = new(AppContext.BaseDirectory);
|
|
while (directory is not null)
|
|
{
|
|
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
|
|
return directory.FullName;
|
|
directory = directory.Parent;
|
|
}
|
|
throw new InvalidOperationException("AcDream.slnx not found.");
|
|
}
|
|
}
|