1use brep_kernel::{BrepSolid, CoedgeRecord, FaceRecord, LoopRecord, NurbsSurface, Vec3, Vec4};
24use serde::{Deserialize, Serialize};
25use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
26
27#[derive(Clone, Debug, Deserialize, Serialize)]
29pub struct NurbsFaceChoice {
30 pub triangles: Vec<u32>,
32 pub tolerance: f64,
34}
35
36#[derive(Clone, Debug, Deserialize, Serialize)]
38pub struct NurbsFaceOutcome {
39 pub applied: bool,
40 pub max_deviation: Option<f64>,
42 pub control_points: Option<[usize; 2]>,
44 pub reason: Option<String>,
46}
47
48impl NurbsFaceOutcome {
49 fn refused(reason: impl Into<String>) -> Self {
50 Self { applied: false, max_deviation: None, control_points: None, reason: Some(reason.into()) }
51 }
52}
53
54fn v3(p: &[f64]) -> Vec3 {
55 Vec3::new(p[0], p[1], p[2])
56}
57
58fn point_triangle_distance(p: Vec3, t: [Vec3; 3]) -> f64 {
60 let [a, b, c] = t;
61 let n = b.sub(a).cross(c.sub(a));
62 let area2 = n.length();
63 if area2 > 0.0 {
64 let n = n.scale(1.0 / area2);
65 let q = p.sub(n.scale(p.sub(a).dot(n)));
66 let inside = [(a, b), (b, c), (c, a)].iter().all(|&(u, v)| v.sub(u).cross(q.sub(u)).dot(n) >= -1e-12 * area2);
67 if inside {
68 return p.sub(q).length();
69 }
70 }
71 [(a, b), (b, c), (c, a)].iter().map(|&(u, v)| p.distance_to_segment(u, v, 0.0)).fold(f64::INFINITY, f64::min)
72}
73
74fn coedge_ends(solid: &BrepSolid, coedge: &CoedgeRecord) -> Option<(u64, u64)> {
76 let edge = solid.edges.iter().find(|e| e.id == coedge.edge_id)?;
77 Some(if coedge.forward { (edge.start_vertex_id, edge.end_vertex_id) } else { (edge.end_vertex_id, edge.start_vertex_id) })
78}
79
80pub fn apply_nurbs_faces(
84 solid: &mut BrepSolid,
85 positions: &[f64],
86 indices: &[u32],
87 choices: &[NurbsFaceChoice],
88) -> Vec<NurbsFaceOutcome> {
89 choices
90 .iter()
91 .map(|choice| {
92 let mut trial = solid.clone();
93 match replace_region(&mut trial, positions, indices, choice) {
94 Ok(outcome) => {
95 let issues = trial.validate();
96 if issues.is_empty() {
97 *solid = trial;
98 outcome
99 } else {
100 NurbsFaceOutcome::refused(format!(
101 "the solid with the NURBS face does not validate: {}",
102 issues.iter().take(2).map(|issue| format!("{issue:?}")).collect::<Vec<_>>().join("; ")
103 ))
104 }
105 }
106 Err(reason) => NurbsFaceOutcome::refused(reason),
107 }
108 })
109 .collect()
110}
111
112fn replace_region(
113 solid: &mut BrepSolid,
114 positions: &[f64],
115 indices: &[u32],
116 choice: &NurbsFaceChoice,
117) -> Result<NurbsFaceOutcome, String> {
118 if !(choice.tolerance.is_finite() && choice.tolerance > 0.0) {
119 return Err("the tolerance must be a positive distance".into());
120 }
121 let vertex_count = positions.len() / 3;
122 let triangles: Vec<[u32; 3]> = choice
123 .triangles
124 .iter()
125 .map(|&t| {
126 let t = t as usize;
127 if 3 * t + 2 >= indices.len() {
128 return Err(format!("triangle {t} is outside the mesh"));
129 }
130 let tri = [indices[3 * t], indices[3 * t + 1], indices[3 * t + 2]];
131 if tri.iter().any(|&v| v as usize >= vertex_count) {
132 return Err(format!("triangle {t} names a vertex outside the mesh"));
133 }
134 Ok(tri)
135 })
136 .collect::<Result<_, _>>()?;
137 if triangles.is_empty() {
138 return Err("the region has no triangles".into());
139 }
140 let tri_points: Vec<[Vec3; 3]> = triangles.iter().map(|t| t.map(|v| v3(&positions[3 * v as usize..]))).collect();
141 let mut low = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
142 let mut high = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
143 for p in positions.chunks_exact(3) {
144 low = Vec3::new(low.x.min(p[0]), low.y.min(p[1]), low.z.min(p[2]));
145 high = Vec3::new(high.x.max(p[0]), high.y.max(p[1]), high.z.max(p[2]));
146 }
147 let radius = (high.sub(low).length() * 1e-9).max(1e-12);
148 let on_region = |p: Vec3| tri_points.iter().any(|t| point_triangle_distance(p, *t) <= radius * 1e3);
150
151 if solid.shells.len() != 1 {
155 return Err(format!("the body has {} shells; a NURBS face is placed on single-shell bodies", solid.shells.len()));
156 }
157 let shell = &solid.shells[0];
158 let point_of = |id: u64| solid.vertices.iter().find(|v| v.id == id).map(|v| v.point);
159 let region_faces: Vec<usize> = shell
160 .faces
161 .iter()
162 .enumerate()
163 .filter(|(_, face)| {
164 if face.surface.degree_u != 1 || face.surface.degree_v != 1 {
165 return false;
166 }
167 let points: Vec<Vec3> = face
168 .loops
169 .iter()
170 .flat_map(|l| &l.coedges)
171 .filter_map(|c| coedge_ends(solid, c))
172 .filter_map(|(a, _)| point_of(a))
173 .collect();
174 if points.is_empty() || !points.iter().all(|&p| on_region(p)) {
175 return false;
176 }
177 let centre = points.iter().fold(Vec3::new(0.0, 0.0, 0.0), |acc, &p| acc.add(p)).scale(1.0 / points.len() as f64);
178 on_region(centre)
179 })
180 .map(|(index, _)| index)
181 .collect();
182 if region_faces.is_empty() {
183 return Err("the region's triangles are not faces of the built body (it was fitted, or merged with a neighbour)".into());
184 }
185 let region_face_set: HashSet<usize> = region_faces.iter().copied().collect();
186 let mut region_edges: HashMap<u64, (u64, u64)> = HashMap::new();
188 for &i in ®ion_faces {
189 for coedge in shell.faces[i].loops.iter().flat_map(|l| &l.coedges) {
190 let (a, b) = coedge_ends(solid, coedge).ok_or("a coedge names a missing edge")?;
191 region_edges.insert(coedge.edge_id, (a.min(b), a.max(b)));
192 }
193 }
194 let region_vertices: BTreeSet<u64> = region_edges.values().flat_map(|&(a, b)| [a, b]).collect();
195 if region_vertices.len() as i64 - region_edges.len() as i64 + region_faces.len() as i64 != 1 {
197 return Err("the region is not a single disk-shaped patch (it has a hole or a handle); one NURBS face needs one boundary loop".into());
198 }
199 let mut uses: HashMap<u64, usize> = HashMap::new();
201 for &i in ®ion_faces {
202 for coedge in shell.faces[i].loops.iter().flat_map(|l| &l.coedges) {
203 *uses.entry(coedge.edge_id).or_default() += 1;
204 }
205 }
206 let mut next: HashMap<u64, CoedgeRecord> = HashMap::new();
208 for &i in ®ion_faces {
209 for coedge in shell.faces[i].loops.iter().flat_map(|l| &l.coedges) {
210 if uses[&coedge.edge_id] == 1 {
211 let (a, _) = coedge_ends(solid, coedge).ok_or("a boundary coedge names a missing edge")?;
212 if next.insert(a, coedge.clone()).is_some() {
213 return Err("the region's boundary touches itself at a vertex; one NURBS face needs a simple boundary loop".into());
214 }
215 }
216 }
217 }
218 let Some(&start) = next.keys().min() else { return Err("the region has no boundary (a closed surface)".into()) };
219 let mut loop_coedges = Vec::new();
220 let mut at = start;
221 loop {
222 let coedge = next.get(&at).ok_or("the region's boundary does not close")?.clone();
223 let (_, b) = coedge_ends(solid, &coedge).unwrap();
224 loop_coedges.push(coedge);
225 at = b;
226 if at == start {
227 break;
228 }
229 if loop_coedges.len() > next.len() {
230 return Err("the region's boundary does not close".into());
231 }
232 }
233 if loop_coedges.len() != next.len() {
234 return Err("the region's boundary is more than one loop (the region has a hole)".into());
235 }
236 if loop_coedges.len() < 4 {
237 return Err(format!("the region's boundary has {} edges; a four-sided patch needs at least four", loop_coedges.len()));
238 }
239 let edge_of = |id: u64| solid.edges.iter().find(|e| e.id == id).unwrap();
241 for coedge in &loop_coedges {
242 let edge = edge_of(coedge.edge_id);
243 let start = solid.vertices.iter().find(|v| v.id == edge.start_vertex_id).unwrap().point;
244 let end = solid.vertices.iter().find(|v| v.id == edge.end_vertex_id).unwrap().point;
245 let mid = edge.curve.evaluate(0.5 * (edge.t0 + edge.t1)).map_err(|e| format!("boundary edge: {e}"))?;
246 let chord = start.add(end).scale(0.5);
247 if mid.sub(chord).length() > radius.max(1e-9 * end.sub(start).length()) * 10.0 {
248 return Err("a boundary edge of the region is curved; the NURBS patch is bounded by straight facet edges only".into());
249 }
250 }
251 let loop_vertices: Vec<u64> = loop_coedges.iter().map(|c| coedge_ends(solid, c).unwrap().0).collect();
253 let boundary_points: Vec<Vec3> = loop_vertices.iter().map(|&id| point_of(id).unwrap()).collect();
254 let n = boundary_points.len();
255
256 let region_points: Vec<Vec3> = region_vertices.iter().filter_map(|&v| point_of(v)).collect();
259 let (centroid, axes) = super::principal_frame_kernel(®ion_points);
260 let project = |p: Vec3| {
261 let d = p.sub(centroid);
262 [d.dot(axes[0]), d.dot(axes[1])]
263 };
264 let uv: Vec<[f64; 2]> = boundary_points.iter().map(|&p| project(p)).collect();
265 let (mut lo, mut hi) = ([f64::INFINITY; 2], [f64::NEG_INFINITY; 2]);
266 for p in &uv {
267 for k in 0..2 {
268 lo[k] = lo[k].min(p[k]);
269 hi[k] = hi[k].max(p[k]);
270 }
271 }
272 let box_corners = [[lo[0], lo[1]], [hi[0], lo[1]], [hi[0], hi[1]], [lo[0], hi[1]]];
273 let mut corners: Vec<usize> = Vec::new();
274 for corner in box_corners {
275 let best = (0..n)
276 .filter(|i| !corners.contains(i))
277 .min_by(|&a, &b| {
278 let da = (uv[a][0] - corner[0]).powi(2) + (uv[a][1] - corner[1]).powi(2);
279 let db = (uv[b][0] - corner[0]).powi(2) + (uv[b][1] - corner[1]).powi(2);
280 da.total_cmp(&db)
281 })
282 .unwrap();
283 corners.push(best);
284 }
285 corners.sort_unstable();
286 corners.dedup();
287 if corners.len() != 4 {
288 return Err("could not split the region's boundary into four sides".into());
289 }
290 let side = |s: usize| -> Vec<usize> {
292 let (a, b) = (corners[s], corners[(s + 1) % 4]);
293 let mut run = vec![a];
294 let mut i = a;
295 while i != b {
296 i = (i + 1) % n;
297 run.push(i);
298 }
299 run
300 };
301 let sides: Vec<Vec<usize>> = (0..4).map(side).collect();
302 let params = |run: &[usize]| -> Result<Vec<f64>, String> {
304 let mut t = vec![0.0];
305 for w in run.windows(2) {
306 t.push(t.last().unwrap() + boundary_points[w[1]].sub(boundary_points[w[0]]).length());
307 }
308 let total = *t.last().unwrap();
309 if total <= 0.0 {
310 return Err("a side of the region has no length".into());
311 }
312 Ok(t.into_iter().map(|x| x / total).collect())
313 };
314 let p0 = params(&sides[0])?;
317 let p1 = params(&sides[1])?;
318 let p2 = params(&sides[2])?;
319 let p3 = params(&sides[3])?;
320 let mut square: HashMap<usize, [f64; 2]> = HashMap::new();
321 for (k, &i) in sides[0].iter().enumerate() {
322 square.insert(i, [p0[k], 0.0]);
323 }
324 for (k, &i) in sides[1].iter().enumerate() {
325 square.insert(i, [1.0, p1[k]]);
326 }
327 for (k, &i) in sides[2].iter().enumerate() {
328 square.insert(i, [1.0 - p2[k], 1.0]);
329 }
330 for (k, &i) in sides[3].iter().enumerate() {
331 square.insert(i, [0.0, 1.0 - p3[k]]);
332 }
333 let merge = |a: &[f64], b: &[f64]| -> Vec<f64> {
335 let mut all: Vec<f64> = a.iter().chain(b).copied().collect();
336 all.sort_by(f64::total_cmp);
337 let mut out: Vec<f64> = Vec::new();
338 for x in all {
339 if out.last().is_none_or(|&last| x - last > 1e-9) {
340 out.push(x);
341 } else if x == 1.0 {
342 *out.last_mut().unwrap() = 1.0;
343 }
344 }
345 *out.first_mut().unwrap() = 0.0;
346 *out.last_mut().unwrap() = 1.0;
347 out
348 };
349 let u_breaks = merge(&p0, &p2.iter().map(|t| 1.0 - t).collect::<Vec<_>>());
350 let v_breaks = merge(&p1, &p3.iter().map(|t| 1.0 - t).collect::<Vec<_>>());
351 let cu = 3 * (u_breaks.len() - 1) + 1;
352 let cv = 3 * (v_breaks.len() - 1) + 1;
353 if cu * cv > 40_000 {
354 return Err(format!("the region's boundary needs a {cu} × {cv} control net; split the region"));
355 }
356 let knots = |breaks: &[f64]| {
357 let mut k = vec![0.0; 4];
358 for &b in &breaks[1..breaks.len() - 1] {
359 k.extend([b, b, b]);
360 }
361 k.extend([1.0; 4]);
362 k
363 };
364 let knots_u = knots(&u_breaks);
365 let knots_v = knots(&v_breaks);
366 let control_param = |breaks: &[f64], i: usize| {
368 let s = (i / 3).min(breaks.len() - 2);
369 let r = i - 3 * s;
370 breaks[s] + (breaks[s + 1] - breaks[s]) * r as f64 / 3.0
371 };
372 let along = |run: &[usize], t_side: &[f64], t: f64| -> Vec3 {
374 let k = t_side.partition_point(|&x| x <= t).clamp(1, run.len() - 1);
375 let (t0, t1) = (t_side[k - 1], t_side[k]);
376 let f = if t1 > t0 { ((t - t0) / (t1 - t0)).clamp(0.0, 1.0) } else { 0.0 };
377 boundary_points[run[k - 1]].scale(1.0 - f).add(boundary_points[run[k]].scale(f))
378 };
379 let mut net: Vec<Option<Vec3>> = vec![None; cu * cv];
380 let at = |i: usize, j: usize| i * cv + j;
381 for i in 0..cu {
382 let u = control_param(&u_breaks, i);
383 net[at(i, 0)] = Some(along(&sides[0], &p0, u));
384 net[at(i, cv - 1)] = Some(along(&sides[2], &p2, 1.0 - u));
385 }
386 for j in 0..cv {
387 let v = control_param(&v_breaks, j);
388 net[at(cu - 1, j)] = Some(along(&sides[1], &p1, v));
389 net[at(0, j)] = Some(along(&sides[3], &p3, 1.0 - v));
390 }
391
392 let boundary_mesh: HashMap<u64, [f64; 2]> = loop_vertices
394 .iter()
395 .enumerate()
396 .map(|(k, &id)| (id, square[&k]))
397 .collect();
398 let mut neighbours: BTreeMap<u64, BTreeSet<u64>> = BTreeMap::new();
399 for &(a, b) in region_edges.values() {
400 neighbours.entry(a).or_default().insert(b);
401 neighbours.entry(b).or_default().insert(a);
402 }
403 let mut param: HashMap<u64, [f64; 2]> = region_vertices.iter().map(|&v| (v, boundary_mesh.get(&v).copied().unwrap_or([0.5, 0.5]))).collect();
404 for &v in ®ion_vertices {
405 if !boundary_mesh.contains_key(&v) && !neighbours.contains_key(&v) {
406 return Err("an isolated vertex in the region".into());
407 }
408 }
409 let interior: Vec<u64> = region_vertices.iter().copied().filter(|v| !boundary_mesh.contains_key(v)).collect();
410 for _ in 0..20_000 {
411 let mut change = 0.0f64;
412 for &v in &interior {
413 let around = &neighbours[&v];
414 let mut sum = [0.0; 2];
415 for w in around {
416 let p = param[w];
417 sum[0] += p[0];
418 sum[1] += p[1];
419 }
420 let new = [sum[0] / around.len() as f64, sum[1] / around.len() as f64];
421 let old = param[&v];
422 let relaxed = [old[0] + 1.8 * (new[0] - old[0]), old[1] + 1.8 * (new[1] - old[1])];
423 change = change.max((relaxed[0] - old[0]).abs()).max((relaxed[1] - old[1]).abs());
424 param.insert(v, relaxed);
425 }
426 if change < 1e-12 {
427 break;
428 }
429 }
430
431 let free: Vec<usize> = (0..cu * cv).filter(|&k| net[k].is_none()).collect();
434 let free_index: HashMap<usize, usize> = free.iter().enumerate().map(|(k, &c)| (c, k)).collect();
435 let samples: Vec<(Vec3, [f64; 2])> = region_vertices.iter().map(|&v| (point_of(v).unwrap(), param[&v])).collect();
436 let rows: Vec<Vec<(usize, f64)>> = samples
437 .iter()
438 .map(|(_, uv)| {
439 let (su, bu) = super::basis(&knots_u, 3, cu, uv[0]);
440 let (sv, bv) = super::basis(&knots_v, 3, cv, uv[1]);
441 let mut row = Vec::with_capacity(16);
442 for (a, wu) in bu.iter().enumerate() {
443 for (b, wv) in bv.iter().enumerate() {
444 if wu * wv != 0.0 {
445 row.push((at(su - 3 + a, sv - 3 + b), wu * wv));
446 }
447 }
448 }
449 row
450 })
451 .collect();
452 let trace = std::env::var("BREP_DEBUG_NURBS_FACE").is_ok();
453 if trace {
454 eprintln!("[nurbs-face] net {cu}x{cv} free {} samples {} boundary {n}", (0..cu * cv).filter(|&k| net[k].is_none()).count(), region_vertices.len());
455 }
456 let pu: Vec<f64> = (0..cu).map(|i| control_param(&u_breaks, i)).collect();
465 let pv: Vec<f64> = (0..cv).map(|j| control_param(&v_breaks, j)).collect();
466 let coons: Vec<Vec3> = (0..cu * cv)
467 .map(|c| {
468 let (i, j) = (c / cv, c % cv);
469 if let Some(p) = net[c] {
470 return p;
471 }
472 let (u, v) = (pu[i], pv[j]);
473 let g = |ii: usize, jj: usize| net[at(ii, jj)].unwrap();
474 g(i, 0)
475 .scale(1.0 - v)
476 .add(g(i, cv - 1).scale(v))
477 .add(g(0, j).scale(1.0 - u))
478 .add(g(cu - 1, j).scale(u))
479 .sub(g(0, 0).scale((1.0 - u) * (1.0 - v)))
480 .sub(g(cu - 1, 0).scale(u * (1.0 - v)))
481 .sub(g(0, cv - 1).scale((1.0 - u) * v))
482 .sub(g(cu - 1, cv - 1).scale(u * v))
483 })
484 .collect();
485 let ladder_started = web_time::Instant::now();
486 let mut best: Option<(NurbsSurface, f64, f64)> = None;
487 let mut folds = 0usize;
488 let mut warm = vec![Vec3::new(0.0, 0.0, 0.0); free.len()];
491 for weight in [1e0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8] {
492 let solved = if free.is_empty() {
493 Vec::new()
494 } else {
495 solve_net(&coons, &free, &free_index, &rows, &samples, cu, cv, &pu, &pv, weight, &warm)
496 };
497 warm.clone_from(&solved);
498 let mut full = coons.clone();
499 for (k, &c) in free.iter().enumerate() {
500 full[c] = coons[c].add(solved[k]);
501 }
502 let mut max = 0.0f64;
503 let mut sum = 0.0f64;
504 for ((point, _), row) in samples.iter().zip(&rows) {
505 let s = row.iter().fold(Vec3::new(0.0, 0.0, 0.0), |acc, &(c, w)| acc.add(full[c].scale(w)));
506 let d = s.sub(*point).length();
507 max = max.max(d);
508 sum += d * d;
509 }
510 let rms = (sum / samples.len() as f64).sqrt();
511 let control_points: Vec<Vec<Vec4>> = (0..cu).map(|i| (0..cv).map(|j| Vec4::from_point(full[at(i, j)], 1.0)).collect()).collect();
512 let surface = NurbsSurface::new(3, 3, knots_u.clone(), knots_v.clone(), control_points).map_err(|e| format!("NURBS surface: {e}"))?;
513 let folded = reversed_normals(&surface, &u_breaks, &v_breaks, &tri_points);
514 if trace {
515 eprintln!("[nurbs-face] weight {weight:e} max {max:.3e} rms {rms:.3e} reversed normals {folded} at {:.2}s", ladder_started.elapsed().as_secs_f64());
516 }
517 if folded > 0 {
518 folds += 1;
519 continue;
520 }
521 let done = max <= choice.tolerance;
522 if best.as_ref().is_none_or(|(_, m, _)| max < *m) || done {
523 best = Some((surface, max, rms));
524 }
525 if done {
526 break;
527 }
528 }
529 let Some((surface, max_deviation, _)) = best else {
530 return Err(format!("every NURBS face with this boundary folds over itself ({folds} tried); split the region or choose its corners differently"));
531 };
532 if max_deviation > choice.tolerance {
533 return Err(format!(
534 "the closest NURBS face with this boundary that does not fold is {max_deviation:.3e} mm from the mesh, over the {:.3e} mm tolerance",
535 choice.tolerance
536 ));
537 }
538
539 let mut next_id = 0u64;
542 for vertex in &solid.vertices {
543 next_id = next_id.max(vertex.id);
544 }
545 for edge in &solid.edges {
546 next_id = next_id.max(edge.id);
547 }
548 for face in solid.shells.iter().flat_map(|s| &s.faces) {
549 next_id = next_id.max(face.id);
550 for l in &face.loops {
551 next_id = next_id.max(l.id);
552 for c in &l.coedges {
553 next_id = next_id.max(c.id);
554 }
555 }
556 }
557 next_id += 1;
558 let mut fresh = || {
559 next_id += 1;
560 next_id - 1
561 };
562 let mut coedges = Vec::new();
563 for (k, coedge) in loop_coedges.iter().enumerate() {
564 let a = square[&k];
565 let b = square[&((k + 1) % n)];
566 let pcurve = brep_kernel::make_line(Vec3::new(a[0], a[1], 0.0), Vec3::new(b[0], b[1], 0.0))?;
567 coedges.push(CoedgeRecord { id: fresh(), edge_id: coedge.edge_id, forward: coedge.forward, pcurve });
568 }
569 let facet_normal = region_faces.iter().fold(Vec3::new(0.0, 0.0, 0.0), |acc, &i| {
573 let face = &shell.faces[i];
574 let sign = if face.same_sense { 1.0 } else { -1.0 };
575 let normal = face.surface.normal(0.5 * (face.surface.knots_u[0] + face.surface.knots_u.last().unwrap()), 0.5 * (face.surface.knots_v[0] + face.surface.knots_v.last().unwrap()));
576 acc.add(normal.map(|n| n.scale(sign)).unwrap_or(Vec3::new(0.0, 0.0, 0.0)))
577 });
578 let mid = surface.normal(0.5, 0.5).map_err(|e| format!("NURBS normal: {e}"))?;
579 let same_sense = mid.dot(facet_normal) >= 0.0;
580 let name = shell.faces[region_faces[0]].name.clone();
581 let face = FaceRecord { id: fresh(), surface, same_sense, loops: vec![LoopRecord { id: fresh(), coedges }], name };
582
583 let interior_edges: HashSet<u64> = uses.iter().filter(|&(_, &n)| n == 2).map(|(&e, _)| e).collect();
585 let kept_vertices: HashSet<u64> = loop_vertices.iter().copied().collect();
586 let region_vertex_ids: HashSet<u64> = region_faces
587 .iter()
588 .flat_map(|&i| shell.faces[i].loops.iter().flat_map(|l| &l.coedges))
589 .filter_map(|c| coedge_ends(solid, c))
590 .flat_map(|(a, b)| [a, b])
591 .collect();
592 let first = region_faces[0];
593 let shell = &mut solid.shells[0];
594 let mut kept = Vec::with_capacity(shell.faces.len());
595 for (index, old) in std::mem::take(&mut shell.faces).into_iter().enumerate() {
596 if index == first {
597 kept.push(face.clone());
598 } else if !region_face_set.contains(&index) {
599 kept.push(old);
600 }
601 }
602 shell.faces = kept;
603 solid.edges.retain(|e| !interior_edges.contains(&e.id));
604 solid.vertices.retain(|v| !region_vertex_ids.contains(&v.id) || kept_vertices.contains(&v.id));
605 solid.mass_properties_cache = Default::default();
606 Ok(NurbsFaceOutcome { applied: true, max_deviation: Some(max_deviation), control_points: Some([cu, cv]), reason: None })
607}
608
609fn reversed_normals(surface: &NurbsSurface, u_breaks: &[f64], v_breaks: &[f64], tri_points: &[[Vec3; 3]]) -> usize {
615 let triangles: Vec<(Vec3, Vec3)> = tri_points
616 .iter()
617 .filter_map(|t| {
618 let n = t[1].sub(t[0]).cross(t[2].sub(t[0]));
619 let length = n.length();
620 (length > 0.0).then(|| (t[0].add(t[1]).add(t[2]).scale(1.0 / 3.0), n.scale(1.0 / length)))
621 })
622 .collect();
623 let mut dots = Vec::new();
624 for su in u_breaks.windows(2) {
625 for sv in v_breaks.windows(2) {
626 for a in [0.1, 0.5, 0.9] {
627 for b in [0.1, 0.5, 0.9] {
628 let (u, v) = (su[0] + a * (su[1] - su[0]), sv[0] + b * (sv[1] - sv[0]));
629 let (Ok(point), Ok(normal)) = (surface.evaluate(u, v), surface.normal(u, v)) else { continue };
630 let Some((_, n)) = triangles.iter().min_by(|x, y| x.0.sub(point).length().total_cmp(&y.0.sub(point).length())) else { continue };
631 dots.push(normal.dot(*n));
632 }
633 }
634 }
635 }
636 let sign = if dots.iter().sum::<f64>() >= 0.0 { 1.0 } else { -1.0 };
637 dots.iter().filter(|&&d| d * sign < 0.0).count()
638}
639
640const CG_ITERATIONS: usize = 1500;
642
643#[allow(clippy::too_many_arguments)]
650fn solve_net(
651 base: &[Vec3],
652 free: &[usize],
653 free_index: &HashMap<usize, usize>,
654 rows: &[Vec<(usize, f64)>],
655 samples: &[(Vec3, [f64; 2])],
656 cu: usize,
657 cv: usize,
658 pu: &[f64],
659 pv: &[f64],
660 weight: f64,
661 start: &[Vec3],
662) -> Vec<Vec3> {
663 let m = free.len();
664 let w = weight.sqrt();
665 let divided = |t: &[f64], k: usize| -> Option<[f64; 3]> {
667 let (h1, h2) = (t[k] - t[k - 1], t[k + 1] - t[k]);
668 if !(h1 > 0.0 && h2 > 0.0) {
669 return None;
670 }
671 let extent = (0.5 * (h1 + h2)).sqrt();
672 Some([2.0 / (h1 * (h1 + h2)), -2.0 / (h1 * h2), 2.0 / (h2 * (h1 + h2))].map(|c| c * extent * w))
673 };
674 let mut stencils: Vec<([usize; 3], [f64; 3])> = Vec::new();
675 for i in 0..cu {
676 for j in 1..cv - 1 {
677 if let Some(co) = divided(pv, j) {
678 stencils.push(([i * cv + j - 1, i * cv + j, i * cv + j + 1], co));
679 }
680 }
681 }
682 for j in 0..cv {
683 for i in 1..cu - 1 {
684 if let Some(co) = divided(pu, i) {
685 stencils.push(([(i - 1) * cv + j, i * cv + j, (i + 1) * cv + j], co));
686 }
687 }
688 }
689 let free_rows: Vec<Vec<(usize, f64)>> = rows
692 .iter()
693 .map(|row| row.iter().filter_map(|&(c, wt)| free_index.get(&c).map(|&k| (k, wt))).collect())
694 .chain(stencils.iter().map(|(st, coefficients)| {
695 st.iter().zip(coefficients).filter_map(|(&c, &co)| free_index.get(&c).map(|&k| (k, co))).collect()
696 }))
697 .filter(|row: &Vec<(usize, f64)>| !row.is_empty())
698 .collect();
699 let apply = |x: &[f64]| -> Vec<f64> {
702 let mut out = vec![0.0; m];
703 for row in &free_rows {
704 let s: f64 = row.iter().map(|&(k, wt)| wt * x[k]).sum();
705 for &(k, wt) in row {
706 out[k] += wt * s;
707 }
708 }
709 out
710 };
711 let mut diagonal = vec![0.0; m];
713 for row in &free_rows {
714 for &(k, wt) in row {
715 diagonal[k] += wt * wt;
716 }
717 }
718 let inverse: Vec<f64> = diagonal.iter().map(|&d| if d > 0.0 { 1.0 / d } else { 1.0 }).collect();
719 let mut result = vec![Vec3::new(0.0, 0.0, 0.0); m];
720 for axis in 0..3 {
721 let coord = |p: Vec3| match axis {
722 0 => p.x,
723 1 => p.y,
724 _ => p.z,
725 };
726 let mut rhs = vec![0.0; m];
729 for ((point, _), row) in samples.iter().zip(rows) {
730 let fixed: f64 = row.iter().map(|&(c, wt)| wt * coord(base[c])).sum();
731 let r = coord(*point) - fixed;
732 for &(c, wt) in row {
733 if let Some(&k) = free_index.get(&c) {
734 rhs[k] += wt * r;
735 }
736 }
737 }
738 let mut x: Vec<f64> = start.iter().map(|&p| coord(p)).collect();
740 let ax = apply(&x);
741 let mut r: Vec<f64> = rhs.iter().zip(&ax).map(|(b, a)| b - a).collect();
742 let mut z: Vec<f64> = r.iter().zip(&inverse).map(|(a, b)| a * b).collect();
743 let mut p = z.clone();
744 let mut rz: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
745 let scale: f64 = rhs.iter().map(|v| v * v).sum::<f64>().max(1e-300);
746 for _ in 0..CG_ITERATIONS.min(4 * m + 50) {
747 let rr: f64 = r.iter().map(|v| v * v).sum();
748 if rr <= 1e-24 * scale {
749 break;
750 }
751 let ap = apply(&p);
752 let pap: f64 = p.iter().zip(&ap).map(|(a, b)| a * b).sum();
753 if pap <= 0.0 {
754 break;
755 }
756 let alpha = rz / pap;
757 for k in 0..m {
758 x[k] += alpha * p[k];
759 r[k] -= alpha * ap[k];
760 }
761 for k in 0..m {
762 z[k] = r[k] * inverse[k];
763 }
764 let next: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
765 let beta = next / rz;
766 rz = next;
767 for k in 0..m {
768 p[k] = z[k] + beta * p[k];
769 }
770 }
771 for k in 0..m {
772 match axis {
773 0 => result[k].x = x[k],
774 1 => result[k].y = x[k],
775 _ => result[k].z = x[k],
776 }
777 }
778 }
779 result
780}