using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; namespace Sutherland { public static class SutherlandHodgman { #region Class: Edge /// /// This represents a line segment /// private class Edge { public Edge(Point from, Point to) { this.From = from; this.To = to; } public readonly Point From; public readonly Point To; } #endregion /// /// This clips the subject polygon against the clip polygon (gets the intersection of the two polygons) /// /// /// Based on the psuedocode from: /// http://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman /// /// Can be concave or convex /// Must be convex /// The intersection of the two polygons (or null) public static Point[] GetIntersectedPolygon(Point[] subjectPoly, Point[] clipPoly) { if (subjectPoly.Length < 3 || clipPoly.Length < 3) { throw new ArgumentException(string.Format("The polygons passed in must have at least 3 points: subject={0}, clip={1}", subjectPoly.Length.ToString(), clipPoly.Length.ToString())); } List outputList = subjectPoly.ToList(); // Make sure it's clockwise if (!IsClockwise(subjectPoly)) { outputList.Reverse(); } // Walk around the clip polygon clockwise foreach (Edge clipEdge in IterateEdgesClockwise(clipPoly)) { List inputList = outputList.ToList(); // clone it outputList.Clear(); if (inputList.Count == 0) { // Sometimes when the polygons don't intersect, this list goes to zero. Jump out to avoid an index out of range exception break; } Point S = inputList[inputList.Count - 1]; foreach (Point E in inputList) { if (IsInside(clipEdge, E)) { if (!IsInside(clipEdge, S)) { Point? point = GetIntersect(S, E, clipEdge.From, clipEdge.To); if (point == null) { throw new ApplicationException("Line segments don't intersect"); // may be colinear, or may be a bug } else { outputList.Add(point.Value); } } outputList.Add(E); } else if (IsInside(clipEdge, S)) { Point? point = GetIntersect(S, E, clipEdge.From, clipEdge.To); if (point == null) { throw new ApplicationException("Line segments don't intersect"); // may be colinear, or may be a bug } else { outputList.Add(point.Value); } } S = E; } } // Exit Function return outputList.ToArray(); } #region Private Methods /// /// This iterates through the edges of the polygon, always clockwise /// private static IEnumerable IterateEdgesClockwise(Point[] polygon) { if (IsClockwise(polygon)) { #region Already clockwise for (int cntr = 0; cntr < polygon.Length - 1; cntr++) { yield return new Edge(polygon[cntr], polygon[cntr + 1]); } yield return new Edge(polygon[polygon.Length - 1], polygon[0]); #endregion } else { #region Reverse for (int cntr = polygon.Length - 1; cntr > 0; cntr--) { yield return new Edge(polygon[cntr], polygon[cntr - 1]); } yield return new Edge(polygon[0], polygon[polygon.Length - 1]); #endregion } } /// /// Returns the intersection of the two lines (line segments are passed in, but they are treated like infinite lines) /// /// /// Got this here: /// http://stackoverflow.com/questions/14480124/how-do-i-detect-triangle-and-rectangle-intersection /// private static Point? GetIntersect(Point line1From, Point line1To, Point line2From, Point line2To) { Vector direction1 = line1To - line1From; Vector direction2 = line2To - line2From; double dotPerp = (direction1.X * direction2.Y) - (direction1.Y * direction2.X); // If it's 0, it means the lines are parallel so have infinite intersection points if (IsNearZero(dotPerp)) { return null; } Vector c = line2From - line1From; double t = (c.X * direction2.Y - c.Y * direction2.X) / dotPerp; //if (t < 0 || t > 1) //{ // return null; // lies outside the line segment //} //double u = (c.X * direction1.Y - c.Y * direction1.X) / dotPerp; //if (u < 0 || u > 1) //{ // return null; // lies outside the line segment //} // Return the intersection point return line1From + (t * direction1); } private static bool IsInside(Edge edge, Point test) { bool? isLeft = IsLeftOf(edge, test); if (isLeft == null) { // Colinear points should be considered inside return true; } return !isLeft.Value; } private static bool IsClockwise(Point[] polygon) { for (int cntr = 2; cntr < polygon.Length; cntr++) { bool? isLeft = IsLeftOf(new Edge(polygon[0], polygon[1]), polygon[cntr]); if (isLeft != null) // some of the points may be colinear. That's ok as long as the overall is a polygon { return !isLeft.Value; } } throw new ArgumentException("All the points in the polygon are colinear"); } /// /// Tells if the test point lies on the left side of the edge line /// private static bool? IsLeftOf(Edge edge, Point test) { Vector tmp1 = edge.To - edge.From; Vector tmp2 = test - edge.To; double x = (tmp1.X * tmp2.Y) - (tmp1.Y * tmp2.X); // dot product of perpendicular? if (x < 0) { return false; } else if (x > 0) { return true; } else { // Colinear points; return null; } } private static bool IsNearZero(double testValue) { return Math.Abs(testValue) <= .000000001d; } #endregion } }