using System; using System.Collections.Generic; namespace Oni.Akira { internal struct Polygon2Clipper { private readonly List result; private readonly RoomBspNode bspTree; #region private struct Line private struct Line { private float a, c, d; public Line(Plane plane) { a = plane.Normal.X; c = plane.Normal.Z; d = plane.D; } public int RelativePosition(Vector2 point) { float r = a * point.X + c * point.Y + d; if (r < -0.00001f) return -1; else if (r > 0.00001f) return 1; else return 0; } public Vector2 Intersect(Vector2 p0, Vector2 p1) { if (p0.X == p1.X) { float x = p0.X; float y = (d + a * x) / -c; return new Vector2(x, y); } else if (p0.Y == p1.Y) { float x = (-c * p0.Y - d) / a; float y = p0.Y; return new Vector2(x, y); } else { float m = (p1.Y - p0.Y) / (p1.X - p0.X); float x = (c * m * p0.X - c * p0.Y - d) / (a + c * m); float y = m * (x - p0.X) + p0.Y; return new Vector2(x, y); } } } #endregion public Polygon2Clipper(RoomBspNode bspTree) { this.result = new List(); this.bspTree = bspTree; } public IEnumerable Clip(Polygon2 polygon) { result.Clear(); Clip(new[] { polygon }, bspTree); return result; } private void Clip(IEnumerable polygons, RoomBspNode node) { var negative = new List(); var positive = new List(); var plane = node.Plane; if (Math.Abs(plane.Normal.Y) > 0.001f) { negative.AddRange(polygons); positive.AddRange(polygons); } else { var line = new Line(plane); foreach (Polygon2 polygon in polygons) Clip(polygon, line, negative, positive); } if (node.FrontChild != null) Clip(positive, node.FrontChild); if (node.BackChild != null) Clip(negative, node.BackChild); else result.AddRange(negative); } private static void Clip(Polygon2 polygon, Line line, List negative, List positive) { var signs = new int[polygon.Length]; int positiveCount = 0, negativeCount = 0; for (int i = 0; i < polygon.Length; i++) { signs[i] = line.RelativePosition(polygon[i]); if (signs[i] >= 0) positiveCount++; if (signs[i] <= 0) negativeCount++; } if (negativeCount == polygon.Length) { // // All points are in the negative half plane, nothing to clip. // negative.Add(polygon); return; } if (positiveCount == polygon.Length) { // // All points are in the positive half plane, nothing to clip. // positive.Add(polygon); return; } var negativePoints = new List(); var positivePoints = new List(); int start = 0; Vector2 p0; int s0; do { p0 = polygon[start]; s0 = signs[start]; start++; // // do not start right on the clip line // } while (s0 == 0); var intersections = new Vector2[2]; int intersectionCount = 0; for (int i = 0; i < polygon.Length; i++) { Vector2 p1 = polygon[(i + start) % polygon.Length]; int s1 = signs[(i + start) % polygon.Length]; if (s0 == s1) { // // Same half plane, no intersection, add the existing edge. // if (s0 < 0) negativePoints.Add(p0); else positivePoints.Add(p0); } else if (s0 == 0) { // // If the previous point was on the clip line then we need // to use the current point sign to figure out the destination polygon. // if (s1 < 0) negativePoints.Add(p0); else positivePoints.Add(p0); } else { // // Different half plane, split the edge in two. // Vector2 intersection; if (s1 == 0) intersection = p1; else intersection = line.Intersect(p0, p1); intersections[intersectionCount++] = intersection; if (s0 < 0) { // the negative polygon needs to be closed // the positive polygon needs to have an edge added from the previous intersection to the new one negativePoints.Add(p0); if (intersectionCount == 2) { negativePoints.Add(intersection); positivePoints.Add(intersections[0]); } if (s1 != 0) positivePoints.Add(intersection); } else { // the positive polygon needs to be closed // the negative polygon needs to have an edge added from the previous intersection to the new one positivePoints.Add(p0); if (intersectionCount == 2) { positivePoints.Add(intersection); negativePoints.Add(intersections[0]); } if (s1 != 0) negativePoints.Add(intersection); } } p0 = p1; s0 = s1; } negative.Add(new Polygon2(negativePoints.ToArray())); positive.Add(new Polygon2(positivePoints.ToArray())); } } }