From 86927f228108ca317c50538d270b3bb8eb3fb51d Mon Sep 17 00:00:00 2001 From: glennDittmann Date: Sat, 31 Jan 2026 02:49:43 +0100 Subject: [PATCH 1/3] provide a wasm interface for triangulation 2d --- Cargo.lock | 2 ++ rita/Cargo.toml | 10 ++++-- rita/src/lib.rs | 3 ++ rita/src/wasm.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 rita/src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index 30b9817..9c18780 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3069,11 +3069,13 @@ dependencies = [ "anyhow", "arbitrary", "geogram_predicates", + "js-sys", "log", "nalgebra", "rayon", "rita_test_utils", "robust", + "wasm-bindgen", ] [[package]] diff --git a/rita/Cargo.toml b/rita/Cargo.toml index 01c253e..1f2caf4 100644 --- a/rita/Cargo.toml +++ b/rita/Cargo.toml @@ -13,12 +13,14 @@ keywords = [ "math", ] categories = ["algorithms", "graphics"] - repository = "https://github.com/glennDittmann/rita" authors = ["Glenn Dittmann "] license = "MIT" readme = "../README.md" +[lib] +crate-type = ["rlib", "cdylib"] + [dependencies] anyhow = { version = "1.0", default-features = false } geogram_predicates = { version = "0.2.1", optional = true } @@ -30,6 +32,8 @@ nalgebra = { version = "0.33", features = [ ], default-features = false } rayon = "1.10" robust = { version = "1.2", optional = true } +wasm-bindgen = { version = "0.2", optional = true } +js-sys = { version = "0.3", optional = true } arbitrary = { version = "1.4", optional = true, features = ["derive"] } [dev-dependencies] @@ -39,8 +43,8 @@ rita_test_utils = { path = "../rita_test_utils" } default = ["std", "geogram"] std = ["anyhow/std", "nalgebra/std"] geogram = ["dep:geogram_predicates"] -# wasm: use pure-Rust robust predicates (no weighted Delaunay). For wasm32 build with: --no-default-features --features "std,wasm" -wasm = ["dep:robust"] +# wasm: use pure-Rust robust predicates + JS API. For wasm32: --no-default-features --features "std,wasm" +wasm = ["dep:robust", "dep:wasm-bindgen", "dep:js-sys"] timing = ["std"] logging = ["dep:log"] log_timing = ["logging", "timing"] diff --git a/rita/src/lib.rs b/rita/src/lib.rs index cb39f71..256ff27 100644 --- a/rita/src/lib.rs +++ b/rita/src/lib.rs @@ -27,3 +27,6 @@ pub mod tetrahedralization; pub mod triangulation; mod trids; mod utils; + +#[cfg(feature = "wasm")] +pub mod wasm; diff --git a/rita/src/wasm.rs b/rita/src/wasm.rs new file mode 100644 index 0000000..e825f9c --- /dev/null +++ b/rita/src/wasm.rs @@ -0,0 +1,81 @@ +//! WASM bindings for 2D Delaunay triangulation. +//! +//! Provides a single function `triangulate` that takes flat vertex coordinates and optional +//! epsilon, and returns triangles and vertices in the same shape as vita's TriangulationResult +//! (vec of Triangle3, vec of Vertex3). For 2D, Vertex3 uses `y: 0` and `x,z` for the plane. + +use crate::triangulation::Triangulation; +use wasm_bindgen::prelude::*; + +/// 2D Delaunay triangulation. +/// +/// # Arguments +/// * `vertices` - Flat array of 2D coordinates: [x1, y1, x2, y2, ...] +/// * `epsilon` - Optional epsilon for regularity (pass `null` or omit for `None`). When provided, +/// a positive value can speed up the triangulation. +/// +/// # Returns +/// A JavaScript object with: +/// * `triangles` - Array of `{ id, a: { x, y, z }, b, c }` (2D: y = 0, x/z are the plane) +/// * `vertices` - Array of `{ x, y, z }` (2D: y = 0) +#[wasm_bindgen(js_name = triangulate)] +pub fn triangulate_2d(vertices: &[f64], epsilon: Option) -> Result { + let vertices_2d = parse_vertices_2d(vertices)?; + if vertices_2d.len() < 3 { + return Err(JsValue::from_str( + "At least 3 vertices are required for 2D triangulation", + )); + } + + let mut t = Triangulation::new(epsilon); + t.insert_vertices(&vertices_2d, None, true) + .map_err(|e| JsValue::from_str(&format!("insert_vertices failed: {}", e)))?; + + let tri_list = t.tris(); + let vert_list = t.vertices(); + + let triangles_js = js_sys::Array::new(); + for (i, tri) in tri_list.iter().enumerate() { + let obj = triangle_to_js(tri, i)?; + triangles_js.push(&obj); + } + + let vertices_js = js_sys::Array::new(); + for v in vert_list.iter() { + let obj = vertex2_to_js(v); + vertices_js.push(&obj); + } + + let result = js_sys::Object::new(); + js_sys::Reflect::set(&result, &"triangles".into(), &triangles_js)?; + js_sys::Reflect::set(&result, &"vertices".into(), &vertices_js)?; + Ok(result.into()) +} + +fn parse_vertices_2d(flat: &[f64]) -> Result, JsValue> { + if flat.len() % 2 != 0 { + return Err(JsValue::from_str( + "Vertices must have even length (pairs of x, y)", + )); + } + Ok(flat.chunks_exact(2).map(|c| [c[0], c[1]]).collect()) +} + +/// [x, y] -> { x, y: 0, z } (vita-style 2D vertex in Vertex3) +fn vertex2_to_js(v: &[f64; 2]) -> JsValue { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"x".into(), &v[0].into()).unwrap(); + js_sys::Reflect::set(&obj, &"y".into(), &0.0_f64.into()).unwrap(); + js_sys::Reflect::set(&obj, &"z".into(), &v[1].into()).unwrap(); + obj.into() +} + +/// Triangle2 -> { id, a, b, c } with Vertex3 (2D: y = 0) +fn triangle_to_js(tri: &[[f64; 2]; 3], index: usize) -> Result { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"id".into(), &format!("tri_{}", index).into())?; + js_sys::Reflect::set(&obj, &"a".into(), &vertex2_to_js(&tri[0]))?; + js_sys::Reflect::set(&obj, &"b".into(), &vertex2_to_js(&tri[1]))?; + js_sys::Reflect::set(&obj, &"c".into(), &vertex2_to_js(&tri[2]))?; + Ok(obj.into()) +} From cbf0d463e4005dfccf1dff3587228028837ebca7 Mon Sep 17 00:00:00 2001 From: glennDittmann Date: Sat, 31 Jan 2026 02:56:57 +0100 Subject: [PATCH 2/3] only export 2d vertices from wasm triangulate 2d api --- rita/src/wasm.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/rita/src/wasm.rs b/rita/src/wasm.rs index e825f9c..c7dc736 100644 --- a/rita/src/wasm.rs +++ b/rita/src/wasm.rs @@ -1,8 +1,7 @@ //! WASM bindings for 2D Delaunay triangulation. //! //! Provides a single function `triangulate` that takes flat vertex coordinates and optional -//! epsilon, and returns triangles and vertices in the same shape as vita's TriangulationResult -//! (vec of Triangle3, vec of Vertex3). For 2D, Vertex3 uses `y: 0` and `x,z` for the plane. +//! epsilon, and returns triangles and vertices as 2D objects only: `{ x, y }`. use crate::triangulation::Triangulation; use wasm_bindgen::prelude::*; @@ -16,8 +15,8 @@ use wasm_bindgen::prelude::*; /// /// # Returns /// A JavaScript object with: -/// * `triangles` - Array of `{ id, a: { x, y, z }, b, c }` (2D: y = 0, x/z are the plane) -/// * `vertices` - Array of `{ x, y, z }` (2D: y = 0) +/// * `triangles` - Array of `{ id, a: { x, y }, b, c }` +/// * `vertices` - Array of `{ x, y }` #[wasm_bindgen(js_name = triangulate)] pub fn triangulate_2d(vertices: &[f64], epsilon: Option) -> Result { let vertices_2d = parse_vertices_2d(vertices)?; @@ -61,16 +60,15 @@ fn parse_vertices_2d(flat: &[f64]) -> Result, JsValue> { Ok(flat.chunks_exact(2).map(|c| [c[0], c[1]]).collect()) } -/// [x, y] -> { x, y: 0, z } (vita-style 2D vertex in Vertex3) +/// [x, y] -> { x, y } (2D vertex, same dimension as input) fn vertex2_to_js(v: &[f64; 2]) -> JsValue { let obj = js_sys::Object::new(); js_sys::Reflect::set(&obj, &"x".into(), &v[0].into()).unwrap(); - js_sys::Reflect::set(&obj, &"y".into(), &0.0_f64.into()).unwrap(); - js_sys::Reflect::set(&obj, &"z".into(), &v[1].into()).unwrap(); + js_sys::Reflect::set(&obj, &"y".into(), &v[1].into()).unwrap(); obj.into() } -/// Triangle2 -> { id, a, b, c } with Vertex3 (2D: y = 0) +/// Triangle2 -> { id, a, b, c } with each corner as { x, y } fn triangle_to_js(tri: &[[f64; 2]; 3], index: usize) -> Result { let obj = js_sys::Object::new(); js_sys::Reflect::set(&obj, &"id".into(), &format!("tri_{}", index).into())?; From a6760785410aa83d34255c276f2220c41abd9959 Mon Sep 17 00:00:00 2001 From: glennDittmann Date: Sat, 31 Jan 2026 11:57:51 +0100 Subject: [PATCH 3/3] add wasm publishing info --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 349cc08..f9243ac 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,19 @@ The workaround is to have an abstraction layer over the geometric predicates. Fo When activating `wasm` the fallback rust-only predicates will be used. The downside of these is that they do not support lifted orientation test, i.e. we can not compute weighted Delaunay triangulations. For the majority of the use cases however this is fine, and maybe `robust` can be extended in the future to support the weighted predicates as well. +### Building and publishing the WASM package +The name `rita` is already taken on npm, so **pass the scope at build time** so the generated `package.json` gets a scoped name (e.g. `@lempf/rita`). If you build without `--scope`, the package name stays `rita` and publish will try to use the existing npm package. + +Build with your npm scope (use `--` so `--no-default-features` and `--features` go to cargo, not wasm-pack): +```bash +wasm-pack build rita --scope lempf --target web -- --no-default-features --features "std,wasm" +``` +Then publish (scoped packages require `--access public`): +```bash +cd rita/pkg && npm publish --access=public +``` +Install as `npm install @lempf/rita`. + ## Testing To make sure both predicate libraries produce the same results tests can be run for both features.