using System.Numerics;
namespace AcDream.App.Rendering.Walk;
///
/// Campaign FW1 — retail's CPU visibility primitives, ported from the named
/// 2013 decomp (extractions:
/// docs/research/2026-08-30-fw-flood-pseudocode-appendix.md, report 4; all
/// x87-ambiguous branches Ghidra-arbitrated). Two families:
///
/// 1. The landscape interval test — a grid corner's vertical column is
/// classified against each active clip plane as a signed height
/// (Render::get_pt_limit @0x0054b840), and a landblock's z-slab
/// is tested against its four corner columns plane by plane
/// (corner_plane_check @0x0054b930, block_plane_check
/// @0x0054d060, block_check @0x0054dc50).
/// 2. The sphere-vs-view test (Render::viewconeCheck @0x0054c250).
///
/// Both consume the same installed state: the CY near plane (built by
/// Render::update_viewpoint) plus the active view's edge planes
/// (installed by Render::set_view).
///
public static class WalkVisibilityMath
{
/// Retail F_EPSILON (raw float 0x3951B717).
public const float Epsilon = 0.000199999995f;
/// get_pt_limit sky cap: a clip height at or above this means
/// the column holds nothing (up-normal arm) / everything (down-normal
/// arm).
public const float SkyHeight = 1000f;
/// Sentinel: the column is wholly inside the plane's positive
/// half-space.
public const float InsideColumn = 0f;
/// Sentinel: the column is wholly outside (static init 1000+1).
public const float OutsideColumn = 1001f;
///
/// Render::get_pt_limit @0x0054b840: classify the vertical column
/// at (x, y) against one plane. Returns /
/// , a positive h (inside only BELOW h —
/// down-pointing normal), or a negative −h (inside only ABOVE h —
/// up-pointing normal). Vertical planes (|N.z| ≤ ε) collapse to
/// all-in/all-out via the plane side of (x, y, 0).
///
public static float GetPointLimit(float x, float y, in WalkPlane plane)
{
Vector3 n = plane.Normal;
if (n.Z > Epsilon)
{
float h = -((x * n.X + y * n.Y + plane.D) / n.Z);
if (h >= SkyHeight) return OutsideColumn;
return h > 0f ? -h : InsideColumn;
}
if (n.Z < -Epsilon)
{
float h = -((x * n.X + y * n.Y + plane.D) / n.Z);
if (h <= 0f) return OutsideColumn;
return h >= SkyHeight ? InsideColumn : h;
}
// Vertical plane: no z dependence; test point (x, y, 0), retail
// Plane::which_side semantics (NEGATIVE = strictly below -epsilon).
float d = x * n.X + y * n.Y + plane.D;
return d < -Epsilon ? OutsideColumn : InsideColumn;
}
///
/// Render::get_clip_height @0x0054cff0: fill one corner's
/// interval vector — bounds[0] vs the CY plane, bounds[1..edgeCount] vs
/// each active edge plane (the loop is INCLUSIVE of edgeCount entries:
/// edgeCount + 1 floats are written).
///
public static void FillClipHeights(
float x, float y, in WalkPlane cyPlane, ReadOnlySpan edgePlanes,
Span bounds)
{
bounds[0] = GetPointLimit(x, y, cyPlane);
for (int i = 0; i < edgePlanes.Length; i++)
bounds[i + 1] = GetPointLimit(x, y, edgePlanes[i]);
}
///
/// Render::corner_plane_check @0x0054b930: one corner's encoding
/// vs the block z-slab [minZ, maxZ]. Boundary semantics are retail's:
/// touching the clip height on the OUT side is Outside; on the IN side
/// is EntirelyInside.
///
public static WalkBoundingType CornerPlaneCheck(float bound, float minZ, float maxZ)
{
if (bound == OutsideColumn) return WalkBoundingType.Outside;
if (bound != InsideColumn)
{
if (bound <= 0f)
{
// inside is z >= h, h = -bound
float h = -bound;
if (h > minZ)
{
if (maxZ <= h) return WalkBoundingType.Outside;
return WalkBoundingType.PartiallyInside;
}
}
else if (bound < maxZ)
{
// inside is z <= h, h = bound; the block top pokes above h
if (bound <= minZ) return WalkBoundingType.Outside;
return WalkBoundingType.PartiallyInside;
}
}
return WalkBoundingType.EntirelyInside;
}
///
/// Render::block_plane_check @0x0054d060: four corners vs one
/// plane. Outside/EntirelyInside require unanimity; anything mixed is
/// PartiallyInside.
///
public static WalkBoundingType BlockPlaneCheck(
float b1, float b2, float b3, float b4, float minZ, float maxZ)
{
WalkBoundingType c1 = CornerPlaneCheck(b1, minZ, maxZ);
WalkBoundingType c2 = CornerPlaneCheck(b2, minZ, maxZ);
WalkBoundingType c3 = CornerPlaneCheck(b3, minZ, maxZ);
WalkBoundingType c4 = CornerPlaneCheck(b4, minZ, maxZ);
if (c1 == WalkBoundingType.Outside)
{
if (c2 == WalkBoundingType.Outside
&& c3 == WalkBoundingType.Outside
&& c4 == WalkBoundingType.Outside)
{
return WalkBoundingType.Outside;
}
}
else if (c1 == WalkBoundingType.EntirelyInside
&& c2 == WalkBoundingType.EntirelyInside
&& c3 == WalkBoundingType.EntirelyInside
&& c4 == WalkBoundingType.EntirelyInside)
{
return WalkBoundingType.EntirelyInside;
}
return WalkBoundingType.PartiallyInside;
}
///
/// Render::block_check @0x0054dc50: the block-vs-view test over
/// four corner interval vectors. Retail's call site passes
/// (max_zval, min_zval) and an internal double positional swap hands
/// corner_plane_check (min, max) — this port takes (maxZ, minZ) to
/// mirror the caller's argument order and performs the same swap, so
/// call sites read like the decomp. Any plane with all four corners
/// outside culls; any partial plane demotes stickily; EntirelyInside
/// requires unanimity on every plane. planeCount = the active view's
/// edge count (bounds[0] is the CY plane, tested first with early-out).
///
public static WalkBoundingType BlockCheck(
ReadOnlySpan corner00, ReadOnlySpan corner01,
ReadOnlySpan corner10, ReadOnlySpan corner11,
int planeCount, float maxZ, float minZ)
{
WalkBoundingType result = BlockPlaneCheck(
corner00[0], corner01[0], corner10[0], corner11[0], minZ, maxZ);
if (result == WalkBoundingType.Outside) return WalkBoundingType.Outside;
for (int k = 1; k <= planeCount; k++)
{
WalkBoundingType r = BlockPlaneCheck(
corner00[k], corner01[k], corner10[k], corner11[k], minZ, maxZ);
if (r == WalkBoundingType.Outside) return WalkBoundingType.Outside;
if (r == WalkBoundingType.PartiallyInside)
result = WalkBoundingType.PartiallyInside;
}
return result;
}
///
/// Render::viewconeCheck @0x0054c250 (plane-test half): a sphere
/// (center already in viewer-block space, radius already scaled) vs the
/// CY plane plus the active view's edge planes. Cull is STRICT
/// (d < −r); the partial flag is INCLUSIVE (d ≤ r) — a sphere exactly
/// tangent from inside counts Partial, not EntirelyInside. The retail
/// body's side effects (publishing local_object_center/radius) belong
/// to the walk context, not this math.
///
public static WalkBoundingType ViewconeCheck(
Vector3 center, float radius, in WalkPlane cyPlane,
ReadOnlySpan edgePlanes)
{
float d = Vector3.Dot(cyPlane.Normal, center) + cyPlane.D;
if (d < -radius) return WalkBoundingType.Outside;
bool partial = d <= radius;
foreach (ref readonly WalkPlane plane in edgePlanes)
{
d = Vector3.Dot(plane.Normal, center) + plane.D;
if (d < -radius) return WalkBoundingType.Outside;
if (d <= radius) partial = true;
}
return partial ? WalkBoundingType.PartiallyInside : WalkBoundingType.EntirelyInside;
}
}
/// Retail Plane: dot(N, p) + d, positive side = inside.
public readonly record struct WalkPlane(Vector3 Normal, float D);
/// Retail BoundingType, values used raw by the walk.
public enum WalkBoundingType
{
Outside = 0,
PartiallyInside = 1,
EntirelyInside = 2,
}