From 4b35103bb233d744fbb2259a7b48ad9dab9540d4 Mon Sep 17 00:00:00 2001 From: oceanful <35951082+oceanful@users.noreply.github.com> Date: Fri, 10 May 2019 12:34:00 -0700 Subject: [PATCH] Resweep an endpoint if it divides its predecessor in the sweep line. (#3) The sweep line algorithm computes the [inside] state of an endpoint and the [inout] state of its corresponding segment based on the previous endpoint in the sweep line. When an endpoint divides its predecessor, the context for the endpoint thus changes, and the endpoint must be re-swept to compute the (potentially changed) [inside] and [inout] states of that endpoint. --- bugs_test.go | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++ clipper.go | 75 ++++++++++++++++++++------------------ 2 files changed, 141 insertions(+), 34 deletions(-) diff --git a/bugs_test.go b/bugs_test.go index 011cd7e..7dc688b 100644 --- a/bugs_test.go +++ b/bugs_test.go @@ -137,6 +137,106 @@ func TestBug3(t *T) { }.verify(t) } +func TestResweepingIntersectingEndpoints(t *T) { + testCases{ + { + op: polyclip.INTERSECTION, + subject: polyclip.Polygon{{ + {160.09449516387843, 200.37992407997774}, + {90.09449516387845, 200.37992407997774}, + {55.094495163878435, 139.75814581506702}, + {90.0944951638784, 79.13636755015634}}}, + clipping: polyclip.Polygon{{ + {82.84661138052363, 131.51881422166852}, + {66.59206311550543, 159.6725176707606}, + {90.09449516387845, 200.37992407997774}, + {160.09449516387843, 200.37992407997774}, + }}, + result: polyclip.Polygon{{ + {82.84661138052363, 131.51881422166852}, + {66.59206311550543, 159.6725176707606}, + {90.09449516387845, 200.37992407997774}, + {160.09449516387843, 200.37992407997774}, + }}, + }, + { + op: polyclip.DIFFERENCE, + subject: polyclip.Polygon{{ + {160.09449516387843, 200.37992407997774}, + {90.09449516387845, 200.37992407997774}, + {55.094495163878435, 139.75814581506702}, + {90.0944951638784, 79.13636755015634}}}, + clipping: polyclip.Polygon{{ + {82.84661138052363, 131.51881422166852}, + {66.59206311550543, 159.6725176707606}, + {90.09449516387845, 200.37992407997774}, + {160.09449516387843, 200.37992407997774}, + }}, + result: polyclip.Polygon{{ + {160.09449516387843, 200.37992407997774}, + {82.84661138052363, 131.51881422166852}, + {66.59206311550543, 159.6725176707606}, + {55.094495163878435, 139.75814581506702}, + {90.0944951638784, 79.13636755015634}, + }}, + }, + { + op: polyclip.UNION, + subject: polyclip.Polygon{{ + {160.09449516387843, 200.37992407997774}, + {90.09449516387845, 200.37992407997774}, + {55.094495163878435, 139.75814581506702}, + {90.0944951638784, 79.13636755015634}}}, + clipping: polyclip.Polygon{{ + {82.84661138052363, 131.51881422166852}, + {66.59206311550543, 159.6725176707606}, + {90.09449516387845, 200.37992407997774}, + {160.09449516387843, 200.37992407997774}, + }}, + result: polyclip.Polygon{{ + {160.09449516387843, 200.37992407997774}, + {90.09449516387845, 200.37992407997774}, + {66.59206311550543, 159.6725176707606}, + {55.094495163878435, 139.75814581506702}, + {90.0944951638784, 79.13636755015634}}}, + }, + { + op: polyclip.UNION, + subject: polyclip.Polygon{{ + {70.78432620601497, -7.668842337087888}, + {42.500054958553065, -19.38457108962598}, + {22.504998288170377, -11.102347436334847}, + {14.215783711091163, -7.668842337087877}, + {2.500054958553072, 20.615428910374025}, + {4.163269713667806, 24.63078452931106}, + {-16.386530327112805, 33.142790410257575}, + {-28.102259079650896, 61.42706165771948}, + }}, + clipping: polyclip.Polygon{{ + {22.504998288170377, -11.102347436334847}, + {14.215783711091163, -7.668842337087877}, + {2.500054958553072, 20.615428910374025}, + {4.163269713667806, 24.63078452931106}, + {-16.386530327112805, 33.142790410257575}, + {-18.453791204657392, 38.133599657789034}, + {-23.270336557414375, 26.505430543378026}, + {16.72966344258562, -13.494569456621978}, + {45.01393469004752, -1.778840704083887}, + {22.504998288170377, -11.102347436334847}, + }}, + result: polyclip.Polygon{{ + {-28.102259079650896, 61.42706165771948}, + {-18.453791204657392, 38.133599657789034}, + {-23.270336557414375, 26.505430543378026}, + {16.72966344258562, -13.494569456621978}, + {22.504998288170377, -11.102347436334847}, + {42.500054958553065, -19.38457108962598}, + {70.78432620601497, -7.668842337087888}, + }}, + }, + }.verify(t) +} + func TestSelfIntersectionAvoidance(t *T) { testCases{ { diff --git a/clipper.go b/clipper.go index 00bab2b..ca0d7b9 100644 --- a/clipper.go +++ b/clipper.go @@ -202,8 +202,17 @@ func (c *clipper) compute(operation Op) Polygon { } // Process a possible intersection between "e" and its previous neighbor in S if prev != nil { - c.possibleIntersection(prev, e) - //c.possibleIntersection(&e, prev) + divided := c.possibleIntersection(prev, e) + // If [prev] was divided, the context (sweep line S) for [e] may have changed, + // altering what e.inout and e.inside should be. [e] must thus be reenqueued to + // recompute e.inout and e.inside. + // + // (This should not be done if [e] was also divided; in that case + // the divided segments are already enqueued). + if len(divided) == 1 && divided[0] == prev { + S.remove(e) + c.eventQueue.enqueue(e) + } } } else { // the line segment must be removed from S otherPos := -1 @@ -380,39 +389,35 @@ func findIntersection2(u0, u1, v0, v1 float64, w *[]float64) int { return 2 } -func (c *clipper) possibleIntersection(e1, e2 *endpoint) { - // [MC]: commented fragment removed - +// Returns the endpoints that were divided. +func (c *clipper) possibleIntersection(e1, e2 *endpoint) []*endpoint { numIntersections, ip1, _ := findIntersection(e1.segment(), e2.segment(), true) if numIntersections == 0 { - return + return nil } if numIntersections == 1 { - if e1.p.Equals(e2.p) || e1.other.p.Equals(e2.other.p) { - return // the line segments intersect at an endpoint of both line segments - } else if !isValidSingleIntersection(e1, e2, ip1) { + switch { + case e1.p.Equals(e2.p) || e1.other.p.Equals(e2.other.p): + return nil // the line segments intersect at an endpoint of both line segments + case !isValidSingleIntersection(e1, e2, ip1): _DBG(func() { fmt.Printf("Dropping invalid intersection %v between %v and %v\n", ip1, e1, e2) }) - return + return nil + case e1.p.Equals(ip1) || e1.other.p.Equals(ip1): // e1 divides e2 + return []*endpoint{c.divideSegment(e2, ip1)} + case e2.p.Equals(ip1) || e2.other.p.Equals(ip1): // e2 divides e1 + return []*endpoint{c.divideSegment(e1, ip1)} + default: // e1 and e2 divide each other + return []*endpoint{ + c.divideSegment(e1, ip1), + c.divideSegment(e2, ip1), + } } } - //if numIntersections == 2 && e1.p.Equals(e2.p) { if numIntersections == 2 && e1.polygonType == e2.polygonType { - return // the line segments overlap, but they belong to the same polygon - } - - if numIntersections == 1 { - if !e1.p.Equals(ip1) && !e1.other.p.Equals(ip1) { - // if ip1 is not an endpoint of the line segment associated to e1 then divide "e1" - c.divideSegment(e1, ip1) - } - if !e2.p.Equals(ip1) && !e2.other.p.Equals(ip1) { - // if ip1 is not an endpoint of the line segment associated to e2 then divide "e2" - c.divideSegment(e2, ip1) - } - return + return nil // the line segments overlap, but they belong to the same polygon } // The line segments overlap @@ -442,7 +447,7 @@ func (c *clipper) possibleIntersection(e1, e2 *endpoint) { } else { e2.edgeType, e2.other.edgeType = _EDGE_DIFFERENT_TRANSITION, _EDGE_DIFFERENT_TRANSITION } - return + return nil } if len(sortedEvents) == 3 { // the line segments share an endpoint @@ -460,11 +465,10 @@ func (c *clipper) possibleIntersection(e1, e2 *endpoint) { sortedEvents[idx].other.edgeType = _EDGE_DIFFERENT_TRANSITION } if sortedEvents[0] != nil { - c.divideSegment(sortedEvents[0], sortedEvents[1].p) + return []*endpoint{c.divideSegment(sortedEvents[0], sortedEvents[1].p)} } else { - c.divideSegment(sortedEvents[2].other, sortedEvents[1].p) + return []*endpoint{c.divideSegment(sortedEvents[2].other, sortedEvents[1].p)} } - return } if sortedEvents[0] != sortedEvents[3].other { @@ -475,9 +479,10 @@ func (c *clipper) possibleIntersection(e1, e2 *endpoint) { } else { sortedEvents[2].edgeType = _EDGE_DIFFERENT_TRANSITION } - c.divideSegment(sortedEvents[0], sortedEvents[1].p) - c.divideSegment(sortedEvents[1], sortedEvents[2].p) - return + return []*endpoint{ + c.divideSegment(sortedEvents[0], sortedEvents[1].p), + c.divideSegment(sortedEvents[1], sortedEvents[2].p), + } } // one line segment includes the other one @@ -488,10 +493,11 @@ func (c *clipper) possibleIntersection(e1, e2 *endpoint) { } else { sortedEvents[3].other.edgeType = _EDGE_DIFFERENT_TRANSITION } - c.divideSegment(sortedEvents[3].other, sortedEvents[2].p) + return []*endpoint{c.divideSegment(sortedEvents[3].other, sortedEvents[2].p)} } -func (c *clipper) divideSegment(e *endpoint, p Point) { +// Returns the original endpoint if successfully divided, otherwise nil. +func (c *clipper) divideSegment(e *endpoint, p Point) *endpoint { // "Right event" of the "left line segment" resulting from dividing e (the line segment associated to e) r := &endpoint{p: p, left: false, polygonType: e.polygonType, other: e, edgeType: e.edgeType} // "Left event" of the "right line segment" resulting from dividing e (the line segment associated to e) @@ -500,7 +506,7 @@ func (c *clipper) divideSegment(e *endpoint, p Point) { // Discard segments of the wrong-direction (including zero-length). See isValidSingleIntersection() for reasoning. if !l.isValidDirection() || !r.isValidDirection() { _DBG(func() { fmt.Printf("Dropping invalid division of %v at %v:\n - %v\n - %v\n", *e, p, l, r) }) - return + return nil } if endpointLess(l, e.other) { // avoid a rounding error. The left event would be processed after the right event @@ -514,6 +520,7 @@ func (c *clipper) divideSegment(e *endpoint, p Point) { c.eventQueue.enqueue(l) c.eventQueue.enqueue(r) + return e } func addProcessedSegment(q *eventQueue, segment segment, polyType polygonType) {