github.com/richardwilkes/toolbox@v1.121.0/xmath/geom/poly/polygon_test.go (about) 1 // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved. 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, version 2.0. If a copy of the MPL was not distributed with 5 // this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 // 7 // This Source Code Form is "Incompatible With Secondary Licenses", as 8 // defined by the Mozilla Public License, version 2.0. 9 10 package poly_test 11 12 import ( 13 "testing" 14 15 "github.com/richardwilkes/toolbox/check" 16 "github.com/richardwilkes/toolbox/xmath/geom" 17 "github.com/richardwilkes/toolbox/xmath/geom/poly" 18 "golang.org/x/exp/constraints" 19 ) 20 21 const floatsAreConsistentAcrossPlatforms = false 22 23 type containsCase[T constraints.Float] struct { 24 geom.Point[T] 25 contains bool 26 } 27 28 func TestContains(t *testing.T) { 29 testContains[float32](t) 30 testContains[float64](t) 31 } 32 33 func testContains[T constraints.Float](t *testing.T) { 34 p := poly.Polygon[T]{ 35 {{200, 20}, {300, 20}, {300, 120}, {200, 120}}, 36 {{250, 50}, {280, 50}, {280, 80}, {250, 80}}, 37 {{260, 60}, {290, 60}, {290, 90}, {260, 90}}, 38 {{290, 110}, {320, 110}, {320, 140}, {290, 140}}, 39 } 40 containsTests := []containsCase[T]{ 41 {Point: geom.Point[T]{X: 199, Y: 20}, contains: false}, 42 {Point: geom.Point[T]{X: 200, Y: 20}, contains: true}, 43 {Point: geom.Point[T]{X: 300, Y: 120}, contains: true}, 44 {Point: geom.Point[T]{X: 250, Y: 50}, contains: true}, 45 {Point: geom.Point[T]{X: 260, Y: 60}, contains: true}, 46 {Point: geom.Point[T]{X: 290, Y: 110}, contains: true}, 47 {Point: geom.Point[T]{X: 319, Y: 139}, contains: true}, 48 {Point: geom.Point[T]{X: 321, Y: 140}, contains: false}, 49 {Point: geom.Point[T]{X: 320, Y: 141}, contains: false}, 50 } 51 for i, test := range containsTests { 52 check.Equal(t, test.contains, p.Contains(test.Point), "%T contains case #%d", p, i) 53 } 54 containsEvenOddTests := []containsCase[T]{ 55 {Point: geom.Point[T]{X: 199, Y: 20}, contains: false}, 56 {Point: geom.Point[T]{X: 200, Y: 20}, contains: true}, 57 {Point: geom.Point[T]{X: 300, Y: 120}, contains: true}, 58 {Point: geom.Point[T]{X: 250, Y: 50}, contains: false}, 59 {Point: geom.Point[T]{X: 260, Y: 60}, contains: true}, 60 {Point: geom.Point[T]{X: 290, Y: 110}, contains: false}, 61 {Point: geom.Point[T]{X: 319, Y: 139}, contains: true}, 62 {Point: geom.Point[T]{X: 321, Y: 140}, contains: false}, 63 {Point: geom.Point[T]{X: 320, Y: 141}, contains: false}, 64 } 65 for i, test := range containsEvenOddTests { 66 check.Equal(t, test.contains, p.ContainsEvenOdd(test.Point), "%T contains even/odd case #%d", p, i) 67 } 68 } 69 70 type testCase[T constraints.Float] struct { 71 name string 72 subject poly.Polygon[T] 73 clipping poly.Polygon[T] 74 expected poly.Polygon[T] 75 } 76 77 func TestUnion(t *testing.T) { 78 testUnion[float32](t) 79 if floatsAreConsistentAcrossPlatforms { 80 testUnion[float64](t) 81 } 82 } 83 84 func testUnion[T constraints.Float](t *testing.T) { 85 tests := []testCase[T]{ 86 { 87 name: "union #1", 88 subject: poly.Polygon[T]{{{1, 1}, {1, 2}, {2, 2}, {2, 1}}}, 89 clipping: poly.Polygon[T]{ 90 {{2, 1}, {2, 2}, {3, 2}, {3, 1}}, 91 {{1, 2}, {1, 3}, {2, 3}, {2, 2}}, 92 {{2, 2}, {2, 3}, {3, 3}, {3, 2}}, 93 }, 94 expected: poly.Polygon[T]{{{3, 2}, {3, 1}, {1, 1}, {1, 2}, {1, 3}, {3, 3}}}, 95 }, 96 { 97 name: "union #2", 98 subject: poly.Polygon[T]{{{2, 1}, {1, 2}, {2, 2}}}, 99 clipping: poly.Polygon[T]{{{1, 2}, {2, 2}, {2, 3}, {1, 2}, {2, 2}, {2, 3}}}, 100 expected: poly.Polygon[T]{{{2, 1}, {1, 2}, {2, 2}}}, 101 }, 102 { 103 name: "union #3", 104 subject: poly.Polygon[T]{{{1, 2}, {2, 2}, {2, 1}}}, 105 clipping: poly.Polygon[T]{ 106 {{2, 1}, {2, 2}, {3, 2}}, 107 {{1, 2}, {2, 3}, {2, 2}}, 108 {{2, 2}, {2, 3}, {3, 2}}, 109 }, 110 expected: poly.Polygon[T]{{{3, 2}, {2, 1}, {1, 2}, {2, 3}}}, 111 }, 112 { 113 name: "union #4", 114 subject: poly.Polygon[T]{{{1, 2}, {2, 2}, {2, 1}}}, 115 clipping: poly.Polygon[T]{ 116 {{1, 2}, {2, 3}, {2, 2}}, 117 {{2, 2}, {2, 3}, {3, 2}}, 118 }, 119 expected: poly.Polygon[T]{{{3, 2}, {2, 2}, {2, 1}, {1, 2}, {2, 3}}}, 120 }, 121 { 122 name: "union #5", 123 subject: poly.Polygon[T]{{{1, 2}, {2, 2}, {2, 1}}}, 124 clipping: poly.Polygon[T]{{{1, 2}, {2, 3}, {2, 2}, {2, 3}, {3, 2}}}, 125 expected: poly.Polygon[T]{{{3, 2}, {2, 2}, {2, 1}, {1, 2}, {2, 3}}}, 126 }, 127 { 128 name: "union #6", 129 subject: poly.Polygon[T]{{{1, 2}, {2, 2}, {2, 1}}}, 130 clipping: poly.Polygon[T]{ 131 {{2, 1}, {2, 2}, {2, 3}, {3, 2}}, 132 {{1, 2}, {2, 3}, {2, 2}}, 133 }, 134 expected: poly.Polygon[T]{{{3, 2}, {2, 1}, {1, 2}, {2, 3}}}, 135 }, 136 { 137 name: "union #7", 138 subject: poly.Polygon[T]{{{1, 2}, {2, 2}, {2, 1}}}, 139 clipping: poly.Polygon[T]{{{1, 2}, {2, 2}, {2, 3}, {1, 2}, {2, 2}, {2, 3}}}, 140 expected: poly.Polygon[T]{{{2, 1}, {1, 2}, {2, 2}}}, 141 }, 142 { 143 name: "union #8", 144 subject: poly.Polygon[T]{{{160.094495, 200.379924}, {90.094495, 200.379924}, {55.094495, 139.758145}, {90.094495, 79.136367}}}, 145 clipping: poly.Polygon[T]{{{82.846611, 131.518814}, {66.592063, 159.672517}, {90.094495, 200.379924}, {160.094495, 200.379924}}}, 146 expected: poly.Polygon[T]{{{90.0945, 79.13637}, {55.094494, 139.75815}, {66.592064, 159.67252}, {90.0945, 200.37993}, {160.0945, 200.37993}}}, 147 }, 148 { 149 name: "union #9", 150 subject: poly.Polygon[T]{{{70.784326, -7.668842}, {42.500055, -19.384571}, {22.504998, -11.102347}, {14.215784, -7.668842}, {2.500055, 20.615429}, {4.16327, 24.630785}, {-16.38653, 33.14279}, {-28.102259, 61.427062}}}, 151 clipping: poly.Polygon[T]{{{22.504998, -11.102347}, {14.215784, -7.668842}, {2.500055, 20.615429}, {4.16327, 24.630785}, {-16.38653, 33.14279}, {-18.453791, 38.1336}, {-23.270337, 26.505431}, {16.729663, -13.494569}, {45.013935, -1.778841}, {22.504998, -11.102347}}}, 152 expected: poly.Polygon[T]{{{70.784325, -7.668842}, {42.500053, -19.384571}, {22.504997, -11.102347}, {14.215784, -7.668842}, {22.504997, -11.102347}, {16.729664, -13.494569}, {-23.270336, 26.50543}, {-18.45379, 38.1336}, {-28.102259, 61.427063}}}, 153 }, 154 { 155 name: "union #10", 156 subject: poly.Polygon[T]{{{24, 7}, {36, 7}, {36, 23}, {24, 23}}}, 157 clipping: poly.Polygon[T]{{{24, 7}, {24.836228, 7.043825}, {25.663294, 7.174819}, {26.472136, 7.391548}, {27.253893, 7.691636}, {28, 8.071797}, {28.702282, 8.527864}, {29.353045, 9.054841}, {29.945159, 9.646955}, {30.472136, 10.297718}, {30.928203, 11}, {31.308364, 11.746107}, {31.608452, 12.527864}, {31.825181, 13.336706}, {31.956175, 14.163772}, {32, 15}, {31.956175, 15.836228}, {31.825181, 16.663294}, {31.608452, 17.472136}, {31.308364, 18.253893}, {30.928203, 19}, {30.472136, 19.702282}, {29.945159, 20.353045}, {29.353045, 20.945159}, {28.702282, 21.472136}, {28, 21.928203}, {27.253893, 22.308364}, {26.472136, 22.608452}, {25.663294, 22.825181}, {24.836228, 22.956175}, {24, 23}, {23.163772, 22.956175}, {22.336706, 22.825181}, {21.527864, 22.608452}, {20.746107, 22.308364}, {20, 21.928203}, {19.297718, 21.472136}, {18.646955, 20.945159}, {18.054841, 20.353045}, {17.527864, 19.702282}, {17.071797, 19}, {16.691636, 18.253893}, {16.391548, 17.472136}, {16.174819, 16.663294}, {16.043825, 15.836228}, {16, 15}, {16.043825, 14.163772}, {16.174819, 13.336706}, {16.391548, 12.527864}, {16.691636, 11.746107}, {17.071797, 11}, {17.527864, 10.297718}, {18.054841, 9.646955}, {18.646955, 9.054841}, {19.297718, 8.527864}, {20, 8.071797}, {20.746107, 7.691636}, {21.527864, 7.391548}, {22.336706, 7.174819}, {23.163772, 7.043825}}}, 158 expected: poly.Polygon[T]{{{36, 7}, {24, 7}, {23.163772, 7.043825}, {22.336706, 7.174819}, {21.527864, 7.391548}, {20.746107, 7.691636}, {20, 8.071797}, {19.297718, 8.527864}, {18.646955, 9.054841}, {18.054841, 9.646955}, {17.527864, 10.297718}, {17.071797, 11}, {16.691636, 11.746107}, {16.391548, 12.527864}, {16.174819, 13.336706}, {16.043825, 14.163772}, {16, 15}, {16.043825, 15.836228}, {16.174819, 16.663294}, {16.391548, 17.472136}, {16.691636, 18.253893}, {17.071797, 19}, {17.527864, 19.702282}, {18.054841, 20.353045}, {18.646955, 20.945159}, {19.297718, 21.472136}, {20, 21.928203}, {20.746107, 22.308364}, {21.527864, 22.608452}, {22.336706, 22.825181}, {23.163772, 22.956175}, {24, 23}, {36, 23}}}, 159 }, 160 { 161 name: "union #11", 162 subject: poly.Polygon[T]{{{160.094495, 200.379924}, {90.094495, 200.379924}, {55.094495, 139.758146}, {90.094495, 79.136368}}}, 163 clipping: poly.Polygon[T]{{{82.846611, 131.518814}, {66.592063, 159.672518}, {90.094495, 200.379924}, {160.094495, 200.379924}}}, 164 expected: poly.Polygon[T]{{{90.0945, 79.13637}, {55.094494, 139.75815}, {66.592064, 159.67252}, {90.0945, 200.37993}, {160.0945, 200.37993}}}, 165 }, 166 { 167 name: "union #12", 168 subject: poly.Polygon[T]{{{2.500055, 20.615429}, {42.500055, -19.384571}, {82.500055, 20.615429}, {42.500055, 60.615429}}}, 169 clipping: poly.Polygon[T]{{{7.604714, 25.720088}, {32.55986, 50.675234}, {36.852887, 46.382207}, {11.897741, 21.427062}}}, 170 expected: poly.Polygon[T]{{{82.50005, 20.615429}, {42.500053, -19.384571}, {2.500055, 20.615429}, {7.6047134, 25.720087}, {7.604714, 25.720087}, {32.55986, 50.675236}, {42.500053, 60.61543}}}, 171 }, 172 { 173 name: "union overlapping segments #1", 174 subject: poly.Polygon[T]{{{0, 0}, {10, 0}, {0, 10}}}, 175 clipping: poly.Polygon[T]{{{6, 5}, {6, 4}, {5, 5}}}, 176 expected: poly.Polygon[T]{{{5, 5}, {6, 5}, {6, 4}, {10, 0}, {0, 0}, {0, 10}}}, 177 }, 178 { 179 name: "union overlapping segments #2", 180 subject: poly.Polygon[T]{{{0, 0}, {10, 0}, {0, 10}}}, 181 clipping: poly.Polygon[T]{{{6, 4}, {4, 5}, {5, 5}}}, 182 expected: poly.Polygon[T]{{{5, 5}, {6, 4}, {10, 0}, {0, 0}, {0, 10}}}, 183 }, 184 { 185 name: "union overlapping segments #3", 186 subject: poly.Polygon[T]{{{0, 10}, {10, 10}, {0, 0}}}, 187 clipping: poly.Polygon[T]{{{6, 5}, {6, 6}, {5, 5}}}, 188 expected: poly.Polygon[T]{{{6, 6}, {6, 5}, {5, 5}, {0, 0}, {0, 10}, {10, 10}}}, 189 }, 190 { 191 name: "union overlapping segments #4", 192 subject: poly.Polygon[T]{{{0, 10}, {10, 10}, {0, 0}}}, 193 clipping: poly.Polygon[T]{{{5, 5}, {5, 6}, {6, 6}}}, 194 expected: poly.Polygon[T]{{{6, 6}, {5, 5}, {0, 0}, {0, 10}, {10, 10}}}, 195 }, 196 { 197 name: "union overlapping segments #5", 198 subject: poly.Polygon[T]{{{40131, 8372}, {40127, 8374}, {40126, 8375}, {40125, 8375}, {40122, 8377}, {40143, 8369}}}, 199 clipping: poly.Polygon[T]{{{40106, 8376}, {40128, 8373}, {40126, 8375}}}, 200 expected: poly.Polygon[T]{{{40143, 8369}, {40131, 8372}, {40127, 8374}, {40128, 8373}, {40106, 8376}, {40124.918919, 8375.054054}, {40122, 8377}}}, 201 }, 202 { 203 name: "union shared vertex #1", 204 subject: poly.Polygon[T]{ 205 {{0, 6}, {5, 5}, {2, 10}}, 206 {{4, 0}, {10, 4}, {5, 5}}, 207 }, 208 clipping: poly.Polygon[T]{{{5, 5}, {10, 8}, {8, 10}}}, 209 expected: poly.Polygon[T]{ 210 {{10, 8}, {5, 5}, {8, 10}}, 211 {{5, 5}, {0, 6}, {2, 10}}, 212 {{10, 4}, {4, 0}, {5, 5}}, 213 }, 214 }, 215 { 216 name: "union shared vertex #2", 217 subject: poly.Polygon[T]{ 218 {{0, 6}, {5, 5}, {5, 10}}, 219 {{5, 0}, {10, 4}, {5, 5}}, 220 }, 221 clipping: poly.Polygon[T]{{{5, 5}, {10, 8}, {8, 10}}}, 222 expected: poly.Polygon[T]{ 223 {{10, 8}, {5, 5}, {8, 10}}, 224 {{5, 5}, {10, 4}, {5, 0}, {5, 5}, {0, 6}, {5, 10}}, 225 }, 226 }, 227 { 228 name: "union shared vertex #3", 229 subject: poly.Polygon[T]{ 230 {{0, 6}, {5, 5}, {2, 10}}, 231 {{6, 0}, {10, 4}, {5, 5}}, 232 }, 233 clipping: poly.Polygon[T]{{{5, 5}, {10, 8}, {8, 10}}}, 234 expected: poly.Polygon[T]{ 235 {{10, 8}, {5, 5}, {8, 10}}, 236 {{5, 5}, {0, 6}, {2, 10}}, 237 {{10, 4}, {6, 0}, {5, 5}}, 238 }, 239 }, 240 { 241 name: "union self-intersection", 242 subject: poly.Polygon[T]{ 243 {{0, 0}, {1, 0}, {1, 1}, {0, 1}}, 244 {{1, 0}, {2, 0}, {2, 1}, {1, 1}}, 245 }, 246 clipping: poly.Polygon[T]{{{0, 0.25}, {3, 0.25}, {3, 0.75}, {0, 0.75}}}, 247 expected: poly.Polygon[T]{{{2, 0.75}, {3, 0.75}, {3, 0.25}, {2, 0.25}, {2, 0}, {0, 0}, {0, 0.25}, {0, 0.75}, {0, 1}, {2, 1}}}, 248 }, 249 } 250 for i, test := range tests { 251 result := test.subject.Union(test.clipping) 252 check.True(t, matchPolys(test.expected, result), "%T test case %d (%s): %v", test.subject, i, test.name, result) 253 } 254 } 255 256 func TestIntersect(t *testing.T) { 257 testIntersect[float32](t) 258 if floatsAreConsistentAcrossPlatforms { 259 testIntersect[float64](t) 260 } 261 } 262 263 func testIntersect[T constraints.Float](t *testing.T) { 264 tests := []testCase[T]{ 265 { 266 name: "intersect #1", 267 subject: poly.Polygon[T]{{{160.094495, 200.379924}, {90.094495, 200.379924}, {55.094495, 139.758146}, {90.094495, 79.136368}}}, 268 clipping: poly.Polygon[T]{{{82.846611, 131.518814}, {66.592063, 159.672518}, {90.094495, 200.379924}, {160.094495, 200.379924}}}, 269 expected: poly.Polygon[T]{{{82.846611, 131.518814}, {66.592063, 159.672518}, {90.094495, 200.379924}, {160.094495, 200.379924}}}, 270 }, 271 { 272 name: "intersect #2", 273 subject: poly.Polygon[T]{{{131.595971, 287.938524}, {100, 273.205081}, {71.442478, 253.208889}, {71.442478, -53.208889}, {100, -73.205081}, {131.595971, -87.938524}}}, 274 clipping: poly.Polygon[T]{{{128.557522, -53.208889}, {100, -73.205081}, {100, -73.205081}, {71.442478, -53.208889}, {71.442478, 253.208889}, {100, 273.205081}, {128.557522, 253.208889}}}, 275 expected: poly.Polygon[T]{{{128.557522, 253.208889}, {128.557522, -53.208889}, {100, -73.205081}, {71.442478, -53.208889}, {71.442478, 253.208889}, {100, 273.205081}}}, 276 }, 277 { 278 name: "intersect #3", 279 subject: poly.Polygon[T]{{{100, 100}, {98.480775, 117.364818}, {98.480775, 82.635182}}}, 280 clipping: poly.Polygon[T]{{{100, 100}, {100, 99.999999}, {100, 100}, {100, 100.000001}, {100, 100}}}, 281 expected: poly.Polygon[T]{}, 282 }, 283 { 284 name: "intersect #4", 285 subject: poly.Polygon[T]{{{100, 100}, {98.480775, 117.364818}, {98.480775, 82.635182}}}, 286 clipping: poly.Polygon[T]{{{100, 100}, {100, 100}, {100, 100}, {100, 100}, {100, 100}}}, 287 expected: poly.Polygon[T]{}, 288 }, 289 { 290 name: "intersect #5", 291 subject: poly.Polygon[T]{{{2.500055, 20.615429}, {42.500055, -19.384571}, {82.500055, 20.615429}, {42.500055, 60.615429}}}, 292 clipping: poly.Polygon[T]{{{7.604714, 25.720088}, {11.897741, 21.427062}, {36.852887, 46.382207}, {32.55986, 50.675234}}}, 293 expected: poly.Polygon[T]{{{36.852887, 46.382207}, {11.897741, 21.427062}, {7.604714, 25.720088}, {32.55986, 50.675234}}}, 294 }, 295 { 296 name: "intersect #6", 297 subject: poly.Polygon[T]{{{300, -100}, {259.807621, 50}, {-259.807621, 50}, {-300, -100}}}, 298 clipping: poly.Polygon[T]{{{273.205081, 0}, {259.807621, -50}, {-259.807621, -50}, {-273.205081, -0}, {-259.807621, 50}, {259.807621, 50}}}, 299 expected: poly.Polygon[T]{{{273.205081, -0.000001}, {259.807621, -50}, {-259.807621, -50}, {-273.205081, -0.000001}, {-259.807621, 50}, {259.807621, 50}}}, 300 }, 301 { 302 name: "intersect #7", 303 subject: poly.Polygon[T]{{{300, -100}, {277.16386, 14.80503}, {-277.16386, 14.80503}, {-300, -100}}}, 304 clipping: poly.Polygon[T]{{{280.108763, 0}, {277.16386, -14.80503}, {-277.16386, -14.80503}, {-280.108763, -0}, {-277.16386, 14.80503}, {277.16386, 14.80503}, {280.108763, 0}}}, 305 expected: poly.Polygon[T]{{{280.108763, 0}, {277.16386, -14.80503}, {-277.16386, -14.80503}, {-280.108763, -0}, {-277.16386, 14.80503}, {277.16386, 14.80503}}}, 306 }, 307 { 308 name: "intersect #8", 309 subject: poly.Polygon[T]{{{-196.961551, 65.270364}, {-187.938524, 31.595971}, {-173.205081, 0}, {-153.208889, -28.557522}, {153.208889, -28.557522}, {173.205081, -0}, {187.938524, 31.595971}, {196.961551, 65.270364}}}, 310 clipping: poly.Polygon[T]{{{196.961551, -65.270364}, {187.938524, -31.595971}, {173.205081, -0}, {153.208889, 28.557522}, {-153.208889, 28.557522}, {-173.205081, -0}, {-187.938524, -31.595971}, {-196.961551, -65.270364}}}, 311 expected: poly.Polygon[T]{{{173.205081, 0}, {153.208889, -28.557522}, {-153.208889, -28.557522}, {-173.205081, 0}, {-153.208889, 28.557522}, {153.208889, 28.557522}}}, 312 }, 313 { 314 name: "intersect #9", 315 subject: poly.Polygon[T]{{{128.557522, 253.208889}, {100, 273.205081}, {68.404029, 287.938524}, {68.404029, -87.938524}, {100, -73.205081}, {128.557522, -53.208889}}}, 316 clipping: poly.Polygon[T]{{{131.595971, 287.938524}, {100, 273.205081}, {71.442478, 253.208889}, {71.442478, -53.208889}, {100, -73.205081}, {131.595971, -87.938524}}}, 317 expected: poly.Polygon[T]{{{100, 273.205081}, {128.557522, 253.208889}, {128.557522, -53.208889}, {100, -73.205081}, {71.442478, -53.208889}, {71.442478, 253.208889}, {100, 273.205081}}}, 318 }, 319 { 320 name: "intersect #10", 321 subject: poly.Polygon[T]{{{24, 7}, {36, 7}, {36, 23}, {24, 23}}}, 322 clipping: poly.Polygon[T]{{{24, 7}, {24.836228, 7.043825}, {25.663294, 7.174819}, {26.472136, 7.391548}, {27.253893, 7.691636}, {28, 8.071797}, {28.702282, 8.527864}, {29.353045, 9.054841}, {29.945159, 9.646955}, {30.472136, 10.297718}, {30.928203, 11}, {31.308364, 11.746107}, {31.608452, 12.527864}, {31.825181, 13.336706}, {31.956175, 14.163772}, {32, 15}, {31.956175, 15.836228}, {31.825181, 16.663294}, {31.608452, 17.472136}, {31.308364, 18.253893}, {30.928203, 19}, {30.472136, 19.702282}, {29.945159, 20.353045}, {29.353045, 20.945159}, {28.702282, 21.472136}, {28, 21.928203}, {27.253893, 22.308364}, {26.472136, 22.608452}, {25.663294, 22.825181}, {24.836228, 22.956175}, {24, 23}, {23.163772, 22.956175}, {22.336706, 22.825181}, {21.527864, 22.608452}, {20.746107, 22.308364}, {20, 21.928203}, {19.297718, 21.472136}, {18.646955, 20.945159}, {18.054841, 20.353045}, {17.527864, 19.702282}, {17.071797, 19}, {16.691636, 18.253893}, {16.391548, 17.472136}, {16.174819, 16.663294}, {16.043825, 15.836228}, {16, 15}, {16.043825, 14.163772}, {16.174819, 13.336706}, {16.391548, 12.527864}, {16.691636, 11.746107}, {17.071797, 11}, {17.527864, 10.297718}, {18.054841, 9.646955}, {18.646955, 9.054841}, {19.297718, 8.527864}, {20, 8.071797}, {20.746107, 7.691636}, {21.527864, 7.391548}, {22.336706, 7.174819}, {23.163772, 7.043825}}}, 323 expected: poly.Polygon[T]{{{24.836228, 22.956175}, {25.663294, 22.825181}, {26.472136, 22.608452}, {27.253893, 22.308364}, {28, 21.928203}, {28.702282, 21.472136}, {29.353045, 20.945159}, {29.945159, 20.353045}, {30.472136, 19.702282}, {30.928203, 19}, {31.308364, 18.253893}, {31.608452, 17.472136}, {31.825181, 16.663294}, {31.956175, 15.836228}, {32, 15}, {31.956175, 14.163772}, {31.825181, 13.336706}, {31.608452, 12.527864}, {31.308364, 11.746107}, {30.928203, 11}, {30.472136, 10.297718}, {29.945159, 9.646955}, {29.353045, 9.054841}, {28.702282, 8.527864}, {28, 8.071797}, {27.253893, 7.691636}, {26.472136, 7.391548}, {25.663294, 7.174819}, {24.836228, 7.043825}, {24, 7}, {24, 23}}}, 324 }, 325 { 326 name: "intersect #11", 327 subject: poly.Polygon[T]{{{1568, 360.5}, {1567.2041, 392.66965}, {1564.8181, 424.76056}, {1560.8481, 456.69424}, {1555.3038, 488.3925}, {1548.1987, 519.7778}, {1539.55, 550.7733}, {1529.3793, 581.30316}, {1517.711, 611.2927}, {1504.574, 640.6685}, {1490.0002, 669.3587}, {1474.0255, 697.2931}, {1456.689, 724.4032}, {1438.0328, 750.6228}, {1418.1029, 775.8878}, {1396.9479, 800.1361}, {1374.6196, 823.3087}, {1351.1727, 845.34863}, {1326.6646, 866.20215}, {1301.155, 885.81805}, {1274.7067, 904.14844}, {1247.3842, 921.14844}, {1219.2544, 936.7764}, {1190.3861, 950.9942}, {1160.8501, 963.7669}, {1130.7186, 975.06335}, {1100.0653, 984.8558}, {1068.9652, 993.12036}, {1037.4944, 999.83685}, {1005.73004, 1004.9888}, {973.7497, 1008.5634}, {941.6318, 1010.55225}, {909.4547, 1010.95026}, {877.2974, 1009.75653}, {845.23846, 1006.97394}, {813.3563, 1002.6093}, {781.7291, 996.67334}, {750.4341, 989.18054}, {719.548, 980.1493}, {689.14624, 969.6016}, {659.3033, 957.5634}, {630.09216, 944.064}, {601.58435, 929.1366}, {573.8496, 912.81757}, {546.95593, 895.1469}, {520.969, 876.1679}, {495.95242, 855.927}, {471.96744, 834.47363}, {449.07278, 811.86035}, {427.32446, 788.1426}, {406.77567, 763.3783}, {387.47675, 737.62805}, {369.4748, 710.95496}, {352.8141, 683.4242}, {337.53516, 655.10315}, {323.67554, 626.0613}, {311.26917, 596.3694}, {300.34625, 566.10034}, {290.93372, 535.3282}, {283.05444, 504.12817}, {276.72772, 472.57666}, {271.96918, 440.75092}, {268.79034, 408.72873}, {267.19897, 376.58856}, {267.19904, 344.409}, {268.79047, 312.26883}, {271.96948, 280.24667}, {276.72815, 248.42093}, {283.055, 216.86945}, {290.93433, 185.66945}, {300.34705, 154.89732}, {311.27002, 124.62831}, {323.67657, 94.93652}, {337.53625, 65.89465}, {352.81525, 37.5737}, {369.47614, 10.042999}, {387.47815, -16.630066}, {406.7772, -42.38022}, {427.32608, -67.14444}, {449.0745, -90.86218}, {471.96924, -113.47528}, {495.95428, -134.92856}, {520.9708, -155.16931}, {546.95764, -174.14813}, {573.8513, -191.8186}, {601.58594, -208.13745}, {630.0936, -223.06476}, {659.3047, -236.56403}, {689.1476, -248.60211}, {719.54926, -259.14972}, {750.4353, -268.18085}, {781.7303, -275.67358}, {813.3575, -281.6095}, {845.2395, -285.974}, {877.2984, -288.75653}, {909.4556, -289.95026}, {941.63257, -289.5522}, {973.7504, -287.56335}, {1005.73065, -283.98865}, {1037.495, -278.83673}, {1068.9657, -272.12024}, {1100.0657, -263.85565}, {1130.7189, -254.06323}, {1160.8503, -242.76678}, {1190.3864, -229.99414}, {1219.2544, -215.77643}, {1247.3843, -200.14844}, {1274.7067, -183.1485}, {1301.1549, -164.81818}, {1326.6643, -145.20227}, {1351.1725, -124.34885}, {1374.6194, -102.30896}, {1396.9476, -79.136505}, {1418.1025, -54.888153}, {1438.0325, -29.623291}, {1456.6886, -3.403748}, {1474.0251, 23.70633}, {1489.9999, 51.640564}, {1504.5736, 80.33066}, {1517.7107, 109.70636}, {1529.3789, 139.69579}, {1539.5498, 170.22556}, {1548.1984, 201.22096}, {1555.3036, 232.60616}, {1560.848, 264.30432}, {1564.8181, 296.23785}, {1567.204, 328.3287}}}, 328 clipping: poly.Polygon[T]{{{449, 399}, {400, 350}, {267, 347}, {267, -290}, {267, -290}, {1567, -290}, {1567, -291}, {1567, 1010}, {1567, 1010}, {268, 1010}, {267, 1010}, {267, 415}}}, 329 expected: poly.Polygon[T]{{{973.7497, 1008.5634}, {1005.73004, 1004.9888}, {1037.4944, 999.83685}, {1068.9652, 993.12036}, {1100.0653, 984.8558}, {1130.7186, 975.06335}, {1160.8501, 963.7669}, {1190.3861, 950.9942}, {1219.2544, 936.7764}, {1247.3842, 921.14844}, {1274.7067, 904.14844}, {1301.155, 885.81805}, {1326.6646, 866.20215}, {1351.1727, 845.34863}, {1374.6196, 823.3087}, {1396.9479, 800.1361}, {1418.1029, 775.8878}, {1438.0328, 750.6228}, {1456.689, 724.4032}, {1474.0255, 697.2931}, {1490.0002, 669.3587}, {1504.574, 640.6685}, {1517.711, 611.2927}, {1529.3793, 581.30316}, {1539.55, 550.7733}, {1548.1987, 519.7778}, {1555.3038, 488.3925}, {1560.8481, 456.69424}, {1564.8181, 424.76056}, {1567, 395.41455}, {1567, 325.58502}, {1564.8181, 296.23785}, {1560.848, 264.30432}, {1555.3036, 232.60616}, {1548.1984, 201.22096}, {1539.5498, 170.22556}, {1529.3789, 139.69579}, {1517.7107, 109.70636}, {1504.5736, 80.33066}, {1489.9999, 51.640564}, {1474.0251, 23.70633}, {1456.6886, -3.403748}, {1438.0325, -29.623291}, {1418.1025, -54.888153}, {1396.9476, -79.136505}, {1374.6194, -102.30896}, {1351.1725, -124.34885}, {1326.6643, -145.20227}, {1301.1549, -164.81818}, {1274.7067, -183.1485}, {1247.3843, -200.14844}, {1219.2544, -215.77643}, {1190.3864, -229.99414}, {1160.8503, -242.76678}, {1130.7189, -254.06323}, {1100.0657, -263.85565}, {1068.9657, -272.12024}, {1037.495, -278.83673}, {1005.73065, -283.98865}, {973.7504, -287.56335}, {941.63257, -289.5522}, {909.4556, -289.95026}, {877.2984, -288.75653}, {845.2395, -285.974}, {813.3575, -281.6095}, {781.7303, -275.67358}, {750.4353, -268.18085}, {719.54926, -259.14972}, {689.1476, -248.60211}, {659.3047, -236.56403}, {630.0936, -223.06476}, {601.58594, -208.13745}, {573.8513, -191.8186}, {546.95764, -174.14813}, {520.9708, -155.16931}, {495.95428, -134.92856}, {471.96924, -113.47528}, {449.0745, -90.86218}, {427.32608, -67.14444}, {406.7772, -42.38022}, {387.47815, -16.630066}, {369.47614, 10.042999}, {352.81525, 37.5737}, {337.53625, 65.89465}, {323.67657, 94.93652}, {311.27002, 124.62831}, {300.34705, 154.89732}, {290.93433, 185.66945}, {283.055, 216.86945}, {276.72815, 248.42093}, {271.96948, 280.24667}, {268.79047, 312.26883}, {267.19904, 344.409}, {267.19904, 347.0045}, {400, 350}, {449, 399}, {269.39203, 414.7897}, {271.96918, 440.75092}, {276.72772, 472.57666}, {283.05444, 504.12817}, {290.93372, 535.3282}, {300.34625, 566.10034}, {311.26917, 596.3694}, {323.67554, 626.0613}, {337.53516, 655.10315}, {352.8141, 683.4242}, {369.4748, 710.95496}, {387.47675, 737.62805}, {406.77567, 763.3783}, {427.32446, 788.1426}, {449.07278, 811.86035}, {471.96744, 834.47363}, {495.95242, 855.927}, {520.969, 876.1679}, {546.95593, 895.1469}, {573.8496, 912.81757}, {601.58435, 929.1366}, {630.09216, 944.064}, {659.3033, 957.5634}, {689.14624, 969.6016}, {719.548, 980.1493}, {750.4341, 989.18054}, {781.7291, 996.67334}, {813.3563, 1002.6093}, {845.23846, 1006.97394}, {877.2974, 1009.75653}, {883.85614, 1010}, {950.55005, 1010}}}, 330 }, 331 { 332 name: "intersect #12", 333 subject: poly.Polygon[T]{{{2.500055, 20.615429}, {42.500055, -19.384571}, {82.500055, 20.615429}, {42.500055, 60.615429}}}, 334 clipping: poly.Polygon[T]{{{7.604714, 25.720088}, {11.897741, 21.427062}, {36.852887, 46.382207}, {32.55986, 50.675234}}}, 335 expected: poly.Polygon[T]{{{36.852887, 46.382207}, {11.897741, 21.427062}, {7.604714, 25.720088}, {32.55986, 50.675234}}}, 336 }, 337 { 338 name: "intersect #13", 339 subject: poly.Polygon[T]{{{0, 10}, {0, 0}, {10, 0}, {10, 10}}}, 340 clipping: poly.Polygon[T]{{{0, 5}, {5, 0}, {10, 5}, {5, 10}}}, 341 expected: poly.Polygon[T]{{{10, 5}, {5, 0}, {0, 5}, {5, 10}}}, 342 }, 343 { 344 name: "intersect self-intersection", 345 subject: poly.Polygon[T]{ 346 {{0, 0}, {1, 0}, {1, 1}, {0, 1}}, 347 {{1, 0}, {2, 0}, {2, 1}, {1, 1}}, 348 }, 349 clipping: poly.Polygon[T]{{{0, 0.25}, {3, 0.25}, {3, 0.75}, {0, 0.75}}}, 350 expected: poly.Polygon[T]{{{2, 0.25}, {0, 0.25}, {0, 0.75}, {2, 0.75}}}, 351 }, 352 } 353 for i, test := range tests { 354 result := test.subject.Intersect(test.clipping) 355 check.True(t, matchPolys(test.expected, result), "%T test case %d (%s): %v", test.subject, i, test.name, result) 356 } 357 } 358 359 func TestSubtract(t *testing.T) { 360 testSubtract[float32](t) 361 if floatsAreConsistentAcrossPlatforms { 362 testSubtract[float64](t) 363 } 364 } 365 366 func testSubtract[T constraints.Float](t *testing.T) { 367 tests := []testCase[T]{ 368 { 369 name: "subtract #1", 370 subject: poly.Polygon[T]{{{160.094495, 200.379924}, {90.094495, 200.379924}, {55.094495, 139.758146}, {90.094495, 79.136368}}}, 371 clipping: poly.Polygon[T]{{{82.846611, 131.518814}, {66.592063, 159.672518}, {90.094495, 200.379924}, {160.094495, 200.379924}}}, 372 expected: poly.Polygon[T]{{{90.094495, 79.136368}, {55.094495, 139.758146}, {66.592063, 159.672518}, {82.846611, 131.518814}, {160.094495, 200.379924}}}, 373 }, 374 { 375 name: "subtract #2", 376 subject: poly.Polygon[T]{{{2.500055, 20.615429}, {42.500055, -19.384571}, {82.500055, 20.615429}, {42.500055, 60.615429}}}, 377 clipping: poly.Polygon[T]{{{7.604714, 25.720088}, {32.55986, 50.675234}, {36.852887, 46.382207}, {11.897741, 21.427062}}}, 378 expected: poly.Polygon[T]{{{82.50005, 20.615429}, {42.500053, -19.384571}, {2.500055, 20.615429}, {7.6047134, 25.720087}, {7.604714, 25.720087}, {11.897741, 21.427061}, {36.852886, 46.382206}, {32.55986, 50.675236}, {42.500053, 60.61543}}}, 379 }, 380 { 381 name: "subtract #3", 382 subject: poly.Polygon[T]{{{38.572124, 172.339556}, {40, 171.339746}, {41.579799, 170.603074}, {43.263518, 170.151922}, {45, 170}, {46.736482, 170.151922}, {48.420201, 170.603074}, {50, 171.339746}, {51.427876, 172.339556}}}, 383 clipping: poly.Polygon[T]{{{51.427876, 172.339556}, {50, 171.339746}, {48.420201, 170.603074}, {46.736482, 170.151922}, {45, 170}, {43.263518, 170.151922}, {42.781168, 170.281168}, {42.651922, 170.763518}, {42.5, 172}}}, 384 expected: poly.Polygon[T]{{{42.5, 172}, {42.651922, 170.763518}, {42.781168, 170.281168}, {43.263518, 170.151922}, {41.579799, 170.603074}, {40, 171.339746}, {38.572124, 172.339556}, {51.427876, 172.339556}}}, 385 }, 386 { 387 name: "subtract #4", 388 subject: poly.Polygon[T]{{{24, 7}, {36, 7}, {36, 23}, {24, 23}}}, 389 clipping: poly.Polygon[T]{{{24, 7}, {24.836228, 7.043825}, {25.663294, 7.174819}, {26.472136, 7.391548}, {27.253893, 7.691636}, {28, 8.071797}, {28.702282, 8.527864}, {29.353045, 9.054841}, {29.945159, 9.646955}, {30.472136, 10.297718}, {30.928203, 11}, {31.308364, 11.746107}, {31.608452, 12.527864}, {31.825181, 13.336706}, {31.956175, 14.163772}, {32, 15}, {31.956175, 15.836228}, {31.825181, 16.663294}, {31.608452, 17.472136}, {31.308364, 18.253893}, {30.928203, 19}, {30.472136, 19.702282}, {29.945159, 20.353045}, {29.353045, 20.945159}, {28.702282, 21.472136}, {28, 21.928203}, {27.253893, 22.308364}, {26.472136, 22.608452}, {25.663294, 22.825181}, {24.836228, 22.956175}, {24, 23}, {23.163772, 22.956175}, {22.336706, 22.825181}, {21.527864, 22.608452}, {20.746107, 22.308364}, {20, 21.928203}, {19.297718, 21.472136}, {18.646955, 20.945159}, {18.054841, 20.353045}, {17.527864, 19.702282}, {17.071797, 19}, {16.691636, 18.253893}, {16.391548, 17.472136}, {16.174819, 16.663294}, {16.043825, 15.836228}, {16, 15}, {16.043825, 14.163772}, {16.174819, 13.336706}, {16.391548, 12.527864}, {16.691636, 11.746107}, {17.071797, 11}, {17.527864, 10.297718}, {18.054841, 9.646955}, {18.646955, 9.054841}, {19.297718, 8.527864}, {20, 8.071797}, {20.746107, 7.691636}, {21.527864, 7.391548}, {22.336706, 7.174819}, {23.163772, 7.043825}}}, 390 expected: poly.Polygon[T]{{{36, 7}, {24, 7}, {24.836228, 7.043825}, {25.663294, 7.174819}, {26.472136, 7.391548}, {27.253893, 7.691636}, {28, 8.071797}, {28.702282, 8.527864}, {29.353045, 9.054841}, {29.945159, 9.646955}, {30.472136, 10.297718}, {30.928203, 11}, {31.308364, 11.746107}, {31.608452, 12.527864}, {31.825181, 13.336706}, {31.956175, 14.163772}, {32, 15}, {31.956175, 15.836228}, {31.825181, 16.663294}, {31.608452, 17.472136}, {31.308364, 18.253893}, {30.928203, 19}, {30.472136, 19.702282}, {29.945159, 20.353045}, {29.353045, 20.945159}, {28.702282, 21.472136}, {28, 21.928203}, {27.253893, 22.308364}, {26.472136, 22.608452}, {25.663294, 22.825181}, {24.836228, 22.956175}, {24, 23}, {36, 23}}}, 391 }, 392 { 393 name: "subtract #5", 394 subject: poly.Polygon[T]{{{114, 0}, {161, 0}, {114, 168}}}, 395 clipping: poly.Polygon[T]{{{99, 164}, {114, 108}, {121, 164}}}, 396 expected: poly.Polygon[T]{ 397 {{115.11905, 164}, {114, 164}, {114, 168}}, 398 {{161, 0}, {114, 0}, {114, 108}, {119.18382, 149.4706}}, 399 }, 400 }, 401 { 402 name: "subtract #6", 403 subject: poly.Polygon[T]{{{426694.636527, -668547.161158}, {426714.57523, -668548.923865}, {426745.396481, -668550.465125}}}, 404 clipping: poly.Polygon[T]{{{426714.57523, -668548.923865}, {426744.637187, -668550.05919}, {426745.396482, -668550.465224}}}, 405 expected: poly.Polygon[T]{{{426732.4, -668549.6}, {426714.56, -668548.94}, {426694.62, -668547.2}}}, 406 }, 407 { 408 name: "subtract #7", 409 subject: poly.Polygon[T]{{{38.572124, 172.339556}, {40, 171.339746}, {41.579799, 170.603074}, {43.263518, 170.151922}, {45, 170}, {46.736482, 170.151922}, {48.420201, 170.603074}, {50, 171.339746}, {51.427876, 172.339556}}}, 410 clipping: poly.Polygon[T]{{{51.427876, 172.339556}, {50, 171.339746}, {48.420201, 170.603074}, {46.736482, 170.151922}, {45, 170}, {43.263518, 170.151922}, {42.781168, 170.281168}, {42.651922, 170.763518}, {42.5, 172}}}, 411 expected: poly.Polygon[T]{{{42.5, 172}, {42.651922, 170.763518}, {42.781168, 170.281168}, {43.263518, 170.151922}, {41.579799, 170.603074}, {40, 171.339746}, {38.572124, 172.339556}, {51.427876, 172.339556}}}, 412 }, 413 { 414 name: "subtract #8", 415 subject: poly.Polygon[T]{ 416 {{0, 0}, {1, 0}, {1, 1}, {0, 1}}, 417 {{1, 0}, {2, 0}, {2, 1}, {1, 1}}, 418 }, 419 clipping: poly.Polygon[T]{{{0, 0.25}, {3, 0.25}, {3, 0.75}, {0, 0.75}}}, 420 expected: poly.Polygon[T]{ 421 {{2, 0.75}, {0, 0.75}, {0, 1}, {2, 1}}, 422 {{2, 0}, {0, 0}, {0, 0.25}, {2, 0.25}}, 423 }, 424 }, 425 { 426 name: "subtract self-intersect", 427 subject: poly.Polygon[T]{ 428 {{0, 0}, {1, 0}, {1, 1}, {0, 1}}, 429 {{1, 0}, {2, 0}, {2, 1}, {1, 1}}, 430 }, 431 clipping: poly.Polygon[T]{{{0, 0.25}, {3, 0.25}, {3, 0.75}, {0, 0.75}}}, 432 expected: poly.Polygon[T]{ 433 {{2, 0.75}, {0, 0.75}, {0, 1}, {2, 1}}, 434 {{2, 0}, {0, 0}, {0, 0.25}, {2, 0.25}}, 435 }, 436 }, 437 } 438 for i, test := range tests { 439 result := test.subject.Sub(test.clipping) 440 check.True(t, matchPolys(test.expected, result), "%T test case %d (%s): %v", test.subject, i, test.name, result) 441 } 442 } 443 444 func TestXor(t *testing.T) { 445 testXor[float32](t) 446 testXor[float64](t) 447 } 448 449 func testXor[T constraints.Float](t *testing.T) { 450 tests := []testCase[T]{ 451 { 452 name: "xor #1", 453 subject: poly.Polygon[T]{{{24, 7}, {36, 7}, {36, 23}, {24, 23}}}, 454 clipping: poly.Polygon[T]{{{24, 7}, {24.836228, 7.043825}, {25.663294, 7.174819}, {26.472136, 7.391548}, {27.253893, 7.691636}, {28, 8.071797}, {28.702282, 8.527864}, {29.353045, 9.054841}, {29.945159, 9.646955}, {30.472136, 10.297718}, {30.928203, 11}, {31.308364, 11.746107}, {31.608452, 12.527864}, {31.825181, 13.336706}, {31.956175, 14.163772}, {32, 15}, {31.956175, 15.836228}, {31.825181, 16.663294}, {31.608452, 17.472136}, {31.308364, 18.253893}, {30.928203, 19}, {30.472136, 19.702282}, {29.945159, 20.353045}, {29.353045, 20.945159}, {28.702282, 21.472136}, {28, 21.928203}, {27.253893, 22.308364}, {26.472136, 22.608452}, {25.663294, 22.825181}, {24.836228, 22.956175}, {24, 23}, {23.163772, 22.956175}, {22.336706, 22.825181}, {21.527864, 22.608452}, {20.746107, 22.308364}, {20, 21.928203}, {19.297718, 21.472136}, {18.646955, 20.945159}, {18.054841, 20.353045}, {17.527864, 19.702282}, {17.071797, 19}, {16.691636, 18.253893}, {16.391548, 17.472136}, {16.174819, 16.663294}, {16.043825, 15.836228}, {16, 15}, {16.043825, 14.163772}, {16.174819, 13.336706}, {16.391548, 12.527864}, {16.691636, 11.746107}, {17.071797, 11}, {17.527864, 10.297718}, {18.054841, 9.646955}, {18.646955, 9.054841}, {19.297718, 8.527864}, {20, 8.071797}, {20.746107, 7.691636}, {21.527864, 7.391548}, {22.336706, 7.174819}, {23.163772, 7.043825}}}, 455 expected: poly.Polygon[T]{ 456 {{36, 7}, {24, 7}, {24.836228, 7.043825}, {25.663294, 7.174819}, {26.472136, 7.391548}, {27.253893, 7.691636}, {28, 8.071797}, {28.702282, 8.527864}, {29.353045, 9.054841}, {29.945159, 9.646955}, {30.472136, 10.297718}, {30.928203, 11}, {31.308364, 11.746107}, {31.608452, 12.527864}, {31.825181, 13.336706}, {31.956175, 14.163772}, {32, 15}, {31.956175, 15.836228}, {31.825181, 16.663294}, {31.608452, 17.472136}, {31.308364, 18.253893}, {30.928203, 19}, {30.472136, 19.702282}, {29.945159, 20.353045}, {29.353045, 20.945159}, {28.702282, 21.472136}, {28, 21.928203}, {27.253893, 22.308364}, {26.472136, 22.608452}, {25.663294, 22.825181}, {24.836228, 22.956175}, {24, 23}, {36, 23}}, 457 {{24, 7}, {23.163772, 7.043825}, {22.336706, 7.174819}, {21.527864, 7.391548}, {20.746107, 7.691636}, {20, 8.071797}, {19.297718, 8.527864}, {18.646955, 9.054841}, {18.054841, 9.646955}, {17.527864, 10.297718}, {17.071797, 11}, {16.691636, 11.746107}, {16.391548, 12.527864}, {16.174819, 13.336706}, {16.043825, 14.163772}, {16, 15}, {16.043825, 15.836228}, {16.174819, 16.663294}, {16.391548, 17.472136}, {16.691636, 18.253893}, {17.071797, 19}, {17.527864, 19.702282}, {18.054841, 20.353045}, {18.646955, 20.945159}, {19.297718, 21.472136}, {20, 21.928203}, {20.746107, 22.308364}, {21.527864, 22.608452}, {22.336706, 22.825181}, {23.163772, 22.956175}, {24, 23}}, 458 }, 459 }, 460 { 461 name: "xor self-intersect", 462 subject: poly.Polygon[T]{ 463 {{0, 0}, {1, 0}, {1, 1}, {0, 1}}, 464 {{1, 0}, {2, 0}, {2, 1}, {1, 1}}, 465 }, 466 clipping: poly.Polygon[T]{{{0, 0.25}, {3, 0.25}, {3, 0.75}, {0, 0.75}}}, 467 expected: poly.Polygon[T]{ 468 {{2, 0.75}, {3, 0.75}, {3, 0.25}, {2, 0.25}, {2, 0.75}, {0, 0.75}, {0, 1}, {2, 1}}, 469 {{2, 0}, {0, 0}, {0, 0.25}, {2, 0.25}}, 470 }, 471 }, 472 } 473 for i, test := range tests { 474 result := test.subject.Xor(test.clipping) 475 check.True(t, matchPolys(test.expected, result), "%T test case %d (%s): %v", test.subject, i, test.name, result) 476 } 477 } 478 479 func matchPolys[T constraints.Float](left, right poly.Polygon[T]) bool { 480 left = simplifyPoly(left) 481 right = simplifyPoly(right) 482 if len(left) != len(right) { 483 return false 484 } 485 for i, c := range left { 486 if len(c) != len(right[i]) { 487 return false 488 } 489 for j, pt := range c { 490 if !pt.EqualWithin(right[i][j], 0.0001) { 491 return false 492 } 493 } 494 } 495 return true 496 } 497 498 func simplifyPoly[T constraints.Float](p poly.Polygon[T]) poly.Polygon[T] { 499 var revised poly.Polygon[T] 500 for _, c := range p { 501 var nc poly.Contour[T] 502 for i, pt := range c { 503 if i == 0 || !pt.EqualWithin(c[i-1], 0.0001) { 504 nc = append(nc, pt) 505 } 506 } 507 revised = append(revised, nc) 508 } 509 return revised 510 }