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 } } } }