1use crate::mesh_weld::{edge_key, weld_vertex};
2use crate::topology::{
3 BrepSolid, CoedgeRecord, EdgeRecord, FaceRecord, LoopRecord, ShellRecord, VertexRecord,
4};
5use crate::{make_line, make_plane, tessellate_face, Mesh, TessellationOptions, Vec3};
6use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
7
8pub(crate) fn close_as_faceted_brep(
14 solid: &BrepSolid,
15 tolerance: f64,
16) -> Result<BrepSolid, String> {
17 let options = TessellationOptions {
18 slabs_per_span_u: 12,
19 steps_per_span_v: 12,
20 };
21 let mut mesh = Mesh::default();
22 let mut face_id = 0;
23 for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
24 if let Ok(face_mesh) = tessellate_face(face, options, face_id) {
25 let base = (mesh.positions.len() / 3) as u32;
26 mesh.positions.extend(face_mesh.positions);
27 mesh.normals.extend(face_mesh.normals);
28 mesh.indices
29 .extend(face_mesh.indices.into_iter().map(|index| index + base));
30 mesh.face_ids.extend(face_mesh.face_ids);
31 }
32 face_id += 1;
33 }
34 if mesh.indices.is_empty() {
35 return Err("faceted shell repair could not tessellate any faces".into());
36 }
37 triangle_soup_to_faceted_brep(&mesh.positions, Some(&mesh.indices), tolerance.max(2e-3))
38}
39
40pub fn mesh_to_faceted_brep(
49 positions: &[f64],
50 indices: Option<&[u32]>,
51 tolerance: f64,
52) -> Result<BrepSolid, String> {
53 if positions.is_empty() || positions.len() % 3 != 0 {
54 return Err("mesh_to_faceted_brep: positions must be xyz triples".into());
55 }
56 let weld_tolerance = if tolerance > 0.0 && tolerance.is_finite() {
57 tolerance
58 } else {
59 let mut low = [f64::INFINITY; 3];
60 let mut high = [f64::NEG_INFINITY; 3];
61 for point in positions.chunks_exact(3) {
62 for axis in 0..3 {
63 low[axis] = low[axis].min(point[axis]);
64 high[axis] = high[axis].max(point[axis]);
65 }
66 }
67 let diagonal = (0..3)
68 .map(|axis| (high[axis] - low[axis]).powi(2))
69 .sum::<f64>()
70 .sqrt();
71 (diagonal * 1e-6).max(1e-12)
72 };
73 triangle_soup_to_faceted_brep(positions, indices, weld_tolerance)
74}
75
76fn triangle_soup_to_faceted_brep(
80 positions: &[f64],
81 indices: Option<&[u32]>,
82 weld_tolerance: f64,
83) -> Result<BrepSolid, String> {
84 let mut points = Vec::<Vec3>::new();
85 let mut buckets = HashMap::<[i64; 3], Vec<usize>>::default();
86 let mut mesh_to_welded = Vec::with_capacity(positions.len() / 3);
87 for position in positions.chunks_exact(3) {
88 mesh_to_welded.push(weld_vertex(
89 Vec3::new(position[0], position[1], position[2]),
90 weld_tolerance,
91 &mut points,
92 &mut buckets,
93 ));
94 }
95 let sequential;
96 let indices = match indices {
97 Some(indices) => indices,
98 None => {
99 sequential = (0..mesh_to_welded.len() as u32).collect::<Vec<_>>();
100 &sequential
101 }
102 };
103 let mut triangles = Vec::<[usize; 3]>::new();
104 let mut seen = HashSet::<[usize; 3]>::default();
105 for triangle in indices.chunks_exact(3) {
106 if triangle
107 .iter()
108 .any(|index| *index as usize >= mesh_to_welded.len())
109 {
110 return Err("mesh_to_faceted_brep: triangle index outside positions".into());
111 }
112 let value = [
113 mesh_to_welded[triangle[0] as usize],
114 mesh_to_welded[triangle[1] as usize],
115 mesh_to_welded[triangle[2] as usize],
116 ];
117 if value[0] == value[1] || value[1] == value[2] || value[2] == value[0] {
118 continue;
119 }
120 let mut key = value;
121 key.sort_unstable();
122 if seen.insert(key) {
123 triangles.push(value);
124 }
125 }
126 if triangles.is_empty() {
127 return Err("mesh_to_faceted_brep: no non-degenerate triangles".into());
128 }
129
130 loop {
133 let mut edge_uses = HashMap::<(usize, usize), Vec<(usize, bool)>>::default();
134 for (triangle_index, triangle) in triangles.iter().enumerate() {
135 for side in 0..3 {
136 let first = triangle[side];
137 let second = triangle[(side + 1) % 3];
138 edge_uses
139 .entry(edge_key(first, second))
140 .or_default()
141 .push((triangle_index, first < second));
142 }
143 }
144 let Some(uses) = edge_uses.values().find(|uses| uses.len() > 2) else {
145 break;
146 };
147 let keep_forward = uses.iter().find(|(_, forward)| *forward).map(|use_| use_.0);
148 let keep_reverse = uses
149 .iter()
150 .find(|(_, forward)| !*forward)
151 .map(|use_| use_.0);
152 let keep = [keep_forward, keep_reverse]
153 .into_iter()
154 .flatten()
155 .collect::<HashSet<_>>();
156 let remove = uses
157 .iter()
158 .map(|use_| use_.0)
159 .filter(|index| !keep.contains(index))
160 .collect::<HashSet<_>>();
161 if remove.is_empty() {
162 break;
163 }
164 triangles = triangles
165 .into_iter()
166 .enumerate()
167 .filter_map(|(index, triangle)| (!remove.contains(&index)).then_some(triangle))
168 .collect();
169 }
170
171 let mut directed_boundary = Vec::<(usize, usize)>::new();
172 let mut counts = HashMap::<(usize, usize), usize>::default();
173 for triangle in &triangles {
174 for side in 0..3 {
175 *counts
176 .entry(edge_key(triangle[side], triangle[(side + 1) % 3]))
177 .or_default() += 1;
178 }
179 }
180 for triangle in &triangles {
181 for side in 0..3 {
182 let first = triangle[side];
183 let second = triangle[(side + 1) % 3];
184 if counts[&edge_key(first, second)] == 1 {
185 directed_boundary.push((first, second));
186 }
187 }
188 }
189 while let Some((start, mut end)) = directed_boundary.pop() {
190 let mut loop_vertices = vec![start, end];
191 while end != start {
192 let Some(index) = directed_boundary
193 .iter()
194 .position(|(candidate_start, _)| *candidate_start == end)
195 else {
196 break;
197 };
198 let (_, next) = directed_boundary.swap_remove(index);
199 end = next;
200 if end != start {
201 loop_vertices.push(end);
202 }
203 if loop_vertices.len() > points.len() + 1 {
204 break;
205 }
206 }
207 if end != start || loop_vertices.len() < 3 {
208 continue;
209 }
210 let center = loop_vertices
211 .iter()
212 .fold(Vec3::default(), |sum, index| sum.add(points[*index]))
213 .scale(1.0 / loop_vertices.len() as f64);
214 let center_index = points.len();
215 points.push(center);
216 for index in 0..loop_vertices.len() {
217 let first = loop_vertices[index];
218 let second = loop_vertices[(index + 1) % loop_vertices.len()];
219 triangles.push([second, first, center_index]);
221 }
222 }
223
224 let vertices = points
225 .iter()
226 .enumerate()
227 .map(|(index, point)| VertexRecord {
228 id: index as u64 + 1,
229 point: *point,
230 })
231 .collect::<Vec<_>>();
232 let mut edges = Vec::<EdgeRecord>::new();
233 let mut edge_ids = HashMap::<(usize, usize), u64>::default();
234 let mut faces = Vec::<FaceRecord>::new();
235 let mut next_id = vertices.len() as u64 + 1;
236 for triangle in triangles {
237 let a = points[triangle[0]];
238 let b = points[triangle[1]];
239 let c = points[triangle[2]];
240 let u = b.sub(a);
241 let u_length = u.length();
242 if u_length <= 1e-10 {
243 continue;
244 }
245 let u_direction = u.scale(1.0 / u_length);
246 let c_delta = c.sub(a);
247 let c_u = c_delta.dot(u_direction);
248 let v = c_delta.sub(u_direction.scale(c_u));
249 let v_length = v.length();
250 if v_length <= 1e-10 {
251 continue;
252 }
253 let v_direction = v.scale(1.0 / v_length);
254 let u_low = c_u.min(0.0);
259 let u_high = c_u.max(u_length);
260 let surface = make_plane(
261 a.add(u_direction.scale(u_low)),
262 u_direction,
263 v_direction,
264 u_high - u_low,
265 v_length,
266 )?;
267 let uv = [
268 Vec3::new(-u_low, 0.0, 0.0),
269 Vec3::new(u_length - u_low, 0.0, 0.0),
270 Vec3::new(c_u - u_low, v_length, 0.0),
271 ];
272 let mut coedges = Vec::with_capacity(3);
273 for side in 0..3 {
274 let first = triangle[side];
275 let second = triangle[(side + 1) % 3];
276 let key = edge_key(first, second);
277 let edge_id = if let Some(id) = edge_ids.get(&key) {
278 *id
279 } else {
280 let id = next_id;
281 next_id += 1;
282 edges.push(EdgeRecord {
283 id,
284 curve: make_line(points[key.0], points[key.1])?,
285 t0: 0.0,
286 t1: 1.0,
287 start_vertex_id: key.0 as u64 + 1,
288 end_vertex_id: key.1 as u64 + 1,
289 degenerate: false,
290 name: None,
291 });
292 edge_ids.insert(key, id);
293 id
294 };
295 coedges.push(CoedgeRecord {
296 id: next_id,
297 edge_id,
298 forward: first < second,
299 pcurve: make_line(uv[side], uv[(side + 1) % 3])?,
300 });
301 next_id += 1;
302 }
303 let loop_record = LoopRecord {
304 id: next_id,
305 coedges,
306 };
307 next_id += 1;
308 faces.push(FaceRecord {
309 id: next_id,
310 surface,
311 same_sense: true,
312 loops: vec![loop_record],
313 name: None,
314 });
315 next_id += 1;
316 }
317 let mut result = BrepSolid {
318 id: next_id + 1,
319 vertices,
320 edges,
321 shells: vec![ShellRecord { id: next_id, faces }],
322 genus: 0,
323 };
324 let vertex_count = result.vertices.len() as i64;
325 let edge_count = result.edges.len() as i64;
326 let face_count = result.shells[0].faces.len() as i64;
327 let numerator = 2 - (vertex_count - edge_count + face_count);
328 if numerator >= 0 && numerator % 2 == 0 {
329 result.genus = numerator / 2;
330 }
331 let issues = result.validate();
332 if !issues.is_empty() {
333 return Err(format!(
334 "faceted shell repair produced invalid topology: {issues:?}"
335 ));
336 }
337 for (index, face) in result.shells[0].faces.iter().enumerate() {
338 tessellate_face(face, TessellationOptions::default(), index as u32)
339 .map_err(|error| format!("faceted face {index} cannot be tessellated: {error}"))?;
340 }
341 Ok(result)
342}
343
344