iTriangle is a high-performance 2D polygon triangulation library for Rust. It turns real-world polygon input into triangle meshes, including shapes with holes, self-intersections, and mixed winding. The public API accepts f32/f64 floating-point data and i16/i32/i64 integer data, while the triangulation pipeline runs through a deterministic integer core for stable, reproducible output.
For detailed performance benchmarks, check out thePerformance Comparison
- Why iTriangle?
- Features
- Data Types and Solvers
- Architecture Overview
- Quick Start
- Documentation
- Examples
- Integer API
- Performance
- Gallery
- Contributing
- License
- Robust on complex input: supports holes, self-intersections, degenerate edges, and mixed winding.
- Deterministic core: geometry is processed with integer math, avoiding many floating-point corner cases.
- Flexible numeric model: use
f32/f64input or work directly withi16/i32/i64integer coordinates. - Mesh-ready outputs: triangles, Delaunay meshes, convex decomposition, tessellation, and centroid nets.
- Sweep-line Triangulation - Fast and simple triangulation of polygons with or without holes.
- Delaunay Triangulation - Efficient and robust implementation for generating Delaunay triangulations.
- Self-Intersection Handling – Fully supports self-intersecting polygons with automatic resolution.
- Adaptive Tessellation - Refine Delaunay triangles using circumcenters for better shape quality.
- Convex Decomposition - Convert triangulation into convex polygons.
- Centroidal Polygon Net: Build per-vertex dual polygons using triangle centers and edge midpoints.
- Steiner Points: Add custom inner points to influence triangulation.
- GPU-Friendly Layout: Triangles and vertices are naturally ordered by X due to the sweep-line algorithm, improving cache locality for rendering.
iTriangle is designed around a deterministic integer core:
- Floating-point APIs accept
f32andf64compatible point types, then map them into integer coordinates before triangulation. - Integer APIs work directly with
i16,i32, ori64coordinates when your geometry is already quantized. i32is the default integer coordinate type for floating-point input.i64is useful for larger coordinate ranges or finer fixed precision.i16can be useful for compact, bounded datasets where memory and cache behavior matter.
Add to your Cargo.toml:
[dependencies]
i_triangle = "0.45"Minimal example:
use i_triangle::float::triangulatable::Triangulatable;let contour = vec![[0.0,0.0],[10.0,0.0],[10.0,10.0],[0.0,10.0],];let triangulation = vec![contour].triangulate().to_triangulation::<u16>();println!("triangles: {}", triangulation.indices.len() / 3);By default, float input is converted to the robust integer core using i32
coordinates. If your geometry needs a different integer precision, choose it
explicitly:
use i_triangle::float::triangulatable::Triangulatable;use i_triangle::float::triangulator::Triangulator;let shape = vec![vec![[0.0,0.0],[10.0,0.0],[10.0,10.0],[0.0,10.0],]];// One-shot triangulation with i64 integer coordinates.let mesh = shape.triangulate_as::<i64>().to_triangulation::<u32>();// Reusable triangulator: first generic is index type, second is coordinate type.letmut triangulator = Triangulator::<u32,i64>::default();let mesh = triangulator.triangulate(&shape);use i_triangle::float::triangulatable::Triangulatable;use i_triangle::float::triangulation::Triangulation;let shape = vec![
vec![// body[0.0,20.0],// 0[-10.0,8.0],// 1[-7.0,6.0],// 2[-6.0,2.0],// 3[-8.0, -2.0],// 4[-13.0, -4.0],// 5[-16.0, -3.0],// 6[-18.0,0.0],// 7[-25.0, -7.0],// 8[-14.0, -15.0],// 9[0.0, -18.0],// 10[14.0, -15.0],// 11[26.0, -7.0],// 12[17.0,1.0],// 13[13.0, -1.0],// 14[9.0,1.0],// 15[7.0,6.0],// 16[8.0,10.0],// 17],
vec![// hole[2.0,0.0],// 0[5.0, -2.0],// 1[7.0, -5.0],// 2[5.0, -9.0],// 3[2.0, -11.0],// 4[-2.0, -9.0],// 5[-4.0, -5.0],// 6[-2.0, -2.0],// 7],];let triangulation = shape.triangulate().to_triangulation::<u16>();println!("points: {:?}", triangulation.points);println!("indices: {:?}", triangulation.indices);let delaunay_triangulation:Triangulation<[f64;2],u16> =
shape.triangulate().into_delaunay().to_triangulation();println!("points: {:?}", delaunay_triangulation.points);println!("indices: {:?}", delaunay_triangulation.indices);let convex_polygons = shape.triangulate().into_delaunay().to_convex_polygons();println!("convex polygons: {:?}", convex_polygons);let tessellation:Triangulation<[f64;2],u16> = shape
.triangulate().into_delaunay().refine_with_circumcenters_by_obtuse_angle(0.0).to_triangulation();println!("points: {:?}", tessellation.points);println!("indices: {:?}", tessellation.indices);let centroids = shape
.triangulate().into_delaunay().refine_with_circumcenters_by_obtuse_angle(0.0).to_centroid_net(0.0);println!("centroids: {:?}", centroids);💡 Output: Triangle indices and vertices, where all triangles oriented in a counter-clockwise direction.
If you need to triangulate many shapes, it is more efficient to use Triangulator.
use i_triangle::float::triangulation::Triangulation;use i_triangle::float::triangulator::Triangulator;let contours = vec![
vec![[0.0,0.0],[4.0,0.0],[4.0,4.0],[0.0,4.0]],
vec![[5.0,0.0],[9.0,0.0],[9.0,4.0],[5.0,4.0]],];// Uses u32 triangle indices and the default i32 integer coordinate solver.letmut triangulator = Triangulator::<u32>::default();// Enable Delaunay refinement
triangulator.delaunay(true);// Use fast Earcut solver for contours with ≤ 64 points
triangulator.earcut(true);letmut triangulation = Triangulation::with_capacity(100);for contour in contours.iter(){// Triangulate using self-intersection resolver
triangulator.triangulate_into(contour,&mut triangulation);println!("points: {:?}", triangulation.points);println!("indices: {:?}", triangulation.indices);}The integer API is useful when your coordinates are already quantized or when you want direct control over the robust integer core. It avoids float-to-int adapter setup and returns integer points unchanged.
Use IntPoint with IntContour, IntShape, or IntShapes-compatible containers:
use i_triangle::int::triangulatable::IntTriangulatable;use i_triangle::i_overlay::i_float::int::point::IntPoint;let contour = vec![IntPoint::new(0,0),IntPoint::new(10,0),IntPoint::new(10,10),IntPoint::new(0,10),];let triangulation = contour.triangulate().into_triangulation::<u16>();assert_eq!(triangulation.points.len(),4);assert_eq!(triangulation.indices.len(),6);For repeated triangulation, use IntTriangulator and reuse its internal buffers:
use i_triangle::int::triangulation::IntTriangulation;use i_triangle::int::triangulator::IntTriangulator;use i_triangle::i_overlay::i_float::int::point::IntPoint;let contours = vec![
vec![IntPoint::new(0,0),IntPoint::new(10,0),IntPoint::new(10,10),IntPoint::new(0,10),],
vec![IntPoint::new(20,0),IntPoint::new(30,0),IntPoint::new(30,10),IntPoint::new(20,10),],];letmut triangulator = IntTriangulator::<i32,u32>::default();letmut output = IntTriangulation::<i32,u32>::default();for contour in&contours {
triangulator.triangulate_contour_into(contour.clone(),&mut output);assert!(!output.indices.is_empty());}If your integer contours are already valid and correctly oriented, the unchecked API skips validation:
use i_triangle::int::unchecked::IntUncheckedTriangulatable;use i_triangle::i_overlay::i_float::int::point::IntPoint;let contour = vec![IntPoint::new(0,0),IntPoint::new(10,0),IntPoint::new(10,10),IntPoint::new(0,10),];let triangulation = contour.uncheck_triangulate().into_triangulation::<u16>();assert_eq!(triangulation.indices.len(),6);Benchmarks and interactive demos are available here:
| Delaunay | Convex Polygons | Steiner Points |
|---|---|---|
| Tessellation | Centroid Net | |
|---|---|---|
See CONTRIBUTING.md for development setup, tests, and PR guidelines.
Licensed under either of:
- MIT license (LICENSE-MIT)
- Apache License, Version 2.0 (LICENSE-APACHE)
