feat(core): UCG Stage 1 — EnvCell + PointInCell (AABB/BSP)
Adds `EnvCell` (sealed, extends `ObjCell`) with a primitive constructor and `PointInCell` that uses the cell-containment BSP when present, else falls back to an AABB test. Retail anchor: CEnvCell (acclient.h:32072). BSP branch delegates to `BSPQuery.PointInsideCellBsp` (BSPQuery.cs:1034); the AABB branch is the genuinely new logic. No `FromDat` factory — that is a separate later task. Consumed by nobody yet (Stage 1 scaffold). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9cb15710be
commit
76c9e2f07d
2 changed files with 66 additions and 0 deletions
33
src/AcDream.Core/World/Cells/EnvCell.cs
Normal file
33
src/AcDream.Core/World/Cells/EnvCell.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics; // BSPQuery
|
||||
using DatReaderWriter.Types; // CellBSPTree
|
||||
|
||||
namespace AcDream.Core.World.Cells;
|
||||
|
||||
/// <summary>Indoor room cell. Retail anchor: CEnvCell (acclient.h:32072).</summary>
|
||||
public sealed class EnvCell : ObjCell
|
||||
{
|
||||
/// <summary>Cell-containment BSP (retail CellStruct.CellBSP). Null => AABB fallback.</summary>
|
||||
public CellBSPTree? ContainmentBsp { get; }
|
||||
|
||||
public EnvCell(uint id, Matrix4x4 worldTransform, Matrix4x4 inverseWorldTransform,
|
||||
Vector3 localBoundsMin, Vector3 localBoundsMax,
|
||||
IReadOnlyList<CellPortal> portals, IReadOnlyList<uint> stabList,
|
||||
bool seenOutside, CellBSPTree? containmentBsp)
|
||||
: base(id, worldTransform, inverseWorldTransform, localBoundsMin, localBoundsMax,
|
||||
portals, stabList, seenOutside)
|
||||
{
|
||||
ContainmentBsp = containmentBsp;
|
||||
}
|
||||
|
||||
public override bool PointInCell(Vector3 worldPoint)
|
||||
{
|
||||
var local = Vector3.Transform(worldPoint, InverseWorldTransform);
|
||||
if (ContainmentBsp?.Root is not null)
|
||||
return BSPQuery.PointInsideCellBsp(ContainmentBsp.Root, local); // BSPQuery.cs:1034
|
||||
return local.X >= LocalBoundsMin.X && local.X <= LocalBoundsMax.X
|
||||
&& local.Y >= LocalBoundsMin.Y && local.Y <= LocalBoundsMax.Y
|
||||
&& local.Z >= LocalBoundsMin.Z && local.Z <= LocalBoundsMax.Z;
|
||||
}
|
||||
}
|
||||
33
tests/AcDream.Core.Tests/World/Cells/EnvCellTests.cs
Normal file
33
tests/AcDream.Core.Tests/World/Cells/EnvCellTests.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.World.Cells;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.World.Cells;
|
||||
|
||||
public class EnvCellTests
|
||||
{
|
||||
private static EnvCell Make(Vector3 min, Vector3 max, Matrix4x4? transform = null)
|
||||
{
|
||||
var t = transform ?? Matrix4x4.Identity;
|
||||
Matrix4x4.Invert(t, out var inv);
|
||||
return new EnvCell(0xA9B40174u, t, inv, min, max,
|
||||
System.Array.Empty<CellPortal>(), System.Array.Empty<uint>(),
|
||||
seenOutside: false, containmentBsp: null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PointInCell_NullBsp_Aabb_InsideIsTrue()
|
||||
=> Assert.True(Make(new Vector3(0,0,0), new Vector3(10,10,10)).PointInCell(new Vector3(5,5,5)));
|
||||
|
||||
[Fact]
|
||||
public void PointInCell_NullBsp_Aabb_OutsideIsFalse()
|
||||
=> Assert.False(Make(new Vector3(0,0,0), new Vector3(10,10,10)).PointInCell(new Vector3(20,5,5)));
|
||||
|
||||
[Fact]
|
||||
public void PointInCell_TransformsWorldToLocalBeforeTesting()
|
||||
{
|
||||
var c = Make(new Vector3(0,0,0), new Vector3(10,10,10), Matrix4x4.CreateTranslation(100,0,0));
|
||||
Assert.True(c.PointInCell(new Vector3(105,5,5)));
|
||||
Assert.False(c.PointInCell(new Vector3(5,5,5)));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue