use super::mesh_outline::MeshOutlineJs;
use ifc_lite_geometry::{
boolean_2d, resolve_2d, sanitize_contours, BooleanOp2D, ContourSet, Ring2D,
};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Contours2D {
set: ContourSet,
}
#[wasm_bindgen]
impl Contours2D {
#[wasm_bindgen(constructor)]
pub fn new(coords: &[f64], ring_lengths: &[u32]) -> Result<Contours2D, JsValue> {
if !coords.len().is_multiple_of(2) {
return Err(js_sys::Error::new("Contours2D: coords length must be even").into());
}
let expected = ring_lengths
.iter()
.try_fold(0usize, |acc, n| acc.checked_add(*n as usize))
.and_then(|v| v.checked_mul(2))
.ok_or_else(|| js_sys::Error::new("Contours2D: ringLengths overflow"))?;
if expected != coords.len() {
return Err(js_sys::Error::new(&format!(
"Contours2D: ringLengths sum to {} vertices but coords hold {}",
expected / 2,
coords.len() / 2
))
.into());
}
let mut rings: Vec<Ring2D> = Vec::with_capacity(ring_lengths.len());
let mut at = 0usize;
for n in ring_lengths {
let n = *n as usize;
let ring = coords[at * 2..(at + n) * 2]
.chunks_exact(2)
.map(|p| [p[0], p[1]])
.collect();
rings.push(ring);
at += n;
}
Ok(Contours2D {
set: ContourSet {
rings: sanitize_contours(&rings),
shape_offsets: Vec::new(),
},
})
}
#[wasm_bindgen(js_name = fromMeshOutline)]
pub fn from_mesh_outline(outline: &MeshOutlineJs) -> Contours2D {
Contours2D {
set: ContourSet {
rings: sanitize_contours(&outline.rings_f64()),
shape_offsets: Vec::new(),
},
}
}
#[wasm_bindgen(getter, js_name = ringCount)]
pub fn ring_count(&self) -> usize {
self.set.rings.len()
}
#[wasm_bindgen(getter, js_name = shapeCount)]
pub fn shape_count(&self) -> usize {
self.set.shape_count()
}
#[wasm_bindgen(getter, js_name = isEmpty)]
pub fn is_empty(&self) -> bool {
self.set.is_empty()
}
#[wasm_bindgen(js_name = shapeOffsets)]
pub fn shape_offsets(&self) -> js_sys::Uint32Array {
let v: Vec<u32> = self
.set
.shape_offsets
.iter()
.map(|o| *o as u32)
.collect();
js_sys::Uint32Array::from(&v[..])
}
pub fn ring(&self, index: usize) -> Option<js_sys::Float64Array> {
let ring = self.set.rings.get(index)?;
let mut flat = Vec::with_capacity(ring.len() * 2);
for p in ring {
flat.push(p[0]);
flat.push(p[1]);
}
Some(js_sys::Float64Array::from(&flat[..]))
}
pub fn coords(&self) -> js_sys::Float64Array {
let total: usize = self.set.rings.iter().map(|r| r.len() * 2).sum();
let mut flat = Vec::with_capacity(total);
for ring in &self.set.rings {
for p in ring {
flat.push(p[0]);
flat.push(p[1]);
}
}
js_sys::Float64Array::from(&flat[..])
}
#[wasm_bindgen(js_name = ringLengths)]
pub fn ring_lengths(&self) -> js_sys::Uint32Array {
let v: Vec<u32> = self.set.rings.iter().map(|r| r.len() as u32).collect();
js_sys::Uint32Array::from(&v[..])
}
pub fn bounds(&self) -> Option<js_sys::Float64Array> {
self.set
.bounds()
.map(|b| js_sys::Float64Array::from(&b[..]))
}
}
#[wasm_bindgen(js_name = union2d)]
pub fn union_2d(a: &Contours2D, b: &Contours2D) -> Contours2D {
Contours2D {
set: boolean_2d(&a.set.rings, &b.set.rings, BooleanOp2D::Union),
}
}
#[wasm_bindgen(js_name = difference2d)]
pub fn difference_2d(a: &Contours2D, b: &Contours2D) -> Contours2D {
Contours2D {
set: boolean_2d(&a.set.rings, &b.set.rings, BooleanOp2D::Difference),
}
}
#[wasm_bindgen(js_name = intersection2d)]
pub fn intersection_2d(a: &Contours2D, b: &Contours2D) -> Contours2D {
Contours2D {
set: boolean_2d(&a.set.rings, &b.set.rings, BooleanOp2D::Intersection),
}
}
#[wasm_bindgen(js_name = resolve2d)]
pub fn resolve_2d_js(a: &Contours2D) -> Contours2D {
Contours2D {
set: resolve_2d(&a.set.rings),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_keeps_multi_ring_offsets_distinct_for_rings_of_different_lengths() {
#[rustfmt::skip]
let coords: Vec<f64> = vec![
0.0, 0.0, 4.0, 0.0, 0.0, 3.0, 10.0, 10.0, 14.0, 10.0, 14.0, 14.0, 10.0, 14.0, 20.0, 20.0, 24.0, 20.0, 26.0, 23.0, 22.0, 26.0, 18.0, 23.0, ];
let ring_lengths: Vec<u32> = vec![3, 4, 5];
let contours = Contours2D::new(&coords, &ring_lengths).expect("well-formed rings");
assert_eq!(contours.set.rings.len(), 3, "one ring per input, none dropped/merged");
assert_eq!(
contours.set.rings[0],
vec![[0.0, 0.0], [4.0, 0.0], [0.0, 3.0]],
"triangle ring must read its own 3 vertices, not a shifted slice"
);
assert_eq!(
contours.set.rings[1],
vec![[10.0, 10.0], [14.0, 10.0], [14.0, 14.0], [10.0, 14.0]],
"quad ring must start right after the triangle's 3 vertices"
);
assert_eq!(
contours.set.rings[2],
vec![
[20.0, 20.0],
[24.0, 20.0],
[26.0, 23.0],
[22.0, 26.0],
[18.0, 23.0]
],
"pentagon ring must start right after the quad's 4 vertices"
);
}
}