From ff18ec0ceeae35e804562066d6972af8ebed55db Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Fri, 12 Feb 2016 17:15:40 -0600 Subject: [PATCH 01/14] Fixed infinite loop bug --- bugs_test.go | 145 +++++++++++++++++++++++++++++++++++---------------- geom.go | 4 +- 2 files changed, 102 insertions(+), 47 deletions(-) diff --git a/bugs_test.go b/bugs_test.go index 7dade6d..9eb0c5c 100644 --- a/bugs_test.go +++ b/bugs_test.go @@ -1,15 +1,13 @@ -package polyclip_test +package polyclip import ( "fmt" "sort" . "testing" "time" - - "github.com/akavel/polyclip-go" ) -type sorter polyclip.Polygon +type sorter Polygon func (s sorter) Len() int { return len(s) } func (s sorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } @@ -30,7 +28,7 @@ func (s sorter) Less(i, j int) bool { } // basic normalization just for tests; to be improved if needed -func normalize(poly polyclip.Polygon) polyclip.Polygon { +func normalize(poly Polygon) Polygon { for i, c := range poly { if len(c) == 0 { continue @@ -52,20 +50,20 @@ func normalize(poly polyclip.Polygon) polyclip.Polygon { return poly } -func dump(poly polyclip.Polygon) string { +func dump(poly Polygon) string { return fmt.Sprintf("%v", normalize(poly)) } func TestBug3(t *T) { - cases := []struct{ subject, clipping, result polyclip.Polygon }{ + cases := []struct{ subject, clipping, result Polygon }{ // original reported github issue #3 { - subject: polyclip.Polygon{{{1, 1}, {1, 2}, {2, 2}, {2, 1}}}, - clipping: polyclip.Polygon{ + subject: Polygon{{{1, 1}, {1, 2}, {2, 2}, {2, 1}}}, + clipping: Polygon{ {{2, 1}, {2, 2}, {3, 2}, {3, 1}}, {{1, 2}, {1, 3}, {2, 3}, {2, 2}}, {{2, 2}, {2, 3}, {3, 3}, {3, 2}}}, - result: polyclip.Polygon{{ + result: Polygon{{ {1, 1}, {2, 1}, {3, 1}, {3, 2}, {3, 3}, {2, 3}, {1, 3}, @@ -73,43 +71,43 @@ func TestBug3(t *T) { }, // simplified variant of issue #3, for easier debugging { - subject: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, - clipping: polyclip.Polygon{ + subject: Polygon{{{1, 2}, {2, 2}, {2, 1}}}, + clipping: Polygon{ {{2, 1}, {2, 2}, {3, 2}}, {{1, 2}, {2, 3}, {2, 2}}, {{2, 2}, {2, 3}, {3, 2}}}, - result: polyclip.Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 1}}}, + result: Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 1}}}, }, { - subject: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, - clipping: polyclip.Polygon{ + subject: Polygon{{{1, 2}, {2, 2}, {2, 1}}}, + clipping: Polygon{ {{1, 2}, {2, 3}, {2, 2}}, {{2, 2}, {2, 3}, {3, 2}}}, - result: polyclip.Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 2}, {2, 1}}}, + result: Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 2}, {2, 1}}}, }, // another variation, now with single degenerated curve { - subject: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, - clipping: polyclip.Polygon{ + subject: Polygon{{{1, 2}, {2, 2}, {2, 1}}}, + clipping: Polygon{ {{1, 2}, {2, 3}, {2, 2}, {2, 3}, {3, 2}}}, - result: polyclip.Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 2}, {2, 1}}}, + result: Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 2}, {2, 1}}}, }, { - subject: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, - clipping: polyclip.Polygon{ + subject: Polygon{{{1, 2}, {2, 2}, {2, 1}}}, + clipping: Polygon{ {{2, 1}, {2, 2}, {2, 3}, {3, 2}}, {{1, 2}, {2, 3}, {2, 2}}}, - result: polyclip.Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 1}}}, + result: Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 1}}}, }, // "union" with effectively empty polygon (wholly self-intersecting) { - subject: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, - clipping: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 3}, {1, 2}, {2, 2}, {2, 3}}}, - result: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, + subject: Polygon{{{1, 2}, {2, 2}, {2, 1}}}, + clipping: Polygon{{{1, 2}, {2, 2}, {2, 3}, {1, 2}, {2, 2}, {2, 3}}}, + result: Polygon{{{1, 2}, {2, 2}, {2, 1}}}, }, } for _, c := range cases { - result := dump(c.subject.Construct(polyclip.UNION, c.clipping)) + result := dump(c.subject.Construct(UNION, c.clipping)) if result != dump(c.result) { t.Errorf("case UNION:\nsubject: %v\nclipping: %v\nexpected: %v\ngot: %v", c.subject, c.clipping, c.result, result) @@ -122,61 +120,116 @@ func TestBug4(t *T) { return } - cases := []struct{ subject, clipping, result polyclip.Polygon }{ + cases := []struct{ subject, clipping, result Polygon }{ // original reported github issue #4, resulting in infinite loop { - subject: polyclip.Polygon{{ + subject: Polygon{{ {1.427255375e+06, -2.3283064365386963e-10}, {1.4271285e+06, 134.7111358642578}, {1.427109e+06, 178.30108642578125}}}, - clipping: polyclip.Polygon{{ + clipping: Polygon{{ {1.416e+06, -12000}, {1.428e+06, -12000}, {1.428e+06, 0}, {1.416e+06, 0}, {1.416e+06, -12000}}}, }, + { + subject: Polygon{{ + {1.427255375e+06, -2.3283064365386963e-10}, + {1.4271285e+06, 134.7111358642578}, {1.427109e+06, 178.30108642578125}}}, + clipping: Polygon{{ + {1.416e+06, -12000}, {1.428e+06, -12000}, {1.428e+06, 0}, + {1.416e+06, 0}, {1.416e+06, -12000}}}, + }, + { + subject: Polygon{ + {Point{X: 1.7714672107465276e+06, Y: -102506.68254093888}, + Point{X: 1.7713768917571804e+06, Y: -102000.75485953009}, + Point{X: 1.7717109214841307e+06, Y: -101912.19625031832}}}, + clipping: Polygon{ + {Point{X: 1.7714593229229522e+06, Y: -102470.35230830211}, + Point{X: 1.7714672107465276e+06, Y: -102506.68254093867}, + Point{X: 1.771439738086082e+06, Y: -102512.92027456204}}}, + }, + { + subject: Polygon{{ + Point{X: -1.8280000000000012e+06, Y: -492999.99999999953}, + Point{X: -1.8289999999999995e+06, Y: -494000.0000000006}, + Point{X: -1.828e+06, Y: -493999.9999999991}, + Point{X: -1.8280000000000012e+06, Y: -492999.99999999953}}}, + clipping: Polygon{{ + Point{X: -1.8280000000000005e+06, Y: -495999.99999999977}, + Point{X: -1.8280000000000007e+06, Y: -492000.0000000014}, + Point{X: -1.8240000000000007e+06, Y: -492000.0000000014}, + Point{X: -1.8280000000000005e+06, Y: -495999.99999999977}}}, + }, + { + subject: Polygon{{ + Point{X: -2.0199999999999988e+06, Y: -394999.99999999825}, + Point{X: -2.0199999999999988e+06, Y: -392000.0000000009}, + Point{X: -2.0240000000000012e+06, Y: -395999.9999999993}, + Point{X: -2.0199999999999988e+06, Y: -394999.99999999825}}}, + clipping: Polygon{{ + Point{X: -2.0199999999999988e+06, Y: -394999.99999999825}, + Point{X: -2.020000000000001e+06, Y: -394000.0000000001}, + Point{X: -2.0190000000000005e+06, Y: -394999.9999999997}, + Point{X: -2.0199999999999988e+06, Y: -394999.99999999825}}}, + }, + { + subject: Polygon{{ + Point{X: -47999.99999999992, Y: -23999.999999998756}, + Point{X: 0, Y: -24000.00000000017}, + Point{X: 0, Y: 24000.00000000017}, + Point{X: -48000.00000000014, Y: 24000.00000000017}, + Point{X: -47999.99999999992, Y: -23999.999999998756}}}, + clipping: Polygon{{ + Point{X: -48000, Y: -24000}, + Point{X: 0, Y: -24000}, + Point{X: 0, Y: 24000}, + Point{X: -48000, Y: 24000}, + Point{X: -48000, Y: -24000}}}, + }, } - for _, c := range cases { + for i, c := range cases { // check that we get a result in finite time - ch := make(chan polyclip.Polygon) + ch := make(chan Polygon) go func() { - ch <- c.subject.Construct(polyclip.UNION, c.clipping) + ch <- c.subject.Construct(UNION, c.clipping) }() select { case <-ch: case <-time.After(1 * time.Second): - // panicking in attempt to get full stacktrace - panic(fmt.Sprintf("case UNION:\nsubject: %v\nclipping: %v\ntimed out.", c.subject, c.clipping)) + t.Errorf("case %d UNION:\nsubject: %v\nclipping: %v\ntimed out.", i, c.subject, c.clipping) } } } func TestBug5(t *T) { - rect := polyclip.Polygon{{{24, 7}, {36, 7}, {36, 23}, {24, 23}}} - circle := polyclip.Polygon{{{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}, {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}, {23.16377229385877, 22.956175162946227}, {22.33670647345792, 22.825180805870485}, {21.527864045000413, 22.608452130361268}, {20.746106855393588, 22.308363661140845}, {19.99999999999999, 21.928203230275546}, {19.297717981660202, 21.472135954999615}, {18.64695514912912, 20.945158603819188}, {18.05484139618083, 20.353044850870898}, {17.527864045000403, 19.702282018339815}, {17.07179676972447, 19.00000000000003}, {16.691636338859173, 18.25389314460643}, {16.39154786963875, 17.472135954999604}, {16.174819194129533, 16.6632935265421}, {16.04382483705379, 15.83622770614125}, {15.999999999999977, 15.00000000000002}, {16.04382483705379, 14.16377229385879}, {16.174819194129533, 13.33670647345794}, {16.39154786963875, 12.527864045000435}, {16.691636338859173, 11.746106855393611}, {17.07179676972447, 11.00000000000001}, {17.527864045000403, 10.297717981660224}, {18.05484139618083, 9.646955149129141}, {18.64695514912912, 9.054841396180851}, {19.297717981660202, 8.527864045000424}, {19.99999999999999, 8.071796769724493}, {20.746106855393588, 7.691636338859194}, {21.527864045000413, 7.391547869638772}, {22.33670647345792, 7.1748191941295545}, {23.16377229385877, 7.043824837053813}}} + rect := Polygon{{{24, 7}, {36, 7}, {36, 23}, {24, 23}}} + circle := Polygon{{{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}, {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}, {23.16377229385877, 22.956175162946227}, {22.33670647345792, 22.825180805870485}, {21.527864045000413, 22.608452130361268}, {20.746106855393588, 22.308363661140845}, {19.99999999999999, 21.928203230275546}, {19.297717981660202, 21.472135954999615}, {18.64695514912912, 20.945158603819188}, {18.05484139618083, 20.353044850870898}, {17.527864045000403, 19.702282018339815}, {17.07179676972447, 19.00000000000003}, {16.691636338859173, 18.25389314460643}, {16.39154786963875, 17.472135954999604}, {16.174819194129533, 16.6632935265421}, {16.04382483705379, 15.83622770614125}, {15.999999999999977, 15.00000000000002}, {16.04382483705379, 14.16377229385879}, {16.174819194129533, 13.33670647345794}, {16.39154786963875, 12.527864045000435}, {16.691636338859173, 11.746106855393611}, {17.07179676972447, 11.00000000000001}, {17.527864045000403, 10.297717981660224}, {18.05484139618083, 9.646955149129141}, {18.64695514912912, 9.054841396180851}, {19.297717981660202, 8.527864045000424}, {19.99999999999999, 8.071796769724493}, {20.746106855393588, 7.691636338859194}, {21.527864045000413, 7.391547869638772}, {22.33670647345792, 7.1748191941295545}, {23.16377229385877, 7.043824837053813}}} expected := []struct { - op polyclip.Op - result polyclip.Polygon + op Op + result Polygon }{ { - polyclip.UNION, - 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}}}, + UNION, + 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.INTERSECTION, - 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}}}, + INTERSECTION, + 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.DIFFERENCE, - 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}}}, + DIFFERENCE, + 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.XOR, - polyclip.Polygon{ + XOR, + 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}}, }, diff --git a/geom.go b/geom.go index a8c08a0..86225fd 100644 --- a/geom.go +++ b/geom.go @@ -27,6 +27,8 @@ package polyclip import ( "math" + + "github.com/gonum/floats" ) type Point struct { @@ -35,7 +37,7 @@ type Point struct { // Equals returns true if both p1 and p2 describe exactly the same point. func (p1 Point) Equals(p2 Point) bool { - return p1.X == p2.X && p1.Y == p2.Y + return floats.EqualWithinULP(p1.X, p2.X, 2) && floats.EqualWithinULP(p1.Y, p2.Y, 2) } // Length returns distance from p to point (0, 0). From 9686bb0ff38468dd4a73844e881f53fb8b7bbd8f Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Wed, 27 Apr 2016 20:26:45 -0700 Subject: [PATCH 02/14] Different fix for infinite loop bug --- bugs_test.go | 16 ++++++++++++++++ clipper.go | 23 ++++++++++++++++++++++- geom.go | 8 ++------ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/bugs_test.go b/bugs_test.go index 9eb0c5c..70d20e1 100644 --- a/bugs_test.go +++ b/bugs_test.go @@ -190,9 +190,25 @@ func TestBug4(t *T) { Point{X: -48000, Y: 24000}, Point{X: -48000, Y: -24000}}}, }, + { + subject: Polygon{ + Contour{ + Point{X: -2.137000000000001e+06, Y: -122000.00000000093}, + Point{X: -2.1360000000000005e+06, Y: -121999.99999999907}, + Point{X: -2.1360000000000014e+06, Y: -121000.00000000186}}, + }, + clipping: Polygon{ + Contour{ + Point{X: -2.1120000000000005e+06, Y: -120000}, + Point{X: -2.136000000000001e+06, Y: -120000.00000000093}, + Point{X: -2.1360000000000005e+06, Y: -144000}}}, + }, } for i, c := range cases { // check that we get a result in finite time + if i != 2 { + continue + } ch := make(chan Polygon) go func() { diff --git a/clipper.go b/clipper.go index 057fe9f..c8f21c1 100644 --- a/clipper.go +++ b/clipper.go @@ -26,6 +26,7 @@ package polyclip import ( "fmt" "math" + "time" ) //func _DBG(f func()) { f() } @@ -117,7 +118,21 @@ func (c *clipper) compute(operation Op) Polygon { } }) + // Checker for infinite loops for debugging + var timeout <-chan time.Time + _DBG(func() { timeout = time.After(60 * time.Second) }) + for !c.eventQueue.IsEmpty() { + + _DBG(func() { + select { + case <-timeout: + panic(fmt.Errorf("polyclip.compute: timeout (probably infinite loop)\n"+ + "subject: %#v\nclipping: %#v", c.subject, c.clipping)) + default: + } + }) + var prev, next *endpoint e := c.eventQueue.dequeue() _DBG(func() { fmt.Printf("\nProcess event: (of %d)\n%v\n", len(c.eventQueue.elements)+1, *e) }) @@ -326,7 +341,7 @@ func findIntersection(seg0, seg1 segment) (int, Point, Point) { s1 := s0 + (d0.X*d1.X+d0.Y*d1.Y)/sqrLen0 smin := math.Min(s0, s1) smax := math.Max(s0, s1) - w := make([]float64, 0) + w := make([]float64, 0, 2) imax := findIntersection2(0.0, 1.0, smin, smax, &w) if imax > 0 { @@ -338,6 +353,12 @@ func findIntersection(seg0, seg1 segment) (int, Point, Point) { if imax > 1 { pi1.X = p0.X + w[1]*d0.X pi1.Y = p0.Y + w[1]*d0.Y + if pi1.Equals(pi0) { + // If d0*w[1] is very small compared to p0, + // pi0 and pi1 will be the same within floating point rounding error. + imax = 1 + pi1 = Point{} + } } } diff --git a/geom.go b/geom.go index 86225fd..3b23727 100644 --- a/geom.go +++ b/geom.go @@ -25,11 +25,7 @@ // For further details, consult the description of Polygon.Construct method. package polyclip -import ( - "math" - - "github.com/gonum/floats" -) +import "math" type Point struct { X, Y float64 @@ -37,7 +33,7 @@ type Point struct { // Equals returns true if both p1 and p2 describe exactly the same point. func (p1 Point) Equals(p2 Point) bool { - return floats.EqualWithinULP(p1.X, p2.X, 2) && floats.EqualWithinULP(p1.Y, p2.Y, 2) + return p1.X == p2.X && p1.Y == p2.Y } // Length returns distance from p to point (0, 0). From 484a049e2dd239fd7190becd8fb79b933a95680c Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Wed, 27 Apr 2016 22:39:06 -0700 Subject: [PATCH 03/14] Fixed another bug? --- bugs_test.go | 14 +++++++++++--- clipper.go | 11 ++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/bugs_test.go b/bugs_test.go index 70d20e1..0ca2627 100644 --- a/bugs_test.go +++ b/bugs_test.go @@ -203,12 +203,20 @@ func TestBug4(t *T) { Point{X: -2.136000000000001e+06, Y: -120000.00000000093}, Point{X: -2.1360000000000005e+06, Y: -144000}}}, }, + { + subject: Polygon{ + Contour{ + Point{X: 1.556e+06, Y: -1.139999999999999e+06}, + Point{X: 1.5600000000000002e+06, Y: -1.140000000000001e+06}, + Point{X: 1.56e+06, Y: -1.136000000000001e+06}}}, + clipping: Polygon{ + Contour{ + Point{X: 1.56e+06, Y: -1.127999999999999e+06}, + Point{X: 1.5600000000000002e+06, Y: -1.151999999999999e+06}}}, + }, } for i, c := range cases { // check that we get a result in finite time - if i != 2 { - continue - } ch := make(chan Polygon) go func() { diff --git a/clipper.go b/clipper.go index c8f21c1..38bc52a 100644 --- a/clipper.go +++ b/clipper.go @@ -29,8 +29,9 @@ import ( "time" ) -//func _DBG(f func()) { f() } -func _DBG(f func()) {} +func _DBG(f func()) { f() } + +//func _DBG(f func()) {} type polygonType int @@ -426,10 +427,10 @@ func (c *clipper) possibleIntersection(e1, e2 *endpoint) { return } - // The line segments overlap + // The line segments overlap and belong to different polygons sortedEvents := make([]*endpoint, 0) switch { - case e1.p.Equals(e2.p): + case e1.p.Equals(e2.p) || e1.p.Equals(e2.other.p): sortedEvents = append(sortedEvents, nil) // WTF [MC: WTF] case endpointLess(e1, e2): sortedEvents = append(sortedEvents, e2, e1) @@ -438,7 +439,7 @@ func (c *clipper) possibleIntersection(e1, e2 *endpoint) { } switch { - case e1.other.p.Equals(e2.other.p): + case e1.other.p.Equals(e2.other.p) || e1.other.p.Equals(e2.p): sortedEvents = append(sortedEvents, nil) case endpointLess(e1.other, e2.other): sortedEvents = append(sortedEvents, e2.other, e1.other) From b8010cf3e96d86825e03a09e1343585cf02038a5 Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Thu, 28 Apr 2016 12:20:27 -0700 Subject: [PATCH 04/14] malloc --- clipper.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/clipper.go b/clipper.go index 38bc52a..53d61be 100644 --- a/clipper.go +++ b/clipper.go @@ -29,9 +29,8 @@ import ( "time" ) -func _DBG(f func()) { f() } - -//func _DBG(f func()) {} +//func _DBG(f func()) { f() } +func _DBG(f func()) {} type polygonType int @@ -428,7 +427,7 @@ func (c *clipper) possibleIntersection(e1, e2 *endpoint) { } // The line segments overlap and belong to different polygons - sortedEvents := make([]*endpoint, 0) + sortedEvents := make([]*endpoint, 0, 4) switch { case e1.p.Equals(e2.p) || e1.p.Equals(e2.other.p): sortedEvents = append(sortedEvents, nil) // WTF [MC: WTF] From 19d69389fb671220011ceb7ef2931a92e52598ae Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Mon, 2 May 2016 10:55:21 -0700 Subject: [PATCH 05/14] Fixed another infinite loop bug --- bugs_test.go | 16 +++++++++ clipper.go | 98 ++++++++++++++++++++++++++++++++++++---------------- endpoint.go | 2 +- 3 files changed, 85 insertions(+), 31 deletions(-) diff --git a/bugs_test.go b/bugs_test.go index 0ca2627..d8bdd4d 100644 --- a/bugs_test.go +++ b/bugs_test.go @@ -214,6 +214,22 @@ func TestBug4(t *T) { Point{X: 1.56e+06, Y: -1.127999999999999e+06}, Point{X: 1.5600000000000002e+06, Y: -1.151999999999999e+06}}}, }, + { + subject: Polygon{ + Contour{ + Point{X: 1.0958876176594219e+06, Y: -567467.5197556159}, + Point{X: 1.0956330600760083e+06, Y: -567223.72588934}, + Point{X: 1.0958876176594219e+06, Y: -567467.5197556159}, + }, + }, + clipping: Polygon{ + Contour{ + Point{X: 1.0953516248896217e+06, Y: -564135.1861293605}, + Point{X: 1.0959085007300845e+06, Y: -568241.1879245406}, + Point{X: 1.0955136237022132e+06, Y: -581389.3748769956}, + }, + }, + }, } for i, c := range cases { // check that we get a result in finite time diff --git a/clipper.go b/clipper.go index 53d61be..1869841 100644 --- a/clipper.go +++ b/clipper.go @@ -26,7 +26,7 @@ package polyclip import ( "fmt" "math" - "time" + "os" ) //func _DBG(f func()) { f() } @@ -56,6 +56,10 @@ type clipper struct { eventQueue } +// erri is a counter for the number of errors that have occured. +// Delete if error debugging is no longer used. +var erri int + func (c *clipper) compute(operation Op) Polygon { // Test 1 for trivial result case @@ -89,17 +93,10 @@ func (c *clipper) compute(operation Op) Polygon { return Polygon{} } + numSegments := 0 // Add each segment to the eventQueue, sorted from left to right. - for _, cont := range c.subject { - for i := range cont { - addProcessedSegment(&c.eventQueue, cont.segment(i), _SUBJECT) - } - } - for _, cont := range c.clipping { - for i := range cont { - addProcessedSegment(&c.eventQueue, cont.segment(i), _CLIPPING) - } - } + numSegments += addPolygonToQueue(&c.eventQueue, c.subject, _SUBJECT) + numSegments += addPolygonToQueue(&c.eventQueue, c.clipping, _CLIPPING) connector := connector{} // to connect the edge solutions @@ -118,24 +115,34 @@ func (c *clipper) compute(operation Op) Polygon { } }) - // Checker for infinite loops for debugging - var timeout <-chan time.Time - _DBG(func() { timeout = time.After(60 * time.Second) }) + i := 0 + + // The maximum possible number of events would be if every segment intersected + // with every other segment times two points per segment. + // If we end up with more iterations than that + // in the following for loop we know we have a problem. + maxPossibleEvents := numSegments * numSegments * 2 for !c.eventQueue.IsEmpty() { - _DBG(func() { - select { - case <-timeout: - panic(fmt.Errorf("polyclip.compute: timeout (probably infinite loop)\n"+ - "subject: %#v\nclipping: %#v", c.subject, c.clipping)) - default: + if i > maxPossibleEvents { + fmt.Printf("polyclip.compute: infinite loop. "+ + "Writing geometries to file error%d.log. "+ + "Please report this issue at github.com/ctessum/polyclip-go.\n", erri) + f, err := os.Create(fmt.Sprintf("error%d.log", erri)) + if err != nil { + panic(err) } - }) + fmt.Fprintf(f, "subject: %#v\nclipping: %#v\n", c.subject, c.clipping) + f.Close() + erri++ + return connector.toPolygon() + } + i++ var prev, next *endpoint e := c.eventQueue.dequeue() - _DBG(func() { fmt.Printf("\nProcess event: (of %d)\n%v\n", len(c.eventQueue.elements)+1, *e) }) + _DBG(func() { fmt.Printf("\nProcess event: (%d of %d)\n%v\n", i, len(c.eventQueue.elements)+1, *e) }) // optimization 1 switch { @@ -205,7 +212,7 @@ func (c *clipper) compute(operation Op) Polygon { } _DBG(func() { - fmt.Println("Status line after insertion: ") + fmt.Println("Status line after left insertion: ") for _, e := range S { fmt.Println(*e) } @@ -320,6 +327,11 @@ func findIntersection(seg0, seg1 segment) (int, Point, Point) { pi0.X = p0.X + s*d0.X pi0.Y = p0.Y + s*d0.Y + if pi0.Equals(p0) { + // Owing to a rounding error, pi0 and p0 are not different. + return 0, Point{}, Point{} + } + // [MC: commented fragment removed] return 1, pi0, pi1 @@ -342,21 +354,29 @@ func findIntersection(seg0, seg1 segment) (int, Point, Point) { smin := math.Min(s0, s1) smax := math.Max(s0, s1) w := make([]float64, 0, 2) - imax := findIntersection2(0.0, 1.0, smin, smax, &w) + imaxOriginal := findIntersection2(0.0, 1.0, smin, smax, &w) + imax := imaxOriginal - if imax > 0 { + if imaxOriginal > 0 { pi0.X = p0.X + w[0]*d0.X pi0.Y = p0.Y + w[0]*d0.Y + if pi0.Equals(p0) { + // If d0*w[1] is very small compared to p0, + // pi0 and p0 will be the same within floating point rounding error. + imax-- + pi0 = Point{} + } + // [MC: commented fragment removed] - if imax > 1 { + if imaxOriginal > 1 { pi1.X = p0.X + w[1]*d0.X pi1.Y = p0.Y + w[1]*d0.Y - if pi1.Equals(pi0) { + if pi1.Equals(p0) { // If d0*w[1] is very small compared to p0, - // pi0 and pi1 will be the same within floating point rounding error. - imax = 1 + // pi1 and p0 will be the same within floating point rounding error. + imax-- pi1 = Point{} } } @@ -521,6 +541,24 @@ func (c *clipper) divideSegment(e *endpoint, p Point) { c.eventQueue.enqueue(r) } +// addPolygonToQueue adds p to the event queue, retuning the number of +// segments that were added. +func addPolygonToQueue(q *eventQueue, p Polygon, polyType polygonType) int { + numSegments := 0 + for _, cont := range p { + if cont[0].Equals(cont[len(cont)-1]) { + // If the beginning point and the end point are the same, + // ignore the end point. + cont = cont[0 : len(cont)-1] + } + for i := range cont { + addProcessedSegment(q, cont.segment(i), polyType) + numSegments++ + } + } + return numSegments +} + func addProcessedSegment(q *eventQueue, segment segment, polyType polygonType) { if segment.start.Equals(segment.end) { // Possible degenerate condition @@ -543,7 +581,7 @@ func addProcessedSegment(q *eventQueue, segment segment, polyType polygonType) { e1.left = false } - // Pushing it so the que is sorted from left to right, with object on the left having the highest priority + // Pushing it so the queue is sorted from left to right, with object on the left having the highest priority q.enqueue(e1) q.enqueue(e2) } diff --git a/endpoint.go b/endpoint.go index ea907ab..443709b 100644 --- a/endpoint.go +++ b/endpoint.go @@ -44,7 +44,7 @@ type endpoint struct { func (e endpoint) String() string { sleft := map[bool]string{true: "left", false: "right"} - return fmt.Sprint("{", e.p, " ", sleft[e.left], " type:", e.polygonType, + return fmt.Sprint("{", e.p, " ", sleft[e.left], " polygonType:", e.polygonType, " other:", e.other.p, " inout:", e.inout, " inside:", e.inside, " edgeType:", e.edgeType, "}") } From fb9444c6a4d7882aaabe7d9ffc7a3934823993a9 Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Mon, 2 May 2016 11:48:16 -0700 Subject: [PATCH 06/14] Implement sort.Search for sorted insertions --- eventqueue.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/eventqueue.go b/eventqueue.go index 0b30924..71db3dd 100644 --- a/eventqueue.go +++ b/eventqueue.go @@ -38,21 +38,15 @@ func (q *eventQueue) enqueue(e *endpoint) { return } - // If already sorted use insertionSort on the inserted item. - // TODO: bisection search? - length := len(q.elements) - if length == 0 { - q.elements = append(q.elements, e) - return - } + // If already sorted, search for the correct location to insert e. + i := sort.Search(len(q.elements), func(i int) bool { + return endpointLess(e, q.elements[i]) + }) + // Insert e in the correct location. q.elements = append(q.elements, nil) - i := length - 1 - for i >= 0 && endpointLess(e, q.elements[i]) { - q.elements[i+1] = q.elements[i] - i-- - } - q.elements[i+1] = e + copy(q.elements[i+1:], q.elements[i:]) + q.elements[i] = e } // The ordering is reversed because push and pop are faster. From 0d69ccb081cc2f09b4f2785d79447dcc19e9bafa Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Mon, 2 May 2016 11:59:35 -0700 Subject: [PATCH 07/14] gofmt --- eventqueue.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/eventqueue.go b/eventqueue.go index 71db3dd..b005304 100644 --- a/eventqueue.go +++ b/eventqueue.go @@ -23,9 +23,7 @@ package polyclip -import ( - "sort" -) +import "sort" type eventQueue struct { elements []*endpoint From 74289fd08b1534d9e06fe112dc2fd1250bd02e61 Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Mon, 2 May 2016 18:38:04 -0700 Subject: [PATCH 08/14] Fixed another infinite loop bug --- bugs_test.go | 18 +++++++++++++++- clipper.go | 58 ++++++++++++++++++---------------------------------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/bugs_test.go b/bugs_test.go index d8bdd4d..06b51be 100644 --- a/bugs_test.go +++ b/bugs_test.go @@ -230,10 +230,26 @@ func TestBug4(t *T) { }, }, }, + { + subject: Polygon{ + []Point{ + Point{X: 608000, Y: -113151.36476426799}, + Point{X: 608000, Y: -114660.04962779157}, + Point{X: 612000, Y: -115414.39205955336}, + Point{X: 1.616e+06, Y: -300000}, + Point{X: 1.608e+06, Y: -303245.6575682382}, + Point{X: 0, Y: 0}, + }, + }, + clipping: Polygon{ + []Point{ + Point{X: 1.612e+06, Y: -296000}, + }, + }, + }, } for i, c := range cases { // check that we get a result in finite time - ch := make(chan Polygon) go func() { ch <- c.subject.Construct(UNION, c.clipping) diff --git a/clipper.go b/clipper.go index 1869841..ed1e878 100644 --- a/clipper.go +++ b/clipper.go @@ -56,10 +56,6 @@ type clipper struct { eventQueue } -// erri is a counter for the number of errors that have occured. -// Delete if error debugging is no longer used. -var erri int - func (c *clipper) compute(operation Op) Polygon { // Test 1 for trivial result case @@ -126,17 +122,15 @@ func (c *clipper) compute(operation Op) Polygon { for !c.eventQueue.IsEmpty() { if i > maxPossibleEvents { - fmt.Printf("polyclip.compute: infinite loop. "+ - "Writing geometries to file error%d.log. "+ - "Please report this issue at github.com/ctessum/polyclip-go.\n", erri) - f, err := os.Create(fmt.Sprintf("error%d.log", erri)) + f, err := os.Create("policlipError.log") if err != nil { panic(err) } - fmt.Fprintf(f, "subject: %#v\nclipping: %#v\n", c.subject, c.clipping) + fmt.Fprintf(f, "subject: %#v,\nclipping: %#v\n,", c.subject, c.clipping) f.Close() - erri++ - return connector.toPolygon() + panic("polyclip.compute: infinite loop. " + + "Writing geometries to file polyclipError.log. " + + "Please report this issue at github.com/ctessum/polyclip-go.") } i++ @@ -300,13 +294,20 @@ func (c *clipper) compute(operation Op) Polygon { return connector.toPolygon() } +var nanPoint Point + +func init() { + nanPoint = Point{X: math.NaN(), Y: math.NaN()} +} + func findIntersection(seg0, seg1 segment) (int, Point, Point) { - var pi0, pi1 Point + pi0 := nanPoint + pi1 := nanPoint p0 := seg0.start d0 := Point{seg0.end.X - p0.X, seg0.end.Y - p0.Y} p1 := seg1.start d1 := Point{seg1.end.X - p1.X, seg1.end.Y - p1.Y} - sqrEpsilon := 1e-7 // was 1e-3 earlier + sqrEpsilon := 0. // was 1e-3 earlier E := Point{p1.X - p0.X, p1.Y - p0.Y} kross := d0.X*d1.Y - d0.Y*d1.X sqrKross := kross * kross @@ -321,20 +322,15 @@ func findIntersection(seg0, seg1 segment) (int, Point, Point) { } t := (E.X*d0.Y - E.Y*d0.X) / kross if t < 0 || t > 1 { - return 0, Point{}, Point{} + return 0, nanPoint, nanPoint } // intersection of lines is a point an each segment [MC: ?] pi0.X = p0.X + s*d0.X pi0.Y = p0.Y + s*d0.Y - if pi0.Equals(p0) { - // Owing to a rounding error, pi0 and p0 are not different. - return 0, Point{}, Point{} - } - // [MC: commented fragment removed] - return 1, pi0, pi1 + return 1, pi0, nanPoint } // lines of the segments are parallel @@ -343,7 +339,7 @@ func findIntersection(seg0, seg1 segment) (int, Point, Point) { sqrKross = kross * kross if sqrKross > sqrEpsilon*sqrLen0*sqrLenE { // lines of the segment are different - return 0, pi0, pi1 + return 0, nanPoint, nanPoint } // Lines of the segment are the same. Need to test for overlap of segments. @@ -354,31 +350,17 @@ func findIntersection(seg0, seg1 segment) (int, Point, Point) { smin := math.Min(s0, s1) smax := math.Max(s0, s1) w := make([]float64, 0, 2) - imaxOriginal := findIntersection2(0.0, 1.0, smin, smax, &w) - imax := imaxOriginal + imax := findIntersection2(0.0, 1.0, smin, smax, &w) - if imaxOriginal > 0 { + if imax > 0 { pi0.X = p0.X + w[0]*d0.X pi0.Y = p0.Y + w[0]*d0.Y - if pi0.Equals(p0) { - // If d0*w[1] is very small compared to p0, - // pi0 and p0 will be the same within floating point rounding error. - imax-- - pi0 = Point{} - } - // [MC: commented fragment removed] - if imaxOriginal > 1 { + if imax > 1 { pi1.X = p0.X + w[1]*d0.X pi1.Y = p0.Y + w[1]*d0.Y - if pi1.Equals(p0) { - // If d0*w[1] is very small compared to p0, - // pi1 and p0 will be the same within floating point rounding error. - imax-- - pi1 = Point{} - } } } From f86d1ac5da5df3a4101fdd247786078c8113731b Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Mon, 2 May 2016 20:31:24 -0700 Subject: [PATCH 09/14] Replaced sweepline.insert() search with sort.Search() --- sweepline.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/sweepline.go b/sweepline.go index 981c53c..4e58b53 100644 --- a/sweepline.go +++ b/sweepline.go @@ -23,6 +23,8 @@ package polyclip +import "sort" + // This is the data structure that simulates the sweepline as it parses through // eventQueue, which holds the events sorted from left to right (x-coordinate). // TODO: optimizations? use sort.Search()? @@ -44,7 +46,17 @@ func (s *sweepline) insert(item *endpoint) int { return 0 } - *s = append(*s, &endpoint{}) + // Search for the correct location to insert item. + i := sort.Search(len(*s), func(i int) bool { + return segmentCompare(item, (*s)[i]) + }) + + // Insert e in the correct location. + *s = append(*s, nil) + copy((*s)[i+1:], (*s)[i:]) + (*s)[i] = item + + /**s = append(*s, &endpoint{}) i := length - 1 for i >= 0 && segmentCompare(item, (*s)[i]) { (*s)[i+1] = (*s)[i] @@ -53,8 +65,11 @@ func (s *sweepline) insert(item *endpoint) int { (*s)[i+1] = item return i + 1 //TODO insertion sort? + */ + return i } +// segmentCompare returns whether e1 is considered less than e2. func segmentCompare(e1, e2 *endpoint) bool { switch { case e1 == e2: From b1164ad13b5bc9cf6725fd42e7bb93fb5b40225d Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Mon, 2 May 2016 20:31:48 -0700 Subject: [PATCH 10/14] clean up --- sweepline.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/sweepline.go b/sweepline.go index 4e58b53..cb64e53 100644 --- a/sweepline.go +++ b/sweepline.go @@ -51,21 +51,11 @@ func (s *sweepline) insert(item *endpoint) int { return segmentCompare(item, (*s)[i]) }) - // Insert e in the correct location. + // Insert item in the correct location. *s = append(*s, nil) copy((*s)[i+1:], (*s)[i:]) (*s)[i] = item - /**s = append(*s, &endpoint{}) - i := length - 1 - for i >= 0 && segmentCompare(item, (*s)[i]) { - (*s)[i+1] = (*s)[i] - i-- - } - (*s)[i+1] = item - return i + 1 - //TODO insertion sort? - */ return i } From fc96ee887f7d7b1f3bc7386d1426d42b2d723bfe Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Tue, 3 May 2016 21:06:16 -0700 Subject: [PATCH 11/14] Fix bug 3 --- bugs_test.go | 19 ++++++++++++++- clipper.go | 66 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/bugs_test.go b/bugs_test.go index 06b51be..3d8fc58 100644 --- a/bugs_test.go +++ b/bugs_test.go @@ -106,7 +106,10 @@ func TestBug3(t *T) { result: Polygon{{{1, 2}, {2, 2}, {2, 1}}}, }, } - for _, c := range cases { + for i, c := range cases { + if i != 5 { + continue + } result := dump(c.subject.Construct(UNION, c.clipping)) if result != dump(c.result) { t.Errorf("case UNION:\nsubject: %v\nclipping: %v\nexpected: %v\ngot: %v", @@ -247,6 +250,20 @@ func TestBug4(t *T) { }, }, }, + { + subject: Polygon{ + Contour{ + Point{X: 1.1458356382266793e+06, Y: -251939.4635597784}, + Point{X: 1.1460824662209095e+06, Y: -251687.86194535438}, + Point{X: 1.1458356382266793e+06, Y: -251939.4635597784}, + }}, + clipping: Polygon{ + Contour{ + Point{X: 1.1486683769211173e+06, Y: -251759.06331944838}, + Point{X: 1.1468807511323579e+06, Y: -251379.90576799586}, + Point{X: 1.1457914974731328e+06, Y: -251816.31287551578}, + }}, + }, } for i, c := range cases { // check that we get a result in finite time diff --git a/clipper.go b/clipper.go index ed1e878..1e034bb 100644 --- a/clipper.go +++ b/clipper.go @@ -113,11 +113,13 @@ func (c *clipper) compute(operation Op) Polygon { i := 0 - // The maximum possible number of events would be if every segment intersected - // with every other segment times two points per segment. - // If we end up with more iterations than that - // in the following for loop we know we have a problem. - maxPossibleEvents := numSegments * numSegments * 2 + // From the manuscript, the cycle is executed + // n + 4k times, where n is the number of segments and k is the number of + // intersections. I believe the maximum k would be about n^2, but + // we assume it here to be 2*n. This may not be the absolute maximum + // number of events, but it is not likely that there would be more intersections + // than this in a real polygon. + maxPossibleEvents := numSegments * 9 for !c.eventQueue.IsEmpty() { @@ -523,18 +525,52 @@ func (c *clipper) divideSegment(e *endpoint, p Point) { c.eventQueue.enqueue(r) } -// addPolygonToQueue adds p to the event queue, retuning the number of +type empty struct{} + +// a polygonGraph holds the points of a polygon in a graph struct. +// The index of the first map is the starting point of each segment +// in the polygon and the index of the second map is the ending point +// of each segment. +type polygonGraph map[Point]map[Point]empty + +// addToGraph adds the segments of the polygon to the graph in a +// way that ensures the same segment is not included twice in the +// polygon. +func addToGraph(g *polygonGraph, seg segment) { + if seg.start.Equals(seg.end) { + // The starting and ending points are the same, so this is + // not in fact a segment. + return + } + + if _, ok := (*g)[seg.end][seg.start]; ok { + // This polygonGraph already has a segment end -> start, adding + // start -> end would make the polygon degenerate, so we delete both. + delete((*g)[seg.end], seg.start) + return + } + + if _, ok := (*g)[seg.start]; !ok { + (*g)[seg.start] = make(map[Point]empty) + } + + // Add the segment. + (*g)[seg.start][seg.end] = empty{} +} + +// addPolygonToQueue adds p to the event queue, returning the number of // segments that were added. func addPolygonToQueue(q *eventQueue, p Polygon, polyType polygonType) int { - numSegments := 0 + g := make(polygonGraph) for _, cont := range p { - if cont[0].Equals(cont[len(cont)-1]) { - // If the beginning point and the end point are the same, - // ignore the end point. - cont = cont[0 : len(cont)-1] - } for i := range cont { - addProcessedSegment(q, cont.segment(i), polyType) + addToGraph(&g, cont.segment(i)) + } + } + numSegments := 0 + for start, gg := range g { + for end := range gg { + addProcessedSegment(q, segment{start: start, end: end}, polyType) numSegments++ } } @@ -542,10 +578,6 @@ func addPolygonToQueue(q *eventQueue, p Polygon, polyType polygonType) int { } func addProcessedSegment(q *eventQueue, segment segment, polyType polygonType) { - if segment.start.Equals(segment.end) { - // Possible degenerate condition - return - } e1 := &endpoint{p: segment.start, left: true, polygonType: polyType} e2 := &endpoint{p: segment.end, left: true, polygonType: polyType, other: e1} From 78f2eac9201378609acb5da0105ea626b15abbec Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Fri, 6 May 2016 20:45:56 -0700 Subject: [PATCH 12/14] Change error file spelling --- clipper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clipper.go b/clipper.go index 1e034bb..34e4541 100644 --- a/clipper.go +++ b/clipper.go @@ -124,7 +124,7 @@ func (c *clipper) compute(operation Op) Polygon { for !c.eventQueue.IsEmpty() { if i > maxPossibleEvents { - f, err := os.Create("policlipError.log") + f, err := os.Create("polyclipError.log") if err != nil { panic(err) } From a928b5b206f4fcbed01534d5a8675febadd98513 Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Sat, 7 May 2016 10:59:53 -0700 Subject: [PATCH 13/14] Changed maxPossibleEvents --- clipper.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/clipper.go b/clipper.go index 34e4541..b4fe009 100644 --- a/clipper.go +++ b/clipper.go @@ -115,11 +115,8 @@ func (c *clipper) compute(operation Op) Polygon { // From the manuscript, the cycle is executed // n + 4k times, where n is the number of segments and k is the number of - // intersections. I believe the maximum k would be about n^2, but - // we assume it here to be 2*n. This may not be the absolute maximum - // number of events, but it is not likely that there would be more intersections - // than this in a real polygon. - maxPossibleEvents := numSegments * 9 + // intersections. I believe the maximum k would be about n^2. + maxPossibleEvents := numSegments + 4*numSegments*numSegments for !c.eventQueue.IsEmpty() { From bc979f8552221fd921c67c036c20aa575ebfad66 Mon Sep 17 00:00:00 2001 From: Chris Tessum Date: Sat, 7 May 2016 11:02:10 -0700 Subject: [PATCH 14/14] Changed bug3 expected result --- bugs_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bugs_test.go b/bugs_test.go index 3d8fc58..c2cc1e8 100644 --- a/bugs_test.go +++ b/bugs_test.go @@ -99,11 +99,11 @@ func TestBug3(t *T) { {{1, 2}, {2, 3}, {2, 2}}}, result: Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 1}}}, }, - // "union" with effectively empty polygon (wholly self-intersecting) + // "union" wholly self-intersecting polygon { subject: Polygon{{{1, 2}, {2, 2}, {2, 1}}}, clipping: Polygon{{{1, 2}, {2, 2}, {2, 3}, {1, 2}, {2, 2}, {2, 3}}}, - result: Polygon{{{1, 2}, {2, 2}, {2, 1}}}, + result: Polygon{{{1, 2}, {2, 3}, {2, 2}, {2, 1}}}, }, } for i, c := range cases {