Skip to main content

brepkit_wasm/bindings/
polygon2d.rs

1//! 2D polygon operation bindings.
2
3#![allow(clippy::missing_errors_doc)]
4
5use wasm_bindgen::prelude::*;
6
7use crate::error::{WasmError, validate_positive};
8use crate::helpers::{parse_polygon_2d, polygons_overlap_2d};
9use crate::kernel::BrepKernel;
10use brepkit_math::polygon2d::{
11    chamfer_polygon_2d, fillet_polygon_2d, find_common_segments, sutherland_hodgman_clip,
12};
13
14#[wasm_bindgen]
15impl BrepKernel {
16    // ── Batch 6: Polygon offset ──────────────────────────────────────
17
18    /// Offset a 2D polygon by a signed distance.
19    ///
20    /// `coords` is a flat array `[x,y, x,y, ...]` of 2D points.
21    /// Returns a flat array of offset polygon coordinates.
22    #[wasm_bindgen(js_name = "offsetPolygon2d")]
23    #[allow(clippy::needless_pass_by_value, clippy::unused_self)]
24    pub fn offset_polygon_2d(
25        &self,
26        coords: Vec<f64>,
27        distance: f64,
28        tolerance: f64,
29    ) -> Result<Vec<f64>, JsError> {
30        if !coords.len().is_multiple_of(2) {
31            return Err(WasmError::InvalidInput {
32                reason: format!(
33                    "2D coordinate array length must be even, got {}",
34                    coords.len()
35                ),
36            }
37            .into());
38        }
39        let points: Vec<brepkit_math::vec::Point2> = coords
40            .chunks_exact(2)
41            .map(|c| brepkit_math::vec::Point2::new(c[0], c[1]))
42            .collect();
43        let result = brepkit_math::polygon_offset::offset_polygon_2d(&points, distance, tolerance)?;
44        Ok(result.iter().flat_map(|p| [p.x(), p.y()]).collect())
45    }
46
47    // ── 2D Blueprint Operations ────────────────────────────────────
48
49    /// Test if a 2D point is inside a closed polygon.
50    ///
51    /// `polygon_coords` is a flat array `[x,y, x,y, ...]`.
52    /// Returns `true` if the point is inside the polygon (winding number test).
53    #[wasm_bindgen(js_name = "pointInPolygon2d")]
54    #[allow(clippy::unused_self)]
55    pub fn point_in_polygon_2d(
56        &self,
57        polygon_coords: Vec<f64>,
58        px: f64,
59        py: f64,
60    ) -> Result<bool, JsError> {
61        if !polygon_coords.len().is_multiple_of(2) || polygon_coords.len() < 6 {
62            return Err(WasmError::InvalidInput {
63                reason: "polygon needs at least 3 points (6 coordinates)".into(),
64            }
65            .into());
66        }
67        let polygon: Vec<brepkit_math::vec::Point2> = polygon_coords
68            .chunks_exact(2)
69            .map(|c| brepkit_math::vec::Point2::new(c[0], c[1]))
70            .collect();
71        let point = brepkit_math::vec::Point2::new(px, py);
72        Ok(brepkit_math::predicates::point_in_polygon(point, &polygon))
73    }
74
75    /// Test if two 2D polygons intersect (overlap).
76    ///
77    /// Both polygons are flat arrays `[x,y, x,y, ...]`.
78    /// Returns `true` if any vertex of one polygon is inside the other
79    /// or if any edges cross.
80    #[wasm_bindgen(js_name = "polygonsIntersect2d")]
81    #[allow(clippy::unused_self)]
82    pub fn polygons_intersect_2d(
83        &self,
84        coords_a: Vec<f64>,
85        coords_b: Vec<f64>,
86    ) -> Result<bool, JsError> {
87        let poly_a = parse_polygon_2d(&coords_a)?;
88        let poly_b = parse_polygon_2d(&coords_b)?;
89        Ok(polygons_overlap_2d(&poly_a, &poly_b))
90    }
91
92    /// Compute the boolean intersection of two 2D polygons.
93    ///
94    /// Both polygons are flat arrays `[x,y, x,y, ...]`.
95    /// Returns a flat array of the intersection polygon coordinates,
96    /// or an empty array if they don't intersect.
97    ///
98    /// Uses the Sutherland-Hodgman algorithm (convex clipper).
99    #[wasm_bindgen(js_name = "intersectPolygons2d")]
100    #[allow(clippy::unused_self)]
101    pub fn intersect_polygons_2d(
102        &self,
103        coords_a: Vec<f64>,
104        coords_b: Vec<f64>,
105    ) -> Result<Vec<f64>, JsError> {
106        let subject = parse_polygon_2d(&coords_a)?;
107        let clip = parse_polygon_2d(&coords_b)?;
108        let result = sutherland_hodgman_clip(&subject, &clip);
109        Ok(result.iter().flat_map(|p| [p.x(), p.y()]).collect())
110    }
111
112    /// Find common (shared) edges between two adjacent 2D polygons.
113    ///
114    /// Both polygons are flat arrays `[x,y, x,y, ...]`.
115    /// Returns a flat array of common segment endpoints `[x1,y1, x2,y2, ...]`,
116    /// or an empty array if no common segments exist.
117    #[wasm_bindgen(js_name = "commonSegment2d")]
118    #[allow(clippy::unused_self)]
119    pub fn common_segment_2d(
120        &self,
121        coords_a: Vec<f64>,
122        coords_b: Vec<f64>,
123    ) -> Result<Vec<f64>, JsError> {
124        let poly_a = parse_polygon_2d(&coords_a)?;
125        let poly_b = parse_polygon_2d(&coords_b)?;
126        let tolerance = 1e-7;
127        let result = find_common_segments(&poly_a, &poly_b, tolerance);
128        Ok(result
129            .iter()
130            .flat_map(|(a, b)| [a.x(), a.y(), b.x(), b.y()])
131            .collect())
132    }
133
134    /// Round corners of a 2D polygon by inserting arc-approximation vertices.
135    ///
136    /// `coords` is a flat array `[x,y, x,y, ...]`.
137    /// `radius` is the fillet radius.
138    /// Returns a flat array of the filleted polygon coordinates.
139    #[wasm_bindgen(js_name = "fillet2d")]
140    #[allow(clippy::unused_self)]
141    pub fn fillet_2d(&self, coords: Vec<f64>, radius: f64) -> Result<Vec<f64>, JsError> {
142        validate_positive(radius, "radius")?;
143        let polygon = parse_polygon_2d(&coords)?;
144        let result = fillet_polygon_2d(&polygon, radius);
145        Ok(result.iter().flat_map(|p| [p.x(), p.y()]).collect())
146    }
147
148    /// Cut corners of a 2D polygon with flat bevels.
149    ///
150    /// `coords` is a flat array `[x,y, x,y, ...]`.
151    /// `distance` is the chamfer distance from each corner.
152    /// Returns a flat array of the chamfered polygon coordinates.
153    #[wasm_bindgen(js_name = "chamfer2d")]
154    #[allow(clippy::unused_self)]
155    pub fn chamfer_2d(&self, coords: Vec<f64>, distance: f64) -> Result<Vec<f64>, JsError> {
156        validate_positive(distance, "distance")?;
157        let polygon = parse_polygon_2d(&coords)?;
158        let result = chamfer_polygon_2d(&polygon, distance);
159        Ok(result.iter().flat_map(|p| [p.x(), p.y()]).collect())
160    }
161}