Skip to main content

brep_kernel/geometry/
polygon.rs

1use crate::{Vec2, Vec3};
2
3/// Newell's unnormalized normal, including the closing edge from last to first.
4/// For planar polygons its magnitude is twice the area and its sign follows winding.
5pub(crate) fn newell_normal(points: &[Vec3]) -> Vec3 {
6    let mut normal = Vec3::default();
7    for index in 0..points.len() {
8        let point = points[index];
9        let next = points[(index + 1) % points.len()];
10        normal.x += (point.y - next.y) * (point.z + next.z);
11        normal.y += (point.z - next.z) * (point.x + next.x);
12        normal.z += (point.x - next.x) * (point.y + next.y);
13    }
14    normal
15}
16
17
18/// Form-K horizontal-ray crossing; operand order is shared with the trim indexes.
19#[inline]
20pub fn crosses_ray(p: [f64; 2], a: [f64; 2], b: [f64; 2]) -> bool {
21    if (a[1] > p[1]) != (b[1] > p[1]) {
22        let t = (p[1] - a[1]) / (b[1] - a[1]);
23        let x = a[0] + t * (b[0] - a[0]);
24        x > p[0]
25    } else {
26        false
27    }
28}
29
30/// Even-odd parity over a closed polygon, including its closing edge.
31#[inline]
32pub fn parity_inside(p: [f64; 2], count: usize, uv: impl Fn(usize) -> [f64; 2]) -> bool {
33    let mut inside = false;
34    for offset in 0..count {
35        if crosses_ray(p, uv(offset), uv((offset + 1) % count)) {
36            inside = !inside;
37        }
38    }
39    inside
40}
41
42#[derive(Clone, Copy, Debug, PartialEq)]
43pub enum PolygonClass {
44    Inside,
45    Outside,
46    Boundary,
47}
48
49
50/// Boundary band followed by form-K parity. Keep the caller's segment guard.
51#[inline]
52pub fn classify(point: Vec2, polygon: &[Vec2], tolerance: f64, segment_guard: f64) -> PolygonClass {
53    for index in 0..polygon.len() {
54        if point.distance_to_segment(polygon[index], polygon[(index + 1) % polygon.len()], segment_guard) <= tolerance {
55            return PolygonClass::Boundary;
56        }
57    }
58    if parity_inside([point.x, point.y], polygon.len(), |i| [polygon[i].x, polygon[i].y]) {
59        PolygonClass::Inside
60    } else {
61        PolygonClass::Outside
62    }
63}
64
65/// One oriented shoelace term (twice the edge's area contribution).
66#[inline]
67pub fn area_term(a: [f64; 2], b: [f64; 2]) -> f64 {
68    a[0] * b[1] - b[0] * a[1]
69}
70
71#[inline]
72pub fn twice_signed_area_2d(count: usize, uv: impl Fn(usize) -> [f64; 2]) -> f64 {
73    let mut area = 0.0;
74    for index in 0..count {
75        area += area_term(uv(index), uv((index + 1) % count));
76    }
77    area
78}
79
80#[inline]
81pub fn signed_area_2d(count: usize, uv: impl Fn(usize) -> [f64; 2]) -> f64 {
82    twice_signed_area_2d(count, uv) * 0.5
83}
84