using Unity.Mathematics;
namespace jeanf.scenemanagement
{
///
/// Single source of truth for zone-volume containment. Both the player test
/// () and the object test ()
/// go through here so the two can never drift apart.
///
/// Convention (inherited from the original VolumeSystem implementation): a volume is an
/// oriented box whose orientation and position come from its LocalToWorld matrix but whose
/// extents come exclusively from Volume.Scale (baked from the authoring transform's
/// localScale) — the matrix scale is deliberately ignored, distances are metric.
///
public static class VolumeMath
{
/// The point expressed in the volume's rotation-only local frame (metric units).
public static float3 ToLocal(in float4x4 localToWorld, in float3 worldPoint)
{
var pos = localToWorld.c3.xyz;
var rot = math.quaternion(localToWorld);
var rotationOnly = float4x4.TRS(pos, rot, new float3(1f, 1f, 1f));
var worldToLocal = math.inverse(rotationOnly);
return math.transform(worldToLocal, worldPoint);
}
public static bool ContainsPoint(in float4x4 localToWorld, in float3 scale, in float3 worldPoint)
{
return ContainsPoint(localToWorld, scale, worldPoint, 0f);
}
///
/// Containment with the box extents expanded by meters on
/// every axis (the epsilon pass of the coverage failsafe chain). Strict comparison,
/// matching the original player test.
///
public static bool ContainsPoint(in float4x4 localToWorld, in float3 scale, in float3 worldPoint, float tolerance)
{
var local = ToLocal(localToWorld, worldPoint);
var range = scale * 0.5f + tolerance;
var distance = math.abs(local);
return math.all(distance < range);
}
///
/// Vertical overshoot (meters, volume-local up) of the point above the box top when its
/// X/Z footprint is inside the box — the ceiling-lifted-object case. Returns
/// when the footprint misses the box; zero or
/// negative when the point is not above the top (exact containment covers those).
/// Downward overshoot is deliberately not forgiven: an object under a box belongs to
/// the storey below, not to that box.
///
public static float LiftAbove(in float4x4 localToWorld, in float3 scale, in float3 worldPoint)
{
var local = ToLocal(localToWorld, worldPoint);
var range = scale * 0.5f;
if (math.abs(local.x) >= range.x || math.abs(local.z) >= range.z) return float.PositiveInfinity;
return local.y - range.y;
}
/// Squared metric distance from the point to the box surface; 0 when inside.
public static float DistanceSq(in float4x4 localToWorld, in float3 scale, in float3 worldPoint)
{
var local = ToLocal(localToWorld, worldPoint);
var d = math.max(math.abs(local) - scale * 0.5f, float3.zero);
return math.lengthsq(d);
}
}
}