// Split a polygon by a plane, this is a bit faster than clipping twice with the flipped // plane, and will guarantee that points are exact public static SplitPolygon SplitPolygonByPlane(IEnumerable <IVertex> vertList, ClipPlane plane) { if (plane == null) { throw new ArgumentNullException("plane"); } if (vertList == null) { throw new ArgumentNullException("vertList"); } List <IVertex> front_list = new List <IVertex>(); List <IVertex> back_list = new List <IVertex>(); ClipSide firstPointSide = ClipSide.CoPlanar; ClipSide lastPointSide = ClipSide.CoPlanar; IVertex lastVertex = null; IVertex firstVertex = null; bool isCoPlanar = true; foreach (var v in vertList) { if (lastVertex == null) { // This is the first vertex in the list, so it is specially handled // because we have no prior state, and, we'll need to refer to it with // regards to the final vertex firstVertex = lastVertex = v; firstPointSide = lastPointSide = plane.ClassifyPoint(v.Position); isCoPlanar = isCoPlanar && firstPointSide == ClipSide.CoPlanar; // Emit the point to both sides if coplanar, otherwise emit // it to the particular side it is if (firstPointSide == ClipSide.CoPlanar) { front_list.Add(v); back_list.Add(v); } else if (firstPointSide == ClipSide.Back) { back_list.Add(v); } else { front_list.Add(v); } continue; } // Classify the current point to the plane ClipSide currPointSide = plane.ClassifyPoint(v.Position); isCoPlanar = isCoPlanar && currPointSide == ClipSide.CoPlanar; // One thing to be mindful of is to generate the intersection points in a consistent manner by // calling the intersection function with the verts in the same order. if (lastPointSide == ClipSide.CoPlanar) { // The last point was on the split plane if (currPointSide == ClipSide.CoPlanar) { // Still on the split plane front_list.Add(v); back_list.Add(v); } else if (currPointSide == ClipSide.Back) { // Going from the split plane to the backside back_list.Add(v); } else { // Going from the split plane to the frontside front_list.Add(v); } } else if (lastPointSide == ClipSide.Front) { // Last point was in the front if (currPointSide == ClipSide.CoPlanar) { // Front -> coplanar front_list.Add(v); back_list.Add(v); } else if (currPointSide == ClipSide.Back) { // Front -> Back var intersected_pt = plane.IntersectEdge(lastVertex, v); front_list.Add(intersected_pt); back_list.Add(intersected_pt); back_list.Add(v); } else { // Front -> Front front_list.Add(v); } } else { // Last point was in the back if (currPointSide == ClipSide.CoPlanar) { // Back -> coplanar front_list.Add(v); back_list.Add(v); } else if (currPointSide == ClipSide.Back) { // Back -> Back back_list.Add(v); } else { // Back -> Front var intersected_pt = plane.IntersectEdge(v, lastVertex); back_list.Add(intersected_pt); front_list.Add(intersected_pt); front_list.Add(v); } } lastPointSide = currPointSide; lastVertex = v; } // To finish up we need to handle the edge from the last vertex back to the first vertex // This is only necessary if the last point and the first point were on different sides of the plane if (firstPointSide != lastPointSide) { // We ended up in a different area then the first point if (firstPointSide == ClipSide.CoPlanar || lastPointSide == ClipSide.CoPlanar) { // If we started or ended on a co-planar point, then we don't have to add more } else if (firstPointSide == ClipSide.Back) { // We started in the back, we ended in the front var intersection_pt = plane.IntersectEdge(lastVertex, firstVertex); front_list.Add(intersection_pt); back_list.Add(intersection_pt); } else if (firstPointSide == ClipSide.Front) { // We started in the front, we ended in the back var intersection_pt = plane.IntersectEdge(firstVertex, lastVertex); front_list.Add(intersection_pt); back_list.Add(intersection_pt); } } var res = new SplitPolygon(); if (isCoPlanar) { front_list = CleanDegenerates(front_list).ToList(); res.coplaner = front_list.Count > 2 ? front_list.ToArray() : null; res.front = null; res.back = null; } else { // If there we some verts co-planar, but not all, and the remaining // verts are on one side, then the other side will have some verts, but not a full triangle front_list = CleanDegenerates(front_list).ToList(); back_list = CleanDegenerates(back_list).ToList(); res.front = front_list.Count > 2 ? front_list.ToArray() : null; res.back = back_list.Count > 2 ? back_list.ToArray() : null; res.coplaner = null; } return(res); }
/// <summary> /// Generic implementation of Sutherland–Hodgman clipping algorithm /// </summary> /// <param name="vertList"></param> /// <returns></returns> public static IEnumerable <IVertex> ClipToPlaneRaw(IEnumerable <IVertex> vertList, ClipPlane plane) { if (plane == null) { throw new ArgumentNullException("plane"); } if (vertList == null) { yield break; } ClipSide firstPointSide = ClipSide.CoPlanar; ClipSide lastPointSide = ClipSide.CoPlanar; IVertex lastVertex = null; IVertex firstVertex = null; foreach (var v in vertList) { if (lastVertex == null) { // This is the first vertex in the list, so it is specially handled // because we have no prior state, and, we'll need to refer to it with // regards to the final vertex firstVertex = lastVertex = v; firstPointSide = lastPointSide = plane.ClassifyPoint(v.Position); // If this point is in front of the plane, emit it if (firstPointSide != ClipSide.Back) { yield return(v); } continue; } // Classify the current point to the plane ClipSide currPointSide = plane.ClassifyPoint(v.Position); // We have 4 possible situations: // 1) Last point in front of the plane, current point in front of the plane: // In this situation emit the current point // 2) Last point in front of the plane, current point is behind the plane: // In this situation emit the intersection point of the plane and the edge (last, curr) // 3) Last point in back of the plane, current point is behind the plane: // In this situation do not emit anything // 4) Last point in back of the plane, current point in front of the plane: // In this situation emit the intersection point of the plane and the edge (curr, last), and // then emit the current point // // One thing to be mindful of is to generate the intersection points in a consistent manner by // calling the intersection function with the verts in the same order. if (lastPointSide != ClipSide.Back) { if (currPointSide != ClipSide.Back) { // Situation 1 yield return(v); } else { // Situation 2 yield return(plane.IntersectEdge(lastVertex, v)); } } else { if (currPointSide != ClipSide.Back) { // Situation 4 yield return(plane.IntersectEdge(v, lastVertex)); yield return(v); } } lastPointSide = currPointSide; lastVertex = v; } // To finish up we need to handle the edge from the last vertex back to the first vertex // This is only necessary if the last point and the first point were on different sides of the plane bool firstInFront = firstPointSide != ClipSide.Back; bool lastInFront = lastPointSide != ClipSide.Back; if (firstInFront != lastInFront) { if (firstInFront) { yield return(plane.IntersectEdge(firstVertex, lastVertex)); } else { yield return(plane.IntersectEdge(lastVertex, firstVertex)); } } }