Skip to content

ydah/clipper2

Repository files navigation

Clipper2

Gem Version Ruby

Clipper2 brings robust 2D polygon clipping and offsetting to Ruby. It wraps Angus Johnson's Clipper2 C export API with FFI and keeps all native geometry on a quantized signed-integer grid.

The gem supports union, intersection, difference, XOR, open paths, polygon and line offsets, rectangle clipping, Minkowski sums, point containment, path simplification, and dependency-free Earcut triangulation into GPU-ready buffers.

Installation

Add the gem to your bundle:

bundle add clipper2

Or install it directly:

gem install clipper2

Platform gems contain a precompiled shared library. When a platform gem is unavailable, the source gem builds the pinned Clipper2 C++17 sources with Ruby's standard extension toolchain and therefore requires a C++ compiler and make. CMake is used by maintainers to build platform gems.

Coordinate model

Ruby callers use ordinary numeric coordinates. Path quantizes them to signed 64-bit integers using a scale and restores floats when enumerated:

require "clipper2"

Clipper2.default_scale = 1_000 # 0.001 coordinate-unit precision

path = Clipper2::Path[[0, 0], [10, 0], [10, 10], [0, 10]]
path.to_a       # => [[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0]]
path.area       # => 100.0 (signed; positive is counter-clockwise)
path.orientation # => :ccw
path.bounds     # => [0.0, 0.0, 10.0, 10.0]

Every Paths collection owns one scale. Operations between different scales raise Clipper2::ScaleError; coordinates that cannot be represented safely raise Clipper2::RangeError. Choose a scale that gives the precision you need without making coordinate * scale unnecessarily large.

Boolean operations

All convenience methods accept a Path, Paths, one raw point array, or an array of raw paths, and return Clipper2::Paths:

a = Clipper2::Path[[0, 0], [10, 0], [10, 10], [0, 10]]
b = Clipper2::Path[[5, 0], [15, 0], [15, 10], [5, 10]]

Clipper2.union(a, b)
Clipper2.intersect(a, b, fill_rule: :non_zero)
Clipper2.difference(a, b)
Clipper2.xor(a, b)

Fill rules are :even_odd, :non_zero, :positive, and :negative. Clipper2 intentionally handles self-intersecting input according to the selected fill rule.

For open subject paths, use the general API. It returns closed and open solutions separately:

line = Clipper2::Path[[-5, 5], [15, 5]]
closed, open = Clipper2.boolean(:intersection, [], a, subject_open: line)

Offsets and clipping

grown = Clipper2.inflate(a, 2.5, join: :round, end_type: :polygon)
shrunk = Clipper2.inflate(a, -1.0)

open_line = Clipper2::Path[[0, 0], [10, 0]]
stroke = Clipper2.inflate(open_line, 3.0, join: :round, end_type: :round)

inside = Clipper2.rect_clip(grown, [0, 0, 12, 12])
line_segments = Clipper2.rect_clip_lines(open_line, [2, -1, 8, 1])

Join types are :square, :bevel, :round, and :miter. End types are :polygon, :joined, :butt, :square, and :round. arc_tolerance defaults to 0.25 public coordinate units and is scaled automatically.

Minkowski sums and differences use the same integer path representation:

pattern = Clipper2::Path[[0, 0], [1, 0], [1, 1], [0, 1]]
Clipper2.minkowski_sum(pattern, a)
Clipper2.minkowski_difference(pattern, a)

Path utilities

path.contains?([5, 5]) # boundary points are included
path.reverse
path.simplify(0.05)
path.to_packed          # little-endian float32 x/y pairs

Paths#simplify applies the same tolerance to each member.

Packed triangulation

Clipper2.triangulate uses the bundled pure Ruby Earcut implementation. Counter-clockwise rings are outer contours and clockwise rings contained by an outer contour are holes:

outer = Clipper2::Path[[0, 0], [10, 0], [10, 10], [0, 10]]
hole = Clipper2::Path[[2, 2], [2, 8], [8, 8], [8, 2]] # clockwise

mesh = Clipper2.triangulate(Clipper2::Paths[outer, hole])
mesh.indices     # little-endian packed u32 triangle indices
mesh.vertices    # little-endian packed float32 x/y vertices
mesh.index_count
mesh.vertex_count

indices, vertices = mesh # array destructuring is also supported

Every emitted triangle is counter-clockwise in a y-up coordinate system. The two packed strings can be uploaded directly by renderers that accept raw buffers. See examples/rugl_mesh.rb and examples/larb_transform.rb.

Empty input and errors

Empty inputs return empty Paths or an empty Triangulation. Invalid options and rectangles raise ArgumentError; scale, range, codec, triangulation, and native failures use subclasses of Clipper2::Error.

Native arrays are always copied once into Ruby and released through Clipper2's matching DisposeArray64 export.

Development

Ruby 3.2 or newer, CMake, and a C++17 compiler are required.

bundle install
bundle exec rake              # generated-binding check, native build, and specs
bundle exec rake native:build # native library only
bundle exec rake native:gem   # platform gem in pkg/
bundle exec rake build        # source gem in pkg/

ext/clipper2/generate_bindings.rb derives enum values and export names from the vendored headers. Run bundle exec rake bindings:generate after updating Clipper2 and commit the generated Ruby file.

The SVG boolean demo is available at examples/svg_boolean.rb.

Licenses

The Ruby wrapper is MIT licensed. Vendored Clipper2 2.0.1 is distributed under the Boost Software License 1.0, and the Ruby Earcut port carries Mapbox's ISC license. The corresponding notices are included in LICENSE.txt, licenses/CLIPPER2-BSL-1.0.txt, and licenses/EARCUT-ISC.txt.

About

Ruby FFI bindings for robust Clipper2 polygon clipping, offsets, and GPU-ready triangulation.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages