using System;
using System.Collections.Generic;
using System.Numerics;
using AcDream.Core.Physics;
namespace AcDream.Core.Selection;
///
/// Indoor walking Phase 1 (2026-05-19). Pure ray-vs-cell-BSP-polygon
/// occlusion test. Given a ray and a set of
/// (currently-loaded EnvCells with resolved polygon planes), returns
/// the nearest world-space t along the ray that hits any cell
/// polygon — or if the ray clears
/// all cells.
///
///
/// Used by to filter entities that sit
/// behind a wall from the camera's POV (issue #86). Möller-Trumbore
/// ray-triangle intersection; one test per triangle. Cells are
/// transformed via their
/// so the ray runs in cell-local space and the resolved-polygon
/// vertices don't need re-transformation per query.
///
///
///
/// No BSP traversal — iterates every polygon in every cell. Cell count
/// in a Holtburg-radius-4 streaming window is ~80 cells × ~50 polys
/// each = ~4K triangles. Möller-Trumbore is ~40 ns per triangle on
/// modern hardware; one Pick call is well under 1 ms.
///
///
public static class CellBspRayOccluder
{
///
/// Returns the nearest positive t such that
/// origin + t * direction intersects a polygon in any cell.
/// Returns if no cell polygon
/// is intersected.
///
/// Need not be normalized; returned t
/// scales with direction length the same as a parametric ray.
public static float NearestWallT(
Vector3 origin,
Vector3 direction,
IEnumerable loadedCells)
{
if (loadedCells is null) return float.PositiveInfinity;
float bestT = float.PositiveInfinity;
foreach (var cell in loadedCells)
{
if (cell?.Resolved is null) continue;
// Bring the ray into cell-local space ONCE per cell.
var localOrigin = Vector3.Transform(origin, cell.InverseWorldTransform);
var localDirection = Vector3.TransformNormal(direction, cell.InverseWorldTransform);
foreach (var (_, poly) in cell.Resolved)
{
// Triangulate the (possibly polygonal) face into a fan.
int n = poly.NumPoints;
if (n < 3 || poly.Vertices is null || poly.Vertices.Length < n)
continue;
for (int i = 1; i < n - 1; i++)
{
if (TryRayTriangle(
localOrigin, localDirection,
poly.Vertices[0], poly.Vertices[i], poly.Vertices[i + 1],
out var t)
&& t < bestT)
{
bestT = t;
}
}
}
}
return bestT;
}
///
/// Möller-Trumbore ray-triangle intersection. Returns true with
/// t in if the ray hits the triangle
/// at a positive distance.
///
private static bool TryRayTriangle(
Vector3 origin, Vector3 direction,
Vector3 v0, Vector3 v1, Vector3 v2,
out float t)
{
const float Epsilon = 1e-7f;
var edge1 = v1 - v0;
var edge2 = v2 - v0;
var pvec = Vector3.Cross(direction, edge2);
float det = Vector3.Dot(edge1, pvec);
// No two-sided handling here — picker should be permissive so
// a wall blocks regardless of which side the camera is on.
if (det > -Epsilon && det < Epsilon) { t = 0f; return false; }
float invDet = 1f / det;
var tvec = origin - v0;
float u = Vector3.Dot(tvec, pvec) * invDet;
if (u < 0f || u > 1f) { t = 0f; return false; }
var qvec = Vector3.Cross(tvec, edge1);
float v = Vector3.Dot(direction, qvec) * invDet;
if (v < 0f || u + v > 1f) { t = 0f; return false; }
t = Vector3.Dot(edge2, qvec) * invDet;
return t > Epsilon;
}
}