fix(picker): Cluster A #86 — cell-BSP ray occlusion in WorldPicker
WorldPicker.Pick previously had no occlusion test — any entity along the click ray within maxDistance was a candidate, including ones behind walls. Adds the CellBspRayOccluder static helper that Möller-Trumbore-tests the click ray against every polygon in every currently-cached EnvCell BSP, returning the nearest wall-hit `t`. Both Pick overloads gate candidate selection by that wall-t (legacy ray-sphere via world-space `t`, screen-rect via camera-space clip.W depth — matching ScreenProjection.TryProjectSphereToScreenRect's convention). PhysicsDataCache exposes a new CellStructIds snapshot accessor so the caller can iterate without needing the private cache dictionary. CellPhysics.BSP/PhysicsPolygons/Vertices relaxed from required to nullable so test fixtures can construct a CellPhysics from Resolved alone without a real DAT BSP object. GameWindow snapshots the loaded cell physics on each Pick call and passes the occluder callback. Closes #86. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
27d7de11d8
commit
3764867566
6 changed files with 355 additions and 6 deletions
|
|
@ -0,0 +1,86 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Selection;
|
||||
using DatReaderWriter.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Selection;
|
||||
|
||||
public class CellBspRayOccluderTests
|
||||
{
|
||||
// Build a CellPhysics with a single triangular poly at world-Y=10.
|
||||
// Triangle vertices in local space, world transform = identity.
|
||||
// Uses the Resolved-only constructor path (BSP = null is allowed after Phase 1 relaxation).
|
||||
private static CellPhysics MakeWallCell()
|
||||
{
|
||||
var verts = new[]
|
||||
{
|
||||
new Vector3(-5, 10, 0),
|
||||
new Vector3( 5, 10, 0),
|
||||
new Vector3( 0, 10, 5),
|
||||
};
|
||||
var poly = new ResolvedPolygon
|
||||
{
|
||||
Vertices = verts,
|
||||
Plane = new System.Numerics.Plane(new Vector3(0, -1, 0), 10f),
|
||||
NumPoints = 3,
|
||||
SidesType = CullMode.None,
|
||||
};
|
||||
return new CellPhysics
|
||||
{
|
||||
BSP = null, // Occluder doesn't use BSP — direct poly iteration.
|
||||
Resolved = new() { [0] = poly },
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
InverseWorldTransform = Matrix4x4.Identity,
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NearestWallT_RayHitsTriangle_ReturnsHitDistance()
|
||||
{
|
||||
var cell = MakeWallCell();
|
||||
var origin = new Vector3(0, 0, 1);
|
||||
var direction = Vector3.UnitY; // travels +Y toward the wall at Y=10
|
||||
float t = CellBspRayOccluder.NearestWallT(origin, direction, new[] { cell });
|
||||
Assert.True(t > 9.9f && t < 10.1f, $"expected ~10, got {t}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NearestWallT_RayMisses_ReturnsPositiveInfinity()
|
||||
{
|
||||
var cell = MakeWallCell();
|
||||
var origin = new Vector3(0, 0, 1);
|
||||
var direction = -Vector3.UnitY; // travels AWAY from the wall
|
||||
float t = CellBspRayOccluder.NearestWallT(origin, direction, new[] { cell });
|
||||
Assert.True(float.IsPositiveInfinity(t), $"expected +inf, got {t}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NearestWallT_EmptyCellList_ReturnsPositiveInfinity()
|
||||
{
|
||||
var origin = Vector3.Zero;
|
||||
var direction = Vector3.UnitY;
|
||||
float t = CellBspRayOccluder.NearestWallT(origin, direction, System.Array.Empty<CellPhysics>());
|
||||
Assert.True(float.IsPositiveInfinity(t));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NearestWallT_TwoCells_ReturnsNearer()
|
||||
{
|
||||
var nearCell = MakeWallCell(); // wall at Y=10
|
||||
var farCell = MakeWallCell();
|
||||
// Move farCell's transform to push it to Y=20.
|
||||
farCell = new CellPhysics
|
||||
{
|
||||
BSP = null,
|
||||
Resolved = nearCell.Resolved,
|
||||
WorldTransform = Matrix4x4.CreateTranslation(0, 10, 0),
|
||||
InverseWorldTransform = Matrix4x4.CreateTranslation(0, -10, 0),
|
||||
};
|
||||
|
||||
var origin = new Vector3(0, 0, 1);
|
||||
var direction = Vector3.UnitY;
|
||||
float t = CellBspRayOccluder.NearestWallT(origin, direction, new[] { farCell, nearCell });
|
||||
Assert.True(t < 11f, $"expected near-cell hit ~10, got {t}");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue