Skip to content

Commit

Permalink
Path intersections: fix further bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
tdewolff committed Jan 5, 2025
1 parent 9b1ab02 commit f6166fb
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 112 deletions.
183 changes: 73 additions & 110 deletions path_intersection.go
Original file line number Diff line number Diff line change
Expand Up @@ -1077,54 +1077,34 @@ func addIntersections(queue *SweepEvents, event *SweepPoint, prev, next *SweepNo
// Note: handle overlapping segments immediately by checking up and down status for segments
// that compare equally with weak ordering (ie. overlapping).

// handle a
retry := false
for i := len(zs) - 1; 0 <= i; i-- {
z := zs[i]
if z == a.Point || z == a.other.Point {
// ignore tangent intersections at the endpoints
continue
} else if !event.left {
// intersection may be to the left (or below) the current event due to floating-point
// precision which would interfere with the sequence in queue, this is a problem when
// handling right-endpoints
if !event.left {
// intersection may be to the left (or below) the current event due to floating-point
// precision which would interfere with the sequence in queue, this is a problem when
// handling right-endpoints
for i := range zs {
z := &zs[i]
if z.X < event.X {
z.X = event.X
if z == a.Point || z == a.other.Point {
// ignore tangent intersections at the endpoints
continue
}
} else if z.X == event.X && z.Y < event.Y {
z.Y = event.Y
if z == a.Point || z == a.other.Point {
// ignore tangent intersections at the endpoints
continue
}
}

aMaxY := math.Max(a.Y, a.other.Y)
bMaxY := math.Max(b.Y, b.other.Y)
if a.other.X < z.X || b.other.X < z.X || aMaxY < z.Y || bMaxY < z.Y {
panic("moved outside of segments")
}
}
}

// split all overlapping segments at z as well
//if prev != nil {
// for n := prev.Prev(); n != nil; n = n.Prev() {
// if n.Point == a.Point && a.compareTangentsV(n.SweepPoint, true) == 0 {
// fmt.Println("OVERLAP", a, n, "at", z)
// r, l := n.SplitAt(z)
// if l.X == l.other.X {
// l.vertical, l.other.vertical = true, true
// if l.other.Y < l.Y {
// fmt.Println(" reverse...")
// }
// } else if r.X == r.other.X {
// r.vertical, r.other.vertical = true, true
// if r.Y < r.other.Y {
// fmt.Println(" reverse...")
// }
// }
// queue.Push(r)
// queue.Push(l)
// }
// }
//}
// handle a
aChanged := false
for i := len(zs) - 1; 0 <= i; i-- {
z := zs[i]
if z == a.Point || z == a.other.Point {
// ignore tangent intersections at the endpoints
continue
}

// split segment at intersection
aRight, aLeft := a.SplitAt(z)
Expand All @@ -1142,8 +1122,23 @@ func addIntersections(queue *SweepEvents, event *SweepPoint, prev, next *SweepNo
if aRight.Y < aRight.other.Y {
// reverse first segment
// this can never happen in theory, but may happen due to floating-point
// precision problems, perhaps we should handle this anyways?
panic("impossible: first segment of A became vertical and needs reversal")
//panic("impossible: first segment of A became vertical and needs reversal")

// reverse first segment
if aRight.other.node != nil {
panic("impossible: first segment of A became vertical and needs reversal, but A was already in the sweep status")
}
aRight.Reverse()

// Note that we swap the content of the currently processed left-endpoint of b with
// the new left-endpoint vertically below. The queue may not be strictly ordered
// with other vertical segments at the new left-endpoint, but this isn't a problem
// since we sort the events in each square after the Bentley-Ottmann phase.

// update references from handled and queue by swapping their contents
aFirst := aRight.other
*aRight, *aFirst = *aFirst, *aRight
aFirst.other, aRight.other = aRight, aFirst
}
}

Expand All @@ -1153,60 +1148,18 @@ func addIntersections(queue *SweepEvents, event *SweepPoint, prev, next *SweepNo
// add to queue
queue.Push(aRight)
queue.Push(aLeft)
if prev == nil {
retry = true
}
aChanged = true
}

// handle b
bChanged := false
for i := len(zs) - 1; 0 <= i; i-- {
z := zs[i]
if z == b.Point || z == b.other.Point {
// ignore tangent intersections at the endpoints
continue
} else if !event.left {
// intersection may be to the left (or below) the current event due to floating-point
// precision which would interfere with the sequence in queue, this is a problem when
// handling right-endpoints
if z.X < event.X {
z.X = event.X
if z == b.Point || z == b.other.Point {
// ignore tangent intersections at the endpoints
continue
}
} else if z.X == event.X && z.Y < event.Y {
z.Y = event.Y
if z == b.Point || z == b.other.Point {
// ignore tangent intersections at the endpoints
continue
}
}
}

// split all overlapping segments at z as well
//if next != nil {
// for n := next.Next(); n != nil; n = n.Next() {
// fmt.Println("next", n)
// if n.Point == b.Point && b.compareTangentsV(n.SweepPoint, true) == 0 {
// fmt.Println("OVERLAP", b, n, "at", z)
// r, l := n.SplitAt(z)
// if l.X == l.other.X {
// l.vertical, l.other.vertical = true, true
// if l.other.Y < l.Y {
// fmt.Println(" reverse...")
// }
// } else if r.X == r.other.X {
// r.vertical, r.other.vertical = true, true
// if r.Y < r.other.Y {
// fmt.Println(" reverse...")
// }
// }
// queue.Push(r)
// queue.Push(l)
// }
// }
//}

// split segment at intersection
bRight, bLeft := b.SplitAt(z)

Expand Down Expand Up @@ -1249,11 +1202,10 @@ func addIntersections(queue *SweepEvents, event *SweepPoint, prev, next *SweepNo
// add to queue
queue.Push(bRight)
queue.Push(bLeft)
if next == nil {
retry = true
}
bChanged = true
}
return retry

return aChanged || bChanged // && prev == nil || bChanged && next == nil
}

type toleranceSquare struct {
Expand Down Expand Up @@ -1856,13 +1808,13 @@ func bentleyOttmann(ps, qs Paths, op pathOp, fillRule FillRule) *Path {

n := event.other.node
if n == nil {
fmt.Println("WARNING: right-endpoint not part of status, probably buggy intersection code")
panic("right-endpoint not part of status, probably buggy intersection code")
// don't put back in boPointPool, rare event
continue
} else if n.SweepPoint == nil {
// this may happen if the left-endpoint is to the right of the right-endpoint for some reason
// usually due to a bug in the segment intersection code
fmt.Println("WARNING: other endpoint already removed, probably buggy intersection code")
// this may happen if the left-endpoint is to the right of the right-endpoint
// for some reason, usually due to a bug in the segment intersection code
panic("other endpoint already removed, probably buggy intersection code")
// don't put back in boPointPool, rare event
continue
}
Expand Down Expand Up @@ -1894,10 +1846,7 @@ func bentleyOttmann(ps, qs Paths, op pathOp, fillRule FillRule) *Path {
if next != nil {
retry = retry || addIntersections(queue, event, nil, next)
}
if queue.Top() != event || retry {
//if queue.Top() == event {
// fmt.Println("NOTE: retry necessary?")
//}
if queue.Top() != event { //|| retry {
// check if the queue order was changed, this happens if the current event
// is the left-endpoint of a segment that intersects with an existing segment
// that goes below, or when two segments become fully overlapping, which sets
Expand Down Expand Up @@ -1968,27 +1917,41 @@ func bentleyOttmann(ps, qs Paths, op pathOp, fillRule FillRule) *Path {

sort.Sort(eventSliceV(events))

// find intersections between neighbouring segments due to snapping
// TODO: ugly!
has := false
centre := &SweepPoint{
Point: Point{square.X, square.Y},
}
if prev := square.Lower.Prev(); prev != nil {
has = addIntersections(queue, centre, prev, square.Lower)
}
if next := square.Upper.Next(); next != nil {
has = has || addIntersections(queue, centre, square.Upper, next)
}

// find intersections between new neighbours in status after sorting
for i, event := range events {
for i, event := range events[:len(events)-1] {
if event != origEvents[i] {
n := event.node
var j int
for origEvents[j] != event {
j++
}

if prev := n.Prev(); prev != nil && i == 0 {
// lowest segment changed order, check with segment below
addIntersections(queue, event, prev, nil)
}
if next := n.Next(); next != nil && (i+1 == len(events) || (j == 0 || next.SweepPoint != origEvents[j-1]) && (j+1 == len(origEvents) || next.SweepPoint != origEvents[j+1])) {
// highest segment changed order, check with segment above
// or any other segment changed order and the segment above was not
// originally its neighbour
addIntersections(queue, event, nil, next)
if next := n.Next(); next != nil && (j == 0 || next.SweepPoint != origEvents[j-1]) && (j+1 == len(origEvents) || next.SweepPoint != origEvents[j+1]) {
// segment changed order and the segment above was not its neighbour
has = has || addIntersections(queue, centre, n, next)
}
}
}

if 0 < len(*queue) && snap(queue.Top().X, BentleyOttmannEpsilon) == x {
fmt.Println("WARNING: new intersections in this column!")
} else if has {
// sort overlapping segments again
sort.Sort(eventSliceV(events))
}
}

sort.Sort(eventSliceH(square.Events))
Expand Down Expand Up @@ -2077,7 +2040,7 @@ func bentleyOttmann(ps, qs Paths, op pathOp, fillRule FillRule) *Path {
}
}
if next == nil {
fmt.Println("WARNING: next node for result polygon is nil, probably buggy intersection code")
panic("next node for result polygon is nil, probably buggy intersection code")
break
} else if next == first {
break // contour is done
Expand Down
10 changes: 8 additions & 2 deletions path_intersection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,8 @@ func TestBentleyOttmannPrecision(t *testing.T) {
// segment crosses square in between
{"L3 0L3 7L1.1 7zM1 1L2 2L2 1zM1 3L2 4L2 3zM1 5L2 6L2 5z", opSettle, "", "M0 0L3 0L3 7L1 7L1 5L2 6L2 5L1 5L1 3L2 4L2 3L1 3zM1 1L2 2L2 1z"},
{"M1.2 0L3 0L3 10L0 10zM1 3L2 3L2 1zM1 6L2 6L2 4zM1 9L2 9L2 7z", opSettle, "", "M0 10L1 6L2 6L2 4L1 6L1 3L2 3L2 1L1 3L1 0L3 0L3 10zM1 9L2 9L2 7z"},

{"M0 0L5 2L5 4L0 4L4 -2L4 -1L0 2z", opSettle, "", "M0 0L2 1L0 2zM0 4L2 1L5 2L5 4z"},
}

origEpsilon := BentleyOttmannEpsilon
Expand Down Expand Up @@ -1339,10 +1341,14 @@ func TestPathSettle(t *testing.T) {
{NonZero, "M1.37118285 6.0003194L1.37137777 6.00027829L1.88426666 5.93378961zM1.37102221 6.00070481L1.37119999 6.00027829L1.9712163 5.9996806000000005z", "M1.37102221 6.00070481L1.37118285 6.0003194L1.37118286 6.0003194L1.37119999 6.00027829L1.37137915 6.00027811L1.88426666 5.93378961L1.37150223 6.00027799L1.9712163 5.9996806000000005z"},
{NonZero, "M-0.052000000000000005 8.052000000000001L-0.052000000000000005 7.999511116117297L-0.017996398619230213 7.998320624626715L-0.052000000000000005 8.002319400000001L-0.052000000000000005 2.695363795711625z", "M-0.052000000000000005 7.99951112L-0.0179964 7.99832062L-0.052000000000000005 8.0023194z"},
{NonZero, "M1.65138996 6.46265112L1.68750998 6.43749002L1.68750998 6.50000998zM1.75000998 6.43749002L1.68749002 6.50000998zM1.66805433 6.43750998L1.68750998 6.43750998zM1.75000998 6.43750998L1.68749002 6.43750998z", "M1.65138996 6.46265112L1.68748133 6.43750998L1.68750998 6.43749002L1.68750998 6.50000998L1.68750017 6.49999983z"},
{NonZero, "M0.15938157957878576 7.958567843990149L0.18949002 7.946636108655732L0.18949002 7.94527026L0.18750958428124603 7.947270164307436L0.18790070591516111 7.947266338117543L0.18824865124077875 7.947128052154844L0.18550998 7.94526946L0.18550998 7.947290036168203L0.19643651485990857 7.947181440654685z", "M0.15938158 7.95856784L0.18789968 7.9472663500000005L0.18790071 7.9472663400000005L0.18790088 7.94726627L0.19643651 7.9471814400000005zM0.18550998 7.9452694600000004L0.1878908 7.9468852000000005L0.18949002 7.94527026L0.18949002 7.94663611L0.18824865000000002 7.94712805L0.18790088 7.94726627L0.18789984 7.94726628L0.18789968 7.9472663500000005L0.18750959 7.94727016L0.18750958 7.94727016L0.18550998 7.94729004z"},
{NonZero, "M0.15938157957878576 7.958567843990149L0.18949002 7.946636108655732L0.18949002 7.94527026L0.18750958428124603 7.947270164307436L0.18790070591516111 7.947266338117543L0.18824865124077875 7.947128052154844L0.18550998 7.94526946L0.18550998 7.947290036168203L0.19643651485990857 7.947181440654685z", "M0.15938158 7.95856784L0.18789968 7.9472663500000005L0.18790071 7.9472663400000005L0.18790088 7.94726627L0.19643651 7.9471814400000005zM0.18550998 7.9452694600000004L0.1878908 7.9468852000000005L0.18949002 7.94527026L0.18949002 7.94663611L0.18824865000000002 7.94712805L0.18790088 7.94726627L0.18789984 7.94726628L0.18789968 7.9472663500000005L0.18752958 7.94726996L0.18750959 7.94727016L0.18750958 7.94727016L0.18550998 7.94729004z"},
{NonZero, "M0.74999002 7.31249002L0.81250998 7.31249002L0.81250998 7.37500998L0.7621670700000001 7.37500998L0.7501199000000001 7.36803658z M0.76224438 7.37511984L0.81250998 7.37499002L0.81250998 7.38459986L0.7910117 7.3870006z M0.81249002 7.31249002L0.87500998 7.31249002L0.87500998 7.37500998L0.84717878 7.37500998L0.84754558 7.37387787L0.81249002 7.37500998z M0.81249002 7.37499002L0.84430604 7.37499002L0.84037723 7.37915786L0.82951334 7.38501444z", "M0.74999002 7.31249002L0.87500998 7.31249002L0.87500998 7.37500998L0.84717878 7.37500998L0.84754558 7.37387787L0.81310808 7.37499002L0.84430604 7.37499002L0.84037723 7.37915786L0.82951334 7.38501444L0.81252215 7.37500894L0.81250998 7.37500934L0.81250998 7.38459986L0.7910117 7.3870006L0.76224438 7.37511984L0.80478158 7.37500998L0.7621670700000001 7.37500998L0.7501199000000001 7.36803658z"},
{NonZero, "M1.87588889 6.4393147L1.93750998 6.43749002L1.93750998 6.50000998L1.8857352 6.50000998L1.87852778 6.48022779z M1.88577778 6.50005704L1.93750998 6.49999002L1.93728625 6.50897677z", "M1.8758888900000001 6.4393147L1.93750998 6.43749002L1.93750998 6.50000998L1.9375094800000001 6.50000998L1.93728625 6.50897677L1.88577778 6.50005704L1.92210302 6.50000998L1.8857352 6.50000998L1.87852778 6.48022779z"},
{NonZero, "M4.052 3.998L1.7328357441061668 6.75201996L1.7323368935370298 6.750676097014102L1.7323549266182294 6.750722871352709L1.7323455886569568 6.750699526449527L1.732130807990627 6.75012088513192L1.7340358225805368 6.751424694798573L1.6918801731330348 6.7520573659498595z", "M1.6918801700000001 6.75205737L4.0520000000000005 3.998L1.73359251 6.75112129L1.7340358200000001 6.75142469L1.73332807 6.75143531L1.73283574 6.75201996L1.73262265 6.7514459z"},
{NonZero, "M1.999680599010034 -0.0003194009899516459L4.000319400989966 -0.0003194009899516459zM1.999680599010034 -2.0003194009899516L4.000319400989966 0.0003194009899516459zM3.999680599010048 -2.0003194009899516L3.999680599010048 0.0003194009899516459z", ""},
{NonZero, "M3.013236014856396 2.0031317319105995L2.999840299505024 1.999840299505024zM2.999840299505024 2.000159700494976L3.0059914294253502 2.000159700494976L2.999840299505024 1.993265873649733zM3.000159700494976 1.999840299505024L2.360148227002341 1.999840299505024zM3.000159700494976 2.000159700494976L2.995741685965072 1.9901318032831057z", "M2.9998403000000002 1.99326587L3.00599143 2.0001597L2.9998403000000002 2.0001597z"},
{NonZero, "M0.070313747660117 7.153464460137324L0.062498752339883 7.148436252339883zM0.062498752339883 7.148438747660117L0.070313747660117 7.148438747660117z", ""},
{NonZero, "M1.828127495320234 7.578127495320234L1.828127495320234 7.562497504679766z M1.828122504679766 7.562502495320234L1.843752495320234 7.562502495320234z M1.841755539006334 7.57144770006846L1.841799370320234 7.562497504679766L1.826169379679766 7.562497504679766z M1.826169379679766 7.562502495320234L1.841799370320234 7.562502495320234z M1.841794379679766 7.562497504679766L1.841794379679766 7.567987843663488z M1.841794379679766 7.546872504679766L1.841794379679766 7.562502495320234L1.857424370320234 7.562502495320234z", "M1.82616938 7.5624975L1.84179438 7.5624975L1.84179438 7.5468725L1.85742437 7.5625025L1.84179935 7.5625025L1.84179438 7.5635166L1.84175554 7.5714477L1.8281275000000001 7.56362194L1.82617807 7.5625025z"},
}
for _, tt := range tts {
t.Run(fmt.Sprint(tt.p), func(t *testing.T) {
Expand Down Expand Up @@ -1459,7 +1465,7 @@ func TestPathAnd(t *testing.T) {
{"L5 0L5 5L0 5zM1 1L1 4L4 4L4 1z", "M3 2L6 2L6 3L3 3z", "M4 2L5 2L5 3L4 3z"},
{"M0.6203785454666688 7.0296030922171955L0.6187340865555582 7.030136875251485z", "M0.62500998 7.018654L0.61075695 7.0453795800000005z", ""},
{"M0.6203785454666688 7.0296030922171955L0.6187340865555582 7.030136875251485z", "M0.62130568 6.99999002L0.62500998 7.018654L0.61075695 7.0453795800000005z", ""},
{"M0.8785593994222234 7.0413469294202145L0.8763131971555573 7.043500136158196z", "M0.88303435 7.0075883900000005L0.87500998 7.06237368L1.06249861 7.18747447z", ""},
{"M0.8785593994222234 7.0413469294202145L0.8763131971555573 7.043500136158196z", "M0.88303435 7.0075883900000005L0.87500998 7.06237368L1.06249861 7.18747447z", "M0.87801304 7.04187066L0.8785594 7.0413469300000004L0.87801304 7.04187067z"},
}
for _, tt := range tts {
t.Run(fmt.Sprint(tt.p, "x", tt.q), func(t *testing.T) {
Expand Down

0 comments on commit f6166fb

Please sign in to comment.