Skip to main content

neco_brep/
shell_view.rs

1//! Lightweight shell view API for downstream inspection and serialization prep.
2
3use crate::boolean3d::classify3d::point_in_face_polygon;
4use crate::boolean3d::intersect3d::face_polygon;
5use crate::vec3;
6use crate::{Face, Shell};
7
8#[derive(Debug, Clone)]
9pub struct FaceSample {
10    pub point: [f64; 3],
11    pub uv: Option<[f64; 2]>,
12    pub normal: Option<[f64; 3]>,
13}
14
15#[derive(Debug, Clone)]
16pub struct FaceView {
17    pub face_index: usize,
18    pub surface_kind: &'static str,
19    pub orientation_reversed: bool,
20    pub loop_edge_count: usize,
21    pub polygon_3d: Vec<[f64; 3]>,
22    pub polygon_uv: Vec<[f64; 2]>,
23    pub sample: Option<FaceSample>,
24}
25
26#[derive(Debug, Clone)]
27pub struct ShellView {
28    pub face_count: usize,
29    pub edge_count: usize,
30    pub vertex_count: usize,
31    pub faces: Vec<FaceView>,
32}
33
34pub fn shell_view(shell: &Shell) -> ShellView {
35    let faces = shell
36        .faces
37        .iter()
38        .enumerate()
39        .map(|(face_index, face)| FaceView {
40            face_index,
41            surface_kind: surface_kind(face),
42            orientation_reversed: face.orientation_reversed,
43            loop_edge_count: face.loop_edges.len(),
44            polygon_3d: face_polygon(face, shell),
45            polygon_uv: face_polygon_uv(face, shell),
46            sample: face_debug_sample(face, shell),
47        })
48        .collect();
49
50    ShellView {
51        face_count: shell.faces.len(),
52        edge_count: shell.edges.len(),
53        vertex_count: shell.vertices.len(),
54        faces,
55    }
56}
57
58fn surface_kind(face: &Face) -> &'static str {
59    match &face.surface {
60        crate::Surface::Plane { .. } => "Plane",
61        crate::Surface::Cylinder { .. } => "Cylinder",
62        crate::Surface::Cone { .. } => "Cone",
63        crate::Surface::Sphere { .. } => "Sphere",
64        crate::Surface::Ellipsoid { .. } => "Ellipsoid",
65        crate::Surface::Torus { .. } => "Torus",
66        crate::Surface::SurfaceOfRevolution { .. } => "SurfaceOfRevolution",
67        crate::Surface::SurfaceOfSweep { .. } => "SurfaceOfSweep",
68        crate::Surface::NurbsSurface { .. } => "NurbsSurface",
69    }
70}
71
72fn face_polygon_uv(face: &Face, shell: &Shell) -> Vec<[f64; 2]> {
73    face_polygon(face, shell)
74        .into_iter()
75        .filter_map(|point| face.surface.inverse_project(&point).map(|(u, v)| [u, v]))
76        .collect()
77}
78
79fn face_debug_sample(face: &Face, shell: &Shell) -> Option<FaceSample> {
80    let polygon = face_polygon(face, shell);
81    let point = interior_point(face, shell, &polygon)?;
82    let uv = face.surface.inverse_project(&point).map(|(u, v)| [u, v]);
83    let normal = uv.map(|[u, v]| {
84        let surf_n = face.surface.normal_at(u, v);
85        orient_normal(face, &polygon, surf_n)
86    });
87    Some(FaceSample { point, uv, normal })
88}
89
90fn orient_normal(face: &Face, polygon: &[[f64; 3]], surf_n: [f64; 3]) -> [f64; 3] {
91    if face.orientation_reversed {
92        vec3::scale(surf_n, -1.0)
93    } else {
94        let poly_n = polygon_normal(polygon);
95        if vec3::dot(poly_n, surf_n) < 0.0 {
96            vec3::scale(surf_n, -1.0)
97        } else {
98            surf_n
99        }
100    }
101}
102
103fn polygon_centroid(poly: &[[f64; 3]]) -> [f64; 3] {
104    let sum = poly.iter().copied().fold([0.0, 0.0, 0.0], vec3::add);
105    vec3::scale(sum, 1.0 / poly.len() as f64)
106}
107
108fn triangle_centroid(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> [f64; 3] {
109    vec3::scale(vec3::add(vec3::add(a, b), c), 1.0 / 3.0)
110}
111
112fn polygon_normal(poly: &[[f64; 3]]) -> [f64; 3] {
113    let mut normal = [0.0, 0.0, 0.0];
114    for i in 0..poly.len() {
115        let a = poly[i];
116        let b = poly[(i + 1) % poly.len()];
117        normal[0] += (a[1] - b[1]) * (a[2] + b[2]);
118        normal[1] += (a[2] - b[2]) * (a[0] + b[0]);
119        normal[2] += (a[0] - b[0]) * (a[1] + b[1]);
120    }
121    vec3::normalized(normal)
122}
123
124fn project_point_to_face_surface(face: &Face, point: &[f64; 3]) -> [f64; 3] {
125    match face.surface.inverse_project(point) {
126        Some((u, v)) => face.surface.evaluate(u, v),
127        None => *point,
128    }
129}
130
131fn interior_point(face: &Face, shell: &Shell, poly: &[[f64; 3]]) -> Option<[f64; 3]> {
132    if poly.len() < 3 {
133        return None;
134    }
135
136    let centroid = project_point_to_face_surface(face, &polygon_centroid(poly));
137    if point_in_face_polygon(&centroid, face, shell) {
138        return Some(centroid);
139    }
140
141    for &vertex in poly {
142        for fraction in [0.1, 0.25, 0.5, 0.75, 0.9] {
143            let toward_centroid = project_point_to_face_surface(
144                face,
145                &vec3::add(
146                    vertex,
147                    vec3::scale(vec3::sub(polygon_centroid(poly), vertex), fraction),
148                ),
149            );
150            if point_in_face_polygon(&toward_centroid, face, shell) {
151                return Some(toward_centroid);
152            }
153        }
154    }
155
156    let root = poly[0];
157    for i in 1..(poly.len() - 1) {
158        let c = project_point_to_face_surface(face, &triangle_centroid(root, poly[i], poly[i + 1]));
159        if point_in_face_polygon(&c, face, shell) {
160            return Some(c);
161        }
162
163        let mid_ab = project_point_to_face_surface(
164            face,
165            &triangle_centroid(root, poly[i], polygon_centroid(poly)),
166        );
167        if point_in_face_polygon(&mid_ab, face, shell) {
168            return Some(mid_ab);
169        }
170
171        for (wa, wb, wc) in [
172            (0.6, 0.2, 0.2),
173            (0.2, 0.6, 0.2),
174            (0.2, 0.2, 0.6),
175            (0.4, 0.4, 0.2),
176        ] {
177            let sample = [
178                root[0] * wa + poly[i][0] * wb + poly[i + 1][0] * wc,
179                root[1] * wa + poly[i][1] * wb + poly[i + 1][1] * wc,
180                root[2] * wa + poly[i][2] * wb + poly[i + 1][2] * wc,
181            ];
182            let sample = project_point_to_face_surface(face, &sample);
183            if point_in_face_polygon(&sample, face, shell) {
184                return Some(sample);
185            }
186        }
187    }
188
189    if let Some(uv_poly) = poly
190        .iter()
191        .map(|point| face.surface.inverse_project(point).map(|(u, v)| [u, v]))
192        .collect::<Option<Vec<_>>>()
193    {
194        let uv_centroid = uv_poly
195            .iter()
196            .fold([0.0, 0.0], |acc, uv| [acc[0] + uv[0], acc[1] + uv[1]]);
197        let uv_centroid = [
198            uv_centroid[0] / uv_poly.len() as f64,
199            uv_centroid[1] / uv_poly.len() as f64,
200        ];
201        let candidate = face.surface.evaluate(uv_centroid[0], uv_centroid[1]);
202        if point_in_face_polygon(&candidate, face, shell) {
203            return Some(candidate);
204        }
205
206        if uv_poly.len() >= 3 {
207            let root = uv_poly[0];
208            for i in 1..(uv_poly.len() - 1) {
209                for (wa, wb, wc) in [(0.6, 0.2, 0.2), (0.2, 0.6, 0.2), (0.2, 0.2, 0.6)] {
210                    let uv = [
211                        root[0] * wa + uv_poly[i][0] * wb + uv_poly[i + 1][0] * wc,
212                        root[1] * wa + uv_poly[i][1] * wb + uv_poly[i + 1][1] * wc,
213                    ];
214                    let candidate = face.surface.evaluate(uv[0], uv[1]);
215                    if point_in_face_polygon(&candidate, face, shell) {
216                        return Some(candidate);
217                    }
218                }
219            }
220        }
221    }
222
223    None
224}