using UnityEngine; using System; using System.Collections; using System.Collections.Generic; namespace TGS.Geom { public enum PolygonOp { UNION, INTERSECTION, DIFFERENCE, XOR } public enum PolygonType { SUBJECT, CLIPPING } public enum EdgeType { NORMAL, NON_CONTRIBUTING, SAME_TRANSITION, DIFFERENT_TRANSITION, } public class IntersectResult { public int max; public Point point1; public Point point2; public IntersectResult(int max, Point point1, Point point2) { this.max = max; this.point1 = point1; this.point2 = point2; } } public class PolygonClipper { public Polygon subject, clipping; EventQueue eventQueue; ListsortedEvents; public PolygonClipper(Polygon subject, Polygon clipping) { this.subject = subject; this.clipping = clipping; } void ProcessSegment(Segment segment, PolygonType polygonType) { if (Point.EqualsBoth(segment.start, segment.end)) return; SweepEvent e1 = new SweepEvent(segment.start, true, polygonType); SweepEvent e2 = new SweepEvent(segment.end, true, polygonType, e1); e1.otherSE = e2; if (e1.p.x < e2.p.x - Point.PRECISION ) { e2.isLeft = false; } else if (e1.p.x > e2.p.x + Point.PRECISION) { e1.isLeft = false; } else if (e1.p.y < e2.p.y - Point.PRECISION) { // the segment is vertical. The bottom endpoint is processed before the top endpoint e2.isLeft = false; } else { e1.isLeft = false; } // Pushing it so the que is sorted from left to right, with object on the left // having the highest priority. eventQueue.Enqueue(e1); eventQueue.Enqueue(e2); } public void Compute(PolygonOp operation) { subject = ComputeInternal(operation); } Polygon ComputeInternal (PolygonOp operation) { Polygon result = null; sortedEvents = new List(); // Init event queue eventQueue = new EventQueue(); // Test 1 for trivial result case if (subject.contours.Count * clipping.contours.Count == 0) { if (operation == PolygonOp.DIFFERENCE) result = subject; else if (operation == PolygonOp.UNION || operation == PolygonOp.XOR) result = (subject.contours.Count == 0) ? clipping : subject; return result; } // Test 2 for trivial result case Rectangle subjectBB = subject.boundingBox; Rectangle clippingBB = clipping.boundingBox; if (!subjectBB.Intersects(clippingBB)) { if (operation == PolygonOp.DIFFERENCE) result = subject; if (operation == PolygonOp.UNION || operation == PolygonOp.XOR) { result = subject; foreach(Contour c in clipping.contours) result.AddContour(c); } return result; } // Add each segment to the eventQueue, sorted from left to right. for(int k=0;k10000) { Debug.Log("PANIC!"); break; } prev = null; next = null; SweepEvent e = eventQueue.Dequeue(); if ((operation == PolygonOp.INTERSECTION && e.p.x > MINMAX_X) || (operation == PolygonOp.DIFFERENCE && e.p.x > subjectBB.right + Point.PRECISION)) return connector.ToPolygonFromLargestLineStrip(); if (operation == PolygonOp.UNION && e.p.x > MINMAX_X) { // add all the non-processed line segments to the result if (!e.isLeft) connector.Add(e.segment); while (!eventQueue.isEmpty) { e = eventQueue.Dequeue(); if (!e.isLeft) connector.Add(e.segment); } return connector.ToPolygonFromLargestLineStrip(); } if (e.isLeft) { // the line segment must be inserted into S int pos = S.Insert(e); prev = (pos > 0) ? S.eventSet[pos - 1] : null; next = (pos < S.eventSet.Count - 1) ? S.eventSet[pos + 1] : null; if (prev == null) { e.inside = e.inOut = false; } else if (prev.edgeType != EdgeType.NORMAL) { if (pos - 2 < 0) { // e overlaps with prev // Not sure how to handle the case when pos - 2 < 0, but judging // from the C++ implementation this looks like how it should be handled. e.inside = e.inOut = false; if (prev.polygonType != e.polygonType) e.inside = true; else e.inOut = true; } else { SweepEvent prevTwo = S.eventSet[pos - 2]; if (prev.polygonType == e.polygonType) { e.inOut = !prev.inOut; e.inside = !prevTwo.inOut; } else { e.inOut = !prevTwo.inOut; e.inside = !prev.inOut; } } } else if (e.polygonType == prev.polygonType) { e.inside = prev.inside; e.inOut = !prev.inOut; } else { e.inside = !prev.inOut; e.inOut = prev.inside; } // Process a possible intersection between "e" and its next neighbor in S if (next != null) PossibleIntersection(e, next); // Process a possible intersection between "e" and its previous neighbor in S if (prev != null) PossibleIntersection(prev, e); } else { // the line segment must be removed from S // Get the next and previous line segments to "e" in S int otherPos = -1; for (int evt=0;evt 0) ? S.eventSet[otherPos - 1] : null; next = (otherPos < S.eventSet.Count - 1) ? S.eventSet[otherPos + 1] : null; } switch (e.edgeType) { case EdgeType.NORMAL: switch (operation) { case PolygonOp.INTERSECTION: if (e.otherSE.inside) connector.Add(e.segment); break; case PolygonOp.UNION: if (!e.otherSE.inside) connector.Add(e.segment); break; case PolygonOp.DIFFERENCE: if ((e.polygonType == PolygonType.SUBJECT && !e.otherSE.inside) || (e.polygonType == PolygonType.CLIPPING && e.otherSE.inside)) connector.Add(e.segment); break; case PolygonOp.XOR: connector.Add (e.segment); break; } break; case EdgeType.SAME_TRANSITION: if (operation == PolygonOp.INTERSECTION || operation == PolygonOp.UNION) connector.Add(e.segment); break; case EdgeType.DIFFERENT_TRANSITION: if (operation == PolygonOp.DIFFERENCE) connector.Add(e.segment); break; } if (otherPos != -1) S.Remove(S.eventSet[otherPos]); if (next != null && prev != null) PossibleIntersection(prev, next); } } return connector.ToPolygonFromLargestLineStrip(); } IntersectResult FindIntersection(Segment seg0, Segment seg1) { Point pi0 = Point.zero; Point pi1 = Point.zero; Point p0 = seg0.start; double d0x = seg0.end.x - p0.x; double d0y = seg0.end.y - p0.y; Point p1 = seg1.start; double d1x = seg1.end.x - p1.x; double d1y = seg1.end.y - p1.y; double Ex = p1.x - p0.x; double Ey = p1.y - p0.y; double kross = d0x * d1y - d0y * d1x; if (kross > Point.PRECISION || kross < -Point.PRECISION) { //sqrEpsilon) { // * sqrLen0 * sqrLen1) { // lines of the segments are not parallel double s = (Ex * d1y - Ey * d1x) / kross; if (s < 0 || s > 1) { return new IntersectResult (max: 0, point1: pi0, point2: pi1); } double t = (Ex * d0y - Ey * d0x) / kross; if (t < 0 || t > 1) { return new IntersectResult (max: 0, point1: pi0, point2: pi1); } // intersection of lines is a point an each segment pi0.x = p0.x + s * d0x; pi0.y = p0.y + s * d0y; return new IntersectResult ( max: 1, point1: pi0, point2: pi1 ); } // lines of the segments are parallel kross = Ex * d0y - Ey * d0x; if (kross > Point.PRECISION || kross < -Point.PRECISION) { // sqrEpsilon ) { //* sqrLen0 * sqrLenE) { // lines of the segment are different return new IntersectResult ( max: 0, point1: pi0, point2: pi1 ); } // Lines of the segments are the same. Need to test for overlap of segments. double sqrLen0 = Math.Sqrt (d0x*d0x+d0y*d0y); // d0.magnitude; double s0 = (d0x * Ex + d0y * Ey) / sqrLen0; // so = Dot (D0, E) * sqrLen0 double s1 = s0 + (d0x * d1x + d0y * d1y) / sqrLen0; // s1 = s0 + Dot (D0, D1) * sqrLen0 double smin = Math.Min(s0, s1); double smax = Math.Max(s0, s1); double[] w = new double[2]; int imax = FindIntersection2(0, 1, smin, smax, w); if (imax > 0) { pi0.x = p0.x + w[0] * d0x; pi0.y = p0.y + w[0] * d0y; if (imax > 1) { pi1.x = p0.x + w[1] * d0x; pi1.y = p0.y + w[1] * d0y; } } return new IntersectResult (max: imax, point1: pi0, point2: pi1); } int FindIntersection2(double u0, double u1, double v0, double v1, double[] w) { if (u1 < v0 || u0 > v1) return 0; if (u1 > v0) { if (u0 < v1) { w[0] = (u0 < v0) ? v0 : u0; w[1] = (u1 > v1) ? v1 : u1; return 2; } else { // u0 == v1 w[0] = u0; return 1; } } // u1 == v0 w[0] = u1; return 1; } void PossibleIntersection(SweepEvent e1, SweepEvent e2) { IntersectResult intData = FindIntersection(e1.segment, e2.segment); int numIntersections = intData.max; Point ip1 = intData.point1; if (numIntersections == 0) return; if (numIntersections == 1 && (Point.EqualsBoth(e1.p, e2.p) || Point.EqualsBoth(e1.otherSE.p, e2.otherSE.p))) return; // the line segments intersect at an endpoint of both line segments if (numIntersections == 2 && e1.polygonType==e2.polygonType) return; // the line segments overlap, but they belong to the same polygon // The line segments associated to e1 and e2 intersect if (numIntersections == 1) { if (!Point.EqualsBoth(e1.p,ip1) && !Point.EqualsBoth(e1.otherSE.p,ip1)) DivideSegment (e1, ip1); // if ip1 is not an endpoint of the line segment associated to e1 then divide "e1" if (!Point.EqualsBoth(e2.p, ip1) && !Point.EqualsBoth(e2.otherSE.p,ip1)) DivideSegment (e2, ip1); // if ip1 is not an endpoint of the line segment associated to e2 then divide "e2" return; } // The line segments overlap sortedEvents.Clear(); if (Point.EqualsBoth(e1.p,e2.p)) { sortedEvents.Add(null); } else if (Sec(e1, e2)) { sortedEvents.Add(e2); sortedEvents.Add(e1); } else { sortedEvents.Add(e1); sortedEvents.Add(e2); } if (Point.EqualsBoth(e1.otherSE.p,e2.otherSE.p)) { sortedEvents.Add(null); } else if (Sec(e1.otherSE, e2.otherSE)) { sortedEvents.Add(e2.otherSE); sortedEvents.Add(e1.otherSE); } else { sortedEvents.Add(e1.otherSE); sortedEvents.Add(e2.otherSE); } if (sortedEvents.Count == 2) { // are both line segments equal? e1.edgeType = e1.otherSE.edgeType = EdgeType.NON_CONTRIBUTING; e2.edgeType = e2.otherSE.edgeType = ((e1.inOut == e2.inOut) ? EdgeType.SAME_TRANSITION : EdgeType.DIFFERENT_TRANSITION); return; } if (sortedEvents.Count == 3) { // the line segments share an endpoint sortedEvents[1].edgeType = sortedEvents[1].otherSE.edgeType = EdgeType.NON_CONTRIBUTING; if (sortedEvents[0] != null) // is the right endpoint the shared point? sortedEvents[0].otherSE.edgeType = (e1.inOut == e2.inOut) ? EdgeType.SAME_TRANSITION : EdgeType.DIFFERENT_TRANSITION; else // the shared point is the left endpoint sortedEvents[2].otherSE.edgeType = (e1.inOut == e2.inOut) ? EdgeType.SAME_TRANSITION : EdgeType.DIFFERENT_TRANSITION; DivideSegment (sortedEvents[0] != null ? sortedEvents[0] : sortedEvents[2].otherSE, sortedEvents[1].p); return; } if (!sortedEvents[0].Equals(sortedEvents[3].otherSE)) { // no segment includes totally the otherSE one sortedEvents[1].edgeType = EdgeType.NON_CONTRIBUTING; sortedEvents[2].edgeType = (e1.inOut == e2.inOut) ? EdgeType.SAME_TRANSITION : EdgeType.DIFFERENT_TRANSITION; DivideSegment (sortedEvents[0], sortedEvents[1].p); DivideSegment (sortedEvents[1], sortedEvents[2].p); return; } // one line segment includes the other one sortedEvents[1].edgeType = sortedEvents[1].otherSE.edgeType = EdgeType.NON_CONTRIBUTING; DivideSegment (sortedEvents[0], sortedEvents[1].p); sortedEvents[3].otherSE.edgeType = (e1.inOut == e2.inOut) ? EdgeType.SAME_TRANSITION : EdgeType.DIFFERENT_TRANSITION; DivideSegment (sortedEvents[3].otherSE, sortedEvents[2].p); } bool Sec(SweepEvent e1, SweepEvent e2) { // Different x coordinate if ( e1.p.x - e2.p.x > Point.PRECISION || e1.p.x - e2.p.x < -Point.PRECISION ) { return e1.p.x > e2.p.x; } // Same x coordinate. The event with lower y coordinate is processed first if ( e1.p.y - e2.p.y > Point.PRECISION || e1.p.y - e2.p.y < -Point.PRECISION) { return e1.p.y > e2.p.y; } // Same point, but one is a left endpoint and the other a right endpoint. The right endpoint is processed first if (e1.isLeft != e2.isLeft) { return e1.isLeft; } // Same point, both events are left endpoints or both are right endpoints. The event associate to the bottom segment is processed first return e1.isAbove(e2.otherSE.p); } void DivideSegment(SweepEvent e, Point p) { // "Right event" of the "left line segment" resulting from dividing e (the line segment associated to e) SweepEvent r = new SweepEvent(p, false, e.polygonType, e, e.edgeType); // "Left event" of the "right line segment" resulting from dividing e (the line segment associated to e) SweepEvent l = new SweepEvent(p, true, e.polygonType, e.otherSE, e.otherSE.edgeType); if (Sec(l, e.otherSE)) { // avoid a rounding error. The left event would be processed after the right event e.otherSE.isLeft = true; e.isLeft = false; } e.otherSE.otherSE = l; e.otherSE = r; eventQueue.Enqueue(l); eventQueue.Enqueue(r); } } }