1use std::collections::HashMap;
66
67use brep_kernel::{
68 containment_lane, intersect_analytic_pair, intersect_curve_surface, intersect_surfaces,
69 make_line, make_plane, parameter_point_in_face, project_point_to_curve,
70 project_point_to_surface, segment_intersection, trim_sample_count, trim_station,
71 AnalyticSurface, BrepSolid, ContainmentLane, FaceRecord, KernelTolerances, NurbsCurve,
72 NurbsSurface, PolygonClass, SurfaceIntersectionOptions, Vec2, Vec3,
73};
74
75use super::project::{ViewFrame, EDGE_ANGLE_DEG};
76use super::PlacedView;
77
78pub const CHORD_MM: f64 = 0.005;
83
84const MAX_SUBDIVISION: u32 = 14;
86
87const GRAZING_COS: f64 = 1e-3;
91
92const TRIM_STATIONS: usize = 64;
96
97const BISECTION_STEPS: u32 = 40;
101
102
103fn v3(p: [f64; 3]) -> Vec3 {
104 Vec3::new(p[0], p[1], p[2])
105}
106
107fn a3(p: Vec3) -> [f64; 3] {
108 [p.x, p.y, p.z]
109}
110
111pub enum Clip<'a> {
117 Whole,
119 Beyond { cap: &'a [Vec<[f64; 2]>] },
124 Circle { radius: f64 },
127}
128
129#[derive(Debug, Clone, Copy)]
131pub struct Floors {
132 pub bar: f64,
134 pub depth_mm: f64,
136 pub crossing_mm: f64,
138}
139
140impl Floors {
141 pub fn of(scale: f64) -> Floors {
142 let bar = KernelTolerances::default().pcurve_consistency;
143 Floors { bar, depth_mm: bar * scale, crossing_mm: bar * scale }
144 }
145}
146
147#[derive(Debug, Clone, Default)]
150pub struct HlrResult {
151 pub runs: Vec<Vec<[f64; 2]>>,
152 pub inked: usize,
154 pub silhouettes: usize,
156 pub pieces: usize,
158 pub trim_queries: usize,
162 pub ray_queries: usize,
165}
166
167thread_local! {
168 static TRIM_QUERIES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
171 static RAY_QUERIES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
172}
173
174
175
176#[derive(Clone)]
182enum Carrier<'a> {
183 Edge(&'a NurbsCurve),
185 Curve(NurbsCurve),
187 Line { a: Vec3, b: Vec3 },
189 Circle { centre: Vec3, e1: Vec3, e2: Vec3 },
191 Traced { surface: &'a NurbsSurface, uv: Vec<[f64; 2]>, dir: Vec3 },
194 Polyline(Vec<Vec3>),
197}
198
199impl Carrier<'_> {
200 fn at(&self, s: f64) -> Option<Vec3> {
201 match self {
202 Carrier::Edge(curve) => curve.evaluate(s).ok(),
203 Carrier::Curve(curve) => curve.evaluate(s).ok(),
204 Carrier::Line { a, b } => Some(a.add(b.sub(*a).scale(s))),
205 Carrier::Circle { centre, e1, e2 } => Some(centre.add(e1.scale(s.cos())).add(e2.scale(s.sin()))),
206 Carrier::Traced { surface, uv, dir } => {
207 let (point, _) = traced_at(surface, uv, *dir, s)?;
208 Some(point)
209 }
210 Carrier::Polyline(points) => {
211 let last = points.len().checked_sub(1)?;
212 let i = (s.floor().max(0.0) as usize).min(last.saturating_sub(1));
213 let f = (s - i as f64).clamp(0.0, 1.0);
214 let (a, b) = (points[i], points[(i + 1).min(last)]);
215 Some(a.add(b.sub(a).scale(f)))
216 }
217 }
218 }
219
220 fn straight(&self) -> bool {
223 match self {
224 Carrier::Line { .. } => true,
225 Carrier::Edge(curve) => curve.degree == 1 && curve.control_points.len() == 2,
226 Carrier::Curve(curve) => curve.degree == 1 && curve.control_points.len() == 2,
227 _ => false,
228 }
229 }
230}
231
232fn grazing(surface: &NurbsSurface, dir: Vec3, u: f64, v: f64) -> Option<f64> {
235 surface.normal(u, v).ok().map(|n| n.dot(dir))
236}
237
238fn traced_at(surface: &NurbsSurface, uv: &[[f64; 2]], dir: Vec3, s: f64) -> Option<(Vec3, [f64; 2])> {
242 let last = uv.len().checked_sub(1)?;
243 if last == 0 {
244 let p = surface.evaluate(uv[0][0], uv[0][1]).ok()?;
245 return Some((p, uv[0]));
246 }
247 let i = (s.floor().max(0.0) as usize).min(last - 1);
248 let f = (s - i as f64).clamp(0.0, 1.0);
249 let (a, b) = (uv[i], uv[i + 1]);
250 let base = [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f];
251 let step = [b[0] - a[0], b[1] - a[1]];
252 let length = (step[0] * step[0] + step[1] * step[1]).sqrt();
253 let solved = if f <= 0.0 || f >= 1.0 || length <= 0.0 {
254 base
255 } else {
256 let across = [-step[1], step[0]];
257 root_along(surface, dir, base, across).unwrap_or(base)
258 };
259 let p = surface.evaluate(solved[0], solved[1]).ok()?;
260 Some((p, solved))
261}
262
263fn root_along(surface: &NurbsSurface, dir: Vec3, base: [f64; 2], across: [f64; 2]) -> Option<[f64; 2]> {
266 let at = |t: f64| [base[0] + across[0] * t, base[1] + across[1] * t];
267 let g0 = {
268 let p = at(0.0);
269 grazing(surface, dir, p[0], p[1])?
270 };
271 if g0 == 0.0 {
272 return Some(base);
273 }
274 for (lo, hi) in [(0.0, 0.5), (0.0, -0.5), (0.5, 1.0), (-0.5, -1.0)] {
275 let (p_lo, p_hi) = (at(lo), at(hi));
276 let g_lo = grazing(surface, dir, p_lo[0], p_lo[1])?;
277 let g_hi = grazing(surface, dir, p_hi[0], p_hi[1])?;
278 if g_lo.signum() == g_hi.signum() {
279 continue;
280 }
281 let (mut lo, mut hi, mut g_lo) = (lo, hi, g_lo);
282 for _ in 0..BISECTION_STEPS {
283 let mid = 0.5 * (lo + hi);
284 let p = at(mid);
285 let g = grazing(surface, dir, p[0], p[1])?;
286 if g.signum() == g_lo.signum() {
287 lo = mid;
288 g_lo = g;
289 } else {
290 hi = mid;
291 }
292 }
293 return Some(at(0.5 * (lo + hi)));
294 }
295 None
296}
297
298fn project(frame: &ViewFrame, placed: &PlacedView, p: Vec3) -> ([f64; 2], [f64; 3]) {
300 let view = frame.to_view(a3(p));
301 ([placed.position[0] + view[0] * placed.scale, placed.position[1] - view[1] * placed.scale], view)
302}
303
304fn paper_distance(a: [f64; 2], b: [f64; 2]) -> f64 {
305 ((a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2)).sqrt()
306}
307
308fn segment_foot(p: [f64; 2], a: [f64; 2], b: [f64; 2]) -> (f64, f64) {
311 let d = [b[0] - a[0], b[1] - a[1]];
312 let length2 = d[0] * d[0] + d[1] * d[1];
313 let t = if length2 > 0.0 {
314 (((p[0] - a[0]) * d[0] + (p[1] - a[1]) * d[1]) / length2).clamp(0.0, 1.0)
315 } else {
316 0.0
317 };
318 let foot = [a[0] + d[0] * t, a[1] + d[1] * t];
319 (paper_distance(p, foot), t)
320}
321
322#[derive(Debug, Clone, Default)]
324struct Samples {
325 params: Vec<f64>,
326 points: Vec<Vec3>,
327 paper: Vec<[f64; 2]>,
328 view: Vec<[f64; 3]>,
329}
330
331impl Samples {
332 fn push(&mut self, s: f64, p: Vec3, frame: &ViewFrame, placed: &PlacedView) {
333 let (paper, view) = project(frame, placed, p);
334 self.params.push(s);
335 self.points.push(p);
336 self.paper.push(paper);
337 self.view.push(view);
338 }
339
340 fn len(&self) -> usize {
341 self.params.len()
342 }
343
344 fn paper_length(&self) -> f64 {
345 self.paper.windows(2).map(|w| paper_distance(w[0], w[1])).sum()
346 }
347}
348
349fn sample(
354 carrier: &Carrier,
355 s0: f64,
356 s1: f64,
357 initial: usize,
358 frame: &ViewFrame,
359 placed: &PlacedView,
360) -> Samples {
361 let mut out = Samples::default();
362 let initial = if carrier.straight() { 1 } else { initial.max(2) };
363 let Some(first) = carrier.at(s0) else { return out };
364 out.push(s0, first, frame, placed);
365 for k in 0..initial {
366 let a = s0 + (s1 - s0) * k as f64 / initial as f64;
367 let b = s0 + (s1 - s0) * (k + 1) as f64 / initial as f64;
368 let (Some(pa), Some(pb)) = (carrier.at(a), carrier.at(b)) else { continue };
369 subdivide(carrier, (a, pa), (b, pb), 0, frame, placed, &mut out);
370 }
371 out
372}
373
374fn subdivide(
375 carrier: &Carrier,
376 (a, pa): (f64, Vec3),
377 (b, pb): (f64, Vec3),
378 depth: u32,
379 frame: &ViewFrame,
380 placed: &PlacedView,
381 out: &mut Samples,
382) {
383 if depth < MAX_SUBDIVISION && !carrier.straight() {
384 let mid = 0.5 * (a + b);
385 if let Some(pm) = carrier.at(mid) {
386 let (paper_a, _) = project(frame, placed, pa);
387 let (paper_b, _) = project(frame, placed, pb);
388 let (paper_m, _) = project(frame, placed, pm);
389 if segment_foot(paper_m, paper_a, paper_b).0 > CHORD_MM {
390 subdivide(carrier, (a, pa), (mid, pm), depth + 1, frame, placed, out);
391 subdivide(carrier, (mid, pm), (b, pb), depth + 1, frame, placed, out);
392 return;
393 }
394 }
395 }
396 out.push(b, pb, frame, placed);
397}
398
399type VertexKey = (usize, u64);
405
406struct Candidate<'a> {
407 carrier: Carrier<'a>,
408 samples: Samples,
409 inked: bool,
411 silhouette: bool,
412 edge: Option<(usize, u64)>,
414 ends: Option<(VertexKey, VertexKey)>,
415 bounds: [f64; 4],
417}
418
419fn bounds_of(paper: &[[f64; 2]]) -> [f64; 4] {
420 let mut b = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
421 for p in paper {
422 b[0] = b[0].min(p[0]);
423 b[1] = b[1].min(p[1]);
424 b[2] = b[2].max(p[0]);
425 b[3] = b[3].max(p[1]);
426 }
427 b
428}
429
430fn curve_initial(curve: &NurbsCurve) -> usize {
432 let spans = curve.control_points.len().saturating_sub(curve.degree).max(1);
433 (spans * (curve.degree + 1)).clamp(2, 256)
434}
435
436fn outward_normal(face: &FaceRecord, p: Vec3) -> Option<Vec3> {
439 let foot = project_point_to_surface(&face.surface, p).ok()?;
440 let n = face.surface.normal(foot.u, foot.v).ok()?;
441 Some(if face.same_sense { n } else { n.scale(-1.0) })
442}
443
444fn is_crease(curve: &NurbsCurve, t0: f64, t1: f64, a: &FaceRecord, b: &FaceRecord) -> bool {
449 let bar = EDGE_ANGLE_DEG.to_radians().cos();
450 for k in 1..=5 {
451 let t = t0 + (t1 - t0) * k as f64 / 6.0;
452 let Ok(p) = curve.evaluate(t) else { return true };
453 let (Some(na), Some(nb)) = (outward_normal(a, p), outward_normal(b, p)) else { return true };
454 if na.dot(nb) < bar {
455 return true;
456 }
457 }
458 false
459}
460
461fn edge_faces(solid: &BrepSolid) -> HashMap<u64, Vec<(usize, usize)>> {
463 let mut map: HashMap<u64, Vec<(usize, usize)>> = HashMap::new();
464 for (shell_index, shell) in solid.shells.iter().enumerate() {
465 for (face_index, face) in shell.faces.iter().enumerate() {
466 for lp in &face.loops {
467 for coedge in &lp.coedges {
468 map.entry(coedge.edge_id).or_default().push((shell_index, face_index));
469 }
470 }
471 }
472 }
473 map
474}
475
476fn pair_extent(solid: &BrepSolid, a: &FaceRecord, b: &FaceRecord, edge_index: &HashMap<u64, usize>) -> f64 {
481 let mut lo = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
482 let mut hi = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
483 for coedge in [a, b].into_iter().flat_map(|face| &face.loops).flat_map(|lp| &lp.coedges) {
484 let Some(edge) = edge_index.get(&coedge.edge_id).map(|&i| &solid.edges[i]) else { continue };
485 for t in [edge.t0, 0.5 * (edge.t0 + edge.t1), edge.t1] {
486 if let Ok(q) = edge.curve.evaluate(t) {
487 lo = Vec3::new(lo.x.min(q.x), lo.y.min(q.y), lo.z.min(q.z));
488 hi = Vec3::new(hi.x.max(q.x), hi.y.max(q.y), hi.z.max(q.z));
489 }
490 }
491 }
492 if lo.x <= hi.x {
493 hi.sub(lo).length()
494 } else {
495 0.0
496 }
497}
498
499fn curvature_across(face: &FaceRecord, p: Vec3, tangent: Vec3) -> Option<f64> {
503 let foot = project_point_to_surface(&face.surface, p).ok()?;
504 let d = face.surface.derivatives(foot.u, foot.v, 2).ok()?;
505 let (su, sv) = (d[1][0], d[0][1]);
506 let normal = su.cross(sv).normalized().ok()?;
507 let normal = if face.same_sense { normal } else { normal.scale(-1.0) };
508 let across = normal.cross(tangent).normalized().ok()?;
509 let (e, f, g) = (su.dot(su), su.dot(sv), sv.dot(sv));
511 let det = e * g - f * f;
512 if det.abs() <= f64::EPSILON * e * g {
513 return None;
514 }
515 let (cu, cv) = (across.dot(su), across.dot(sv));
516 let (a, b) = ((g * cu - f * cv) / det, (e * cv - f * cu) / det);
517 let second = d[2][0].dot(normal) * a * a + 2.0 * d[1][1].dot(normal) * a * b + d[0][2].dot(normal) * b * b;
518 let first = e * a * a + 2.0 * f * a * b + g * b * b;
519 (first > 0.0).then(|| second / first)
520}
521
522fn one_surface(curve: &NurbsCurve, t0: f64, t1: f64, a: &FaceRecord, b: &FaceRecord, extent: f64, bar: f64) -> bool {
537 for k in 1..=5 {
538 let t = t0 + (t1 - t0) * k as f64 / 6.0;
539 let (Ok(p), Ok(d)) = (curve.evaluate(t), curve.derivatives(t, 1)) else { return false };
540 let (Some(ka), Some(kb)) = (curvature_across(a, p, d[1]), curvature_across(b, p, d[1])) else { return false };
541 if (ka - kb).abs() * extent * extent * 0.5 > bar {
542 return false;
543 }
544 }
545 true
546}
547
548fn edge_candidates<'a>(
549 solid_index: usize,
550 solid: &'a BrepSolid,
551 frame: &ViewFrame,
552 placed: &PlacedView,
553 floors: &Floors,
554) -> Vec<Candidate<'a>> {
555 let faces = edge_faces(solid);
556 let edge_index: HashMap<u64, usize> = solid.edges.iter().enumerate().map(|(i, edge)| (edge.id, i)).collect();
557 let mut extents: HashMap<[(usize, usize); 2], f64> = HashMap::new();
559 let mut out = Vec::new();
560 for edge in &solid.edges {
561 if edge.degenerate {
562 continue;
563 }
564 let owners = faces.get(&edge.id).cloned().unwrap_or_default();
565 let mut distinct = owners.clone();
566 distinct.sort_unstable();
567 distinct.dedup();
568 let inked = match distinct.len() {
569 1 if owners.len() >= 2 => continue,
573 2 => {
576 let face = |(s, f): (usize, usize)| &solid.shells[s].faces[f];
577 let (a, b) = (face(distinct[0]), face(distinct[1]));
578 is_crease(&edge.curve, edge.t0, edge.t1, a, b) || {
579 let extent = *extents
580 .entry([distinct[0], distinct[1]])
581 .or_insert_with(|| pair_extent(solid, a, b, &edge_index));
582 !one_surface(&edge.curve, edge.t0, edge.t1, a, b, extent, floors.bar)
583 }
584 }
585 _ => true,
586 };
587 let carrier = Carrier::Edge(&edge.curve);
588 let samples = sample(&carrier, edge.t0, edge.t1, curve_initial(&edge.curve), frame, placed);
589 if samples.len() < 2 {
590 continue;
591 }
592 let bounds = bounds_of(&samples.paper);
593 out.push(Candidate {
594 carrier,
595 samples,
596 inked,
597 silhouette: false,
598 edge: Some((solid_index, edge.id)),
599 ends: Some(((solid_index, edge.start_vertex_id), (solid_index, edge.end_vertex_id))),
600 bounds,
601 });
602 }
603 out
604}
605
606fn planar(surface: &NurbsSurface) -> bool {
608 matches!(surface.analytic(), Some(AnalyticSurface::Plane { .. })) || surface.is_affine().unwrap_or(false)
609}
610
611fn trim_box(face: &FaceRecord) -> Option<[f64; 4]> {
614 let du = face.surface.domain_u().ok()?;
615 let dv = face.surface.domain_v().ok()?;
616 let mut b = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
617 for lp in &face.loops {
618 for coedge in &lp.coedges {
619 for cp in &coedge.pcurve.control_points {
620 let w = if cp.w != 0.0 { cp.w } else { 1.0 };
621 b[0] = b[0].min(cp.x / w);
622 b[1] = b[1].min(cp.y / w);
623 b[2] = b[2].max(cp.x / w);
624 b[3] = b[3].max(cp.y / w);
625 }
626 }
627 }
628 if !(b[0] <= b[2]) {
629 return Some([du[0], dv[0], du[1], dv[1]]);
630 }
631 Some([b[0].max(du[0]), b[1].max(dv[0]), b[2].min(du[1]), b[3].min(dv[1])])
632}
633
634pub struct SolidTrims {
640 faces: Vec<Vec<TrimTests>>,
641}
642
643impl SolidTrims {
644 pub fn of(solid: &BrepSolid) -> SolidTrims {
645 SolidTrims { faces: solid.shells.iter().map(|shell| shell.faces.iter().map(|_| TrimTests::new()).collect()).collect() }
646 }
647}
648
649struct TrimTests {
656 lane: std::cell::OnceCell<Option<ContainmentLane>>,
657 reach: std::cell::OnceCell<Option<UvReach>>,
658 uv: std::cell::OnceCell<Option<UvTrim>>,
659}
660
661impl TrimTests {
662 fn new() -> TrimTests {
663 TrimTests { lane: std::cell::OnceCell::new(), reach: std::cell::OnceCell::new(), uv: std::cell::OnceCell::new() }
664 }
665
666 fn lane(&self, face: &FaceRecord) -> Option<&ContainmentLane> {
669 self.lane.get_or_init(|| containment_lane(face).ok()).as_ref()
670 }
671
672 fn reach(&self, face: &FaceRecord) -> Option<&UvReach> {
674 self.reach.get_or_init(|| self.lane(face).and_then(|lane| UvReach::of(face, lane))).as_ref()
675 }
676
677 fn uv_trim(&self, face: &FaceRecord) -> Option<&UvTrim> {
679 self.uv
680 .get_or_init(|| {
681 let reach = self.reach(face)?;
682 UvTrim::of(face, reach, self.lane(face)?)
683 })
684 .as_ref()
685 }
686}
687
688struct UvTrim {
726 segments: Vec<([f64; 2], [f64; 2], usize)>,
728 loops: usize,
729 grid: Grid,
730 margin: Margin,
732 images: Vec<f64>,
734}
735
736#[derive(Debug, Clone, Copy)]
737enum Margin {
738 Plus(f64),
740 Twice,
742}
743
744impl UvTrim {
745 fn of(face: &FaceRecord, reach: &UvReach, lane: &ContainmentLane) -> Option<UvTrim> {
746 match lane {
747 ContainmentLane::WrappedHorizon { loops, u_period, cross_frame: true } => {
748 let loops = loops.iter().map(|lp| lp.iter().map(|p| [p.x, p.y]).collect()).collect();
749 return Some(UvTrim::grid_of(loops, Margin::Twice, vec![-u_period, 0.0, *u_period]));
750 }
751 ContainmentLane::Generic { sphere_chart: false } => {}
752 _ => return None,
753 }
754 let surface = &face.surface;
755 let centre = [0.5 * (reach.lo[0] + reach.hi[0]), 0.5 * (reach.lo[1] + reach.hi[1])];
758 let fine = trim_band(surface, centre, KernelTolerances::default().pcurve_consistency);
759 let mut rings: Vec<Vec<[f64; 2]>> = Vec::with_capacity(face.loops.len());
760 let mut sag: f64 = 0.0;
761 for lp in &face.loops {
762 let mut ring = Vec::new();
763 for coedge in &lp.coedges {
764 let curve = &coedge.pcurve;
765 let [start, end] = curve.domain().ok()?;
766 let count = trim_sample_count(curve);
767 let station = |i: usize| if i < count { trim_station(start, end, i, count) } else { end };
768 let at = |i: usize| -> Option<[f64; 2]> { curve.evaluate(station(i)).ok().map(|p| [p.x, p.y]) };
769 let mut stride = 1;
772 while count % (stride * 2) == 0 && count / (stride * 2) >= 2 {
773 stride *= 2;
774 }
775 let mut a = at(0)?;
776 if let Some(previous) = ring.last() {
780 sag = sag.max(paper_distance(*previous, a));
781 }
782 for from in (0..count).step_by(stride) {
783 let to = from + stride;
784 let b = at(to)?;
785 let mut pending = vec![(from, a, to, b)];
789 while let Some((i, pi, j, pj)) = pending.pop() {
790 if j - i >= 2 {
791 let m = (i + j) / 2;
792 let pm = at(m)?;
793 let off = segment_foot(pm, pi, pj).0;
794 if off > fine {
795 pending.push((m, pm, j, pj));
796 pending.push((i, pi, m, pm));
797 continue;
798 }
799 sag = sag.max(off);
800 } else {
801 let pm = curve.evaluate(0.5 * (station(i) + station(j))).ok()?;
802 sag = sag.max(segment_foot([pm.x, pm.y], pi, pj).0);
803 }
804 ring.push(pi);
805 }
806 a = b;
807 }
808 ring.push(a);
809 }
810 if let (Some(first), Some(last)) = (ring.first(), ring.last()) {
811 sag = sag.max(paper_distance(*first, *last));
812 }
813 if ring.len() >= 2 {
814 rings.push(ring);
815 }
816 }
817 let area = |ring: &[[f64; 2]]| -> f64 {
818 (0..ring.len()).map(|i| {
819 let (a, b) = (ring[i], ring[(i + 1) % ring.len()]);
820 a[0] * b[1] - b[0] * a[1]
821 }).sum()
822 };
823 let areas: Vec<f64> = rings.iter().map(|ring| area(ring)).collect();
824 let outer = (0..rings.len()).max_by(|&i, &j| areas[i].abs().partial_cmp(&areas[j].abs()).unwrap_or(std::cmp::Ordering::Equal))?;
825 let left = if face.same_sense { 1.0 } else { -1.0 };
826 for (index, a) in areas.iter().enumerate() {
827 let expected = if index == outer { left } else { -left };
828 if !(a * expected > 0.0) {
829 return None;
830 }
831 }
832 Some(UvTrim::grid_of(rings, Margin::Plus(sag), vec![0.0]))
833 }
834
835 fn grid_of(rings: Vec<Vec<[f64; 2]>>, margin: Margin, images: Vec<f64>) -> UvTrim {
836 let mut segments = Vec::new();
837 for (index, ring) in rings.iter().enumerate() {
838 for i in 0..ring.len() {
839 segments.push((ring[i], ring[(i + 1) % ring.len()], index));
840 }
841 }
842 let mut extent = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
843 let boxed = |(a, b, _): &([f64; 2], [f64; 2], usize)| [a[0].min(b[0]), a[1].min(b[1]), a[0].max(b[0]), a[1].max(b[1])];
844 for segment in &segments {
845 let g = boxed(segment);
846 extent = [extent[0].min(g[0]), extent[1].min(g[1]), extent[2].max(g[2]), extent[3].max(g[3])];
847 }
848 let mut grid = Grid::new(extent, segments.len().max(1));
849 for (index, segment) in segments.iter().enumerate() {
850 grid.insert(boxed(segment), index);
851 }
852 UvTrim { segments, loops: rings.len(), grid, margin, images }
853 }
854
855 fn covers(&self, uv: [f64; 2], band: f64) -> Option<bool> {
858 let near = match self.margin {
859 Margin::Plus(sag) => band + sag,
860 Margin::Twice => 2.0 * band,
861 };
862 let mut odd = vec![0u32; self.loops];
863 for shift in &self.images {
864 let image = [uv[0] + shift, uv[1]];
865 let (x0, y0, x1, y1) = self.grid.range([image[0] - near, image[1] - near, image[0] + near, image[1] + near]);
866 for y in y0..=y1 {
867 for x in x0..=x1 {
868 for &index in &self.grid.buckets[y * self.grid.width + x] {
869 let (a, b, _) = self.segments[index];
870 if segment_foot(image, a, b).0 <= near {
871 return None;
872 }
873 }
874 }
875 }
876 let (column, row, _, _) = self.grid.range([image[0], image[1], image[0], image[1]]);
878 let mut inside = vec![false; self.loops];
879 for x in column..self.grid.width {
880 for &index in &self.grid.buckets[row * self.grid.width + x] {
881 let (a, b, ring) = self.segments[index];
882 if (a[1] > image[1]) == (b[1] > image[1]) {
883 continue;
884 }
885 let crossing = a[0] + (image[1] - a[1]) / (b[1] - a[1]) * (b[0] - a[0]);
886 if crossing > image[0] && self.grid.range([crossing, image[1], crossing, image[1]]).0 == x {
887 inside[ring] = !inside[ring];
888 }
889 }
890 }
891 for (ring, inside) in inside.into_iter().enumerate() {
892 odd[ring] += inside as u32;
893 }
894 }
895 Some(odd.iter().map(|hits| hits % 2).sum::<u32>() % 2 == 1)
896 }
897}
898
899#[derive(Debug, Clone)]
930struct PaperTrim {
931 segments: Vec<([f64; 2], [f64; 2], i32)>,
934 grid: Grid,
936 band: f64,
937}
938
939impl PaperTrim {
940 fn of(solid: &BrepSolid, face: &FaceRecord, paper_of_edge: &HashMap<u64, &[[f64; 2]]>, floors: &Floors) -> Option<PaperTrim> {
944 let band = CHORD_MM + 2.0 * floors.crossing_mm;
945 let gap = 2.0 * floors.crossing_mm + CHORD_MM;
946 let mut rings: Vec<Vec<[f64; 2]>> = Vec::with_capacity(face.loops.len());
947 for lp in &face.loops {
948 let mut pieces: Vec<&[[f64; 2]]> = Vec::with_capacity(lp.coedges.len());
949 for coedge in &lp.coedges {
950 match paper_of_edge.get(&coedge.edge_id) {
951 Some(paper) if paper.len() >= 2 => pieces.push(paper),
952 _ if solid.edges.iter().any(|edge| edge.id == coedge.edge_id && edge.degenerate) => {}
953 _ => return None,
954 }
955 }
956 let Some(first) = pieces.first() else { continue };
957 let ends = |piece: &[[f64; 2]]| (piece[0], piece[piece.len() - 1]);
961 let first_forward = pieces.get(1).is_none_or(|next| {
962 let (n0, n1) = ends(next);
963 let near = |p: [f64; 2]| paper_distance(p, n0).min(paper_distance(p, n1));
964 near(ends(first).1) <= near(ends(first).0)
965 });
966 let mut ring: Vec<[f64; 2]> = if first_forward { first.to_vec() } else { first.iter().rev().copied().collect() };
967 for piece in &pieces[1..] {
968 let last = ring[ring.len() - 1];
969 let (p0, p1) = ends(piece);
970 let forward = paper_distance(last, p0) <= paper_distance(last, p1);
971 if paper_distance(last, if forward { p0 } else { p1 }) > gap {
972 return None;
973 }
974 if forward {
975 ring.extend_from_slice(piece);
976 } else {
977 ring.extend(piece.iter().rev());
978 }
979 }
980 if paper_distance(ring[0], ring[ring.len() - 1]) > gap {
981 return None;
982 }
983 rings.push(ring);
984 }
985 let areas: Vec<f64> = rings
987 .iter()
988 .map(|ring| (0..ring.len()).map(|i| {
989 let (a, b) = (ring[i], ring[(i + 1) % ring.len()]);
990 a[0] * b[1] - b[0] * a[1]
991 }).sum())
992 .collect();
993 let outer = (0..rings.len()).max_by(|&i, &j| areas[i].abs().partial_cmp(&areas[j].abs()).unwrap_or(std::cmp::Ordering::Equal))?;
994 let mut segments: Vec<([f64; 2], [f64; 2], i32)> = Vec::new();
995 for (index, ring) in rings.iter().enumerate() {
996 let turned = if areas[index] < 0.0 { -1 } else { 1 };
997 let sign = if index == outer { turned } else { -turned };
998 segments.extend((0..ring.len()).map(|i| (ring[i], ring[(i + 1) % ring.len()], sign)));
999 }
1000 let mut extent = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
1001 let grown = |(a, b, _): &([f64; 2], [f64; 2], i32)| {
1002 [a[0].min(b[0]) - band, a[1].min(b[1]) - band, a[0].max(b[0]) + band, a[1].max(b[1]) + band]
1003 };
1004 for segment in &segments {
1005 let g = grown(segment);
1006 extent = [extent[0].min(g[0]), extent[1].min(g[1]), extent[2].max(g[2]), extent[3].max(g[3])];
1007 }
1008 let mut grid = Grid::new(extent, segments.len());
1009 for (index, segment) in segments.iter().enumerate() {
1010 grid.insert(grown(segment), index);
1011 }
1012 Some(PaperTrim { segments, grid, band })
1013 }
1014
1015 fn covers(&self, p: [f64; 2]) -> Option<bool> {
1019 if self.grid.at(p).iter().any(|&index| {
1022 let (a, b, _) = self.segments[index];
1023 segment_foot(p, a, b).0 <= self.band
1024 }) {
1025 return None;
1026 }
1027 let (column, row, _, _) = self.grid.range([p[0], p[1], p[0], p[1]]);
1030 let mut winding = 0;
1031 for x in column..self.grid.width {
1032 for &index in &self.grid.buckets[row * self.grid.width + x] {
1033 let (a, b, sign) = self.segments[index];
1034 if (a[1] > p[1]) == (b[1] > p[1]) {
1035 continue;
1036 }
1037 let crossing = a[0] + (p[1] - a[1]) / (b[1] - a[1]) * (b[0] - a[0]);
1038 if crossing > p[0] && self.grid.range([crossing, p[1], crossing, p[1]]).0 == x {
1039 winding += if b[1] > p[1] { sign } else { -sign };
1040 }
1041 }
1042 }
1043 Some(winding != 0)
1044 }
1045}
1046
1047fn trim_band(surface: &NurbsSurface, uv: [f64; 2], bar: f64) -> f64 {
1050 let speed = surface
1051 .derivatives(uv[0], uv[1], 1)
1052 .ok()
1053 .map(|d| d[1][0].length().max(d[0][1].length()))
1054 .unwrap_or(0.0);
1055 if speed > 1e-12 {
1056 bar / speed
1057 } else {
1058 1e-9
1059 }
1060}
1061
1062fn in_face(face: &FaceRecord, tests: Option<&TrimTests>, uv: [f64; 2], bar: f64) -> bool {
1066 let band = trim_band(&face.surface, uv, bar);
1067 if let Some(tests) = tests {
1068 if tests.reach(face).is_some_and(|reach| reach.outside(uv, band)) {
1069 return false;
1070 }
1071 if let Some(inside) = tests.uv_trim(face).and_then(|trim| trim.covers(uv, band)) {
1072 return inside;
1073 }
1074 }
1075 TRIM_QUERIES.with(|count| count.set(count.get() + 1));
1076 !matches!(
1077 parameter_point_in_face(face, Vec2 { x: uv[0], y: uv[1] }, band),
1078 Ok(PolygonClass::Outside) | Err(_)
1079 )
1080}
1081
1082#[derive(Debug, Clone)]
1096struct UvReach {
1097 lo: [f64; 2],
1098 hi: [f64; 2],
1099 period: [Option<f64>; 2],
1100}
1101
1102impl UvReach {
1103 fn of(face: &FaceRecord, lane: &ContainmentLane) -> Option<UvReach> {
1110 if !matches!(lane, ContainmentLane::Generic { sphere_chart: false } | ContainmentLane::WrappedHorizon { .. }) {
1111 return None;
1112 }
1113 let surface = &face.surface;
1114 let closed = surface.closed_directions().ok()?;
1115 let (du, dv) = (surface.domain_u().ok()?, surface.domain_v().ok()?);
1116 let mut lo = [f64::INFINITY; 2];
1117 let mut hi = [f64::NEG_INFINITY; 2];
1118 for cp in face.loops.iter().flat_map(|lp| &lp.coedges).flat_map(|coedge| &coedge.pcurve.control_points) {
1119 if !(cp.w > 0.0) {
1120 return None;
1121 }
1122 let (u, v) = (cp.x / cp.w, cp.y / cp.w);
1123 lo = [lo[0].min(u), lo[1].min(v)];
1124 hi = [hi[0].max(u), hi[1].max(v)];
1125 }
1126 if !(lo[0] <= hi[0] && lo[1] <= hi[1]) {
1127 return None;
1128 }
1129 let period = [closed.0.then_some(du[1] - du[0]), closed.1.then_some(dv[1] - dv[0])];
1130 Some(UvReach { lo, hi, period })
1131 }
1132
1133 fn outside(&self, uv: [f64; 2], band: f64) -> bool {
1134 (0..2).any(|k| {
1135 let (lo, hi) = (self.lo[k] - band, self.hi[k] + band);
1136 match self.period[k] {
1137 None => uv[k] < lo || uv[k] > hi,
1138 Some(period) if period > 0.0 && hi - lo < period => {
1139 uv[k] - ((uv[k] - lo) / period).floor() * period > hi
1140 }
1141 Some(_) => false,
1142 }
1143 })
1144 }
1145}
1146
1147fn uv_on(face: &FaceRecord, p: Vec3, bar: f64) -> Option<[f64; 2]> {
1149 let foot = project_point_to_surface(&face.surface, p).ok()?;
1150 (foot.distance <= bar).then_some([foot.u, foot.v])
1151}
1152
1153fn silhouette_candidates<'a>(
1156 face: &'a FaceRecord,
1157 tests: &TrimTests,
1158 frame: &ViewFrame,
1159 placed: &PlacedView,
1160 floors: &Floors,
1161) -> Vec<Candidate<'a>> {
1162 let surface = &face.surface;
1163 if planar(surface) {
1164 return Vec::new();
1165 }
1166 let dir = v3(frame.forward);
1167 let mut carriers: Vec<(Carrier<'a>, f64, f64, usize)> = Vec::new();
1168 match surface.analytic() {
1169 Some(AnalyticSurface::RuledRevolution { frame: rev, rho0, rho1, height }) => {
1170 let a = height * dir.dot(rev.x_axis);
1172 let b = height * dir.dot(rev.y_axis);
1173 let c = (rho1 - rho0) * dir.dot(rev.axis);
1174 let r = (a * a + b * b).sqrt();
1175 if r > 1e-12 * (height.abs() + 1.0) && c.abs() <= r {
1176 let base = b.atan2(a);
1177 let spread = (c / r).clamp(-1.0, 1.0).acos();
1178 let mut angles = vec![base + spread];
1179 if spread > 1e-12 {
1180 angles.push(base - spread);
1181 }
1182 for theta in angles {
1183 let radial = rev.x_axis.scale(theta.cos()).add(rev.y_axis.scale(theta.sin()));
1184 let start = rev.origin.add(radial.scale(*rho0));
1185 let end = rev.origin.add(rev.axis.scale(*height)).add(radial.scale(*rho1));
1186 carriers.push((Carrier::Line { a: start, b: end }, 0.0, 1.0, 1));
1187 }
1188 }
1189 }
1190 Some(AnalyticSurface::Sphere { frame: rev, radius }) => {
1191 let Ok(e1) = dir.perpendicular().and_then(|e| e.normalized()) else { return Vec::new() };
1192 let e2 = dir.cross(e1);
1193 carriers.push((
1194 Carrier::Circle { centre: rev.origin, e1: e1.scale(*radius), e2: e2.scale(*radius) },
1195 0.0,
1196 std::f64::consts::TAU,
1197 16,
1198 ));
1199 }
1200 _ => {
1201 let traced = trace_silhouette(face, dir);
1202 for uv in traced {
1203 let n = uv.len();
1204 if n < 2 {
1205 continue;
1206 }
1207 carriers.push((Carrier::Traced { surface, uv, dir }, 0.0, (n - 1) as f64, n - 1));
1208 }
1209 }
1210 }
1211 let mut out = Vec::new();
1212 for (carrier, s0, s1, initial) in carriers {
1213 let samples = sample(&carrier, s0, s1, initial, frame, placed);
1214 let spans_found = trimmed_spans(&carrier, &samples, face, tests, floors.bar);
1215 for (lo, hi) in spans_found {
1216 let piece = sample(&carrier, lo, hi, initial_between(&samples, lo, hi), frame, placed);
1217 if piece.len() < 2 || piece.paper_length() < floors.crossing_mm {
1218 continue;
1219 }
1220 let bounds = bounds_of(&piece.paper);
1221 out.push(Candidate {
1222 carrier: carrier.clone(),
1223 samples: piece,
1224 inked: true,
1225 silhouette: true,
1226 edge: None,
1227 ends: None,
1228 bounds,
1229 });
1230 }
1231 }
1232 out
1233}
1234
1235fn initial_between(samples: &Samples, lo: f64, hi: f64) -> usize {
1238 samples.params.iter().filter(|s| **s > lo && **s < hi).count() + 1
1239}
1240
1241fn trimmed_spans(carrier: &Carrier, samples: &Samples, face: &FaceRecord, tests: &TrimTests, bar: f64) -> Vec<(f64, f64)> {
1244 let (s0, s1) = (samples.params[0], *samples.params.last().unwrap());
1248 let mut stations: Vec<f64> = (0..=TRIM_STATIONS).map(|k| s0 + (s1 - s0) * k as f64 / TRIM_STATIONS as f64).collect();
1249 stations.extend(samples.params.iter().copied());
1250 stations.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1251 stations.dedup();
1252 let inside = |s: f64| -> bool {
1253 let uv = match carrier {
1254 Carrier::Traced { surface, uv, dir } => traced_at(surface, uv, *dir, s).map(|(_, uv)| uv),
1255 _ => carrier.at(s).and_then(|p| uv_on(face, p, bar)),
1256 };
1257 uv.is_some_and(|uv| in_face(face, Some(tests), uv, bar))
1258 };
1259 let flags: Vec<bool> = stations.iter().map(|s| inside(*s)).collect();
1260 let mut spans = Vec::new();
1261 let mut start: Option<f64> = None;
1262 for i in 0..flags.len() {
1263 if i > 0 && flags[i] != flags[i - 1] {
1264 let (mut lo, mut hi) = (stations[i - 1], stations[i]);
1265 let lo_flag = flags[i - 1];
1266 for _ in 0..BISECTION_STEPS {
1267 let mid = 0.5 * (lo + hi);
1268 if inside(mid) == lo_flag {
1269 lo = mid;
1270 } else {
1271 hi = mid;
1272 }
1273 }
1274 let cut = 0.5 * (lo + hi);
1275 if flags[i] {
1276 start = Some(cut);
1277 } else if let Some(s) = start.take() {
1278 spans.push((s, cut));
1279 }
1280 } else if i == 0 && flags[0] {
1281 start = Some(stations[0]);
1282 }
1283 }
1284 if let (Some(s), Some(last)) = (start, stations.last()) {
1285 spans.push((s, *last));
1286 }
1287 spans
1288}
1289
1290fn trace_silhouette(face: &FaceRecord, dir: Vec3) -> Vec<Vec<[f64; 2]>> {
1294 let surface = &face.surface;
1295 let Some(uv_box) = trim_box(face) else { return Vec::new() };
1296 let spans = |knots: &[f64], degree: usize| knots.len().saturating_sub(2 * degree + 1).max(1);
1297 let nu = (spans(&surface.knots_u, surface.degree_u) * (surface.degree_u + 1) * 2).clamp(12, 96);
1298 let nv = (spans(&surface.knots_v, surface.degree_v) * (surface.degree_v + 1) * 2).clamp(12, 96);
1299 let at = |i: usize, j: usize| {
1300 [
1301 uv_box[0] + (uv_box[2] - uv_box[0]) * i as f64 / nu as f64,
1302 uv_box[1] + (uv_box[3] - uv_box[1]) * j as f64 / nv as f64,
1303 ]
1304 };
1305 let mut values = vec![None; (nu + 1) * (nv + 1)];
1306 for j in 0..=nv {
1307 for i in 0..=nu {
1308 let p = at(i, j);
1309 values[j * (nu + 1) + i] = grazing(surface, dir, p[0], p[1]);
1310 }
1311 }
1312 let value = |i: usize, j: usize| values[j * (nu + 1) + i];
1313 let mut nodes: HashMap<(usize, usize, bool), [f64; 2]> = HashMap::new();
1315 let mut crossing = |i: usize, j: usize, horizontal: bool| -> Option<(usize, usize, bool)> {
1316 let (i2, j2) = if horizontal { (i + 1, j) } else { (i, j + 1) };
1317 let (g0, g1) = (value(i, j)?, value(i2, j2)?);
1318 if (g0 < 0.0) == (g1 < 0.0) {
1319 return None;
1320 }
1321 let key = (i, j, horizontal);
1322 if !nodes.contains_key(&key) {
1323 let (a, b) = (at(i, j), at(i2, j2));
1324 let (mut lo, mut hi, mut g_lo) = (0.0, 1.0, g0);
1325 for _ in 0..BISECTION_STEPS {
1326 let mid = 0.5 * (lo + hi);
1327 let p = [a[0] + (b[0] - a[0]) * mid, a[1] + (b[1] - a[1]) * mid];
1328 let Some(g) = grazing(surface, dir, p[0], p[1]) else { break };
1329 if (g < 0.0) == (g_lo < 0.0) {
1330 lo = mid;
1331 g_lo = g;
1332 } else {
1333 hi = mid;
1334 }
1335 }
1336 let t = 0.5 * (lo + hi);
1337 nodes.insert(key, [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
1338 }
1339 Some(key)
1340 };
1341 let mut links: Vec<[(usize, usize, bool); 2]> = Vec::new();
1342 for j in 0..nv {
1343 for i in 0..nu {
1344 let edges = [
1345 crossing(i, j, true),
1346 crossing(i + 1, j, false),
1347 crossing(i, j + 1, true),
1348 crossing(i, j, false),
1349 ];
1350 let hits: Vec<_> = edges.iter().flatten().copied().collect();
1351 match hits.len() {
1352 2 => links.push([hits[0], hits[1]]),
1353 4 => {
1354 let centre = at(i, j);
1356 let half = [
1357 centre[0] + (uv_box[2] - uv_box[0]) * 0.5 / nu as f64,
1358 centre[1] + (uv_box[3] - uv_box[1]) * 0.5 / nv as f64,
1359 ];
1360 let g = grazing(surface, dir, half[0], half[1]).unwrap_or(0.0);
1361 let corner = value(i, j).unwrap_or(0.0);
1362 if (g < 0.0) == (corner < 0.0) {
1363 links.push([hits[0], hits[1]]);
1364 links.push([hits[2], hits[3]]);
1365 } else {
1366 links.push([hits[0], hits[3]]);
1367 links.push([hits[1], hits[2]]);
1368 }
1369 }
1370 _ => {}
1371 }
1372 }
1373 }
1374 let mut incident: HashMap<(usize, usize, bool), Vec<usize>> = HashMap::new();
1376 for (index, link) in links.iter().enumerate() {
1377 incident.entry(link[0]).or_default().push(index);
1378 incident.entry(link[1]).or_default().push(index);
1379 }
1380 let mut used = vec![false; links.len()];
1381 let mut order: Vec<usize> = (0..links.len()).collect();
1382 order.sort_by_key(|&i| {
1385 let open = incident[&links[i][0]].len() == 1 || incident[&links[i][1]].len() == 1;
1386 (!open, i)
1387 });
1388 let mut out = Vec::new();
1389 for start in order {
1390 if used[start] {
1391 continue;
1392 }
1393 used[start] = true;
1394 let [a, b] = links[start];
1395 let (head, mut tail) = if incident[&a].len() == 1 { (a, b) } else { (b, a) };
1396 let mut path = vec![head, tail];
1397 while let Some(next) = incident[&tail].iter().copied().find(|i| !used[*i]) {
1398 used[next] = true;
1399 tail = if links[next][0] == tail { links[next][1] } else { links[next][0] };
1400 path.push(tail);
1401 if tail == head {
1402 break;
1403 }
1404 }
1405 out.push(path.into_iter().map(|key| nodes[&key]).collect());
1406 }
1407 out
1408}
1409
1410struct Occluder<'a> {
1416 cover: Cover<'a>,
1417 bounds: [f64; 4],
1418}
1419
1420enum Cover<'a> {
1422 Face {
1424 face: &'a FaceRecord,
1425 plane: Option<(Vec3, Vec3)>,
1427 spans: Vec<SpanHull>,
1436 tests: &'a TrimTests,
1438 trim: Option<PaperTrim>,
1440 },
1441 Triangle { corners: [Vec3; 3], normal: Vec3 },
1444}
1445
1446fn face_bounds(
1450 face: &FaceRecord,
1451 boundary: &[[f64; 4]],
1452 silhouettes: &[[f64; 4]],
1453 frame: &ViewFrame,
1454 placed: &PlacedView,
1455) -> [f64; 4] {
1456 let exact_outline = planar(&face.surface)
1457 || matches!(face.surface.analytic(), Some(AnalyticSurface::RuledRevolution { .. } | AnalyticSurface::Sphere { .. }));
1458 let mut b = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
1459 let mut grow = |c: [f64; 4]| {
1460 b[0] = b[0].min(c[0]);
1461 b[1] = b[1].min(c[1]);
1462 b[2] = b[2].max(c[2]);
1463 b[3] = b[3].max(c[3]);
1464 };
1465 if exact_outline && !boundary.is_empty() {
1466 for c in boundary.iter().chain(silhouettes) {
1467 grow(*c);
1468 }
1469 } else {
1470 for row in &face.surface.control_points {
1471 for cp in row {
1472 let w = if cp.w != 0.0 { cp.w } else { 1.0 };
1473 let (paper, _) = project(frame, placed, Vec3::new(cp.x / w, cp.y / w, cp.z / w));
1474 grow([paper[0], paper[1], paper[0], paper[1]]);
1475 }
1476 }
1477 }
1478 b
1479}
1480
1481struct SpanHull {
1483 bounds: [f64; 4],
1484 hull: Vec<[f64; 2]>,
1486 near: f64,
1488}
1489
1490impl SpanHull {
1491 fn holds(&self, p: [f64; 2], pad: f64) -> bool {
1493 let b = self.bounds;
1494 if p[0] < b[0] - pad || p[0] > b[2] + pad || p[1] < b[1] - pad || p[1] > b[3] + pad {
1495 return false;
1496 }
1497 let n = self.hull.len();
1498 if n >= 3 && (0..n).all(|k| {
1499 let (a, c) = (self.hull[k], self.hull[(k + 1) % n]);
1500 (c[0] - a[0]) * (p[1] - a[1]) - (c[1] - a[1]) * (p[0] - a[0]) >= 0.0
1501 }) {
1502 return true;
1503 }
1504 (0..n.max(1)).any(|k| segment_foot(p, self.hull[k], self.hull[(k + 1) % n]).0 <= pad)
1505 }
1506}
1507
1508fn convex_hull(mut points: Vec<[f64; 2]>) -> Vec<[f64; 2]> {
1510 points.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1511 points.dedup();
1512 if points.len() < 3 {
1513 return points;
1514 }
1515 let turn = |o: [f64; 2], a: [f64; 2], b: [f64; 2]| (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
1516 let mut hull: Vec<[f64; 2]> = Vec::with_capacity(points.len() + 1);
1517 for pass in 0..2 {
1518 let start = hull.len();
1519 let ordered: Box<dyn Iterator<Item = &[f64; 2]>> =
1520 if pass == 0 { Box::new(points.iter()) } else { Box::new(points.iter().rev()) };
1521 for &p in ordered {
1522 while hull.len() >= start + 2 && turn(hull[hull.len() - 2], hull[hull.len() - 1], p) <= 0.0 {
1523 hull.pop();
1524 }
1525 hull.push(p);
1526 }
1527 hull.pop();
1528 }
1529 hull
1530}
1531
1532fn span_hulls(face: &FaceRecord, frame: &ViewFrame, placed: &PlacedView) -> Vec<SpanHull> {
1535 let surface = &face.surface;
1536 let uv_box = trim_box(face).unwrap_or([f64::NEG_INFINITY, f64::NEG_INFINITY, f64::INFINITY, f64::INFINITY]);
1537 let spans = |knots: &[f64], degree: usize, count: usize, lo: f64, hi: f64| -> Vec<usize> {
1538 (degree..count)
1541 .filter(|&i| knots[i + 1] > knots[i] && knots[i + 1] >= lo && knots[i] <= hi)
1542 .collect()
1543 };
1544 let rows = surface.control_points.len();
1545 let columns = surface.control_points.first().map_or(0, Vec::len);
1546 let su = spans(&surface.knots_u, surface.degree_u, rows, uv_box[0], uv_box[2]);
1547 let sv = spans(&surface.knots_v, surface.degree_v, columns, uv_box[1], uv_box[3]);
1548 let mut out = Vec::with_capacity(su.len() * sv.len());
1549 for &i in &su {
1550 for &j in &sv {
1551 let mut b = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
1552 let mut near = f64::INFINITY;
1553 let mut points = Vec::with_capacity((surface.degree_u + 1) * (surface.degree_v + 1));
1554 for row in &surface.control_points[i - surface.degree_u..=i] {
1555 for cp in &row[j - surface.degree_v..=j] {
1556 let w = if cp.w != 0.0 { cp.w } else { 1.0 };
1557 let (paper, view) = project(frame, placed, Vec3::new(cp.x / w, cp.y / w, cp.z / w));
1558 b = [b[0].min(paper[0]), b[1].min(paper[1]), b[2].max(paper[0]), b[3].max(paper[1])];
1559 near = near.min(view[2]);
1560 points.push(paper);
1561 }
1562 }
1563 out.push(SpanHull { bounds: b, hull: convex_hull(points), near });
1564 }
1565 }
1566 out
1567}
1568
1569#[derive(Debug, Clone)]
1571struct Grid {
1572 origin: [f64; 2],
1573 cell: f64,
1574 width: usize,
1575 height: usize,
1576 buckets: Vec<Vec<usize>>,
1577}
1578
1579impl Grid {
1580 fn new(extent: [f64; 4], count: usize) -> Grid {
1581 let span = (extent[2] - extent[0]).max(extent[3] - extent[1]).max(1e-9);
1582 let per_side = ((count as f64).sqrt().ceil() as usize).clamp(1, 256);
1583 let cell = span / per_side as f64;
1584 let width = (((extent[2] - extent[0]) / cell).ceil() as usize).clamp(1, 512);
1585 let height = (((extent[3] - extent[1]) / cell).ceil() as usize).clamp(1, 512);
1586 Grid { origin: [extent[0], extent[1]], cell, width, height, buckets: vec![Vec::new(); width * height] }
1587 }
1588
1589 fn range(&self, b: [f64; 4]) -> (usize, usize, usize, usize) {
1590 let clamp = |v: f64, n: usize| (v.floor().max(0.0) as usize).min(n - 1);
1591 (
1592 clamp((b[0] - self.origin[0]) / self.cell, self.width),
1593 clamp((b[1] - self.origin[1]) / self.cell, self.height),
1594 clamp((b[2] - self.origin[0]) / self.cell, self.width),
1595 clamp((b[3] - self.origin[1]) / self.cell, self.height),
1596 )
1597 }
1598
1599 fn insert(&mut self, b: [f64; 4], id: usize) {
1600 let (x0, y0, x1, y1) = self.range(b);
1601 for y in y0..=y1 {
1602 for x in x0..=x1 {
1603 self.buckets[y * self.width + x].push(id);
1604 }
1605 }
1606 }
1607
1608 fn at(&self, p: [f64; 2]) -> &[usize] {
1609 let (x, y, _, _) = self.range([p[0], p[1], p[0], p[1]]);
1610 &self.buckets[y * self.width + x]
1611 }
1612}
1613
1614struct Scene<'a> {
1616 frame: ViewFrame,
1617 placed: &'a PlacedView,
1618 floors: Floors,
1619 occluders: Vec<Occluder<'a>>,
1620 grid: Grid,
1621 near: f64,
1624 clip: &'a Clip<'a>,
1625}
1626
1627impl Scene<'_> {
1628 fn hidden(&self, p: Vec3, hint: &mut Option<usize>) -> bool {
1633 let (paper, view) = project(&self.frame, self.placed, p);
1634 let bar = self.floors.bar;
1635 if let Clip::Beyond { cap } = self.clip {
1636 if view[2] > bar && inside_region(cap, paper) {
1637 return true;
1638 }
1639 }
1640 let forward = v3(self.frame.forward);
1641 let in_front = |hit: Vec3| {
1644 let depth = dot3(sub3(a3(hit), self.frame.target), self.frame.forward);
1645 depth <= view[2] - bar && !(matches!(self.clip, Clip::Beyond { .. }) && depth < -bar)
1646 };
1647 let plane_hit = |origin: Vec3, normal: Vec3| -> Option<Vec3> {
1649 let denominator = normal.dot(forward);
1650 if denominator.abs() <= GRAZING_COS {
1651 return None;
1652 }
1653 let lambda = normal.dot(origin.sub(p)) / denominator;
1654 (lambda < -bar).then(|| p.add(forward.scale(lambda)))
1655 };
1656 let reach = view[2] - self.near;
1660 let ray = std::cell::OnceCell::new();
1661 let curved_hides = |face: &FaceRecord, tests: &TrimTests| -> bool {
1662 if reach <= bar {
1663 return false;
1664 }
1665 let Some(ray) = ray.get_or_init(|| make_line(p.sub(forward.scale(reach)), p.sub(forward.scale(bar))).ok()) else {
1666 return false;
1667 };
1668 RAY_QUERIES.with(|count| count.set(count.get() + 1));
1669 let fit = KernelTolerances::default().intersection_fit;
1670 let hits = intersect_curve_surface(ray, &face.surface, fit);
1671 hits.is_ok_and(|hits| {
1672 hits.into_iter()
1673 .filter(|hit| !hit.tangential)
1674 .any(|hit| in_front(hit.point) && in_face(face, Some(tests), [hit.u, hit.v], bar))
1675 })
1676 };
1677 let flat_hides = |face: &FaceRecord, tests: &TrimTests, hit: Vec3| -> bool {
1679 uv_on(face, hit, bar).is_some_and(|uv| in_face(face, Some(tests), uv, bar))
1680 };
1681 let mut curved: Vec<usize> = Vec::new();
1689 let mut flat: Vec<(usize, Vec3)> = Vec::new();
1690 let first = hint.take();
1691 for index in first.into_iter().chain(self.grid.at(paper).iter().copied().filter(|index| Some(*index) != first)) {
1692 let occluder = &self.occluders[index];
1693 let pad = self.floors.crossing_mm + CHORD_MM;
1694 let holds = |b: &[f64; 4]| {
1695 paper[0] >= b[0] - pad && paper[0] <= b[2] + pad && paper[1] >= b[1] - pad && paper[1] <= b[3] + pad
1696 };
1697 if !holds(&occluder.bounds) {
1698 continue;
1699 }
1700 let hides = match &occluder.cover {
1701 Cover::Triangle { corners, normal } => {
1702 plane_hit(corners[0], *normal).is_some_and(|hit| in_front(hit) && in_triangle(corners, *normal, hit, bar))
1703 }
1704 Cover::Face { face, plane, spans, tests, trim } => {
1705 if !spans.iter().any(|span| span.near <= view[2] - bar && span.holds(paper, pad)) {
1706 continue;
1707 }
1708 let asked_first = Some(index) == first;
1709 match plane {
1710 Some((origin, normal)) => {
1711 let Some(hit) = plane_hit(*origin, *normal).filter(|hit| in_front(*hit)) else { continue };
1712 if trim.as_ref().and_then(|trim| trim.covers(paper)) == Some(true) {
1713 true
1714 } else if asked_first {
1715 flat_hides(face, tests, hit)
1716 } else {
1717 flat.push((index, hit));
1718 continue;
1719 }
1720 }
1721 None if asked_first => curved_hides(face, tests),
1722 None => {
1723 curved.push(index);
1724 continue;
1725 }
1726 }
1727 }
1728 };
1729 if hides {
1730 *hint = Some(index);
1731 return true;
1732 }
1733 }
1734 let face_of = |index: usize| match &self.occluders[index].cover {
1735 Cover::Face { face, tests, .. } => Some((*face, tests)),
1736 Cover::Triangle { .. } => None,
1737 };
1738 let hider = curved
1739 .into_iter()
1740 .find(|&index| face_of(index).is_some_and(|(face, tests)| curved_hides(face, tests)))
1741 .or_else(|| {
1742 flat.into_iter()
1743 .find(|&(index, hit)| face_of(index).is_some_and(|(face, tests)| flat_hides(face, tests, hit)))
1744 .map(|(index, _)| index)
1745 });
1746 if hider.is_some() {
1747 *hint = hider;
1748 }
1749 hider.is_some()
1750 }
1751}
1752
1753fn in_triangle(corners: &[Vec3; 3], normal: Vec3, hit: Vec3, bar: f64) -> bool {
1758 (0..3).all(|k| {
1759 let (a, b) = (corners[k], corners[(k + 1) % 3]);
1760 let side = b.sub(a);
1761 let length = side.length();
1762 length <= 0.0 || side.cross(hit.sub(a)).dot(normal) / length >= -bar
1763 })
1764}
1765
1766fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
1767 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
1768}
1769
1770fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
1771 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
1772}
1773
1774pub fn inside_region(region: &[Vec<[f64; 2]>], p: [f64; 2]) -> bool {
1777 let mut inside = false;
1778 for line in region {
1779 for w in line.windows(2) {
1780 let (a, b) = (w[0], w[1]);
1781 if (a[1] <= p[1]) == (b[1] <= p[1]) {
1782 continue;
1783 }
1784 let x = a[0] + (b[0] - a[0]) * (p[1] - a[1]) / (b[1] - a[1]);
1785 if x > p[0] {
1786 inside = !inside;
1787 }
1788 }
1789 }
1790 inside
1791}
1792
1793pub fn visible_edges(solids: &[&BrepSolid], frame: &ViewFrame, placed: &PlacedView, clip: &Clip) -> HlrResult {
1800 let trims: Vec<SolidTrims> = solids.iter().map(|solid| SolidTrims::of(solid)).collect();
1801 let trims: Vec<&SolidTrims> = trims.iter().collect();
1802 visible_edges_with(solids, &trims, frame, placed, clip)
1803}
1804
1805pub fn visible_edges_with(
1807 solids: &[&BrepSolid],
1808 trims: &[&SolidTrims],
1809 frame: &ViewFrame,
1810 placed: &PlacedView,
1811 clip: &Clip,
1812) -> HlrResult {
1813 TRIM_QUERIES.with(|count| count.set(0));
1814 RAY_QUERIES.with(|count| count.set(0));
1815 let floors = Floors::of(placed.scale);
1816 let mut candidates: Vec<Candidate> = Vec::new();
1817 let mut face_boxes: HashMap<(usize, usize, usize), (Vec<[f64; 4]>, Vec<[f64; 4]>)> = HashMap::new();
1819 let mut paper_trims: HashMap<(usize, usize, usize), Option<PaperTrim>> = HashMap::new();
1821 for (solid_index, solid) in solids.iter().enumerate() {
1822 let owners = edge_faces(solid);
1823 let edges = edge_candidates(solid_index, solid, frame, placed, &floors);
1824 for candidate in &edges {
1825 let Some((_, edge_id)) = candidate.edge else { continue };
1826 for (shell, face) in owners.get(&edge_id).into_iter().flatten() {
1827 face_boxes.entry((solid_index, *shell, *face)).or_default().0.push(candidate.bounds);
1828 }
1829 }
1830 let paper_of_edge: HashMap<u64, &[[f64; 2]]> =
1831 edges.iter().filter_map(|c| c.edge.map(|(_, id)| (id, c.samples.paper.as_slice()))).collect();
1832 let mut silhouettes: Vec<Candidate> = Vec::new();
1833 for (shell_index, shell) in solid.shells.iter().enumerate() {
1834 for (face_index, face) in shell.faces.iter().enumerate() {
1835 let found = silhouette_candidates(face, &trims[solid_index].faces[shell_index][face_index], frame, placed, &floors);
1836 let entry = face_boxes.entry((solid_index, shell_index, face_index)).or_default();
1837 entry.1.extend(found.iter().map(|c| c.bounds));
1838 silhouettes.extend(found);
1839 if planar(&face.surface) {
1840 paper_trims.insert((solid_index, shell_index, face_index), PaperTrim::of(solid, face, &paper_of_edge, &floors));
1841 }
1842 }
1843 }
1844 candidates.extend(edges);
1845 candidates.extend(silhouettes);
1846 }
1847 let mut occluders: Vec<Occluder> = Vec::new();
1848 let mut near = f64::INFINITY;
1849 let mut extent = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
1850 for (solid_index, solid) in solids.iter().enumerate() {
1851 for (shell_index, shell) in solid.shells.iter().enumerate() {
1852 for (face_index, face) in shell.faces.iter().enumerate() {
1853 let (boundary, silhouettes) =
1854 face_boxes.get(&(solid_index, shell_index, face_index)).cloned().unwrap_or_default();
1855 let bounds = face_bounds(face, &boundary, &silhouettes, frame, placed);
1856 if !(bounds[0] <= bounds[2]) {
1857 continue;
1858 }
1859 let spans = span_hulls(face, frame, placed);
1860 near = spans.iter().fold(near, |near, span| near.min(span.near));
1861 extent = [
1862 extent[0].min(bounds[0]),
1863 extent[1].min(bounds[1]),
1864 extent[2].max(bounds[2]),
1865 extent[3].max(bounds[3]),
1866 ];
1867 let plane = if planar(&face.surface) {
1868 let du = face.surface.domain_u().unwrap_or([0.0, 1.0]);
1869 let dv = face.surface.domain_v().unwrap_or([0.0, 1.0]);
1870 let (u, v) = (0.5 * (du[0] + du[1]), 0.5 * (dv[0] + dv[1]));
1871 match (face.surface.evaluate(u, v), face.surface.normal(u, v)) {
1872 (Ok(origin), Ok(normal)) => Some((origin, normal)),
1873 _ => None,
1874 }
1875 } else {
1876 None
1877 };
1878 let trim = paper_trims.remove(&(solid_index, shell_index, face_index)).flatten();
1879 let tests = &trims[solid_index].faces[shell_index][face_index];
1880 occluders.push(Occluder { cover: Cover::Face { face, plane, spans, tests, trim }, bounds });
1881 }
1882 }
1883 }
1884 decide(candidates, occluders, extent, near, frame, placed, clip)
1885}
1886
1887fn decide(
1891 candidates: Vec<Candidate>,
1892 occluders: Vec<Occluder>,
1893 extent: [f64; 4],
1894 near: f64,
1895 frame: &ViewFrame,
1896 placed: &PlacedView,
1897 clip: &Clip,
1898) -> HlrResult {
1899 let floors = Floors::of(placed.scale);
1900 let mut result = HlrResult::default();
1901 if occluders.is_empty() {
1902 return result;
1903 }
1904 let mut grid = Grid::new(extent, occluders.len());
1905 for (index, occluder) in occluders.iter().enumerate() {
1906 grid.insert(occluder.bounds, index);
1907 }
1908 let scene = Scene {
1909 frame: *frame,
1910 placed,
1911 floors,
1912 occluders,
1913 grid,
1914 near: near - floors.bar * 10.0 - 1.0,
1915 clip,
1916 };
1917
1918 let stations = split_parameters(&candidates, &scene);
1919 result.inked = candidates.iter().filter(|c| c.inked).count();
1920 result.silhouettes = candidates.iter().filter(|c| c.silhouette).count();
1921
1922 let mut valence: HashMap<VertexKey, usize> = HashMap::new();
1926 for candidate in candidates.iter().filter(|c| c.inked) {
1927 if let Some((start, end)) = candidate.ends {
1928 *valence.entry(start).or_default() += 1;
1929 *valence.entry(end).or_default() += 1;
1930 }
1931 }
1932 let mut runs: Vec<Run> = Vec::new();
1933 for (index, candidate) in candidates.iter().enumerate() {
1934 if !candidate.inked {
1935 continue;
1936 }
1937 let pieces = pieces_of(candidate, &stations[index], &scene);
1938 result.pieces += pieces.len();
1939 runs.extend(runs_of(candidate, &pieces, &scene));
1940 }
1941 result.runs = chain_runs(runs, &valence, floors.crossing_mm);
1942 result.trim_queries = TRIM_QUERIES.with(|count| count.get());
1943 result.ray_queries = RAY_QUERIES.with(|count| count.get());
1944 result
1945}
1946
1947#[derive(Debug, Clone, Default)]
1954pub struct MeshSolid {
1955 pub positions: Vec<[f64; 3]>,
1956 pub triangles: Vec<[u32; 3]>,
1957 pub faces: Vec<u32>,
1959 pub edges: Vec<MeshEdge>,
1961}
1962
1963#[derive(Debug, Clone, Default)]
1968pub struct MeshEdge {
1969 pub points: Vec<[f64; 3]>,
1970 pub ends: Option<(u64, u64)>,
1971}
1972
1973pub fn visible_mesh_lines(meshes: &[MeshSolid], frame: &ViewFrame, placed: &PlacedView, clip: &Clip) -> HlrResult {
1991 TRIM_QUERIES.with(|count| count.set(0));
1992 RAY_QUERIES.with(|count| count.set(0));
1993 let forward = v3(frame.forward);
1994 let mut candidates: Vec<Candidate> = Vec::new();
1995 let mut occluders: Vec<Occluder> = Vec::new();
1996 let mut near = f64::INFINITY;
1997 let mut extent = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
1998 for (solid_index, mesh) in meshes.iter().enumerate() {
1999 let points: Vec<Vec3> = mesh.positions.iter().map(|p| v3(*p)).collect();
2000 let welded = WeldedMesh::of(mesh, &points, forward);
2001 for edge in &mesh.edges {
2002 let chain: Vec<Vec3> = edge.points.iter().map(|p| v3(*p)).collect();
2003 if welded.seam(&chain) {
2004 continue;
2005 }
2006 if let Some(candidate) = polyline_candidate(chain, false, frame, placed) {
2009 candidates.push(Candidate {
2010 ends: edge.ends.map(|(start, end)| ((solid_index, start), (solid_index, end))),
2011 ..candidate
2012 });
2013 }
2014 }
2015 for chain in welded.silhouettes() {
2016 candidates.extend(polyline_candidate(chain, true, frame, placed));
2017 }
2018 for triangle in &mesh.triangles {
2019 let Some(corners) = triangle.iter().map(|&i| points.get(i as usize).copied()).collect::<Option<Vec<Vec3>>>() else {
2020 continue;
2021 };
2022 let corners = [corners[0], corners[1], corners[2]];
2023 let Ok(normal) = corners[1].sub(corners[0]).cross(corners[2].sub(corners[0])).normalized() else {
2024 continue;
2025 };
2026 let mut bounds = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
2027 for corner in corners {
2028 let (paper, view) = project(frame, placed, corner);
2029 bounds = [bounds[0].min(paper[0]), bounds[1].min(paper[1]), bounds[2].max(paper[0]), bounds[3].max(paper[1])];
2030 near = near.min(view[2]);
2031 }
2032 extent = [extent[0].min(bounds[0]), extent[1].min(bounds[1]), extent[2].max(bounds[2]), extent[3].max(bounds[3])];
2033 occluders.push(Occluder { cover: Cover::Triangle { corners, normal }, bounds });
2034 }
2035 }
2036 decide(candidates, occluders, extent, near, frame, placed, clip)
2037}
2038
2039fn polyline_candidate<'a>(chain: Vec<Vec3>, silhouette: bool, frame: &ViewFrame, placed: &PlacedView) -> Option<Candidate<'a>> {
2042 if chain.len() < 2 {
2043 return None;
2044 }
2045 let last = (chain.len() - 1) as f64;
2046 let intervals = chain.len() - 1;
2047 let carrier = Carrier::Polyline(chain);
2048 let samples = sample(&carrier, 0.0, last, intervals, frame, placed);
2049 if samples.len() < 2 || samples.paper_length() <= 0.0 {
2050 return None;
2051 }
2052 let bounds = bounds_of(&samples.paper);
2053 Some(Candidate { carrier, samples, inked: true, silhouette, edge: None, ends: None, bounds })
2054}
2055
2056struct WeldedMesh {
2060 index: HashMap<[u64; 3], u32>,
2061 position: Vec<Vec3>,
2062 owners: HashMap<[u32; 2], Vec<(u32, bool)>>,
2064}
2065
2066impl WeldedMesh {
2067 fn of(mesh: &MeshSolid, points: &[Vec3], forward: Vec3) -> WeldedMesh {
2068 let key = |p: Vec3| [p.x.to_bits(), p.y.to_bits(), p.z.to_bits()];
2069 let mut index: HashMap<[u64; 3], u32> = HashMap::new();
2070 let mut position = Vec::new();
2071 let welded: Vec<u32> = points
2072 .iter()
2073 .map(|p| {
2074 *index.entry(key(*p)).or_insert_with(|| {
2075 position.push(*p);
2076 (position.len() - 1) as u32
2077 })
2078 })
2079 .collect();
2080 let mut owners: HashMap<[u32; 2], Vec<(u32, bool)>> = HashMap::new();
2081 for (t, triangle) in mesh.triangles.iter().enumerate() {
2082 if triangle.iter().any(|&i| i as usize >= points.len()) {
2083 continue;
2084 }
2085 let [a, b, c] = [points[triangle[0] as usize], points[triangle[1] as usize], points[triangle[2] as usize]];
2086 let facing = b.sub(a).cross(c.sub(a)).dot(forward);
2087 if facing == 0.0 {
2088 continue;
2089 }
2090 let face = mesh.faces.get(t).copied().unwrap_or(u32::MAX);
2091 let w = [welded[triangle[0] as usize], welded[triangle[1] as usize], welded[triangle[2] as usize]];
2092 for k in 0..3 {
2093 let (i, j) = (w[k], w[(k + 1) % 3]);
2094 if i != j {
2095 owners.entry([i.min(j), i.max(j)]).or_default().push((face, facing < 0.0));
2096 }
2097 }
2098 }
2099 WeldedMesh { index, position, owners }
2100 }
2101
2102 fn faces_on(&self, a: Vec3, b: Vec3) -> Option<&[(u32, bool)]> {
2105 let key = |p: Vec3| [p.x.to_bits(), p.y.to_bits(), p.z.to_bits()];
2106 let (i, j) = (*self.index.get(&key(a))?, *self.index.get(&key(b))?);
2107 self.owners.get(&[i.min(j), i.max(j)]).map(Vec::as_slice)
2108 }
2109
2110 fn seam(&self, chain: &[Vec3]) -> bool {
2114 chain.windows(2).all(|w| {
2115 self.faces_on(w[0], w[1]).is_some_and(|faces| faces.len() == 2 && faces[0].0 == faces[1].0)
2116 })
2117 }
2118
2119 fn silhouettes(&self) -> Vec<Vec<Vec3>> {
2124 let mut segments: Vec<[u32; 2]> = self
2125 .owners
2126 .iter()
2127 .filter(|(_, list)| list.len() == 2 && list[0].0 == list[1].0 && list[0].1 != list[1].1)
2128 .map(|(edge, _)| *edge)
2129 .collect();
2130 segments.sort_unstable();
2132 let mut incident: HashMap<u32, Vec<usize>> = HashMap::new();
2133 for (i, segment) in segments.iter().enumerate() {
2134 incident.entry(segment[0]).or_default().push(i);
2135 incident.entry(segment[1]).or_default().push(i);
2136 }
2137 let valence = |v: u32| incident.get(&v).map_or(0, Vec::len);
2138 let mut used = vec![false; segments.len()];
2139 let mut starts: Vec<usize> = (0..segments.len()).collect();
2140 starts.sort_by_key(|&i| (valence(segments[i][0]) == 2 && valence(segments[i][1]) == 2, i));
2142 let mut out = Vec::new();
2143 for start in starts {
2144 if used[start] {
2145 continue;
2146 }
2147 used[start] = true;
2148 let segment = segments[start];
2149 let (head, mut tail) = if valence(segment[0]) != 2 { (segment[0], segment[1]) } else { (segment[1], segment[0]) };
2150 let mut path = vec![head, tail];
2151 while valence(tail) == 2 && tail != head {
2152 let Some(next) = incident[&tail].iter().copied().find(|i| !used[*i]) else { break };
2153 used[next] = true;
2154 tail = if segments[next][0] == tail { segments[next][1] } else { segments[next][0] };
2155 path.push(tail);
2156 }
2157 out.push(path.into_iter().map(|v| self.position[v as usize]).collect());
2158 }
2159 out
2160 }
2161}
2162
2163fn split_parameters(candidates: &[Candidate], scene: &Scene) -> Vec<Vec<f64>> {
2166 let floors = scene.floors;
2167 let mut splits: Vec<Vec<f64>> = vec![Vec::new(); candidates.len()];
2168 let mut extent = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
2170 let mut count = 0;
2171 for c in candidates {
2172 extent = [extent[0].min(c.bounds[0]), extent[1].min(c.bounds[1]), extent[2].max(c.bounds[2]), extent[3].max(c.bounds[3])];
2173 count += c.samples.len();
2174 }
2175 if !(extent[0] <= extent[2]) {
2176 return splits;
2177 }
2178 let mut grid = Grid::new(extent, count.max(1));
2179 let mut segments: Vec<(usize, usize)> = Vec::new();
2180 for (ci, c) in candidates.iter().enumerate() {
2181 for si in 0..c.samples.len().saturating_sub(1) {
2182 let (a, b) = (c.samples.paper[si], c.samples.paper[si + 1]);
2183 let pad = floors.crossing_mm + CHORD_MM;
2184 grid.insert(
2185 [a[0].min(b[0]) - pad, a[1].min(b[1]) - pad, a[0].max(b[0]) + pad, a[1].max(b[1]) + pad],
2186 segments.len(),
2187 );
2188 segments.push((ci, si));
2189 }
2190 }
2191 let param_at = |ci: usize, si: usize, t: f64| {
2192 let params = &candidates[ci].samples.params;
2193 params[si] + (params[si + 1] - params[si]) * t
2194 };
2195 let tolerance = floors.crossing_mm + CHORD_MM;
2196 let mut seen: std::collections::HashSet<(usize, usize)> = std::collections::HashSet::new();
2197 for bucket in &grid.buckets {
2198 for (k, &first) in bucket.iter().enumerate() {
2199 for &second in &bucket[k + 1..] {
2200 let (lo, hi) = (first.min(second), first.max(second));
2201 let ((ca, sa), (cb, sb)) = (segments[lo], segments[hi]);
2202 if ca == cb && sa.abs_diff(sb) <= 1 {
2203 continue;
2204 }
2205 if !candidates[ca].inked && !candidates[cb].inked {
2206 continue;
2207 }
2208 if !seen.insert((lo, hi)) {
2209 continue;
2210 }
2211 let pa = &candidates[ca].samples.paper;
2212 let pb = &candidates[cb].samples.paper;
2213 let vec2 = |p: [f64; 2]| Vec2 { x: p[0], y: p[1] };
2214 if let Some([ta, tb]) = segment_intersection(vec2(pa[sa]), vec2(pa[sa + 1]), vec2(pb[sb]), vec2(pb[sb + 1]), tolerance) {
2215 splits[ca].push(param_at(ca, sa, ta));
2216 splits[cb].push(param_at(cb, sb, tb));
2217 }
2218 for (from, from_seg, onto, onto_seg) in [(ca, sa, cb, sb), (cb, sb, ca, sa)] {
2221 let fp = &candidates[from].samples.paper;
2222 let op = &candidates[onto].samples.paper;
2223 let last = fp.len() - 1;
2224 for (end_seg, end) in [(0usize, fp[0]), (last - 1, fp[last])] {
2225 if from_seg != end_seg {
2226 continue;
2227 }
2228 let (distance, t) = segment_foot(end, op[onto_seg], op[onto_seg + 1]);
2229 if distance <= tolerance {
2230 splits[onto].push(param_at(onto, onto_seg, t));
2231 }
2232 }
2233 }
2234 }
2235 }
2236 }
2237 for (ci, c) in candidates.iter().enumerate() {
2239 if !c.inked {
2240 continue;
2241 }
2242 let side = |view: [f64; 3]| -> f64 {
2243 match scene.clip {
2244 Clip::Whole => 1.0,
2245 Clip::Beyond { .. } => view[2],
2246 Clip::Circle { radius } => radius - (view[0] * view[0] + view[1] * view[1]).sqrt(),
2247 }
2248 };
2249 if matches!(scene.clip, Clip::Whole) {
2250 continue;
2251 }
2252 for i in 1..c.samples.len() {
2253 let (g0, g1) = (side(c.samples.view[i - 1]), side(c.samples.view[i]));
2254 if (g0 < 0.0) == (g1 < 0.0) {
2255 continue;
2256 }
2257 let (mut lo, mut hi) = (c.samples.params[i - 1], c.samples.params[i]);
2258 for _ in 0..BISECTION_STEPS {
2259 let mid = 0.5 * (lo + hi);
2260 let Some(p) = c.carrier.at(mid) else { break };
2261 let g = side(scene.frame.to_view(a3(p)));
2262 if (g < 0.0) == (g0 < 0.0) {
2263 lo = mid;
2264 } else {
2265 hi = mid;
2266 }
2267 }
2268 splits[ci].push(0.5 * (lo + hi));
2269 }
2270 }
2271 for list in &mut splits {
2272 list.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
2273 }
2274 splits
2275}
2276
2277struct Piece {
2279 lo: f64,
2280 hi: f64,
2281 visible: bool,
2282}
2283
2284fn pieces_of(candidate: &Candidate, splits: &[f64], scene: &Scene) -> Vec<Piece> {
2287 let samples = &candidate.samples;
2288 let (s0, s1) = (samples.params[0], *samples.params.last().unwrap());
2289 let mut cuts = vec![s0];
2290 let paper_at = |s: f64| candidate.carrier.at(s).map(|p| project(&scene.frame, scene.placed, p).0);
2291 let mut last_paper = samples.paper[0];
2292 for &s in splits {
2293 if s <= s0 || s >= s1 {
2294 continue;
2295 }
2296 let Some(p) = paper_at(s) else { continue };
2297 if paper_distance(p, last_paper) < scene.floors.crossing_mm {
2298 continue;
2299 }
2300 cuts.push(s);
2301 last_paper = p;
2302 }
2303 let end_paper = *samples.paper.last().unwrap();
2304 if cuts.len() > 1 && paper_distance(last_paper, end_paper) < scene.floors.crossing_mm {
2305 cuts.pop();
2306 }
2307 cuts.push(s1);
2308 let mut pieces = Vec::with_capacity(cuts.len() - 1);
2309 let mut hint = None;
2310 for w in cuts.windows(2) {
2311 let (lo, hi) = (w[0], w[1]);
2312 let Some(mid) = candidate.carrier.at(0.5 * (lo + hi)) else { continue };
2313 let view = scene.frame.to_view(a3(mid));
2314 let kept = match scene.clip {
2315 Clip::Whole => true,
2316 Clip::Beyond { .. } => view[2] >= -scene.floors.bar,
2317 Clip::Circle { radius } => (view[0] * view[0] + view[1] * view[1]).sqrt() <= radius + scene.floors.bar,
2318 };
2319 let visible = kept && !scene.hidden(mid, &mut hint);
2320 pieces.push(Piece { lo, hi, visible });
2321 }
2322 pieces
2323}
2324
2325struct Run {
2328 points: Vec<[f64; 2]>,
2329 start: Option<VertexKey>,
2330 end: Option<VertexKey>,
2331}
2332
2333fn runs_of(candidate: &Candidate, pieces: &[Piece], scene: &Scene) -> Vec<Run> {
2337 let samples = &candidate.samples;
2338 let closed = samples.len() > 2
2339 && paper_distance(samples.paper[0], *samples.paper.last().unwrap()) < scene.floors.crossing_mm
2340 && samples.points[0].sub(*samples.points.last().unwrap()).length() <= scene.floors.bar;
2341 let mut spans: Vec<(f64, f64, bool, bool)> = Vec::new(); let mut i = 0;
2343 while i < pieces.len() {
2344 if !pieces[i].visible {
2345 i += 1;
2346 continue;
2347 }
2348 let first = i;
2349 while i < pieces.len() && pieces[i].visible {
2350 i += 1;
2351 }
2352 spans.push((pieces[first].lo, pieces[i - 1].hi, first == 0, i == pieces.len()));
2353 }
2354 let (s0, s1) = (samples.params[0], *samples.params.last().unwrap());
2355 let mut wrapped: Option<(f64, f64)> = None;
2357 if closed && spans.len() >= 2 && spans[0].2 && spans.last().unwrap().3 {
2358 let head = spans.remove(0);
2359 let tail = spans.pop().unwrap();
2360 wrapped = Some((tail.0, head.1));
2361 }
2362 let point_at = |s: f64| candidate.carrier.at(s).map(|p| project(&scene.frame, scene.placed, p).0);
2363 let mut out = Vec::new();
2364 let mut emit = |pieces: &[(f64, f64)], start: Option<VertexKey>, end: Option<VertexKey>| {
2365 let mut points = Vec::new();
2366 for (k, &(lo, hi)) in pieces.iter().enumerate() {
2367 if k == 0 {
2368 if let Some(p) = point_at(lo) {
2369 points.push(p);
2370 }
2371 }
2372 for (index, s) in samples.params.iter().enumerate() {
2373 if *s > lo && *s < hi {
2374 points.push(samples.paper[index]);
2375 }
2376 }
2377 if let Some(p) = point_at(hi) {
2378 points.push(p);
2379 }
2380 }
2381 if points.len() >= 2 {
2382 out.push(Run { points, start, end });
2383 }
2384 };
2385 if let Some((lo, hi)) = wrapped {
2386 emit(&[(lo, s1), (s0, hi)], None, None);
2387 }
2388 for (lo, hi, at_start, at_end) in spans {
2389 let ends = candidate.ends;
2390 let whole_closed = closed && at_start && at_end;
2391 emit(
2392 &[(lo, hi)],
2393 if at_start && !whole_closed { ends.map(|e| e.0) } else { None },
2394 if at_end && !whole_closed { ends.map(|e| e.1) } else { None },
2395 );
2396 }
2397 out
2398}
2399
2400fn chain_runs(runs: Vec<Run>, valence: &HashMap<VertexKey, usize>, floor: f64) -> Vec<Vec<[f64; 2]>> {
2403 let mut at_vertex: HashMap<VertexKey, Vec<(usize, bool)>> = HashMap::new();
2404 for (index, run) in runs.iter().enumerate() {
2405 if let Some(v) = run.start {
2406 at_vertex.entry(v).or_default().push((index, true));
2407 }
2408 if let Some(v) = run.end {
2409 at_vertex.entry(v).or_default().push((index, false));
2410 }
2411 }
2412 let mut next: Vec<[Option<(usize, bool)>; 2]> = vec![[None, None]; runs.len()];
2414 for (vertex, list) in &at_vertex {
2415 if valence.get(vertex) != Some(&2) || list.len() != 2 || list[0].0 == list[1].0 {
2416 continue;
2417 }
2418 let ((a, a_start), (b, b_start)) = (list[0], list[1]);
2419 let (pa, pb) = (
2420 if a_start { runs[a].points[0] } else { *runs[a].points.last().unwrap() },
2421 if b_start { runs[b].points[0] } else { *runs[b].points.last().unwrap() },
2422 );
2423 if paper_distance(pa, pb) > floor + CHORD_MM {
2424 continue;
2425 }
2426 next[a][if a_start { 0 } else { 1 }] = Some((b, b_start));
2427 next[b][if b_start { 0 } else { 1 }] = Some((a, a_start));
2428 }
2429 let mut used = vec![false; runs.len()];
2430 let mut out = Vec::new();
2431 let mut order: Vec<usize> = (0..runs.len()).collect();
2433 order.sort_by_key(|&i| (next[i][0].is_some() && next[i][1].is_some(), i));
2434 for start in order {
2435 if used[start] {
2436 continue;
2437 }
2438 used[start] = true;
2439 let mut points = runs[start].points.clone();
2442 if next[start][0].is_some() && next[start][1].is_none() {
2443 points.reverse();
2444 let mut cursor = next[start][0];
2445 extend_chain(&runs, &next, &mut used, &mut points, &mut cursor);
2446 } else {
2447 let mut cursor = next[start][1];
2448 extend_chain(&runs, &next, &mut used, &mut points, &mut cursor);
2449 }
2450 out.push(points);
2451 }
2452 out
2453}
2454
2455fn extend_chain(
2456 runs: &[Run],
2457 next: &[[Option<(usize, bool)>; 2]],
2458 used: &mut [bool],
2459 points: &mut Vec<[f64; 2]>,
2460 cursor: &mut Option<(usize, bool)>,
2461) {
2462 while let Some((run, enter_at_start)) = *cursor {
2463 if used[run] {
2464 break;
2465 }
2466 used[run] = true;
2467 let mut piece = runs[run].points.clone();
2468 if !enter_at_start {
2469 piece.reverse();
2470 }
2471 points.extend(piece.into_iter().skip(1));
2472 *cursor = next[run][if enter_at_start { 1 } else { 0 }];
2473 }
2474}
2475
2476pub type CutCurve = Vec<[f64; 2]>;
2484
2485pub fn section_curves(solids: &[&BrepSolid], frame: &ViewFrame, placed: &PlacedView) -> Vec<CutCurve> {
2493 let trims: Vec<SolidTrims> = solids.iter().map(|solid| SolidTrims::of(solid)).collect();
2494 let trims: Vec<&SolidTrims> = trims.iter().collect();
2495 section_curves_with(solids, &trims, frame, placed)
2496}
2497
2498pub fn section_curves_with(solids: &[&BrepSolid], trims: &[&SolidTrims], frame: &ViewFrame, placed: &PlacedView) -> Vec<CutCurve> {
2500 let floors = Floors::of(placed.scale);
2501 let tolerances = KernelTolerances::default();
2502 let normal = v3(frame.forward);
2503 let target = v3(frame.target);
2504 let mut out = Vec::new();
2505 for (solid, trims) in solids.iter().zip(trims) {
2506 let mut reach: f64 = 1.0;
2507 for shell in &solid.shells {
2508 for face in &shell.faces {
2509 for row in &face.surface.control_points {
2510 for cp in row {
2511 let w = if cp.w != 0.0 { cp.w } else { 1.0 };
2512 reach = reach.max(Vec3::new(cp.x / w, cp.y / w, cp.z / w).sub(target).length());
2513 }
2514 }
2515 }
2516 }
2517 let reach = reach * 1.5;
2518 let (right, up) = (v3(frame.right), v3(frame.up));
2519 let Ok(cutting) = make_plane(target.sub(right.scale(reach)).sub(up.scale(reach)), right, up, 2.0 * reach, 2.0 * reach)
2520 else {
2521 continue;
2522 };
2523 for (shell, shell_trims) in solid.shells.iter().zip(&trims.faces) {
2524 for (face, tests) in shell.faces.iter().zip(shell_trims) {
2525 for carrier in section_carriers(&cutting, face, normal, target, reach, &tolerances) {
2526 for curve in trim_section(solid, face, Some(tests), carrier, &cutting, frame, placed, &floors) {
2527 out.push(curve);
2528 }
2529 }
2530 }
2531 }
2532 }
2533 out
2534}
2535
2536fn section_carriers<'a>(
2538 cutting: &NurbsSurface,
2539 face: &'a FaceRecord,
2540 normal: Vec3,
2541 target: Vec3,
2542 reach: f64,
2543 tolerances: &KernelTolerances,
2544) -> Vec<(Carrier<'a>, f64, f64)> {
2545 let surface = &face.surface;
2546 if planar(surface) {
2547 let du = surface.domain_u().unwrap_or([0.0, 1.0]);
2548 let dv = surface.domain_v().unwrap_or([0.0, 1.0]);
2549 let (u, v) = (0.5 * (du[0] + du[1]), 0.5 * (dv[0] + dv[1]));
2550 let (Ok(origin), Ok(face_normal)) = (surface.evaluate(u, v), surface.normal(u, v)) else { return Vec::new() };
2551 let direction = normal.cross(face_normal);
2552 let Ok(direction) = direction.normalized() else { return Vec::new() }; let (h1, h2) = (normal.dot(target), face_normal.dot(origin));
2556 let c = normal.dot(face_normal);
2557 let det = 1.0 - c * c;
2558 if det.abs() < 1e-18 {
2559 return Vec::new();
2560 }
2561 let a = (h1 - h2 * c) / det;
2562 let b = (h2 - h1 * c) / det;
2563 let point = normal.scale(a).add(face_normal.scale(b));
2564 let point = point.add(direction.scale(target.sub(point).dot(direction)));
2565 return vec![(
2566 Carrier::Line { a: point.sub(direction.scale(reach)), b: point.add(direction.scale(reach)) },
2567 0.0,
2568 1.0,
2569 )];
2570 }
2571 if let Some(curves) = intersect_analytic_pair(cutting, surface, tolerances.intersection_fit) {
2572 return curves
2573 .into_iter()
2574 .filter_map(|curve| {
2575 let [t0, t1] = curve.domain().ok()?;
2576 Some((Carrier::Curve(curve), t0, t1))
2577 })
2578 .collect();
2579 }
2580 let options = SurfaceIntersectionOptions { tolerance: tolerances.intersection_fit, ..Default::default() };
2581 match intersect_surfaces(cutting, surface, &options) {
2582 Ok(curves) => curves
2583 .into_iter()
2584 .filter(|curve| curve.points.len() >= 2)
2585 .map(|curve| {
2586 let mut points = curve.points;
2587 if curve.closed && points.first().map(|p| p.sub(*points.last().unwrap()).length()) > Some(0.0) {
2588 points.push(points[0]);
2589 }
2590 let last = (points.len() - 1) as f64;
2591 (Carrier::Polyline(points), 0.0, last)
2592 })
2593 .collect(),
2594 Err(_) => Vec::new(),
2595 }
2596}
2597
2598fn trim_section(
2603 solid: &BrepSolid,
2604 face: &FaceRecord,
2605 tests: Option<&TrimTests>,
2606 carrier: (Carrier, f64, f64),
2607 cutting: &NurbsSurface,
2608 frame: &ViewFrame,
2609 placed: &PlacedView,
2610 floors: &Floors,
2611) -> Vec<CutCurve> {
2612 let (carrier, s0, s1) = carrier;
2613 let tolerances = KernelTolerances::default();
2614 let initial = match &carrier {
2615 Carrier::Polyline(points) => points.len().saturating_sub(1).max(1),
2616 Carrier::Curve(curve) => curve_initial(curve),
2617 _ => 1,
2618 };
2619 let base = sample(&carrier, s0, s1, initial, frame, placed);
2620 if base.len() < 2 {
2621 return Vec::new();
2622 }
2623 let mut cuts: Vec<f64> = vec![s0, s1];
2625 for lp in &face.loops {
2626 for coedge in &lp.coedges {
2627 let Some(edge) = solid.edges.iter().find(|e| e.id == coedge.edge_id) else { continue };
2628 let Ok(hits) = intersect_curve_surface(&edge.curve, cutting, tolerances.intersection_fit) else { continue };
2629 for hit in hits {
2630 if hit.t < edge.t0.min(edge.t1) - 1e-12 || hit.t > edge.t0.max(edge.t1) + 1e-12 {
2631 continue;
2632 }
2633 if let Some(s) = nearest_parameter(&carrier, &base, hit.point) {
2634 cuts.push(s);
2635 }
2636 }
2637 }
2638 }
2639 cuts.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
2640 let mut out = Vec::new();
2641 for w in cuts.windows(2) {
2642 let (lo, hi) = (w[0], w[1]);
2643 if hi - lo <= 0.0 {
2644 continue;
2645 }
2646 let Some(mid) = carrier.at(0.5 * (lo + hi)) else { continue };
2647 let Some(uv) = uv_on(face, mid, floors.bar) else { continue };
2648 if !in_face(face, tests, uv, floors.bar) {
2649 continue;
2650 }
2651 let piece = sample(&carrier, lo, hi, initial_between(&base, lo, hi), frame, placed);
2652 if piece.len() < 2 || piece.paper_length() < floors.crossing_mm {
2653 continue;
2654 }
2655 let mut paper = piece.paper;
2656 let step = (hi - lo) * 1e-4;
2660 let tangent = match (carrier.at(0.5 * (lo + hi) + step), carrier.at(0.5 * (lo + hi) - step)) {
2661 (Some(a), Some(b)) => a.sub(b),
2662 _ => piece.points[piece.points.len() - 1].sub(piece.points[0]),
2663 };
2664 if let Some(n_face) = outward_normal(face, mid) {
2665 let eye = v3(frame.forward).scale(-1.0);
2666 if tangent.dot(eye.cross(n_face)) < 0.0 {
2667 paper.reverse();
2668 }
2669 }
2670 out.push(paper);
2671 }
2672 out
2673}
2674
2675fn nearest_parameter(carrier: &Carrier, samples: &Samples, p: Vec3) -> Option<f64> {
2677 if let Carrier::Edge(curve) = carrier {
2678 return project_point_to_curve(curve, p).ok().map(|foot| foot.u);
2679 }
2680 let mut best: Option<(f64, f64)> = None;
2682 for i in 0..samples.len().saturating_sub(1) {
2683 let (a, b) = (samples.points[i], samples.points[i + 1]);
2684 let d = b.sub(a);
2685 let length2 = d.dot(d);
2686 let t = if length2 > 0.0 { (p.sub(a).dot(d) / length2).clamp(0.0, 1.0) } else { 0.0 };
2687 let distance = p.sub(a.add(d.scale(t))).length();
2688 let s = samples.params[i] + (samples.params[i + 1] - samples.params[i]) * t;
2689 if best.is_none_or(|(bd, _)| distance < bd) {
2690 best = Some((distance, s));
2691 }
2692 }
2693 best.map(|(_, s)| s)
2694}
2695