flow_gates/
polygon.rs

1use super::error::{GateError, Result};
2use super::traits::*;
3use super::types::GateNode;
4
5#[derive(Debug, Clone)]
6pub struct PolygonGateGeometry {
7    pub nodes: Vec<GateNode>,
8    pub closed: bool,
9}
10
11impl GateCenter for PolygonGateGeometry {
12    fn calculate_center(&self, x_param: &str, y_param: &str) -> Result<(f32, f32)> {
13        let (sum_x, sum_y, count) = self
14            .nodes
15            .iter()
16            .filter_map(|node| {
17                let x = node.get_coordinate(x_param)?;
18                let y = node.get_coordinate(y_param)?;
19                Some((x, y))
20            })
21            .fold((0.0, 0.0, 0), |(sx, sy, c), (x, y)| (sx + x, sy + y, c + 1));
22
23        if count > 0 {
24            Ok((sum_x / count as f32, sum_y / count as f32))
25        } else {
26            Err(GateError::invalid_geometry(
27                "Polygon has no valid coordinates",
28            ))
29        }
30    }
31}
32
33impl GateContainment for PolygonGateGeometry {
34    fn contains_point(&self, x: f32, y: f32, x_param: &str, y_param: &str) -> Result<bool> {
35        if !self.closed {
36            return Ok(false);
37        }
38
39        // Extract coordinates
40        let coords: Vec<(f32, f32)> = self
41            .nodes
42            .iter()
43            .filter_map(|node| {
44                let x_coord = node.get_coordinate(x_param)?;
45                let y_coord = node.get_coordinate(y_param)?;
46                Some((x_coord, y_coord))
47            })
48            .collect();
49
50        if coords.len() < 3 {
51            return Ok(false);
52        }
53
54        // Ray casting algorithm
55        Ok(point_in_polygon(x, y, &coords))
56    }
57}
58
59impl GateBounds for PolygonGateGeometry {
60    fn bounding_box(&self, x_param: &str, y_param: &str) -> Result<(f32, f32, f32, f32)> {
61        let coords: Vec<(f32, f32)> = self
62            .nodes
63            .iter()
64            .filter_map(|node| {
65                let x_coord = node.get_coordinate(x_param)?;
66                let y_coord = node.get_coordinate(y_param)?;
67                Some((x_coord, y_coord))
68            })
69            .collect();
70
71        if coords.is_empty() {
72            return Err(GateError::invalid_geometry(
73                "No valid coordinates for bounding box",
74            ));
75        }
76
77        let min_x = coords
78            .iter()
79            .map(|(x, _)| x)
80            .fold(f32::INFINITY, |a, &b| a.min(b));
81        let max_x = coords
82            .iter()
83            .map(|(x, _)| x)
84            .fold(f32::NEG_INFINITY, |a, &b| a.max(b));
85        let min_y = coords
86            .iter()
87            .map(|(_, y)| y)
88            .fold(f32::INFINITY, |a, &b| a.min(b));
89        let max_y = coords
90            .iter()
91            .map(|(_, y)| y)
92            .fold(f32::NEG_INFINITY, |a, &b| a.max(b));
93
94        Ok((min_x, min_y, max_x, max_y))
95    }
96}
97
98impl GateValidation for PolygonGateGeometry {
99    fn is_valid(&self, x_param: &str, y_param: &str) -> Result<bool> {
100        // Need at least 3 nodes for a valid polygon
101        if self.nodes.len() < 3 {
102            return Ok(false);
103        }
104
105        // All nodes must have valid coordinates
106        let valid_coords = self.nodes.iter().all(|node| {
107            node.get_coordinate(x_param).is_some() && node.get_coordinate(y_param).is_some()
108        });
109
110        Ok(valid_coords)
111    }
112}
113
114impl GateGeometryOps for PolygonGateGeometry {
115    fn gate_type_name(&self) -> &'static str {
116        "Polygon"
117    }
118}
119
120/// Point-in-polygon using ray casting algorithm
121fn point_in_polygon(x: f32, y: f32, polygon: &[(f32, f32)]) -> bool {
122    let mut inside = false;
123    let n = polygon.len();
124
125    for i in 0..n {
126        let (x1, y1) = polygon[i];
127        let (x2, y2) = polygon[(i + 1) % n];
128
129        if ((y1 > y) != (y2 > y)) && (x < (x2 - x1) * (y - y1) / (y2 - y1) + x1) {
130            inside = !inside;
131        }
132    }
133
134    inside
135}