polyclip is a pure-Go library for 2D polygon operations: boolean ops
(union, intersection, difference, XOR), polygon offsetting (Minkowski
sum / erosion with a disk), and the surrounding toolbox a slicer needs.
It is a slicer-grade replacement for the older Vatti ports in the Go
ecosystem.
The engine is a Vatti scanline that works internally on an exact fixed-point integer grid for numeric robustness, so it handles the cases naive float clippers choke on: concentric circles, self-touching polygons, collinear and coincident edges, and near-degenerate slivers.
- Pure Go, no cgo. The library itself has no dependencies beyond the standard library (testify is used only by the test suite).
- Closed API: every operation takes a
MultiPolygonand returns aMultiPolygon— no separate post-processing step to make the result usable. - Robust on adversarial input; correctness is held to a Monte-Carlo
differential oracle plus fuzzing (see
DESIGN.md§6).
The planar-polygon feature surface — boolean ops, offsetting, and the toolbox
above — is complete. The API
under the top-level polyclip package is the stable public surface; packages
under internal/ are implementation detail and may change without notice.
See DESIGN.md for the full design rationale and engine internals.
go get github.com/lestrrat-go/polyclip
Requires Go 1.26 or later.
| Area | API |
|---|---|
| Boolean ops | Union, Intersect, Difference, Xor, UnionAll |
| Self-intersection cleanup | Simplify |
| Offset (closed regions) | Offset with miter / round / square / bevel joins |
| Offset (open polylines → ribbons) | OffsetPaths with butt / square / round / joined end caps |
| Minkowski | MinkowskiSum, MinkowskiDiff |
| Fast axis-aligned clip | RectClip, RectClipLines |
| Path reduction | SimplifyPaths (Douglas–Peucker), Clean (dedup / tiny-feature removal) |
| Triangulation | Triangulate |
| Validation | Validate (structural diagnostics) |
Advanced (Builder) |
open-path clipping, selectable fill rules (incl. even-odd), nested PolyTree output, Z-coordinate tracking |
- A
Polygonis a ring of points; the closing edge from the last point back to the first is implicit — do not repeat the first point. - Outer rings are counter-clockwise, holes clockwise. Both orientations are accepted on input and normalized internally.
- Inputs are
float64in user units; the engine snaps to a fixed-point grid internally (DESIGN.md§5).
import "github.com/lestrrat-go/polyclip/geom"
// A square with a triangular hole, plus a second disjoint piece.
m := geom.New().
Point(0, 0).Point(10, 0).Point(10, 10).Point(0, 10).
Hole(geom.New().Point(3, 3).Point(6, 3).Point(5, 6).MustPolygon()).
NextPiece().
Point(20, 0).Point(24, 0).Point(22, 4).
MustBuild()
_ = mgeom.New() builds a MultiPolygon fluently: Point extends the current
piece's outer ring, Hole attaches one or more Polygon rings as holes
(pre-built, literal, or spread from a []Polygon), NextPiece starts a
disjoint piece, and Build/MustBuild normalizes winding (outer CCW, holes
CW). A ring can itself be built fluently and extracted with MustPolygon —
geom.New().Point(…)….MustPolygon() — then passed to Hole, as the hole above
shows. The value types are plain structs, so composite literals work too where
they read more clearly.
import (
"github.com/lestrrat-go/polyclip"
"github.com/lestrrat-go/polyclip/geom"
)
a := geom.New().Point(0, 0).Point(10, 0).Point(10, 10).Point(0, 10).MustBuild()
b := geom.New().Point(5, 5).Point(15, 5).Point(15, 15).Point(5, 15).MustBuild()
u, err := polyclip.Union(a, b) // a ∪ b
i, err := polyclip.Intersect(a, b) // a ∩ b
d, err := polyclip.Difference(a, b) // a ∖ b
x, err := polyclip.Xor(a, b) // a △ b
_, _, _, _, _ = u, i, d, x, err// Inflate by 2 units with rounded corners; a negative distance shrinks.
out, err := polyclip.Offset(a, 2, polyclip.OffsetOptions{Join: polyclip.JoinRound})
_, _ = out, errres, err := polyclip.New().
AddSubject(a).
AddClip(b).
Fill(polyclip.FillEvenOdd).
Execute(polyclip.OpUnion)
_, _ = res.Closed, err // res.Open carries any clipped open subjectsRunnable programs live in examples/.
MIT. See LICENSE.
The sweep engine's algorithm and data model are derived from
Clipper2 by Angus Johnson
(Boost Software License 1.0); see NOTICE for attribution.