Files
2025-09-29 00:52:08 +02:00

237 lines
7.9 KiB
C#
Executable File

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace Sutherland
{
public static class SutherlandHodgman
{
#region Class: Edge
/// <summary>
/// This represents a line segment
/// </summary>
private class Edge
{
public Edge(Point from, Point to)
{
this.From = from;
this.To = to;
}
public readonly Point From;
public readonly Point To;
}
#endregion
/// <summary>
/// This clips the subject polygon against the clip polygon (gets the intersection of the two polygons)
/// </summary>
/// <remarks>
/// Based on the psuedocode from:
/// http://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman
/// </remarks>
/// <param name="subjectPoly">Can be concave or convex</param>
/// <param name="clipPoly">Must be convex</param>
/// <returns>The intersection of the two polygons (or null)</returns>
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<Point> 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<Point> 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
/// <summary>
/// This iterates through the edges of the polygon, always clockwise
/// </summary>
private static IEnumerable<Edge> 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
}
}
/// <summary>
/// Returns the intersection of the two lines (line segments are passed in, but they are treated like infinite lines)
/// </summary>
/// <remarks>
/// Got this here:
/// http://stackoverflow.com/questions/14480124/how-do-i-detect-triangle-and-rectangle-intersection
/// </remarks>
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");
}
/// <summary>
/// Tells if the test point lies on the left side of the edge line
/// </summary>
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
}
}