Skip to content

Commit

Permalink
Improve snapping behavior by preferring endpoints in the middle. (aka…
Browse files Browse the repository at this point in the history
…vel#8)

Modify the snapping algorithm to:

* Avoid snapping if the intersection point is equivalent to an existing endpoint.
* Prefer snapping to an endpoint in the middle of the 4 endpoint candidates.
* Increase the tolerance to cover more cases.

This improves robustness against floating imprecision.
  • Loading branch information
oceanful authored May 17, 2019
1 parent 6758def commit 584bdbb
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 22 deletions.
122 changes: 117 additions & 5 deletions bugs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,118 @@ func TestCorruptionResistanceFromFloatingPointImprecision(t *T) {
{7.604714313123809, 25.720088264944764},
}},
},
{
op: polyclip.INTERSECTION,
subject: polyclip.Polygon{{
{300, -100},
{259.8076211353316, 49.99999999999997},
{-259.80762113533154, 50.000000000000114},
{-300, -99.99999999999996},
}},
clipping: polyclip.Polygon{{
{273.2050807568877, 7.815970093361102e-14},
{259.8076211353315, -50.00000000000014},
{-259.80762113533166, -49.99999999999994},
{-273.20508075688775, -7.105427357601002e-14},
{-259.80762113533154, 50.000000000000114},
{259.8076211353316, 49.99999999999997},
}},
result: polyclip.Polygon{{
{273.2050807568877, 7.815970093361102e-14},
{259.8076211353315, -50.00000000000014},
{-259.80762113533166, -49.99999999999994},
{-273.20508075688775, -7.105427357601002e-14},
{-259.80762113533154, 50.000000000000114},
{259.8076211353316, 49.99999999999997},
}},
},
{
op: polyclip.INTERSECTION,
subject: polyclip.Polygon{{
{300, -100},
{277.163859753386, 14.805029709526934},
{-277.163859753386, 14.805029709526949},
{-300, -99.99999999999996},
}},
clipping: polyclip.Polygon{{
{280.1087632620342, 4.618527782440651e-14},
{277.16385975338596, -14.805029709527119},
{-277.1638597533861, -14.805029709526906},
{-280.10876326203424, -1.1368683772161603e-13},
{-277.163859753386, 14.805029709526949},
{277.163859753386, 14.805029709526934},
{280.1087632620342, 4.618527782440651e-14},
}},
result: polyclip.Polygon{{
{280.1087632620342, 4.618527782440651e-14},
{277.16385975338596, -14.805029709527119},
{-277.1638597533861, -14.805029709526906},
{-280.10876326203424, -1.1368683772161603e-13},
{-277.163859753386, 14.805029709526949},
{277.163859753386, 14.805029709526934},
{280.1087632620342, 4.618527782440651e-14},
}},
},
{
op: polyclip.INTERSECTION,
subject: polyclip.Polygon{{
{-196.96155060244163, 65.270364466614},
{-187.9385241571817, 31.595971334866263},
{-173.20508075688775, 4.263256414560601e-14},
{-153.20888862379562, -28.557521937307854},
{153.2088886237956, -28.55752193730791},
{173.20508075688767, -8.526512829121202e-14},
{187.93852415718163, 31.59597133486612},
{196.96155060244163, 65.27036446661393},
}},
clipping: polyclip.Polygon{{
{196.96155060244163, -65.27036446661393},
{187.9385241571817, -31.595971334866263},
{173.20508075688775, -1.4210854715202004e-14},
{153.20888862379562, 28.557521937307854},
{-153.2088886237956, 28.557521937307882},
{-173.20508075688775, -1.4210854715202004e-14},
{-187.93852415718166, -31.59597133486622},
{-196.9615506024416, -65.27036446661387},
}},
result: polyclip.Polygon{{
{-173.20508075688775, 4.263256414560601e-14},
{-153.20888862379562, -28.557521937307854},
{153.2088886237956, -28.55752193730791},
{173.20508075688767, -8.526512829121202e-14},
{173.20508075688775, -1.4210854715202004e-14},
{153.20888862379562, 28.557521937307854},
{-153.2088886237956, 28.557521937307882},
}},
},
{
op: polyclip.INTERSECTION,
subject: polyclip.Polygon{{
{128.55752193730788, 253.20888862379562},
{100.00000000000003, 273.2050807568877},
{68.40402866513377, 287.9385241571817},
{68.40402866513364, -87.93852415718172},
{100, -73.20508075688772},
{128.55752193730785, -53.208888623795616},
}},
clipping: polyclip.Polygon{{
{131.59597133486625, 287.9385241571817},
{100.00000000000004, 273.20508075688775},
{71.44247806269215, 253.20888862379562},
{71.44247806269209, -53.20888862379559},
{99.99999999999991, -73.20508075688767},
{131.59597133486614, -87.93852415718163},
}},
result: polyclip.Polygon{{
{71.44247806269209, -53.20888862379559},
{99.99999999999991, -73.20508075688767},
{100, -73.20508075688772},
{128.55752193730785, -53.208888623795616},
{128.55752193730788, 253.20888862379562},
{100.00000000000003, 273.2050807568877},
{71.44247806269215, 253.20888862379562},
}},
},
}.verify(t)
}

Expand Down Expand Up @@ -590,21 +702,21 @@ func TestBug5(t *T) {
testCases{
{
polyclip.UNION, rect, circle,
polyclip.Polygon{{{36, 23}, {36, 7}, {24, 7}, {23.16377229385877, 7.043824837053813}, {22.33670647345792, 7.1748191941295545}, {21.527864045000413, 7.391547869638772}, {20.746106855393588, 7.691636338859194}, {19.99999999999999, 8.071796769724493}, {19.297717981660202, 8.527864045000424}, {18.64695514912912, 9.054841396180851}, {18.05484139618083, 9.646955149129141}, {17.527864045000403, 10.297717981660224}, {17.07179676972447, 11.00000000000001}, {16.691636338859173, 11.746106855393611}, {16.39154786963875, 12.527864045000435}, {16.174819194129533, 13.33670647345794}, {16.04382483705379, 14.16377229385879}, {15.999999999999977, 15.00000000000002}, {16.04382483705379, 15.83622770614125}, {16.174819194129533, 16.6632935265421}, {16.39154786963875, 17.472135954999604}, {16.691636338859173, 18.25389314460643}, {17.07179676972447, 19.00000000000003}, {17.527864045000403, 19.702282018339815}, {18.05484139618083, 20.353044850870898}, {18.64695514912912, 20.945158603819188}, {19.297717981660202, 21.472135954999615}, {19.99999999999999, 21.928203230275546}, {20.746106855393588, 22.308363661140845}, {21.527864045000413, 22.608452130361268}, {22.33670647345792, 22.825180805870485}, {23.16377229385877, 22.956175162946227}, {24, 23.00000000000004}, {24.000000000000746, 23}}},
polyclip.Polygon{{{36, 23}, {36, 7}, {24, 7}, {23.16377229385877, 7.043824837053813}, {22.33670647345792, 7.1748191941295545}, {21.527864045000413, 7.391547869638772}, {20.746106855393588, 7.691636338859194}, {19.99999999999999, 8.071796769724493}, {19.297717981660202, 8.527864045000424}, {18.64695514912912, 9.054841396180851}, {18.05484139618083, 9.646955149129141}, {17.527864045000403, 10.297717981660224}, {17.07179676972447, 11.00000000000001}, {16.691636338859173, 11.746106855393611}, {16.39154786963875, 12.527864045000435}, {16.174819194129533, 13.33670647345794}, {16.04382483705379, 14.16377229385879}, {15.999999999999977, 15.00000000000002}, {16.04382483705379, 15.83622770614125}, {16.174819194129533, 16.6632935265421}, {16.39154786963875, 17.472135954999604}, {16.691636338859173, 18.25389314460643}, {17.07179676972447, 19.00000000000003}, {17.527864045000403, 19.702282018339815}, {18.05484139618083, 20.353044850870898}, {18.64695514912912, 20.945158603819188}, {19.297717981660202, 21.472135954999615}, {19.99999999999999, 21.928203230275546}, {20.746106855393588, 22.308363661140845}, {21.527864045000413, 22.608452130361268}, {22.33670647345792, 22.825180805870485}, {23.16377229385877, 22.956175162946227}, {24, 23.00000000000004}}},
},
{
polyclip.INTERSECTION, rect, circle,
polyclip.Polygon{{{31.95617516294621, 15.83622770614125}, {31.825180805870467, 16.6632935265421}, {31.60845213036125, 17.472135954999604}, {31.308363661140827, 18.25389314460643}, {30.92820323027553, 19.00000000000003}, {30.472135954999597, 19.702282018339815}, {29.94515860381917, 20.353044850870898}, {29.35304485087088, 20.945158603819188}, {28.702282018339798, 21.472135954999615}, {28.00000000000001, 21.928203230275546}, {27.253893144606412, 22.308363661140845}, {26.472135954999587, 22.608452130361268}, {25.66329352654208, 22.825180805870485}, {24.83622770614123, 22.956175162946227}, {24.000000000000746, 23}, {24, 23}, {24, 7}, {24.83622770614123, 7.043824837053814}, {25.66329352654208, 7.174819194129555}, {26.472135954999587, 7.391547869638773}, {27.253893144606412, 7.691636338859195}, {28.00000000000001, 8.071796769724493}, {28.702282018339798, 8.527864045000424}, {29.35304485087088, 9.054841396180851}, {29.94515860381917, 9.646955149129141}, {30.472135954999597, 10.297717981660224}, {30.92820323027553, 11.00000000000001}, {31.308363661140827, 11.746106855393611}, {31.60845213036125, 12.527864045000435}, {31.825180805870467, 13.33670647345794}, {31.95617516294621, 14.16377229385879}, {32.00000000000002, 15.00000000000002}}},
polyclip.Polygon{{{31.95617516294621, 15.83622770614125}, {31.825180805870467, 16.6632935265421}, {31.60845213036125, 17.472135954999604}, {31.308363661140827, 18.25389314460643}, {30.92820323027553, 19.00000000000003}, {30.472135954999597, 19.702282018339815}, {29.94515860381917, 20.353044850870898}, {29.35304485087088, 20.945158603819188}, {28.702282018339798, 21.472135954999615}, {28.00000000000001, 21.928203230275546}, {27.253893144606412, 22.308363661140845}, {26.472135954999587, 22.608452130361268}, {25.66329352654208, 22.825180805870485}, {24.83622770614123, 22.956175162946227}, {24, 23.00000000000004}, {24, 23}, {24, 7}, {24.83622770614123, 7.043824837053814}, {25.66329352654208, 7.174819194129555}, {26.472135954999587, 7.391547869638773}, {27.253893144606412, 7.691636338859195}, {28.00000000000001, 8.071796769724493}, {28.702282018339798, 8.527864045000424}, {29.35304485087088, 9.054841396180851}, {29.94515860381917, 9.646955149129141}, {30.472135954999597, 10.297717981660224}, {30.92820323027553, 11.00000000000001}, {31.308363661140827, 11.746106855393611}, {31.60845213036125, 12.527864045000435}, {31.825180805870467, 13.33670647345794}, {31.95617516294621, 14.16377229385879}, {32.00000000000002, 15.00000000000002}}},
},
{
polyclip.DIFFERENCE, rect, circle,
polyclip.Polygon{{{24.000000000000746, 23}, {24.83622770614123, 22.956175162946227}, {25.66329352654208, 22.825180805870485}, {26.472135954999587, 22.608452130361268}, {27.253893144606412, 22.308363661140845}, {28.00000000000001, 21.928203230275546}, {28.702282018339798, 21.472135954999615}, {29.35304485087088, 20.945158603819188}, {29.94515860381917, 20.353044850870898}, {30.472135954999597, 19.702282018339815}, {30.92820323027553, 19.00000000000003}, {31.308363661140827, 18.25389314460643}, {31.60845213036125, 17.472135954999604}, {31.825180805870467, 16.6632935265421}, {31.95617516294621, 15.83622770614125}, {32.00000000000002, 15.00000000000002}, {31.95617516294621, 14.16377229385879}, {31.825180805870467, 13.33670647345794}, {31.60845213036125, 12.527864045000435}, {31.308363661140827, 11.746106855393611}, {30.92820323027553, 11.00000000000001}, {30.472135954999597, 10.297717981660224}, {29.94515860381917, 9.646955149129141}, {29.35304485087088, 9.054841396180851}, {28.702282018339798, 8.527864045000424}, {28.00000000000001, 8.071796769724493}, {27.253893144606412, 7.691636338859195}, {26.472135954999587, 7.391547869638773}, {25.66329352654208, 7.174819194129555}, {24.83622770614123, 7.043824837053814}, {24, 7}, {36, 7}, {36, 23}}},
polyclip.Polygon{{{24, 23.00000000000004}, {24.83622770614123, 22.956175162946227}, {25.66329352654208, 22.825180805870485}, {26.472135954999587, 22.608452130361268}, {27.253893144606412, 22.308363661140845}, {28.00000000000001, 21.928203230275546}, {28.702282018339798, 21.472135954999615}, {29.35304485087088, 20.945158603819188}, {29.94515860381917, 20.353044850870898}, {30.472135954999597, 19.702282018339815}, {30.92820323027553, 19.00000000000003}, {31.308363661140827, 18.25389314460643}, {31.60845213036125, 17.472135954999604}, {31.825180805870467, 16.6632935265421}, {31.95617516294621, 15.83622770614125}, {32.00000000000002, 15.00000000000002}, {31.95617516294621, 14.16377229385879}, {31.825180805870467, 13.33670647345794}, {31.60845213036125, 12.527864045000435}, {31.308363661140827, 11.746106855393611}, {30.92820323027553, 11.00000000000001}, {30.472135954999597, 10.297717981660224}, {29.94515860381917, 9.646955149129141}, {29.35304485087088, 9.054841396180851}, {28.702282018339798, 8.527864045000424}, {28.00000000000001, 8.071796769724493}, {27.253893144606412, 7.691636338859195}, {26.472135954999587, 7.391547869638773}, {25.66329352654208, 7.174819194129555}, {24.83622770614123, 7.043824837053814}, {24, 7}, {36, 7}, {36, 23}}},
},
{
polyclip.XOR, rect, circle,
polyclip.Polygon{
{{24.000000000000746, 23}, {24, 23}, {24, 7}, {23.16377229385877, 7.043824837053813}, {22.33670647345792, 7.1748191941295545}, {21.527864045000413, 7.391547869638772}, {20.746106855393588, 7.691636338859194}, {19.99999999999999, 8.071796769724493}, {19.297717981660202, 8.527864045000424}, {18.64695514912912, 9.054841396180851}, {18.05484139618083, 9.646955149129141}, {17.527864045000403, 10.297717981660224}, {17.07179676972447, 11.00000000000001}, {16.691636338859173, 11.746106855393611}, {16.39154786963875, 12.527864045000435}, {16.174819194129533, 13.33670647345794}, {16.04382483705379, 14.16377229385879}, {15.999999999999977, 15.00000000000002}, {16.04382483705379, 15.83622770614125}, {16.174819194129533, 16.6632935265421}, {16.39154786963875, 17.472135954999604}, {16.691636338859173, 18.25389314460643}, {17.07179676972447, 19.00000000000003}, {17.527864045000403, 19.702282018339815}, {18.05484139618083, 20.353044850870898}, {18.64695514912912, 20.945158603819188}, {19.297717981660202, 21.472135954999615}, {19.99999999999999, 21.928203230275546}, {20.746106855393588, 22.308363661140845}, {21.527864045000413, 22.608452130361268}, {22.33670647345792, 22.825180805870485}, {23.16377229385877, 22.956175162946227}, {24, 23.00000000000004}},
{{24.000000000000746, 23}, {24.83622770614123, 22.956175162946227}, {25.66329352654208, 22.825180805870485}, {26.472135954999587, 22.608452130361268}, {27.253893144606412, 22.308363661140845}, {28.00000000000001, 21.928203230275546}, {28.702282018339798, 21.472135954999615}, {29.35304485087088, 20.945158603819188}, {29.94515860381917, 20.353044850870898}, {30.472135954999597, 19.702282018339815}, {30.92820323027553, 19.00000000000003}, {31.308363661140827, 18.25389314460643}, {31.60845213036125, 17.472135954999604}, {31.825180805870467, 16.6632935265421}, {31.95617516294621, 15.83622770614125}, {32.00000000000002, 15.00000000000002}, {31.95617516294621, 14.16377229385879}, {31.825180805870467, 13.33670647345794}, {31.60845213036125, 12.527864045000435}, {31.308363661140827, 11.746106855393611}, {30.92820323027553, 11.00000000000001}, {30.472135954999597, 10.297717981660224}, {29.94515860381917, 9.646955149129141}, {29.35304485087088, 9.054841396180851}, {28.702282018339798, 8.527864045000424}, {28.00000000000001, 8.071796769724493}, {27.253893144606412, 7.691636338859195}, {26.472135954999587, 7.391547869638773}, {25.66329352654208, 7.174819194129555}, {24.83622770614123, 7.043824837053814}, {24, 7}, {36, 7}, {36, 23}},
{{24, 23}, {24, 7}, {23.16377229385877, 7.043824837053813}, {22.33670647345792, 7.1748191941295545}, {21.527864045000413, 7.391547869638772}, {20.746106855393588, 7.691636338859194}, {19.99999999999999, 8.071796769724493}, {19.297717981660202, 8.527864045000424}, {18.64695514912912, 9.054841396180851}, {18.05484139618083, 9.646955149129141}, {17.527864045000403, 10.297717981660224}, {17.07179676972447, 11.00000000000001}, {16.691636338859173, 11.746106855393611}, {16.39154786963875, 12.527864045000435}, {16.174819194129533, 13.33670647345794}, {16.04382483705379, 14.16377229385879}, {15.999999999999977, 15.00000000000002}, {16.04382483705379, 15.83622770614125}, {16.174819194129533, 16.6632935265421}, {16.39154786963875, 17.472135954999604}, {16.691636338859173, 18.25389314460643}, {17.07179676972447, 19.00000000000003}, {17.527864045000403, 19.702282018339815}, {18.05484139618083, 20.353044850870898}, {18.64695514912912, 20.945158603819188}, {19.297717981660202, 21.472135954999615}, {19.99999999999999, 21.928203230275546}, {20.746106855393588, 22.308363661140845}, {21.527864045000413, 22.608452130361268}, {22.33670647345792, 22.825180805870485}, {23.16377229385877, 22.956175162946227}, {24, 23.00000000000004}},
{{24, 23.00000000000004}, {24.83622770614123, 22.956175162946227}, {25.66329352654208, 22.825180805870485}, {26.472135954999587, 22.608452130361268}, {27.253893144606412, 22.308363661140845}, {28.00000000000001, 21.928203230275546}, {28.702282018339798, 21.472135954999615}, {29.35304485087088, 20.945158603819188}, {29.94515860381917, 20.353044850870898}, {30.472135954999597, 19.702282018339815}, {30.92820323027553, 19.00000000000003}, {31.308363661140827, 18.25389314460643}, {31.60845213036125, 17.472135954999604}, {31.825180805870467, 16.6632935265421}, {31.95617516294621, 15.83622770614125}, {32.00000000000002, 15.00000000000002}, {31.95617516294621, 14.16377229385879}, {31.825180805870467, 13.33670647345794}, {31.60845213036125, 12.527864045000435}, {31.308363661140827, 11.746106855393611}, {30.92820323027553, 11.00000000000001}, {30.472135954999597, 10.297717981660224}, {29.94515860381917, 9.646955149129141}, {29.35304485087088, 9.054841396180851}, {28.702282018339798, 8.527864045000424}, {28.00000000000001, 8.071796769724493}, {27.253893144606412, 7.691636338859195}, {26.472135954999587, 7.391547869638773}, {25.66329352654208, 7.174819194129555}, {24.83622770614123, 7.043824837053814}, {24, 7}, {36, 7}, {36, 23}},
},
},
}.verify(t)
Expand Down
43 changes: 32 additions & 11 deletions clipper.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ package polyclip
import (
"fmt"
"math"
"sort"
)

//func _DBG(f func()) { f() }
Expand Down Expand Up @@ -400,9 +401,23 @@ func findIntersection2(u0, u1, v0, v1 float64, w *[]float64) int {

// snaps the [pt] to one of [toPts] if they are equal within a tolerance factor.
// If none of the points are within the tolerance, the original pt is returned.
func snap(pt Point, toPts ...Point) Point {
const tolerance = 3e-14
for _, p := range toPts {
func snap(pt Point, e1, e2 *endpoint) Point {
pts := []Point{e1.p, e2.p, e1.other.p, e2.other.p}
for _, p := range pts {
if pt.Equals(p) { // Prefer strict equality over snapping.
return p
}
}
// Order the points in sweep-line ordering and test the middle points
// first (i.e. 2, 1, 0, 3) to avoid creating invalid divisions when
// two endpoints are within the tolerance.
sort.Slice(pts, func(i, j int) bool {
return pts[i].isBefore(pts[j])
})
pts[0], pts[2] = pts[2], pts[0]

const tolerance = 8e-14
for _, p := range pts {
if pt.equalWithin(p, tolerance) {
return p
}
Expand All @@ -414,25 +429,26 @@ func snap(pt Point, toPts ...Point) Point {
func (c *clipper) possibleIntersection(e1, e2 *endpoint) []*endpoint {
numIntersections, ip1, _ := findIntersection(e1.segment(), e2.segment(), true)

if numIntersections == 0 {
switch {
case numIntersections == 0:
return nil
case numIntersections == 1 && (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
}

// Adjust for floating point imprecision when intersections are created at endpoints, which
// otherwise has the tendency to corrupt the original polygons with new, almost-parallel segments.
ip1 = snap(ip1, e1.p, e2.p, e1.other.p, e2.other.p)
ip1 = snap(ip1, e1, e2)

if numIntersections == 1 {
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 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)}
case !isValidSingleIntersection(e1, e2, ip1):
_DBG(func() { fmt.Printf("Dropping invalid intersection %v between %v and %v\n", ip1, e1, e2) })
return nil
default: // e1 and e2 divide each other
return []*endpoint{
c.divideSegment(e1, ip1),
Expand All @@ -442,7 +458,12 @@ func (c *clipper) possibleIntersection(e1, e2 *endpoint) []*endpoint {
}

if numIntersections == 2 && e1.polygonType == e2.polygonType {
return nil // the line segments overlap, but they belong to the same polygon
_DBG(func() {
fmt.Printf("Dropping intersection %v from overlapping edges of the same polygon %v and %v\n", ip1, e1, e2)
})
// Note: This case is technically not handled by the algorithm. The original C++ code
// outputs: "Sorry, edges of the same polygon overlap" and exits.
return nil
}

// The line segments overlap
Expand Down
Loading

0 comments on commit 584bdbb

Please sign in to comment.