diff --git a/path_intersection.go b/path_intersection.go index 87004a9..8601c0e 100644 --- a/path_intersection.go +++ b/path_intersection.go @@ -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) @@ -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 } } @@ -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) @@ -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 { @@ -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 } @@ -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 @@ -1968,8 +1917,21 @@ 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 @@ -1977,18 +1939,19 @@ func bentleyOttmann(ps, qs Paths, op pathOp, fillRule FillRule) *Path { 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)) @@ -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 diff --git a/path_intersection_test.go b/path_intersection_test.go index 2aa8a63..deb301f 100644 --- a/path_intersection_test.go +++ b/path_intersection_test.go @@ -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 @@ -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) { @@ -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) {