Skip to content

Commit

Permalink
Resweep an endpoint if it divides its predecessor in the sweep line. (#3
Browse files Browse the repository at this point in the history
)

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.
  • Loading branch information
oceanful authored May 10, 2019
1 parent 81284b8 commit 4b35103
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 34 deletions.
100 changes: 100 additions & 0 deletions bugs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
{
Expand Down
75 changes: 41 additions & 34 deletions clipper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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) {
Expand Down

0 comments on commit 4b35103

Please sign in to comment.