1use 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(¢roid, 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}