using System.Buffers;
using System.Collections.Generic;
using Unity.Collections;
namespace UnityEngine.Rendering
{
///
/// Utility class that computes the total rect area via a sweep-line.
///
public static class SweepLineRectUtils
{
struct EventComparer : IComparer
{
public int Compare(Vector4 a, Vector4 b)
{
int cx = a.x.CompareTo(b.x);
if (cx != 0) return cx;
// tie on x: larger y first (+1 before -1)
return b.y.CompareTo(a.y);
}
}
struct ActiveComparer : IComparer
{
public int Compare(Vector2 a, Vector2 b)
{
return a.x.CompareTo(b.x);
}
}
///
/// Computes the total covered area (union) of a set of axis-aligned rectangles, counting overlaps only once.
///
/// List of rects to compute.
/// The normalized union area in [0,1], with overlaps counted once.
public static float CalculateRectUnionArea(List rects)
{
int rectsCount = rects.Count;
var eventsBuffer = ArrayPool.Shared.Rent(rectsCount * 2);
var activeBuffer = ArrayPool.Shared.Rent(rectsCount);
int eventCount = 0;
foreach (var rect in rects)
InsertEvents(rect, eventsBuffer, ref eventCount);
float area = CalculateRectUnionArea(eventsBuffer, activeBuffer, eventCount);
ArrayPool.Shared.Return(eventsBuffer);
ArrayPool.Shared.Return(activeBuffer);
return area;
}
// Merge overlapping intervals and return total covered Y length
static float MergeLengthY(Vector2[] activeBuffer, int count)
{
if (count <= 0)
return 0f;
// ActiveBuffer stores (yMin, yMax)
float total = 0f;
float cy0 = activeBuffer[0].x;
float cy1 = activeBuffer[0].y;
for (int i = 1; i < count; i++)
{
float y0 = activeBuffer[i].x;
float y1 = activeBuffer[i].y;
if (y0 <= cy1)
{
if (y1 > cy1) cy1 = y1;
}
else
{
total += (cy1 - cy0);
cy0 = y0; cy1 = y1;
}
}
total += (cy1 - cy0);
return Mathf.Clamp01(total);
}
///
/// Computes the total covered area (union) of a set of axis-aligned rectangles using a sweep-line.
///
static unsafe float CalculateRectUnionArea(Vector4[] eventsBuffer, Vector2[] activeBuffer, int eventCount)
{
if (eventCount == 0)
return 0f;
// Sort events by (x asc, enter first)
fixed (Vector4* ptr = eventsBuffer)
{
NativeSortExtension.Sort(ptr, eventCount, new EventComparer());
}
int activeCount = 0;
float area = 0f;
float lastX = eventsBuffer[0].x;
bool needLastXUpdate = false;
int i = 0;
while (i < eventCount)
{
float x = eventsBuffer[i].x;
if (needLastXUpdate)
{
lastX = x;
needLastXUpdate = false;
}
// Accumulate area over [lastX, x)
float dx = x - lastX;
if (dx > 0f && activeCount > 0)
{
fixed (Vector2* ptr = activeBuffer)
{
NativeSortExtension.Sort(ptr, activeCount, new ActiveComparer());
}
area += MergeLengthY(activeBuffer, activeCount) * dx;
lastX = x;
}
// Consume all events at this x (group approx-equal x's)
do
{
Vector4 ev = eventsBuffer[i];
float y0 = ev.z;
float y1 = ev.w;
if (ev.y > 0f) // Enter
{
activeBuffer[activeCount++] = new Vector2(y0, y1);
}
else // Leave
{
// Remove once (swap with last)
for (int k = 0; k < activeCount; k++)
{
Vector2 v = activeBuffer[k];
if (Mathf.Approximately(v.x, y0) && Mathf.Approximately(v.y, y1))
{
int last = activeCount - 1;
activeBuffer[k] = activeBuffer[last];
activeCount = last;
break;
}
}
if (activeCount == 0)
needLastXUpdate = true;
}
i++;
}
while (i < eventCount && Mathf.Approximately(eventsBuffer[i].x, x));
}
return area;
}
// Insert events with clamped rects
static void InsertEvents(in Rect rect, Vector4[] eventsBuffer, ref int eventCount)
{
if (rect.width > 0f && rect.height > 0f)
{
var y0 = Mathf.Clamp01(rect.yMin);
var y1 = Mathf.Clamp01(rect.yMax);
eventsBuffer[eventCount++] = new Vector4(Mathf.Clamp01(rect.xMin), +1f, y0, y1); // enter
eventsBuffer[eventCount++] = new Vector4(Mathf.Clamp01(rect.xMax), -1f, y0, y1); // leave
}
}
}
}