using System;
using System.Collections.Generic;
using System.Numerics;
using AcDream.Core.Physics;
using Xunit;
namespace AcDream.Core.Tests.Physics;
///
/// Indoor walking Phase D (2026-05-19): tests for the indoor-cell-containment
/// check added to .
/// Covers the four scenarios described in the Phase D implementation plan.
///
public class ResolveOutdoorCellIdIndoorContainmentTests
{
///
/// Build a whose local AABB spans ±
/// around the origin, placed at via the
/// WorldTransform / InverseWorldTransform pair.
///
private static CellPhysics MakeIndoorCellAt(Vector3 worldOrigin, Vector3 halfExtent)
{
// Four vertices defining a floor quad — enough for AABB computation at
// cache time (in production this is done by CacheCellStruct, in tests
// we pre-supply LocalAabbMin / LocalAabbMax directly).
var min = -halfExtent;
var max = halfExtent;
var verts = new[]
{
new Vector3(min.X, min.Y, min.Z),
new Vector3(max.X, min.Y, min.Z),
new Vector3(max.X, max.Y, max.Z),
new Vector3(min.X, max.Y, max.Z),
};
var poly = new ResolvedPolygon
{
Vertices = verts,
Plane = new Plane(Vector3.UnitZ, 0f),
NumPoints = 4,
SidesType = DatReaderWriter.Enums.CullMode.None,
};
var world = Matrix4x4.CreateTranslation(worldOrigin);
Matrix4x4.Invert(world, out var inv);
return new CellPhysics
{
Resolved = new Dictionary { [0] = poly },
WorldTransform = world,
InverseWorldTransform = inv,
};
}
// -----------------------------------------------------------------------
// Test 1: player inside a cached EnvCell → returns that cell's full id.
// -----------------------------------------------------------------------
[Fact]
public void ResolveOutdoorCellId_PlayerInsideCachedEnvCell_ReturnsEnvCellId()
{
var engine = new PhysicsEngine();
engine.DataCache = new PhysicsDataCache();
// Cache an EnvCell at world origin spanning ±5 m on each axis.
var cell = MakeIndoorCellAt(Vector3.Zero, new Vector3(5f, 5f, 5f));
engine.DataCache.RegisterCellStructForTest(0xA9B40172u, cell);
// Player at world origin → inside the EnvCell's AABB.
uint result = engine.ResolveOutdoorCellId(Vector3.Zero, fallbackCellId: 0x00000031u);
Assert.Equal(0xA9B40172u, result);
}
// -----------------------------------------------------------------------
// Test 2: player outside all cached EnvCells → falls through to outdoor
// (and since no landblocks are registered, returns the fallback unchanged).
// -----------------------------------------------------------------------
[Fact]
public void ResolveOutdoorCellId_PlayerOutsideAllCachedEnvCells_FallsThroughToOutdoor()
{
var engine = new PhysicsEngine();
engine.DataCache = new PhysicsDataCache();
var cell = MakeIndoorCellAt(Vector3.Zero, new Vector3(5f, 5f, 5f));
engine.DataCache.RegisterCellStructForTest(0xA9B40172u, cell);
// Player at (100, 100, 0) — far outside the cached EnvCell.
// No landblocks registered → outdoor branch can't match either.
uint result = engine.ResolveOutdoorCellId(new Vector3(100f, 100f, 0f), fallbackCellId: 0x00000031u);
Assert.Equal(0x00000031u, result);
}
// -----------------------------------------------------------------------
// Test 3: EnvCell with a non-identity WorldTransform (rotation around Z).
// Player at world (3, 0, 0) is still inside the rotated local AABB.
// -----------------------------------------------------------------------
[Fact]
public void ResolveOutdoorCellId_PlayerInsideEnvCellWithRotatedTransform_StillDetectsContainment()
{
var halfExtent = new Vector3(5f, 5f, 5f);
var verts = new[]
{
new Vector3(-5f, -5f, -5f),
new Vector3( 5f, -5f, -5f),
new Vector3( 5f, 5f, 5f),
new Vector3(-5f, 5f, 5f),
};
var poly = new ResolvedPolygon
{
Vertices = verts,
Plane = new Plane(Vector3.UnitZ, 0f),
NumPoints = 4,
SidesType = DatReaderWriter.Enums.CullMode.None,
};
// 90° rotation around Z. A point at world (3, 0, 0) transforms to
// local (0, -3, 0) — still within ±5 on every axis.
var rotation = Matrix4x4.CreateRotationZ(MathF.PI / 2f);
Matrix4x4.Invert(rotation, out var inv);
var cell = new CellPhysics
{
Resolved = new Dictionary { [0] = poly },
WorldTransform = rotation,
InverseWorldTransform = inv,
};
var engine = new PhysicsEngine();
engine.DataCache = new PhysicsDataCache();
engine.DataCache.RegisterCellStructForTest(0xA9B40172u, cell);
uint result = engine.ResolveOutdoorCellId(new Vector3(3f, 0f, 0f), fallbackCellId: 0x00000031u);
Assert.Equal(0xA9B40172u, result);
}
// -----------------------------------------------------------------------
// Test 4: fallbackCellId == 0 → always returns 0 (existing early-return).
// -----------------------------------------------------------------------
[Fact]
public void ResolveOutdoorCellId_FallbackZero_ReturnsZero()
{
var engine = new PhysicsEngine();
engine.DataCache = new PhysicsDataCache();
// Even if the player is inside a cell, fallback=0 should still return 0.
var cell = MakeIndoorCellAt(Vector3.Zero, new Vector3(5f, 5f, 5f));
engine.DataCache.RegisterCellStructForTest(0xA9B40172u, cell);
uint result = engine.ResolveOutdoorCellId(Vector3.Zero, fallbackCellId: 0u);
Assert.Equal(0u, result);
}
}