Constrained Delaunay triangulation and meshing for R with CGAL: a persistent mesh handle you can edit and refine, with the same vertices / segments / triangles contract as cdtr.
laridae is the CGAL-backed member of the hypertidy meshing stack. cdtr is the header-only, builds-anywhere baseline; laridae is the ceiling: everything CGAL's 2D constrained triangulations and Mesh_2 can do, exposed in the same shape so the backend is a choice, not a rewrite.
Constrained_triangulation_plus_2 over a constrained Delaunay
triangulation with exact predicates), vertex ids are stable for the life of
the handledepth per triangle: constraint crossings from the outside, computed
topologically (odd inside, even hole for nested rings; matches cdtr)Delaunay_mesher_2, an edge-length floor, and a report of what could not
be refinedlaridae uses CGAL's Delaunay_mesher_2 (GPL). The CGAL (>= 6.0) and Boost
headers are supplied at build time by the CRAN packages RcppCGAL and BH
(via LinkingTo), so there is no system CGAL to install; CGAL is header-only
here and nothing is linked (no GMP: the exact-predicates kernel is enough).
vignette("laridae")).The header dependencies (RcppCGAL, BH, Rcpp, cpp11) come from CRAN
automatically; no system CGAL is required.
# remotes::install_github("hypertidy/laridae")
install.packages("laridae", repos = c('https://hypertidy.r-universe.dev', 'https://cloud.r-project.org'))
RcppCGAL ships patched CGAL headers: it rewrites CGAL's std::cout /
std::cerr / abort / exit calls so the compiled code is clean under
R CMD check --as-cran. That patched build is the default and the only
CRAN-acceptable route.
For development you can instead build against pristine upstream CGAL (>= 6.0)
headers by setting CGAL_INCLUDE_DIR before install; configure then uses
them in preference to RcppCGAL (Boost still comes from BOOST_INCLUDE_DIR,
the system, or BH):
# a CGAL "library" release tarball, or brew install cgal (macOS)
export CGAL_INCLUDE_DIR=/path/to/CGAL-6.1/include
R CMD INSTALL .
Stock headers build and run, but R CMD check reports a checking compiled code ... WARNING (references to std::cout/abort/etc.), so this route is
for local development only and is not the CRAN build.
library(laridae)
## a square with a square hole: two closed rings of segments
x <- c(0, 1, 1, 0, 0.25, 0.75, 0.75, 0.25)
y <- c(0, 0, 1, 1, 0.25, 0.25, 0.75, 0.75)
m <- lari_new(x, y, s0 = 1:8, s1 = c(2, 3, 4, 1, 6, 7, 8, 5))
table(lari_depth(m)) # 1 = in the outer ring, 2 = in the hole
lari_refine(m, max_area = 0.01, min_angle = 25)
lari_counts(m)
## edit and re-refine: only the neighbourhood of the edit is touched
ids <- lari_add_points(m, c(0.1, 0.15), c(0.1, 0.12))
lari_add_segments(m, ids[1], ids[2])
lari_refine(m, max_area = 0.01, min_angle = 25)
## or one point at a time
lari_step(m, n = 10)
## a sizing field: local maximum area as a function of position
m2 <- lari_new(c(0, 1, 1, 0), c(0, 0, 1, 1), 1:4, c(2, 3, 4, 1))
lari_refine(m2, size = function(x, y) 0.0005 + 0.01 * x)
## the contract tables
lari_vertices(m) # x, y, attributes, origin (input/crossing/steiner), id
lari_triangles(m) # v0, v1, v2 (rows of the vertices table), depth
lari_segments(m) # v0, v1, input segment id, count of covering segments
lari_triangulate() is the one-shot form, returning the three tables like
cdtr's cdt_triangulate(). Per-vertex attributes (PA = cbind(z_ = z)) ride
along: crossings and Steiner points get linearly interpolated values.
| function | |
|---|---|
lari_new(x, y, s0, s1, PA) |
create a handle (empty with no arguments) |
lari_add_points(m, x, y, PA) |
insert points, returns stable ids |
lari_add_segments(m, s0, s1) |
insert constraints between ids, crossings inserted |
lari_remove_points(m, ids) |
remove vertices and the segments ending at them |
lari_remove_segments(m, s0, s1, id) |
remove constraints (and crossings left without one) |
lari_refine(m, min_angle, max_area, size, max_steiner, min_edge_length, seeds, erase) |
Mesh_2 refinement |
lari_step(m, n) |
step-by-step refinement with the last criteria |
lari_vertices, lari_triangles, lari_segments, lari_depth, lari_tables
|
output tables |
lari_counts(m) |
counts by origin and the unrefined report |
inst/benchmarks/ runs laridae against cdtr and RTriangle on sf's North
Carolina counties and anglr's Tasmanian contours and cadastre
(results.txt holds the last full run). On one Linux core (CGAL 6.1,
R 4.3), nc at max_area = A/5000 with min_angle = 20:
| median | |
|---|---|
| RTriangle (Triangle) | 5.8 ms |
| laridae | 15 ms |
| cdtr | 19 ms |
Constrained-only output (no refinement) is identical across the three on
all cases. For bare points (points.R) CGAL is the fastest: a million normal
points triangulate in 1.9 s including table extraction, against 2.5 s for
cdtr and 3.5 s for RTriangle.
After a small edit (a short new constraint) a second refine of the refined nc mesh takes 3 ms and inserts 6 vertices, against 16 ms for a rebuild.
The original laridae (2017-2022, Rcpp and cgalh experiments with
point triangulation and segment insertion) is preserved on the
legacy-rcpp
branch.
GPL (>= 3), as required by CGAL's 2D meshing package.