├── .travis.yml ├── pointchain_test.go ├── endpoint_test.go ├── sweepline_test.go ├── polyutil ├── encdec_test.go ├── draw_test.go ├── draw.go └── encdec.go ├── connector_test.go ├── test └── drawpolys.go ├── sweepline.go ├── connector.go ├── endpoint.go ├── eventqueue.go ├── README.md ├── pointchain.go ├── geom_test.go ├── geom.go ├── bugs_test.go └── clipper.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | notifications: 3 | email: false 4 | -------------------------------------------------------------------------------- /pointchain_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | // THE SOFTWARE. 19 | 20 | package polyclip 21 | 22 | import ( 23 | . "testing" 24 | ) 25 | 26 | func TestChainLinkChain(t *T) { 27 | a := chain{points: []Point{{0, 1}, {0, 2}, {0, 3}, {1, 1}}} 28 | b := chain{points: []Point{{1, 1}, {1, 2}}} 29 | verify(t, a.linkChain(&b), "Expected being able to link chains") 30 | verify(t, len(a.points) == 5, "Expected len==5, got %d", len(a.points)) 31 | } 32 | -------------------------------------------------------------------------------- /endpoint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | // THE SOFTWARE. 19 | 20 | package polyclip 21 | 22 | import ( 23 | . "testing" 24 | ) 25 | 26 | func TestAbove(t *T) { 27 | cases := []struct { 28 | left, right Point 29 | result bool 30 | x Point 31 | }{ 32 | {Point{0, 1}, Point{2, 1}, true, Point{1, 0}}, 33 | {Point{0, 1}, Point{2, 1}, false, Point{1, 3}}, 34 | } 35 | for i, v := range cases { 36 | e := &endpoint{p: v.left, left: true, other: &endpoint{p: v.right, left: false}} 37 | verify(t, e.above(v.x) == v.result, "Expected %v above %v (case %d/a)", e, v.x, i) 38 | e = &endpoint{p: v.right, left: false, other: &endpoint{p: v.left, left: true}} 39 | verify(t, e.above(v.x) == v.result, "Expected %v above %v (case %d/b)", e, v.x, i) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sweepline_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | // THE SOFTWARE. 19 | 20 | package polyclip 21 | 22 | import ( 23 | . "testing" 24 | ) 25 | 26 | func TestVerticalZigzag(t *T) { 27 | mkseg := func(xl, yl, xr, yr float64) (left *endpoint) { 28 | left = &endpoint{p: Point{xl, yl}, left: true, polygonType: _SUBJECT} 29 | right := &endpoint{p: Point{xr, yr}, polygonType: _SUBJECT, other: left} 30 | left.other = right 31 | return left 32 | } 33 | 34 | seq := []*endpoint{ 35 | mkseg(1, 0, 3, 1), 36 | mkseg(1, 2, 3, 1), 37 | mkseg(1, 2, 3, 3), 38 | mkseg(1, 4, 3, 3), 39 | } 40 | 41 | line := &sweepline{} 42 | for i := 0; i < len(seq); i++ { 43 | line.insert(seq[i]) 44 | for j := 0; j < len(*line); j++ { 45 | verify(t, (*line)[j] == seq[j], "Inserting seq[%d], expected line[%d]==%v, got %v", i, j, seq[j], (*line)[j]) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /polyutil/encdec_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | // THE SOFTWARE. 19 | 20 | package polyutil 21 | 22 | import ( 23 | "bytes" 24 | "github.com/akavel/polyclip-go" 25 | . "testing" 26 | ) 27 | 28 | func verify(t *T, cond bool, format string, args ...interface{}) { 29 | if !cond { 30 | t.Errorf(format, args...) 31 | } 32 | } 33 | 34 | func TestPolygonDecodeEncode(t *T) { 35 | txt1 := "1\n3 1\n\t0 0\n\t1 1\n\t0.5 0.5\n" 36 | 37 | p, err := DecodePolygon(bytes.NewBufferString(txt1)) 38 | verify(t, err == nil, "Expected no error decoding, got: %v", err) 39 | verify(t, len(*p) == 1, "Expected 1 contour") 40 | c := (*p)[0] 41 | verify(t, len(c) == 3, "Expected 3 points") 42 | verify(t, c[0].Equals(polyclip.Point{0, 0}), "Expected p0") 43 | verify(t, c[1].Equals(polyclip.Point{1, 1}), "Expected p1") 44 | verify(t, c[2].Equals(polyclip.Point{.5, .5}), "Expected p2") 45 | 46 | buf := &bytes.Buffer{} 47 | err = EncodePolygon(buf, *p) 48 | verify(t, err == nil, "Expected no error encoding, got: %v", err) 49 | verify(t, buf.String() == txt1, "Expected: %v, got: %v", txt1, buf) 50 | } 51 | -------------------------------------------------------------------------------- /polyutil/draw_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | // THE SOFTWARE. 19 | 20 | package polyutil 21 | 22 | import ( 23 | . "testing" 24 | ) 25 | 26 | func max(a, b int) int { 27 | if a >= b { 28 | return a 29 | } 30 | return b 31 | } 32 | 33 | func TestLine(t *T) { 34 | cases := []struct{ x0, y0, x1, y1, sx, sy int }{ 35 | {0, 0, 0, 0, 0, 0}, 36 | {0, 0, 1, 0, 1, 0}, 37 | {0, 0, -1, 1, -1, 1}, 38 | {-2, -2, 2, 2, 1, 1}, 39 | {10, 2, 20, 4, 1, 1}, 40 | {2, 10, 10, 5, 1, -1}, 41 | {1000, -50, -100, -3, -1, 1}, 42 | {10, 20, 0, -30, -1, -1}, 43 | } 44 | for i, c := range cases { 45 | p := 0 46 | lastx, lasty := c.x0, c.y0 47 | 48 | drawline(c.x0, c.y0, c.x1, c.y1, func(x, y int) { 49 | verify(t, x == lastx || x == (lastx+c.sx), "Line %d, point %d, got x=%d, expected %d or %d", i, p, x, lastx, lastx+c.sx) 50 | verify(t, y == lasty || y == (lasty+c.sy), "Line %d, point %d, got y=%d, expected %d or %d", i, p, y, lasty, lasty+c.sy) 51 | 52 | lastx, lasty = x, y 53 | p++ 54 | }) 55 | 56 | verify(t, lastx == c.x1, "After line %d, got x==%d, expected %d", i, lastx, c.x1) 57 | verify(t, lasty == c.y1, "After line %d, got y==%d, expected %d", i, lasty, c.y1) 58 | pn := max(abs(c.x1-c.x0), abs(c.y1-c.y0)) + 1 59 | verify(t, p == pn, "After line %d, got %d points, expected %d", i, p, pn) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /connector_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | // THE SOFTWARE. 19 | 20 | package polyclip 21 | 22 | import ( 23 | . "testing" 24 | ) 25 | 26 | func connopen(openchains ...[]Point) connector { 27 | c := connector{openPolys: []chain{}} 28 | for _, pts := range openchains { 29 | c.openPolys = append(c.openPolys, chain{points: pts}) 30 | } 31 | return c 32 | } 33 | 34 | func TestConnectorAddClosing1(t *T) { 35 | cases := []struct { 36 | c connector 37 | add segment 38 | length int 39 | }{ 40 | { 41 | c: connopen( 42 | []Point{{0.527105, 0.24687}, {0.2705720799269327, 0.2795780221218095}, {0.262624807729291, 0.30113844655235167}, {0.43093, 0.407828}, {0.48944187037949144, 0.6116041332606713}, {0.502984, 0.612599}}, 43 | []Point{{0.5813234786695596, 0.6602679842620749}, {0.569772, 0.46489}}), 44 | add: segment{Point{0.5813234786695596, 0.6602679842620749}, Point{0.502984, 0.612599}}, 45 | length: 8, 46 | }, 47 | { // simplified version of the above case 48 | c: connopen( 49 | []Point{{0, 1}, {0, 2}, {0, 3}}, 50 | []Point{{1, 1}, {1, 2}}), 51 | add: segment{Point{1, 1}, Point{0, 3}}, 52 | length: 5, 53 | }, 54 | } 55 | 56 | for i, x := range cases { 57 | x.c.add(x.add) 58 | verify(t, len(x.c.openPolys[0].points) == x.length, "Case %d, expected len(openPolys[0])==%d, got: %v", i, x.length, x.c) 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /polyutil/draw.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | // THE SOFTWARE. 19 | 20 | // Package polyutil contains very simple utility functions for drawing and (de)serialization of polygons. 21 | package polyutil 22 | 23 | import "github.com/akavel/polyclip-go" 24 | 25 | // Putpixel describes a function expected to draw a point on a bitmap at (x, y) coordinates. 26 | type Putpixel func(x, y int) 27 | 28 | func abs(x int) int { 29 | if x >= 0 { 30 | return x 31 | } 32 | return -x 33 | } 34 | 35 | // Bresenham's algorithm, http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm 36 | // TODO: handle int overflow etc. 37 | func drawline(x0, y0, x1, y1 int, brush Putpixel) { 38 | dx := abs(x1 - x0) 39 | dy := abs(y1 - y0) 40 | sx, sy := 1, 1 41 | if x0 >= x1 { 42 | sx = -1 43 | } 44 | if y0 >= y1 { 45 | sy = -1 46 | } 47 | err := dx - dy 48 | 49 | for { 50 | brush(x0, y0) 51 | if x0 == x1 && y0 == y1 { 52 | return 53 | } 54 | e2 := err * 2 55 | if e2 > -dy { 56 | err -= dy 57 | x0 += sx 58 | } 59 | if e2 < dx { 60 | err += dx 61 | y0 += sy 62 | } 63 | } 64 | } 65 | 66 | // DrawPolyline is a simple function for drawing a series of rasterized lines, 67 | // connecting the points specified in pts (treated as a closed polygon). 68 | // This function uses a very basic implementation of the Bresenham's algorithm 69 | // (http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm), 70 | // thus with no antialiasing. Moreover, the coordinates of the nodes are rounded 71 | // down towards the nearest integer. 72 | // The computed points are passed to brush function for final rendering. 73 | func DrawPolyline(pts []polyclip.Point, brush Putpixel) { 74 | last := len(pts) - 1 75 | for i := 0; i < len(pts); i++ { 76 | drawline(int(pts[last].X), int(pts[last].Y), int(pts[i].X), int(pts[i].Y), brush) 77 | last = i 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/drawpolys.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | // THE SOFTWARE. 19 | 20 | package test 21 | 22 | import ( 23 | "github.com/akavel/polyclip-go" 24 | "github.com/akavel/polyclip-go/polyutil" 25 | "image" 26 | "image/color" 27 | "image/draw" 28 | ) 29 | 30 | func max(a float64, b ...float64) float64 { 31 | for _, f := range b { 32 | if f > a { 33 | a = f 34 | } 35 | } 36 | return a 37 | } 38 | 39 | func brush(img draw.Image, id int) func(x, y int) { 40 | colors := [][]uint8{ 41 | {0, 0, 0xff}, 42 | {0xff, 0, 0}, 43 | {0xff, 0xff, 0xff}, 44 | } 45 | c := colors[id%len(colors)] 46 | return func(x, y int) { 47 | img.Set(x, y, &color.NRGBA{R: c[0], G: c[1], B: c[2], A: 0xff}) 48 | } 49 | } 50 | 51 | func safebbox(p polyclip.Polygon) polyclip.Rectangle { 52 | if len(p) == 0 { 53 | return polyclip.Rectangle{} 54 | } 55 | return p.BoundingBox() 56 | } 57 | 58 | // Warning: does modify contents of polys 59 | func DrawPolygons(mul float64, polys []polyclip.Polygon) *image.NRGBA { 60 | img := image.NewNRGBA(image.Rect(0, 0, 0, 0)) 61 | min := polyclip.Point{0, 0} 62 | 63 | for i, polygon := range polys { 64 | r0 := safebbox(polygon) 65 | translate := image.Point{} 66 | if r0.Min.X < min.X { 67 | translate.X = int(mul*min.X) - int(mul*r0.Min.X) 68 | min.X = r0.Min.X 69 | } 70 | if r0.Min.Y < min.Y { 71 | translate.Y = int(mul*min.Y) - int(mul*r0.Min.Y) 72 | min.Y = r0.Min.Y 73 | } 74 | 75 | for _, c := range polygon { 76 | for j, p := range c { 77 | c[j].X = (p.X - min.X) * mul 78 | c[j].Y = (p.Y - min.Y) * mul 79 | } 80 | } 81 | r := safebbox(polygon) 82 | 83 | img2 := image.NewNRGBA(img.Bounds().Add(translate).Union(image.Rect(int(r.Min.X), int(r.Min.Y), int(r.Max.X), int(r.Max.Y)))) 84 | draw.Draw(img2, img2.Bounds(), image.Black, image.Pt(0, 0), draw.Src) 85 | draw.Draw(img2, img.Bounds().Add(translate), img, image.Pt(0, 0), draw.Src) 86 | img = img2 87 | 88 | for _, c := range polygon { 89 | polyutil.DrawPolyline(c, brush(img, i)) 90 | } 91 | } 92 | 93 | return img 94 | } 95 | -------------------------------------------------------------------------------- /sweepline.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński (Go port) 2 | // Copyright (c) 2011 Mahir Iqbal (as3 version) 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // based on http://code.google.com/p/as3polyclip/ (MIT licensed) 22 | // and code by Martínez et al: http://wwwdi.ujaen.es/~fmartin/bool_op.html (public domain) 23 | 24 | package polyclip 25 | 26 | // This is the data structure that simulates the sweepline as it parses through 27 | // eventQueue, which holds the events sorted from left to right (x-coordinate). 28 | // TODO: optimizations? use sort.Search()? 29 | type sweepline []*endpoint 30 | 31 | func (s *sweepline) remove(key *endpoint) { 32 | for i, el := range *s { 33 | if el.equals(key) { 34 | *s = append((*s)[:i], (*s)[i+1:]...) 35 | return 36 | } 37 | } 38 | } 39 | 40 | func (s *sweepline) insert(item *endpoint) int { 41 | length := len(*s) 42 | if length == 0 { 43 | *s = append(*s, item) 44 | return 0 45 | } 46 | 47 | *s = append(*s, &endpoint{}) 48 | i := length - 1 49 | for i >= 0 && segmentCompare(item, (*s)[i]) { 50 | (*s)[i+1] = (*s)[i] 51 | i-- 52 | } 53 | (*s)[i+1] = item 54 | return i + 1 55 | //TODO insertion sort? 56 | } 57 | 58 | func segmentCompare(e1, e2 *endpoint) bool { 59 | switch { 60 | case e1 == e2: 61 | return false 62 | case signedArea(e1.p, e1.other.p, e2.p) != 0: 63 | fallthrough 64 | case signedArea(e1.p, e1.other.p, e2.other.p) != 0: 65 | // Segments are not collinear 66 | // If they share their left endpoint use the right endpoint to sort 67 | if e1.p.Equals(e2.p) { 68 | return e1.below(e2.other.p) 69 | } 70 | // Different points 71 | if endpointLess(e1, e2) { // has the line segment associated to e1 been inserted into S after the line segment associated to e2 ? 72 | return e2.above(e1.p) 73 | } 74 | // The line segment associated to e2 has been inserted into S after the line segment associated to e1 75 | return e1.below(e2.p) 76 | // Segments are collinear. Just a consistent criterion is used 77 | case e1.p.Equals(e2.p): 78 | //return e1 < e2 79 | return false 80 | } 81 | return endpointLess(e1, e2) 82 | } 83 | -------------------------------------------------------------------------------- /connector.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński (Go port) 2 | // Copyright (c) 2011 Mahir Iqbal (as3 version) 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // based on http://code.google.com/p/as3polyclip/ (MIT licensed) 22 | // and code by Martínez et al: http://wwwdi.ujaen.es/~fmartin/bool_op.html (public domain) 23 | 24 | package polyclip 25 | 26 | // Holds intermediate results (pointChains) of the clipping operation and forms them into 27 | // the final polygon. 28 | type connector struct { 29 | openPolys []chain 30 | closedPolys []chain 31 | } 32 | 33 | func (c *connector) add(s segment) { 34 | // j iterates through the openPolygon chains. 35 | for j := range c.openPolys { 36 | chain := &c.openPolys[j] 37 | if !chain.linkSegment(s) { 38 | continue 39 | } 40 | 41 | if chain.closed { 42 | if len(chain.points) == 2 { 43 | // We tried linking the same segment (but flipped end and start) to 44 | // a chain. (i.e. chain was , we tried linking Segment(p1, p0) 45 | // so the chain was closed illegally. 46 | chain.closed = false 47 | return 48 | } 49 | // move the chain from openPolys to closedPolys 50 | c.closedPolys = append(c.closedPolys, c.openPolys[j]) 51 | c.openPolys = append(c.openPolys[:j], c.openPolys[j+1:]...) 52 | return 53 | } 54 | 55 | // !chain.closed 56 | k := len(c.openPolys) 57 | for i := j + 1; i < k; i++ { 58 | // Try to connect this open link to the rest of the chains. 59 | // We won't be able to connect this to any of the chains preceding this one 60 | // because we know that linkSegment failed on those. 61 | if chain.linkChain(&c.openPolys[i]) { 62 | // delete 63 | c.openPolys = append(c.openPolys[:i], c.openPolys[i+1:]...) 64 | return 65 | } 66 | } 67 | return 68 | } 69 | 70 | // The segment cannot be connected with any open polygon 71 | c.openPolys = append(c.openPolys, *newChain(s)) 72 | } 73 | 74 | func (c *connector) toPolygon() Polygon { 75 | poly := Polygon{} 76 | for _, chain := range c.closedPolys { 77 | con := Contour{} 78 | for _, p := range chain.points { 79 | con.Add(p) 80 | } 81 | poly.Add(con) 82 | } 83 | return poly 84 | } 85 | -------------------------------------------------------------------------------- /polyutil/encdec.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński (Go port) 2 | // Copyright (c) 2011 Mahir Iqbal (as3 version) 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // based on http://code.google.com/p/as3polyclip/ (MIT licensed) 22 | // and code by Martínez et al: http://wwwdi.ujaen.es/~fmartin/bool_op.html (public domain) 23 | 24 | package polyutil 25 | 26 | import ( 27 | "fmt" 28 | "github.com/akavel/polyclip-go" 29 | "io" 30 | ) 31 | 32 | // EncodeContour serializes all points of a specified contour using a simple textual format. 33 | func EncodeContour(w io.Writer, c polyclip.Contour) error { 34 | _, err := fmt.Fprint(w, len(c), " 1\n") 35 | if err != nil { 36 | return err 37 | } 38 | for _, p := range c { 39 | _, err = fmt.Fprint(w, "\t", p.X, " ", p.Y, "\n") 40 | if err != nil { 41 | return err 42 | } 43 | } 44 | return nil 45 | } 46 | 47 | // EncodePolygon serializes all contours of a polygon using a simple textual format. 48 | func EncodePolygon(w io.Writer, p polyclip.Polygon) error { 49 | _, err := fmt.Fprint(w, len(p), "\n") 50 | if err != nil { 51 | return err 52 | } 53 | for _, c := range p { 54 | err = EncodeContour(w, c) 55 | if err != nil { 56 | return err 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | // DecodePolygon loads a polygon saved using EncodePolygon function. 63 | func DecodePolygon(in io.Reader) (*polyclip.Polygon, error) { 64 | var ncontours int 65 | _, err := fmt.Fscan(in, &ncontours) 66 | if err != nil { 67 | return nil, err 68 | } 69 | polygon := polyclip.Polygon{} 70 | for i := 0; i < ncontours; i++ { 71 | var npoints, level int 72 | _, err = fmt.Fscan(in, &npoints, &level) 73 | if err != nil { 74 | return nil, err 75 | } 76 | c := polyclip.Contour{} 77 | for j := 0; j < npoints; j++ { 78 | p := polyclip.Point{} 79 | _, err = fmt.Fscan(in, &p.X, &p.Y) 80 | if err != nil { 81 | return nil, err 82 | } 83 | if j > 0 && p.Equals(c[len(c)-1]) { 84 | continue 85 | } 86 | if j == npoints-1 && p.Equals(c[0]) { 87 | continue 88 | } 89 | c.Add(p) 90 | } 91 | if len(c) < 3 { 92 | continue 93 | } 94 | polygon.Add(c) 95 | } 96 | return &polygon, nil 97 | } 98 | -------------------------------------------------------------------------------- /endpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński (Go port) 2 | // Copyright (c) 2011 Mahir Iqbal (as3 version) 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // based on http://code.google.com/p/as3polyclip/ (MIT licensed) 22 | // and code by Martínez et al: http://wwwdi.ujaen.es/~fmartin/bool_op.html (public domain) 23 | 24 | package polyclip 25 | 26 | import ( 27 | "fmt" 28 | ) 29 | 30 | // A container for endpoint data. A endpoint represents a location of interest (vertex between two polygon edges) 31 | // as the sweep line passes through the polygons. 32 | type endpoint struct { 33 | p Point 34 | left bool // Is the point the left endpoint of the segment (p, other->p)? 35 | polygonType // polygonType to which this event belongs to 36 | other *endpoint // Event associated to the other endpoint of the segment 37 | 38 | // Does the segment (p, other->p) represent an inside-outside transition 39 | // in the polygon for a vertical ray from (p.x, -infinite) that crosses the segment? 40 | inout bool 41 | edgeType 42 | inside bool // Only used in "left" events. Is the segment (p, other->p) inside the other polygon? 43 | } 44 | 45 | func (e endpoint) String() string { 46 | sleft := map[bool]string{true: "left", false: "right"} 47 | return fmt.Sprint("{", e.p, " ", sleft[e.left], " type:", e.polygonType, 48 | " other:", e.other.p, " inout:", e.inout, " inside:", e.inside, " edgeType:", e.edgeType, "}") 49 | } 50 | 51 | func (e1 *endpoint) equals(e2 *endpoint) bool { 52 | return e1.p.Equals(e2.p) && 53 | e1.left == e2.left && 54 | e1.polygonType == e2.polygonType && 55 | e1.other == e2.other && 56 | e1.inout == e2.inout && 57 | e1.edgeType == e2.edgeType && 58 | e1.inside == e2.inside 59 | } 60 | 61 | func (se *endpoint) segment() segment { 62 | return segment{se.p, se.other.p} 63 | } 64 | 65 | func signedArea(p0, p1, p2 Point) float64 { 66 | return (p0.X-p2.X)*(p1.Y-p2.Y) - 67 | (p1.X-p2.X)*(p0.Y-p2.Y) 68 | } 69 | 70 | // Checks if this sweep event is below point p. 71 | func (se *endpoint) below(x Point) bool { 72 | if se.left { 73 | return signedArea(se.p, se.other.p, x) > 0 74 | } 75 | return signedArea(se.other.p, se.p, x) > 0 76 | } 77 | 78 | func (se *endpoint) above(x Point) bool { 79 | return !se.below(x) 80 | } 81 | -------------------------------------------------------------------------------- /eventqueue.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński (Go port) 2 | // Copyright (c) 2011 Mahir Iqbal (as3 version) 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // based on http://code.google.com/p/as3polyclip/ (MIT licensed) 22 | // and code by Martínez et al: http://wwwdi.ujaen.es/~fmartin/bool_op.html (public domain) 23 | 24 | package polyclip 25 | 26 | import ( 27 | "sort" 28 | ) 29 | 30 | type eventQueue struct { 31 | elements []*endpoint 32 | sorted bool 33 | } 34 | 35 | func (q *eventQueue) enqueue(e *endpoint) { 36 | if !q.sorted { 37 | q.elements = append(q.elements, e) 38 | return 39 | } 40 | 41 | // If already sorted use insertionSort on the inserted item. 42 | // TODO: bisection search? 43 | length := len(q.elements) 44 | if length == 0 { 45 | q.elements = append(q.elements, e) 46 | return 47 | } 48 | 49 | q.elements = append(q.elements, nil) 50 | i := length - 1 51 | for i >= 0 && endpointLess(e, q.elements[i]) { 52 | q.elements[i+1] = q.elements[i] 53 | i-- 54 | } 55 | q.elements[i+1] = e 56 | } 57 | 58 | // The ordering is reversed because push and pop are faster. 59 | // [MC: fragment from .c source below:] 60 | // Return true means that [...] e1 is processed by the algorithm after e2 61 | func endpointLess(e1, e2 *endpoint) bool { 62 | // Different x coordinate 63 | if e1.p.X != e2.p.X { 64 | return e1.p.X > e2.p.X 65 | } 66 | 67 | // Same x coordinate. The event with lower y coordinate is processed first 68 | if e1.p.Y != e2.p.Y { 69 | return e1.p.Y > e2.p.Y 70 | } 71 | 72 | // Same point, but one is a left endpoint and the other a right endpoint. The right endpoint is processed first 73 | if e1.left != e2.left { 74 | return e1.left 75 | } 76 | 77 | // Same point, both events are left endpoints or both are right endpoints. The event associate to the bottom segment is processed first 78 | return e1.above(e2.other.p) 79 | } 80 | 81 | func (q *eventQueue) dequeue() *endpoint { 82 | if !q.sorted { 83 | sort.Sort(queueComparer(q.elements)) 84 | q.sorted = true 85 | } 86 | 87 | // pop 88 | x := q.elements[len(q.elements)-1] 89 | q.elements = q.elements[:len(q.elements)-1] 90 | return x 91 | } 92 | 93 | type queueComparer []*endpoint 94 | 95 | func (q queueComparer) Len() int { return len(q) } 96 | func (q queueComparer) Less(i, j int) bool { 97 | return endpointLess(q[i], q[j]) 98 | } 99 | func (q queueComparer) Swap(i, j int) { 100 | q[i], q[j] = q[j], q[i] 101 | } 102 | 103 | func (q *eventQueue) IsEmpty() bool { 104 | return len(q.elements) == 0 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WARNING 2 | ------- 3 | 4 | **The library is KNOWN TO HAVE BUGS!!!** Unfortunately, currently I don't have resources to investigate them thoroughly enough and in timely fashion. In case somebody is interested in taking ownership of the library, I'm open to ceding it. That said, the issues totally haunt me and occasionally I stubbornly try to come back to them and pick the fight up again. In particular: 5 | 6 | - #3 was confirmed to be **an omission in the original paper/algorithm**. As far as I understand, it surfaces when one of the polygons used has self-overlapping edges (e.g. when an edge (0,0)-(1,1) is used twice in the same polygon). I believe it should be possible to fix, but it requires thorough analysis of the algorithm and good testing. One attempt I made at a fix which seemed OK initially was later found to break the library even more and thus I reverted it. 7 | - #8 was reported recently and I haven't yet had time to even start investigating it. 8 | 9 | About 10 | ----- 11 | 12 | [![Build Status on Travis-CI.](https://travis-ci.org/akavel/polyclip-go.svg?branch=master)](https://travis-ci.org/akavel/polyclip-go) 13 | ![License: MIT.](https://img.shields.io/badge/license-MIT-orange.svg) 14 | [![Documentation on godoc.org.](https://godoc.org/github.com/akavel/polyclip-go?status.svg)](https://godoc.org/github.com/akavel/polyclip-go) 15 | 16 | Library polyclip-go is a pure Go, MIT-licensed implementation of an [algorithm for Boolean operations on 2D polygons] [fmartin] (invented by F. Martínez, A.J. Rueda, F.R. Feito) -- that is, for calculation of polygon intersection, union, difference and xor. 17 | 18 | The original paper describes the algorithm as performing in time _O((n+k) log n)_, where _n_ is number of all edges of all polygons in operation, and _k_ is number of intersections of all polygon edges. 19 | 20 | [fmartin]: http://wwwdi.ujaen.es/~fmartin/bool_op.html 21 | 22 | ![](http://img684.imageshack.us/img684/5296/drawqk.png "Polygons intersection example, calculated using polyclip-go") 23 | 24 | Example 25 | ------- 26 | 27 | Simplest Go program using polyclip-go for calculating intersection of a square and triangle: 28 | 29 | // example.go 30 | package main 31 | 32 | import ( 33 | "fmt" 34 | "github.com/akavel/polyclip-go" // or: bitbucket.org/... 35 | ) 36 | 37 | func main() { 38 | subject := polyclip.Polygon{{{1, 1}, {1, 2}, {2, 2}, {2, 1}}} // small square 39 | clipping := polyclip.Polygon{{{0, 0}, {0, 3}, {3, 0}}} // overlapping triangle 40 | result := subject.Construct(polyclip.INTERSECTION, clipping) 41 | 42 | // will print triangle: [[{1 1} {1 2} {2 1}]] 43 | fmt.Println(result) 44 | } 45 | 46 | To compile and run the program above, execute the usual sequence of commands: 47 | 48 | go get github.com/akavel/polyclip-go # or: bitbucket.org/... 49 | go build example.go 50 | ./example # Windows: example.exe 51 | 52 | For full package documentation, run locally `godoc github.com/akavel/polyclip-go`, or visit [online documentation for polyclip-go][godoc]. 53 | 54 | [godoc]: http://godoc.org/github.com/akavel/polyclip-go 55 | 56 | See also 57 | -------- 58 | * [Online docs for polyclip-go][godoc]. 59 | * Microsite about [the original algorithm][fmartin], from its authors (with PDF, and public-domain code in C++). 60 | * The [as3polyclip] library -- a MIT-licensed ActionScript3 library implementing this same algorithm (it actually served as a base for polyclip-go). The page also contains some thoughts with regards to speed of the algorithm. 61 | 62 | [as3polyclip]: http://code.google.com/p/as3polyclip/ 63 | -------------------------------------------------------------------------------- /pointchain.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński (Go port) 2 | // Copyright (c) 2011 Mahir Iqbal (as3 version) 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // based on http://code.google.com/p/as3polyclip/ (MIT licensed) 22 | // and code by Martínez et al: http://wwwdi.ujaen.es/~fmartin/bool_op.html (public domain) 23 | 24 | package polyclip 25 | 26 | // Represents a connected sequence of segments. The sequence can only be extended by connecting 27 | // new segments that share an endpoint with the chain. 28 | type chain struct { 29 | closed bool 30 | points []Point 31 | } 32 | 33 | func newChain(s segment) *chain { 34 | return &chain{ 35 | closed: false, 36 | points: []Point{s.start, s.end}} 37 | } 38 | 39 | func (c *chain) pushFront(p Point) { c.points = append([]Point{p}, c.points...) } 40 | func (c *chain) pushBack(p Point) { c.points = append(c.points, p) } 41 | 42 | // Links a segment to the chain 43 | func (c *chain) linkSegment(s segment) bool { 44 | front := c.points[0] 45 | back := c.points[len(c.points)-1] 46 | 47 | switch true { 48 | case s.start.Equals(front): 49 | if s.end.Equals(back) { 50 | c.closed = true 51 | } else { 52 | c.pushFront(s.end) 53 | } 54 | return true 55 | case s.end.Equals(back): 56 | if s.start.Equals(front) { 57 | c.closed = true 58 | } else { 59 | c.pushBack(s.start) 60 | } 61 | return true 62 | case s.end.Equals(front): 63 | if s.start.Equals(back) { 64 | c.closed = true 65 | } else { 66 | c.pushFront(s.start) 67 | } 68 | return true 69 | case s.start.Equals(back): 70 | if s.end.Equals(front) { 71 | c.closed = true 72 | } else { 73 | c.pushBack(s.end) 74 | } 75 | return true 76 | } 77 | return false 78 | } 79 | 80 | // Links another chain onto this point chain. 81 | func (c *chain) linkChain(other *chain) bool { 82 | 83 | front := c.points[0] 84 | back := c.points[len(c.points)-1] 85 | 86 | otherFront := other.points[0] 87 | otherBack := other.points[len(other.points)-1] 88 | 89 | if otherFront.Equals(back) { 90 | c.points = append(c.points, other.points[1:]...) 91 | goto success 92 | //c.points = append(c.points[:len(c.points)-1], other.points...) 93 | //return true 94 | } 95 | 96 | if otherBack.Equals(front) { 97 | c.points = append(other.points, c.points[1:]...) 98 | goto success 99 | //return true 100 | } 101 | 102 | if otherFront.Equals(front) { 103 | // Remove the first element, and join to reversed chain.points 104 | c.points = append(reversed(other.points), c.points[1:]...) 105 | goto success 106 | //return true 107 | } 108 | 109 | if otherBack.Equals(back) { 110 | c.points = append(c.points[:len(c.points)-1], reversed(other.points)...) 111 | goto success 112 | //c.points = append(other.points, reversed(c.points)...) 113 | //return true 114 | } 115 | 116 | return false 117 | 118 | success: 119 | other.points = []Point{} 120 | return true 121 | } 122 | 123 | func reversed(list []Point) []Point { 124 | length := len(list) 125 | other := make([]Point, length) 126 | for i := range list { 127 | other[length-i-1] = list[i] 128 | } 129 | return other 130 | } 131 | -------------------------------------------------------------------------------- /geom_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | // THE SOFTWARE. 19 | 20 | package polyclip 21 | 22 | import ( 23 | "fmt" 24 | "math" 25 | "sort" 26 | . "testing" 27 | ) 28 | 29 | func verify(t *T, cond bool, format string, args ...interface{}) { 30 | if !cond { 31 | t.Errorf(format, args...) 32 | } 33 | } 34 | 35 | func circa(f, g float64) bool { 36 | //TODO: (f-g)/g < 1e-6 ? 37 | return math.Abs(f-g) < 1e-6 38 | } 39 | 40 | func TestPoint(t *T) { 41 | verify(t, Point{0, 0}.Equals(Point{0, 0}), "Expected equal points") 42 | verify(t, Point{1, 2}.Equals(Point{1, 2}), "Expected equal points") 43 | verify(t, circa(Point{3, 4}.Length(), 5), "Expected length 5") 44 | } 45 | 46 | func rect(x, y, w, h float64) Rectangle { 47 | return Rectangle{Min: Point{x, y}, Max: Point{x + w, y + h}} 48 | } 49 | 50 | func TestRectangleUnion(t *T) { 51 | cases := []struct{ a, b, result Rectangle }{ 52 | {rect(0, 0, 20, 30), rect(0, 0, 30, 20), rect(0, 0, 30, 30)}, 53 | {rect(10, 10, 10, 10), rect(-10, -10, 10, 10), rect(-10, -10, 30, 30)}, 54 | } 55 | for i, v := range cases { 56 | u := v.a.union(v.b) 57 | r := v.result 58 | verify(t, u.Min.X == r.Min.X && u.Min.Y == r.Min.Y && u.Max.X == r.Max.X && u.Max.Y == r.Max.Y, "Expected equal rectangles in case %d", i) 59 | } 60 | } 61 | 62 | func TestRectangleIntersects(t *T) { 63 | r1 := rect(5, 5, 10, 10) 64 | cases := []struct { 65 | a, b Rectangle 66 | result bool 67 | }{ 68 | {rect(0, 0, 10, 20), rect(0, 10, 20, 10), true}, 69 | {rect(0, 0, 10, 20), rect(20, 0, 10, 20), false}, 70 | {rect(10, 50, 10, 10), rect(0, 0, 50, 45), false}, 71 | {r1, rect(0, 0, 10, 10), true}, // diagonal intersections 72 | {r1, rect(10, 0, 10, 10), true}, 73 | {r1, rect(0, 10, 10, 10), true}, 74 | {r1, rect(10, 10, 10, 10), true}, 75 | {r1, rect(-10, -10, 10, 10), false}, // non-intersecting rects on diagonal axes 76 | {r1, rect(20, -10, 10, 10), false}, 77 | {r1, rect(-10, 20, 10, 10), false}, 78 | {r1, rect(20, 20, 10, 10), false}, 79 | } 80 | for i, v := range cases { 81 | verify(t, v.a.Overlaps(v.b) == v.result, "Expected result %v in case %d", v.result, i) 82 | } 83 | } 84 | 85 | func TestContourAdd(t *T) { 86 | c := Contour{} 87 | pp := []Point{{1, 2}, {3, 4}, {5, 6}} 88 | for i := range pp { 89 | c.Add(pp[i]) 90 | } 91 | verify(t, len(c) == len(pp), "Expected all points in contour") 92 | for i := range pp { 93 | verify(t, c[i].Equals(pp[i]), "Wrong point at position %d", i) 94 | } 95 | } 96 | 97 | func TestContourBoundingBox(t *T) { 98 | // TODO 99 | } 100 | 101 | func TestContourSegment(t *T) { 102 | c := Contour([]Point{{1, 2}, {3, 4}, {5, 6}}) 103 | segeq := func(s1, s2 segment) bool { 104 | return s1.start.Equals(s2.start) && s1.end.Equals(s2.end) 105 | } 106 | verify(t, segeq(c.segment(0), segment{Point{1, 2}, Point{3, 4}}), "Expected segment 0") 107 | verify(t, segeq(c.segment(1), segment{Point{3, 4}, Point{5, 6}}), "Expected segment 1") 108 | verify(t, segeq(c.segment(2), segment{Point{5, 6}, Point{1, 2}}), "Expected segment 2") 109 | } 110 | 111 | func TestContourSegmentError1(t *T) { 112 | c := Contour([]Point{{1, 2}, {3, 4}, {5, 6}}) 113 | 114 | defer func() { 115 | verify(t, recover() != nil, "Expected error") 116 | }() 117 | _ = c.segment(3) 118 | } 119 | 120 | type pointresult struct { 121 | p Point 122 | result bool 123 | } 124 | 125 | func TestContourContains(t *T) { 126 | var cases1 []pointresult 127 | c1 := Contour([]Point{{0, 0}, {10, 0}, {0, 10}}) 128 | c2 := Contour([]Point{{0, 0}, {0, 10}, {10, 0}}) // opposite rotation 129 | cases1 = []pointresult{ 130 | {Point{1, 1}, true}, 131 | {Point{2, .1}, true}, 132 | {Point{10, 10}, false}, 133 | {Point{11, 0}, false}, 134 | {Point{0, 11}, false}, 135 | {Point{-1, -1}, false}, 136 | } 137 | for i, v := range cases1 { 138 | verify(t, c1.Contains(v.p) == v.result, "Expected %v for point %d for c1", v.result, i) 139 | verify(t, c2.Contains(v.p) == v.result, "Expected %v for point %d for c2", v.result, i) 140 | } 141 | } 142 | 143 | func TestContourContains2(t *T) { 144 | c1 := Contour{{55, 35}, {25, 35}, {25, 119}, {55, 119}} 145 | c2 := Contour{{145, 35}, {145, 77}, {105, 77}, {105, 119}, {55, 119}, {55, 35}} 146 | cases := []struct { 147 | Contour 148 | Point 149 | Result bool 150 | }{ 151 | {c1, Point{54.95, 77}, true}, 152 | {c1, Point{55.05, 77}, false}, 153 | {c2, Point{54.95, 77}, false}, 154 | {c2, Point{55.05, 77}, true}, 155 | } 156 | for _, c := range cases { 157 | result := c.Contour.Contains(c.Point) 158 | if result != c.Result { 159 | t.Errorf("case %v expected %v, got %v", c, c.Result, result) 160 | } 161 | } 162 | } 163 | 164 | func ExamplePolygon_Construct() { 165 | subject := Polygon{{{1, 1}, {1, 2}, {2, 2}, {2, 1}}} // small square 166 | clipping := Polygon{{{0, 0}, {0, 3}, {3, 0}}} // overlapping triangle 167 | result := subject.Construct(INTERSECTION, clipping) 168 | 169 | out := []string{} 170 | for _, point := range result[0] { 171 | out = append(out, fmt.Sprintf("%v", point)) 172 | } 173 | sort.Strings(out) 174 | fmt.Println(out) 175 | // Output: [{1 1} {1 2} {2 1}] 176 | } 177 | -------------------------------------------------------------------------------- /geom.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński (Go port) 2 | // Copyright (c) 2011 Mahir Iqbal (as3 version) 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // based on http://code.google.com/p/as3polyclip/ (MIT licensed) 22 | // and code by Martínez et al: http://wwwdi.ujaen.es/~fmartin/bool_op.html (public domain) 23 | 24 | // Package polyclip provides implementation of algorithm for Boolean operations on 2D polygons. 25 | // For further details, consult the description of Polygon.Construct method. 26 | package polyclip 27 | 28 | import ( 29 | "math" 30 | ) 31 | 32 | type Point struct { 33 | X, Y float64 34 | } 35 | 36 | // Equals returns true if both p1 and p2 describe exactly the same point. 37 | func (p1 Point) Equals(p2 Point) bool { 38 | return p1.X == p2.X && p1.Y == p2.Y 39 | } 40 | 41 | // Length returns distance from p to point (0, 0). 42 | func (p Point) Length() float64 { 43 | return math.Sqrt(p.X*p.X + p.Y*p.Y) 44 | } 45 | 46 | type Rectangle struct { 47 | Min, Max Point 48 | } 49 | 50 | func (r1 Rectangle) union(r2 Rectangle) Rectangle { 51 | return Rectangle{ 52 | Min: Point{ 53 | X: math.Min(r1.Min.X, r2.Min.X), 54 | Y: math.Min(r1.Min.Y, r2.Min.Y), 55 | }, 56 | Max: Point{ 57 | X: math.Max(r1.Max.X, r2.Max.X), 58 | Y: math.Max(r1.Max.Y, r2.Max.Y), 59 | }} 60 | } 61 | 62 | // Overlaps returns whether r1 and r2 have a non-empty intersection. 63 | func (r1 Rectangle) Overlaps(r2 Rectangle) bool { 64 | return r1.Min.X <= r2.Max.X && r1.Max.X >= r2.Min.X && 65 | r1.Min.Y <= r2.Max.Y && r1.Max.Y >= r2.Min.Y 66 | } 67 | 68 | // Used to represent an edge of a polygon. 69 | type segment struct { 70 | start, end Point 71 | } 72 | 73 | // Contour represents a sequence of vertices connected by line segments, forming a closed shape. 74 | type Contour []Point 75 | 76 | // Add is a convenience method for appending a point to a contour. 77 | func (c *Contour) Add(p Point) { 78 | *c = append(*c, p) 79 | } 80 | 81 | // BoundingBox finds minimum and maximum coordinates of points in a contour. 82 | func (c Contour) BoundingBox() Rectangle { 83 | bb := Rectangle{} 84 | bb.Min.X = math.Inf(1) 85 | bb.Min.Y = math.Inf(1) 86 | bb.Max.X = math.Inf(-1) 87 | bb.Max.Y = math.Inf(-1) 88 | 89 | for _, p := range c { 90 | if p.X > bb.Max.X { 91 | bb.Max.X = p.X 92 | } 93 | if p.X < bb.Min.X { 94 | bb.Min.X = p.X 95 | } 96 | if p.Y > bb.Max.Y { 97 | bb.Max.Y = p.Y 98 | } 99 | if p.Y < bb.Min.Y { 100 | bb.Min.Y = p.Y 101 | } 102 | } 103 | return bb 104 | } 105 | 106 | func (c Contour) segment(index int) segment { 107 | if index == len(c)-1 { 108 | return segment{c[len(c)-1], c[0]} 109 | } 110 | return segment{c[index], c[index+1]} 111 | // if out-of-bounds, we expect panic detected by runtime 112 | } 113 | 114 | // Checks if a point is inside a contour using the "point in polygon" raycast method. 115 | // This works for all polygons, whether they are clockwise or counter clockwise, 116 | // convex or concave. 117 | // See: http://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm 118 | // Returns true if p is inside the polygon defined by contour. 119 | func (c Contour) Contains(p Point) bool { 120 | // Cast ray from p.x towards the right 121 | intersections := 0 122 | for i := range c { 123 | curr := c[i] 124 | ii := i + 1 125 | if ii == len(c) { 126 | ii = 0 127 | } 128 | next := c[ii] 129 | 130 | // Is the point out of the edge's bounding box? 131 | // bottom vertex is inclusive (belongs to edge), top vertex is 132 | // exclusive (not part of edge) -- i.e. p lies "slightly above 133 | // the ray" 134 | bottom, top := curr, next 135 | if bottom.Y > top.Y { 136 | bottom, top = top, bottom 137 | } 138 | if p.Y < bottom.Y || p.Y >= top.Y { 139 | continue 140 | } 141 | // Edge is from curr to next. 142 | 143 | if p.X >= math.Max(curr.X, next.X) || 144 | next.Y == curr.Y { 145 | continue 146 | } 147 | 148 | // Find where the line intersects... 149 | xint := (p.Y-curr.Y)*(next.X-curr.X)/(next.Y-curr.Y) + curr.X 150 | if curr.X != next.X && p.X > xint { 151 | continue 152 | } 153 | 154 | intersections++ 155 | } 156 | 157 | return intersections%2 != 0 158 | } 159 | 160 | // Clone returns a copy of a contour. 161 | func (c Contour) Clone() Contour { 162 | return append([]Point{}, c...) 163 | } 164 | 165 | // Polygon is carved out of a 2D plane by a set of (possibly disjoint) contours. 166 | // It can thus contain holes, and can be self-intersecting. 167 | type Polygon []Contour 168 | 169 | // NumVertices returns total number of all vertices of all contours of a polygon. 170 | func (p Polygon) NumVertices() int { 171 | num := 0 172 | for _, c := range p { 173 | num += len(c) 174 | } 175 | return num 176 | } 177 | 178 | // BoundingBox finds minimum and maximum coordinates of points in a polygon. 179 | func (p Polygon) BoundingBox() Rectangle { 180 | bb := p[0].BoundingBox() 181 | for _, c := range p[1:] { 182 | bb = bb.union(c.BoundingBox()) 183 | } 184 | 185 | return bb 186 | } 187 | 188 | // Add is a convenience method for appending a contour to a polygon. 189 | func (p *Polygon) Add(c Contour) { 190 | *p = append(*p, c) 191 | } 192 | 193 | // Clone returns a duplicate of a polygon. 194 | func (p Polygon) Clone() Polygon { 195 | r := Polygon(make([]Contour, len(p))) 196 | for i := range p { 197 | r[i] = p[i].Clone() 198 | } 199 | return r 200 | } 201 | 202 | // Op describes an operation which can be performed on two polygons. 203 | type Op int 204 | 205 | const ( 206 | UNION Op = iota 207 | INTERSECTION 208 | DIFFERENCE 209 | XOR 210 | ) 211 | 212 | // Construct computes a 2D polygon, which is a result of performing 213 | // specified Boolean operation on the provided pair of polygons (p clipping). 214 | // It uses algorithm described by F. Martínez, A. J. Rueda, F. R. Feito 215 | // in "A new algorithm for computing Boolean operations on polygons" 216 | // - see: http://wwwdi.ujaen.es/~fmartin/bool_op.html 217 | // The paper describes the algorithm as performing in time O((n+k) log n), 218 | // where n is number of all edges of all polygons in operation, and 219 | // k is number of intersections of all polygon edges. 220 | func (p Polygon) Construct(operation Op, clipping Polygon) Polygon { 221 | c := clipper{ 222 | subject: p, 223 | clipping: clipping, 224 | } 225 | return c.compute(operation) 226 | } 227 | -------------------------------------------------------------------------------- /bugs_test.go: -------------------------------------------------------------------------------- 1 | package polyclip_test 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | . "testing" 7 | "time" 8 | 9 | "github.com/akavel/polyclip-go" 10 | ) 11 | 12 | type sorter polyclip.Polygon 13 | 14 | func (s sorter) Len() int { return len(s) } 15 | func (s sorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 16 | func (s sorter) Less(i, j int) bool { 17 | if len(s[i]) != len(s[j]) { 18 | return len(s[i]) < len(s[j]) 19 | } 20 | for k := range s[i] { 21 | pi, pj := s[i][k], s[j][k] 22 | if pi.X != pj.X { 23 | return pi.X < pj.X 24 | } 25 | if pi.Y != pj.Y { 26 | return pi.Y < pj.Y 27 | } 28 | } 29 | return false 30 | } 31 | 32 | // basic normalization just for tests; to be improved if needed 33 | func normalize(poly polyclip.Polygon) polyclip.Polygon { 34 | for i, c := range poly { 35 | if len(c) == 0 { 36 | continue 37 | } 38 | 39 | // find bottom-most of leftmost points, to have fixed anchor 40 | min := 0 41 | for j, p := range c { 42 | if p.X < c[min].X || p.X == c[min].X && p.Y < c[min].Y { 43 | min = j 44 | } 45 | } 46 | 47 | // rotate points to make sure min is first 48 | poly[i] = append(c[min:], c[:min]...) 49 | } 50 | 51 | sort.Sort(sorter(poly)) 52 | return poly 53 | } 54 | 55 | func dump(poly polyclip.Polygon) string { 56 | return fmt.Sprintf("%v", normalize(poly)) 57 | } 58 | 59 | func TestBug3(t *T) { 60 | cases := []struct{ subject, clipping, result polyclip.Polygon }{ 61 | // original reported github issue #3 62 | { 63 | subject: polyclip.Polygon{{{1, 1}, {1, 2}, {2, 2}, {2, 1}}}, 64 | clipping: polyclip.Polygon{ 65 | {{2, 1}, {2, 2}, {3, 2}, {3, 1}}, 66 | {{1, 2}, {1, 3}, {2, 3}, {2, 2}}, 67 | {{2, 2}, {2, 3}, {3, 3}, {3, 2}}}, 68 | result: polyclip.Polygon{{ 69 | {1, 1}, {2, 1}, {3, 1}, 70 | {3, 2}, {3, 3}, 71 | {2, 3}, {1, 3}, 72 | {1, 2}}}, 73 | }, 74 | // simplified variant of issue #3, for easier debugging 75 | { 76 | subject: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, 77 | clipping: polyclip.Polygon{ 78 | {{2, 1}, {2, 2}, {3, 2}}, 79 | {{1, 2}, {2, 3}, {2, 2}}, 80 | {{2, 2}, {2, 3}, {3, 2}}}, 81 | result: polyclip.Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 1}}}, 82 | }, 83 | { 84 | subject: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, 85 | clipping: polyclip.Polygon{ 86 | {{1, 2}, {2, 3}, {2, 2}}, 87 | {{2, 2}, {2, 3}, {3, 2}}}, 88 | result: polyclip.Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 2}, {2, 1}}}, 89 | }, 90 | // another variation, now with single degenerated curve 91 | { 92 | subject: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, 93 | clipping: polyclip.Polygon{ 94 | {{1, 2}, {2, 3}, {2, 2}, {2, 3}, {3, 2}}}, 95 | result: polyclip.Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 2}, {2, 1}}}, 96 | }, 97 | { 98 | subject: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, 99 | clipping: polyclip.Polygon{ 100 | {{2, 1}, {2, 2}, {2, 3}, {3, 2}}, 101 | {{1, 2}, {2, 3}, {2, 2}}}, 102 | result: polyclip.Polygon{{{1, 2}, {2, 3}, {3, 2}, {2, 1}}}, 103 | }, 104 | // "union" with effectively empty polygon (wholly self-intersecting) 105 | { 106 | subject: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, 107 | clipping: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 3}, {1, 2}, {2, 2}, {2, 3}}}, 108 | result: polyclip.Polygon{{{1, 2}, {2, 2}, {2, 1}}}, 109 | }, 110 | } 111 | for _, c := range cases { 112 | result := dump(c.subject.Construct(polyclip.UNION, c.clipping)) 113 | if result != dump(c.result) { 114 | t.Errorf("case UNION:\nsubject: %v\nclipping: %v\nexpected: %v\ngot: %v", 115 | c.subject, c.clipping, c.result, result) 116 | } 117 | } 118 | } 119 | 120 | func TestBug4(t *T) { 121 | if Short() { 122 | return 123 | } 124 | 125 | cases := []struct{ subject, clipping, result polyclip.Polygon }{ 126 | // original reported github issue #4, resulting in infinite loop 127 | { 128 | subject: polyclip.Polygon{{ 129 | {1.427255375e+06, -2.3283064365386963e-10}, 130 | {1.4271285e+06, 134.7111358642578}, 131 | {1.427109e+06, 178.30108642578125}}}, 132 | clipping: polyclip.Polygon{{ 133 | {1.416e+06, -12000}, 134 | {1.428e+06, -12000}, 135 | {1.428e+06, 0}, 136 | {1.416e+06, 0}, 137 | {1.416e+06, -12000}}}, 138 | }, 139 | } 140 | for _, c := range cases { 141 | // check that we get a result in finite time 142 | 143 | ch := make(chan polyclip.Polygon) 144 | go func() { 145 | ch <- c.subject.Construct(polyclip.UNION, c.clipping) 146 | }() 147 | 148 | select { 149 | case <-ch: 150 | case <-time.After(1 * time.Second): 151 | // panicking in attempt to get full stacktrace 152 | panic(fmt.Sprintf("case UNION:\nsubject: %v\nclipping: %v\ntimed out.", c.subject, c.clipping)) 153 | } 154 | } 155 | } 156 | 157 | func TestBug5(t *T) { 158 | rect := polyclip.Polygon{{{24, 7}, {36, 7}, {36, 23}, {24, 23}}} 159 | 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}}} 160 | 161 | expected := []struct { 162 | op polyclip.Op 163 | result polyclip.Polygon 164 | }{ 165 | { 166 | polyclip.UNION, 167 | 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}}}, 168 | }, 169 | { 170 | polyclip.INTERSECTION, 171 | 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}}}, 172 | }, 173 | { 174 | polyclip.DIFFERENCE, 175 | 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}}}, 176 | }, 177 | { 178 | polyclip.XOR, 179 | polyclip.Polygon{ 180 | {{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}}, 181 | {{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}}, 182 | }, 183 | }, 184 | } 185 | 186 | for _, e := range expected { 187 | result := rect.Construct(e.op, circle) 188 | if dump(result) != dump(e.result) { 189 | t.Errorf("case %d expected:\n%v\ngot:\n%v", e.op, dump(e.result), dump(result)) 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /clipper.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 Mateusz Czapliński (Go port) 2 | // Copyright (c) 2011 Mahir Iqbal (as3 version) 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // based on http://code.google.com/p/as3polyclip/ (MIT licensed) 22 | // and code by Martínez et al: http://wwwdi.ujaen.es/~fmartin/bool_op.html (public domain) 23 | 24 | package polyclip 25 | 26 | import ( 27 | "fmt" 28 | "math" 29 | ) 30 | 31 | //func _DBG(f func()) { f() } 32 | func _DBG(f func()) {} 33 | 34 | type polygonType int 35 | 36 | const ( 37 | _SUBJECT polygonType = iota 38 | _CLIPPING 39 | ) 40 | 41 | type edgeType int 42 | 43 | const ( 44 | _EDGE_NORMAL edgeType = iota 45 | _EDGE_NON_CONTRIBUTING 46 | _EDGE_SAME_TRANSITION 47 | _EDGE_DIFFERENT_TRANSITION 48 | ) 49 | 50 | // This class contains methods for computing clipping operations on polygons. 51 | // It implements the algorithm for polygon intersection given by Francisco Martínez del Río. 52 | // See http://wwwdi.ujaen.es/~fmartin/bool_op.html 53 | type clipper struct { 54 | subject, clipping Polygon 55 | eventQueue 56 | } 57 | 58 | func (c *clipper) compute(operation Op) Polygon { 59 | 60 | // Test 1 for trivial result case 61 | if len(c.subject)*len(c.clipping) == 0 { 62 | switch operation { 63 | case DIFFERENCE: 64 | return c.subject.Clone() 65 | case UNION: 66 | if len(c.subject) == 0 { 67 | return c.clipping.Clone() 68 | } 69 | return c.subject.Clone() 70 | } 71 | return Polygon{} 72 | } 73 | 74 | // Test 2 for trivial result case 75 | subjectbb := c.subject.BoundingBox() 76 | clippingbb := c.clipping.BoundingBox() 77 | if !subjectbb.Overlaps(clippingbb) { 78 | switch operation { 79 | case DIFFERENCE: 80 | return c.subject.Clone() 81 | case UNION: 82 | result := c.subject.Clone() 83 | for _, cont := range c.clipping { 84 | result.Add(cont.Clone()) 85 | } 86 | return result 87 | } 88 | return Polygon{} 89 | } 90 | 91 | // Add each segment to the eventQueue, sorted from left to right. 92 | for _, cont := range c.subject { 93 | for i := range cont { 94 | addProcessedSegment(&c.eventQueue, cont.segment(i), _SUBJECT) 95 | } 96 | } 97 | for _, cont := range c.clipping { 98 | for i := range cont { 99 | addProcessedSegment(&c.eventQueue, cont.segment(i), _CLIPPING) 100 | } 101 | } 102 | 103 | connector := connector{} // to connect the edge solutions 104 | 105 | // This is the sweepline. That is, we go through all the polygon edges 106 | // by sweeping from left to right. 107 | S := sweepline{} 108 | 109 | MINMAX_X := math.Min(subjectbb.Max.X, clippingbb.Max.X) 110 | 111 | _DBG(func() { 112 | e := c.eventQueue.dequeue() 113 | c.eventQueue.enqueue(e) 114 | fmt.Print("\nInitial queue:\n") 115 | for i, e := range c.eventQueue.elements { 116 | fmt.Println(i, "=", *e) 117 | } 118 | }) 119 | 120 | for !c.eventQueue.IsEmpty() { 121 | var prev, next *endpoint 122 | e := c.eventQueue.dequeue() 123 | _DBG(func() { fmt.Printf("\nProcess event: (of %d)\n%v\n", len(c.eventQueue.elements)+1, *e) }) 124 | 125 | // optimization 1 126 | switch { 127 | case operation == INTERSECTION && e.p.X > MINMAX_X: 128 | fallthrough 129 | case operation == DIFFERENCE && e.p.X > subjectbb.Max.X: 130 | return connector.toPolygon() 131 | //case operation == UNION && e.p.X > MINMAX_X: 132 | // _DBG(func() { fmt.Print("\nUNION optimization, fast quit\n") }) 133 | // // add all the non-processed line segments to the result 134 | // if !e.left { 135 | // connector.add(e.segment()) 136 | // } 137 | // 138 | // for !c.eventQueue.IsEmpty() { 139 | // e = c.eventQueue.dequeue() 140 | // if !e.left { 141 | // connector.add(e.segment()) 142 | // } 143 | // } 144 | // return connector.toPolygon() 145 | } 146 | 147 | if e.left { // the line segment must be inserted into S 148 | pos := S.insert(e) 149 | //e.PosInS = pos 150 | 151 | prev = nil 152 | if pos > 0 { 153 | prev = S[pos-1] 154 | } 155 | next = nil 156 | if pos < len(S)-1 { 157 | next = S[pos+1] 158 | } 159 | 160 | // Compute the inside and inOut flags 161 | switch { 162 | case prev == nil: // there is not a previous line segment in S? 163 | e.inside, e.inout = false, false 164 | case prev.edgeType != _EDGE_NORMAL: 165 | if pos-2 < 0 { // e overlaps with prev 166 | // Not sure how to handle the case when pos - 2 < 0, but judging 167 | // from the C++ implementation this looks like how it should be handled. 168 | e.inside, e.inout = false, false 169 | if prev.polygonType != e.polygonType { // [MC: where does this come from?] 170 | e.inside = true 171 | } else { 172 | e.inout = true 173 | } 174 | } else { // the previous two line segments in S are overlapping line segments 175 | prevTwo := S[pos-2] 176 | if prev.polygonType == e.polygonType { 177 | e.inout = !prev.inout 178 | e.inside = !prevTwo.inout 179 | } else { 180 | e.inout = !prevTwo.inout 181 | e.inside = !prev.inout 182 | } 183 | } 184 | case e.polygonType == prev.polygonType: // previous line segment in S belongs to the same polygon that "e" belongs to 185 | e.inside = prev.inside 186 | e.inout = !prev.inout 187 | default: // previous line segment in S belongs to a different polygon that "e" belongs to 188 | e.inside = !prev.inout 189 | e.inout = prev.inside 190 | } 191 | 192 | _DBG(func() { 193 | fmt.Println("Status line after insertion: ") 194 | for _, e := range S { 195 | fmt.Println(*e) 196 | } 197 | }) 198 | 199 | // Process a possible intersection between "e" and its next neighbor in S 200 | if next != nil { 201 | c.possibleIntersection(e, next) 202 | } 203 | // Process a possible intersection between "e" and its previous neighbor in S 204 | if prev != nil { 205 | c.possibleIntersection(prev, e) 206 | //c.possibleIntersection(&e, prev) 207 | } 208 | } else { // the line segment must be removed from S 209 | otherPos := -1 210 | for i := range S { 211 | if S[i].equals(e.other) { 212 | otherPos = i 213 | break 214 | } 215 | } 216 | // otherPos := S.IndexOf(e.other) 217 | // [or:] otherPos := e.other.PosInS 218 | 219 | if otherPos != -1 { 220 | prev = nil 221 | if otherPos > 0 { 222 | prev = S[otherPos-1] 223 | } 224 | next = nil 225 | if otherPos < len(S)-1 { 226 | next = S[otherPos+1] 227 | } 228 | } 229 | 230 | // Check if the line segment belongs to the Boolean operation 231 | switch e.edgeType { 232 | case _EDGE_NORMAL: 233 | switch operation { 234 | case INTERSECTION: 235 | if e.other.inside { 236 | connector.add(e.segment()) 237 | } 238 | case UNION: 239 | if !e.other.inside { 240 | connector.add(e.segment()) 241 | } 242 | case DIFFERENCE: 243 | if (e.polygonType == _SUBJECT && !e.other.inside) || 244 | (e.polygonType == _CLIPPING && e.other.inside) { 245 | connector.add(e.segment()) 246 | } 247 | case XOR: 248 | connector.add(e.segment()) 249 | } 250 | case _EDGE_SAME_TRANSITION: 251 | if operation == INTERSECTION || operation == UNION { 252 | connector.add(e.segment()) 253 | } 254 | case _EDGE_DIFFERENT_TRANSITION: 255 | if operation == DIFFERENCE { 256 | connector.add(e.segment()) 257 | } 258 | } 259 | 260 | // delete line segment associated to e from S and check for intersection between the neighbors of "e" in S 261 | if otherPos != -1 { 262 | S.remove(S[otherPos]) 263 | } 264 | 265 | if next != nil && prev != nil { 266 | c.possibleIntersection(next, prev) 267 | } 268 | 269 | _DBG(func() { fmt.Print("Connector:\n", connector, "\n") }) 270 | } 271 | _DBG(func() { 272 | fmt.Println("Status line after processing intersections: ") 273 | for _, e := range S { 274 | fmt.Println(*e) 275 | } 276 | }) 277 | } 278 | return connector.toPolygon() 279 | } 280 | 281 | func findIntersection(seg0, seg1 segment) (int, Point, Point) { 282 | var pi0, pi1 Point 283 | p0 := seg0.start 284 | d0 := Point{seg0.end.X - p0.X, seg0.end.Y - p0.Y} 285 | p1 := seg1.start 286 | d1 := Point{seg1.end.X - p1.X, seg1.end.Y - p1.Y} 287 | sqrEpsilon := 1e-7 // was 1e-3 earlier 288 | E := Point{p1.X - p0.X, p1.Y - p0.Y} 289 | kross := d0.X*d1.Y - d0.Y*d1.X 290 | sqrKross := kross * kross 291 | sqrLen0 := d0.Length() 292 | sqrLen1 := d1.Length() 293 | 294 | if sqrKross > sqrEpsilon*sqrLen0*sqrLen1 { 295 | // lines of the segments are not parallel 296 | s := (E.X*d1.Y - E.Y*d1.X) / kross 297 | if s < 0 || s > 1 { 298 | return 0, Point{}, Point{} 299 | } 300 | t := (E.X*d0.Y - E.Y*d0.X) / kross 301 | if t < 0 || t > 1 { 302 | return 0, Point{}, Point{} 303 | } 304 | // intersection of lines is a point an each segment [MC: ?] 305 | pi0.X = p0.X + s*d0.X 306 | pi0.Y = p0.Y + s*d0.Y 307 | 308 | // [MC: commented fragment removed] 309 | 310 | return 1, pi0, pi1 311 | } 312 | 313 | // lines of the segments are parallel 314 | sqrLenE := E.Length() 315 | kross = E.X*d0.Y - E.Y*d0.X 316 | sqrKross = kross * kross 317 | if sqrKross > sqrEpsilon*sqrLen0*sqrLenE { 318 | // lines of the segment are different 319 | return 0, pi0, pi1 320 | } 321 | 322 | // Lines of the segment are the same. Need to test for overlap of segments. 323 | // s0 = Dot (D0, E) * sqrLen0 324 | s0 := (d0.X*E.X + d0.Y*E.Y) / sqrLen0 325 | // s1 = s0 + Dot (D0, D1) * sqrLen0 326 | s1 := s0 + (d0.X*d1.X+d0.Y*d1.Y)/sqrLen0 327 | smin := math.Min(s0, s1) 328 | smax := math.Max(s0, s1) 329 | w := make([]float64, 0) 330 | imax := findIntersection2(0.0, 1.0, smin, smax, &w) 331 | 332 | if imax > 0 { 333 | pi0.X = p0.X + w[0]*d0.X 334 | pi0.Y = p0.Y + w[0]*d0.Y 335 | 336 | // [MC: commented fragment removed] 337 | 338 | if imax > 1 { 339 | pi1.X = p0.X + w[1]*d0.X 340 | pi1.Y = p0.Y + w[1]*d0.Y 341 | } 342 | } 343 | 344 | return imax, pi0, pi1 345 | } 346 | 347 | func findIntersection2(u0, u1, v0, v1 float64, w *[]float64) int { 348 | if u1 < v0 || u0 > v1 { 349 | return 0 350 | } 351 | if u1 == v0 { 352 | *w = append(*w, u1) 353 | return 1 354 | } 355 | 356 | // u1 > v0 357 | 358 | if u0 == v1 { 359 | *w = append(*w, u0) 360 | return 1 361 | } 362 | 363 | // u0 < v1 364 | 365 | if u0 < v0 { 366 | *w = append(*w, v0) 367 | } else { 368 | *w = append(*w, u0) 369 | } 370 | if u1 > v1 { 371 | *w = append(*w, v1) 372 | } else { 373 | *w = append(*w, u1) 374 | } 375 | return 2 376 | } 377 | 378 | func (c *clipper) possibleIntersection(e1, e2 *endpoint) { 379 | // [MC]: commented fragment removed 380 | 381 | numIntersections, ip1, _ := findIntersection(e1.segment(), e2.segment()) 382 | 383 | if numIntersections == 0 { 384 | return 385 | } 386 | 387 | if numIntersections == 1 && (e1.p.Equals(e2.p) || e1.other.p.Equals(e2.other.p)) { 388 | return // the line segments intersect at an endpoint of both line segments 389 | } 390 | 391 | //if numIntersections == 2 && e1.p.Equals(e2.p) { 392 | if numIntersections == 2 && e1.polygonType == e2.polygonType { 393 | return // the line segments overlap, but they belong to the same polygon 394 | } 395 | 396 | if numIntersections == 1 { 397 | if !e1.p.Equals(ip1) && !e1.other.p.Equals(ip1) { 398 | // if ip1 is not an endpoint of the line segment associated to e1 then divide "e1" 399 | c.divideSegment(e1, ip1) 400 | } 401 | if !e2.p.Equals(ip1) && !e2.other.p.Equals(ip1) { 402 | // if ip1 is not an endpoint of the line segment associated to e2 then divide "e2" 403 | c.divideSegment(e2, ip1) 404 | } 405 | return 406 | } 407 | 408 | // The line segments overlap 409 | sortedEvents := make([]*endpoint, 0) 410 | switch { 411 | case e1.p.Equals(e2.p): 412 | sortedEvents = append(sortedEvents, nil) // WTF [MC: WTF] 413 | case endpointLess(e1, e2): 414 | sortedEvents = append(sortedEvents, e2, e1) 415 | default: 416 | sortedEvents = append(sortedEvents, e1, e2) 417 | } 418 | 419 | switch { 420 | case e1.other.p.Equals(e2.other.p): 421 | sortedEvents = append(sortedEvents, nil) 422 | case endpointLess(e1.other, e2.other): 423 | sortedEvents = append(sortedEvents, e2.other, e1.other) 424 | default: 425 | sortedEvents = append(sortedEvents, e1.other, e2.other) 426 | } 427 | 428 | if len(sortedEvents) == 2 { // are both line segments equal? 429 | e1.edgeType, e1.other.edgeType = _EDGE_NON_CONTRIBUTING, _EDGE_NON_CONTRIBUTING 430 | if e1.inout == e2.inout { 431 | e2.edgeType, e2.other.edgeType = _EDGE_SAME_TRANSITION, _EDGE_SAME_TRANSITION 432 | } else { 433 | e2.edgeType, e2.other.edgeType = _EDGE_DIFFERENT_TRANSITION, _EDGE_DIFFERENT_TRANSITION 434 | } 435 | return 436 | } 437 | 438 | if len(sortedEvents) == 3 { // the line segments share an endpoint 439 | sortedEvents[1].edgeType, sortedEvents[1].other.edgeType = _EDGE_NON_CONTRIBUTING, _EDGE_NON_CONTRIBUTING 440 | var idx int 441 | // is the right endpoint the shared point? 442 | if sortedEvents[0] != nil { 443 | idx = 0 444 | } else { // the shared point is the left endpoint 445 | idx = 2 446 | } 447 | if e1.inout == e2.inout { 448 | sortedEvents[idx].other.edgeType = _EDGE_SAME_TRANSITION 449 | } else { 450 | sortedEvents[idx].other.edgeType = _EDGE_DIFFERENT_TRANSITION 451 | } 452 | if sortedEvents[0] != nil { 453 | c.divideSegment(sortedEvents[0], sortedEvents[1].p) 454 | } else { 455 | c.divideSegment(sortedEvents[2].other, sortedEvents[1].p) 456 | } 457 | return 458 | } 459 | 460 | if sortedEvents[0] != sortedEvents[3].other { 461 | // no line segment includes totally the OtherEnd one 462 | sortedEvents[1].edgeType = _EDGE_NON_CONTRIBUTING 463 | if e1.inout == e2.inout { 464 | sortedEvents[2].edgeType = _EDGE_SAME_TRANSITION 465 | } else { 466 | sortedEvents[2].edgeType = _EDGE_DIFFERENT_TRANSITION 467 | } 468 | c.divideSegment(sortedEvents[0], sortedEvents[1].p) 469 | c.divideSegment(sortedEvents[1], sortedEvents[2].p) 470 | return 471 | } 472 | 473 | // one line segment includes the other one 474 | sortedEvents[1].edgeType, sortedEvents[1].other.edgeType = _EDGE_NON_CONTRIBUTING, _EDGE_NON_CONTRIBUTING 475 | c.divideSegment(sortedEvents[0], sortedEvents[1].p) 476 | if e1.inout == e2.inout { 477 | sortedEvents[3].other.edgeType = _EDGE_SAME_TRANSITION 478 | } else { 479 | sortedEvents[3].other.edgeType = _EDGE_DIFFERENT_TRANSITION 480 | } 481 | c.divideSegment(sortedEvents[3].other, sortedEvents[2].p) 482 | } 483 | 484 | func (c *clipper) divideSegment(e *endpoint, p Point) { 485 | // "Right event" of the "left line segment" resulting from dividing e (the line segment associated to e) 486 | r := &endpoint{p: p, left: false, polygonType: e.polygonType, other: e, edgeType: e.edgeType} 487 | // "Left event" of the "right line segment" resulting from dividing e (the line segment associated to e) 488 | l := &endpoint{p: p, left: true, polygonType: e.polygonType, other: e.other, edgeType: e.other.edgeType} 489 | 490 | if endpointLess(l, e.other) { // avoid a rounding error. The left event would be processed after the right event 491 | // println("Oops") 492 | e.other.left = true 493 | e.left = false 494 | } 495 | 496 | e.other.other = l 497 | e.other = r 498 | 499 | c.eventQueue.enqueue(l) 500 | c.eventQueue.enqueue(r) 501 | } 502 | 503 | func addProcessedSegment(q *eventQueue, segment segment, polyType polygonType) { 504 | if segment.start.Equals(segment.end) { 505 | // Possible degenerate condition 506 | return 507 | } 508 | 509 | e1 := &endpoint{p: segment.start, left: true, polygonType: polyType} 510 | e2 := &endpoint{p: segment.end, left: true, polygonType: polyType, other: e1} 511 | e1.other = e2 512 | 513 | switch { 514 | case e1.p.X < e2.p.X: 515 | e2.left = false 516 | case e1.p.X > e2.p.X: 517 | e1.left = false 518 | case e1.p.Y < e2.p.Y: 519 | // the line segment is vertical. The bottom endpoint is the left endpoint 520 | e2.left = false 521 | default: 522 | e1.left = false 523 | } 524 | 525 | // Pushing it so the que is sorted from left to right, with object on the left having the highest priority 526 | q.enqueue(e1) 527 | q.enqueue(e2) 528 | } 529 | --------------------------------------------------------------------------------