|
| 1 | +using System.Buffers; |
| 2 | +using System.Collections.Generic; |
| 3 | +using Unity.Collections; |
| 4 | + |
| 5 | +namespace UnityEngine.Rendering |
| 6 | +{ |
| 7 | + /// <summary> |
| 8 | + /// Utility class that computes the total rect area via a sweep-line. |
| 9 | + /// </summary> |
| 10 | + public static class SweepLineRectUtils |
| 11 | + { |
| 12 | + struct EventComparer : IComparer<Vector4> |
| 13 | + { |
| 14 | + public int Compare(Vector4 a, Vector4 b) |
| 15 | + { |
| 16 | + int cx = a.x.CompareTo(b.x); |
| 17 | + if (cx != 0) return cx; |
| 18 | + // tie on x: larger y first (+1 before -1) |
| 19 | + return b.y.CompareTo(a.y); |
| 20 | + } |
| 21 | + } |
| 22 | + struct ActiveComparer : IComparer<Vector2> |
| 23 | + { |
| 24 | + public int Compare(Vector2 a, Vector2 b) |
| 25 | + { |
| 26 | + return a.x.CompareTo(b.x); |
| 27 | + } |
| 28 | + } |
| 29 | + |
| 30 | + /// <summary> |
| 31 | + /// Computes the total covered area (union) of a set of axis-aligned rectangles, counting overlaps only once. |
| 32 | + /// </summary> |
| 33 | + /// <param name="rects">List of rects to compute.</param> |
| 34 | + /// <returns>The normalized union area in [0,1], with overlaps counted once.</returns> |
| 35 | + public static float CalculateRectUnionArea(List<Rect> rects) |
| 36 | + { |
| 37 | + int rectsCount = rects.Count; |
| 38 | + var eventsBuffer = ArrayPool<Vector4>.Shared.Rent(rectsCount * 2); |
| 39 | + var activeBuffer = ArrayPool<Vector2>.Shared.Rent(rectsCount); |
| 40 | + |
| 41 | + int eventCount = 0; |
| 42 | + foreach (var rect in rects) |
| 43 | + InsertEvents(rect, eventsBuffer, ref eventCount); |
| 44 | + |
| 45 | + float area = CalculateRectUnionArea(eventsBuffer, activeBuffer, eventCount); |
| 46 | + ArrayPool<Vector4>.Shared.Return(eventsBuffer); |
| 47 | + ArrayPool<Vector2>.Shared.Return(activeBuffer); |
| 48 | + |
| 49 | + return area; |
| 50 | + } |
| 51 | + |
| 52 | + // Merge overlapping intervals and return total covered Y length |
| 53 | + static float MergeLengthY(Vector2[] activeBuffer, int count) |
| 54 | + { |
| 55 | + if (count <= 0) |
| 56 | + return 0f; |
| 57 | + |
| 58 | + // ActiveBuffer stores (yMin, yMax) |
| 59 | + float total = 0f; |
| 60 | + float cy0 = activeBuffer[0].x; |
| 61 | + float cy1 = activeBuffer[0].y; |
| 62 | + |
| 63 | + for (int i = 1; i < count; i++) |
| 64 | + { |
| 65 | + float y0 = activeBuffer[i].x; |
| 66 | + float y1 = activeBuffer[i].y; |
| 67 | + if (y0 <= cy1) |
| 68 | + { |
| 69 | + if (y1 > cy1) cy1 = y1; |
| 70 | + } |
| 71 | + else |
| 72 | + { |
| 73 | + total += (cy1 - cy0); |
| 74 | + cy0 = y0; cy1 = y1; |
| 75 | + } |
| 76 | + } |
| 77 | + total += (cy1 - cy0); |
| 78 | + return Mathf.Clamp01(total); |
| 79 | + } |
| 80 | + |
| 81 | + /// <summary> |
| 82 | + /// Computes the total covered area (union) of a set of axis-aligned rectangles using a sweep-line. |
| 83 | + /// </summary> |
| 84 | + static unsafe float CalculateRectUnionArea(Vector4[] eventsBuffer, Vector2[] activeBuffer, int eventCount) |
| 85 | + { |
| 86 | + if (eventCount == 0) |
| 87 | + return 0f; |
| 88 | + |
| 89 | + // Sort events by (x asc, enter first) |
| 90 | + fixed (Vector4* ptr = eventsBuffer) |
| 91 | + { |
| 92 | + NativeSortExtension.Sort(ptr, eventCount, new EventComparer()); |
| 93 | + } |
| 94 | + |
| 95 | + int activeCount = 0; |
| 96 | + float area = 0f; |
| 97 | + float lastX = eventsBuffer[0].x; |
| 98 | + bool needLastXUpdate = false; |
| 99 | + int i = 0; |
| 100 | + |
| 101 | + while (i < eventCount) |
| 102 | + { |
| 103 | + float x = eventsBuffer[i].x; |
| 104 | + |
| 105 | + if (needLastXUpdate) |
| 106 | + { |
| 107 | + lastX = x; |
| 108 | + needLastXUpdate = false; |
| 109 | + } |
| 110 | + |
| 111 | + // Accumulate area over [lastX, x) |
| 112 | + float dx = x - lastX; |
| 113 | + if (dx > 0f && activeCount > 0) |
| 114 | + { |
| 115 | + fixed (Vector2* ptr = activeBuffer) |
| 116 | + { |
| 117 | + NativeSortExtension.Sort(ptr, activeCount, new ActiveComparer()); |
| 118 | + } |
| 119 | + area += MergeLengthY(activeBuffer, activeCount) * dx; |
| 120 | + lastX = x; |
| 121 | + } |
| 122 | + |
| 123 | + // Consume all events at this x (group approx-equal x's) |
| 124 | + do |
| 125 | + { |
| 126 | + Vector4 ev = eventsBuffer[i]; |
| 127 | + float y0 = ev.z; |
| 128 | + float y1 = ev.w; |
| 129 | + |
| 130 | + if (ev.y > 0f) // Enter |
| 131 | + { |
| 132 | + activeBuffer[activeCount++] = new Vector2(y0, y1); |
| 133 | + } |
| 134 | + else // Leave |
| 135 | + { |
| 136 | + // Remove once (swap with last) |
| 137 | + for (int k = 0; k < activeCount; k++) |
| 138 | + { |
| 139 | + Vector2 v = activeBuffer[k]; |
| 140 | + if (Mathf.Approximately(v.x, y0) && Mathf.Approximately(v.y, y1)) |
| 141 | + { |
| 142 | + int last = activeCount - 1; |
| 143 | + activeBuffer[k] = activeBuffer[last]; |
| 144 | + activeCount = last; |
| 145 | + break; |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + if (activeCount == 0) |
| 150 | + needLastXUpdate = true; |
| 151 | + } |
| 152 | + |
| 153 | + i++; |
| 154 | + } |
| 155 | + while (i < eventCount && Mathf.Approximately(eventsBuffer[i].x, x)); |
| 156 | + } |
| 157 | + |
| 158 | + return area; |
| 159 | + } |
| 160 | + |
| 161 | + // Insert events with clamped rects |
| 162 | + static void InsertEvents(in Rect rect, Vector4[] eventsBuffer, ref int eventCount) |
| 163 | + { |
| 164 | + if (rect.width > 0f && rect.height > 0f) |
| 165 | + { |
| 166 | + var y0 = Mathf.Clamp01(rect.yMin); |
| 167 | + var y1 = Mathf.Clamp01(rect.yMax); |
| 168 | + eventsBuffer[eventCount++] = new Vector4(Mathf.Clamp01(rect.xMin), +1f, y0, y1); // enter |
| 169 | + eventsBuffer[eventCount++] = new Vector4(Mathf.Clamp01(rect.xMax), -1f, y0, y1); // leave |
| 170 | + } |
| 171 | + } |
| 172 | + } |
| 173 | +} |
0 commit comments