// ScreenPolygonClip.cs // // Phase A8.F: 2D convex-polygon intersection (Sutherland-Hodgman). // Ports the BEHAVIOR of retail ACRender::polyClipFinish (the screen-space // portal clipper invoked by PView::GetClip, decomp:432344). Both inputs are // convex (a cell's accumulated view polygon and a projected portal polygon), // so the result is convex. We match retail's clipping relationship, not its // exact float output. using System.Collections.Generic; using System.Numerics; namespace AcDream.App.Rendering; public static class ScreenPolygonClip { private const float Eps = 1e-7f; /// Intersect two convex polygons given CCW. Returns the clipped /// vertices (CCW) or an array with <3 verts when the intersection is empty. public static Vector2[] Intersect(IReadOnlyList subject, IReadOnlyList clip) { if (subject == null || clip == null || subject.Count < 3 || clip.Count < 3) return System.Array.Empty(); var output = new List(subject); for (int i = 0; i < clip.Count; i++) { if (output.Count < 3) return System.Array.Empty(); Vector2 a = clip[i]; Vector2 b = clip[(i + 1) % clip.Count]; output = ClipByEdge(output, a, b); } return output.Count >= 3 ? output.ToArray() : System.Array.Empty(); } // Keep the part of `poly` on the left of directed edge a->b (CCW inside). private static List ClipByEdge(List poly, Vector2 a, Vector2 b) { var result = new List(poly.Count + 1); Vector2 edge = b - a; for (int i = 0; i < poly.Count; i++) { Vector2 cur = poly[i]; Vector2 prev = poly[(i + poly.Count - 1) % poly.Count]; float curSide = Cross(edge, cur - a); // > 0 = left (inside) float prevSide = Cross(edge, prev - a); bool curIn = curSide >= -Eps; bool prevIn = prevSide >= -Eps; if (curIn) { if (!prevIn) result.Add(Intersection(prev, cur, prevSide, curSide)); result.Add(cur); } else if (prevIn) { result.Add(Intersection(prev, cur, prevSide, curSide)); } } return result; } private static float Cross(Vector2 u, Vector2 v) => u.X * v.Y - u.Y * v.X; private static Vector2 Intersection(Vector2 p, Vector2 q, float dp, float dq) { float t = dp / (dp - dq); // dp, dq are signed distances (cross products); opposite signs here return p + t * (q - p); } }