#![cfg(feature = "wasm")]
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
#[derive(Serialize, Deserialize)]
struct Point2D {
x: f64,
y: f64,
}
#[derive(Serialize, Deserialize)]
struct Aabb {
min: Point2D,
max: Point2D,
}
impl Point2D {
fn to_tuple(&self) -> (f64, f64) {
(self.x, self.y)
}
fn from_tuple(t: (f64, f64)) -> Self {
Self { x: t.0, y: t.1 }
}
}
fn from_js<T: serde::de::DeserializeOwned>(value: JsValue, param: &str) -> Result<T, JsValue> {
if value.as_string().is_some() {
return Err(JsValue::from_str(&format!(
"{param}: expected a native JS object/array, got a string — \
pass the value directly, not JSON.stringify(...)"
)));
}
serde_wasm_bindgen::from_value(value).map_err(|e| JsValue::from_str(&format!("{param}: {e}")))
}
fn parse_points(js: JsValue, param: &str) -> Result<Vec<Point2D>, JsValue> {
from_js(js, param)
}
#[wasm_bindgen]
pub fn polygon_area(points: JsValue) -> Result<f64, JsValue> {
let points = parse_points(points, "points")?;
let tuples: Vec<(f64, f64)> = points.iter().map(|p| p.to_tuple()).collect();
Ok(crate::polygon::area(&tuples))
}
#[wasm_bindgen]
pub fn convex_hull(points: JsValue) -> Result<JsValue, JsValue> {
let points = parse_points(points, "points")?;
let tuples: Vec<(f64, f64)> = points.iter().map(|p| p.to_tuple()).collect();
let hull = crate::polygon::convex_hull(&tuples);
let result: Vec<Point2D> = hull.into_iter().map(Point2D::from_tuple).collect();
serde_wasm_bindgen::to_value(&result).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen]
pub fn point_in_polygon(point: JsValue, polygon: JsValue) -> Result<bool, JsValue> {
let pt: Point2D = from_js(point, "point")?;
let polygon = parse_points(polygon, "polygon")?;
let tuples: Vec<(f64, f64)> = polygon.iter().map(|p| p.to_tuple()).collect();
Ok(crate::polygon::contains_point(&tuples, pt.to_tuple()))
}
#[wasm_bindgen]
pub fn polygons_intersect(poly_a: JsValue, poly_b: JsValue) -> Result<bool, JsValue> {
let a = parse_points(poly_a, "poly_a")?;
let b = parse_points(poly_b, "poly_b")?;
let ta: Vec<(f64, f64)> = a.iter().map(|p| p.to_tuple()).collect();
let tb: Vec<(f64, f64)> = b.iter().map(|p| p.to_tuple()).collect();
Ok(crate::collision::polygons_intersect(&ta, &tb))
}
#[wasm_bindgen]
pub fn polygon_bounds(points: JsValue) -> Result<JsValue, JsValue> {
let points = parse_points(points, "points")?;
if points.is_empty() {
return Err(JsValue::from_str("points: expected a non-empty array"));
}
let mut min_x = points[0].x;
let mut min_y = points[0].y;
let mut max_x = points[0].x;
let mut max_y = points[0].y;
for p in points.iter().skip(1) {
min_x = min_x.min(p.x);
min_y = min_y.min(p.y);
max_x = max_x.max(p.x);
max_y = max_y.max(p.y);
}
let aabb = Aabb {
min: Point2D { x: min_x, y: min_y },
max: Point2D { x: max_x, y: max_y },
};
serde_wasm_bindgen::to_value(&aabb).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen]
pub fn transform_points(points: JsValue, tx: f64, ty: f64, angle: f64) -> Result<JsValue, JsValue> {
let points = parse_points(points, "points")?;
let tuples: Vec<(f64, f64)> = points.iter().map(|p| p.to_tuple()).collect();
let t = crate::transform::Transform2D::new(tx, ty, angle);
let out: Vec<Point2D> = t
.apply_points(&tuples)
.into_iter()
.map(Point2D::from_tuple)
.collect();
serde_wasm_bindgen::to_value(&out).map_err(|e| JsValue::from_str(&e.to_string()))
}