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/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. 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..c7dc736 --- /dev/null +++ b/rita/src/wasm.rs @@ -0,0 +1,79 @@ +//! WASM bindings for 2D Delaunay triangulation. +//! +//! Provides a single function `triangulate` that takes flat vertex coordinates and optional +//! epsilon, and returns triangles and vertices as 2D objects only: `{ x, y }`. + +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 }, 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)?; + 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 } (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(), &v[1].into()).unwrap(); + obj.into() +} + +/// 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())?; + 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()) +}