doc_quad/geom/
validate.rs1use glam::Vec2;
3
4pub struct GeometryValidator;
5
6impl GeometryValidator {
7 pub fn validate_and_score(simplified: &(Vec<Vec2>, bool)) -> Option<(f32, [Vec2; 4])> {
8 let pts = &simplified.0;
9 let is_closed = simplified.1;
10
11 if !is_closed || pts.len() != 5 {
12 log::debug!(
13 "[Geom::Validate] - Failed base check: is_closed={}, len={} (expected closed, len=5).",
14 is_closed, pts.len()
15 );
16 return None;
17 }
18
19 let p = [pts[0], pts[1], pts[2], pts[3]];
20
21 let cross = [
22 Self::cross_product(p[0], p[1], p[2]),
23 Self::cross_product(p[1], p[2], p[3]),
24 Self::cross_product(p[2], p[3], p[0]),
25 Self::cross_product(p[3], p[0], p[1]),
26 ];
27
28 if cross.iter().any(|&c| c.abs() < 1.0) {
29 log::warn!(
30 "[Geom::Validate] - Quadrilateral failed collinear test (degenerate polygon). Cross products: {:?}",
31 cross
32 );
33 return None;
34 }
35
36 let non_zero: Vec<f32> = cross.iter().copied().filter(|&c| c != 0.0).collect();
37 if non_zero.is_empty() {
38 log::warn!("[Geom::Validate] - Quadrilateral completely degenerate (all cross products zero).");
39 return None;
40 }
41
42 let positive_count = non_zero.iter().filter(|&&c| c > 0.0).count();
43 let negative_count = non_zero.len() - positive_count;
44 let minority_count = positive_count.min(negative_count);
45
46 if minority_count > 0 {
47 log::warn!(
48 "[Geom::Validate] - Quadrilateral failed convexity test (concave or self-intersecting detected). \
49 Positive crosses: {}, Negative crosses: {}",
50 positive_count, negative_count
51 );
52 return None;
53 }
54
55 let area = Self::polygon_area(&p);
56 Some((area, p))
57 }
58
59 #[inline]
60 fn cross_product(p0: Vec2, p1: Vec2, p2: Vec2) -> f32 {
61 (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x)
62 }
63
64 #[inline]
65 fn polygon_area(pts: &[Vec2; 4]) -> f32 {
66 let mut area = 0.0;
67 for i in 0..4 {
68 let j = (i + 1) % 4;
69 area += pts[i].x * pts[j].y - pts[j].x * pts[i].y;
70 }
71 (area / 2.0).abs()
72 }
73}