1use crate::{KernelRefusal, KernelStage, OrRefuse};
2use crate::fit::solve_dense;
3use crate::topology::{BrepSolid, CoedgeRecord, EdgeRecord, FaceRecord, LoopRecord, VertexRecord};
4use crate::{
5 interpolate_curve, measure_edge_against_pcurve_image,
6 measure_surface_fit_against_pointwise_offset, offset_construction_band, solid_model_scale,
7 vertex_endpoint_gap, vertex_tolerance_from_edges, KnotVector, MeasuredTolerance, NurbsCurve,
8 NurbsSurface, OffsetEvaluator, OffsetNormal, Vec2, Vec3, Vec4,
9};
10use rustc_hash::FxHashMap as HashMap;
11use serde::Serialize;
12
13fn domains(surface: &NurbsSurface) -> Result<([f64; 2], [f64; 2]), KernelRefusal> {
14 Ok((
15 KnotVector::new(surface.knots_u.clone(), surface.degree_u).or_refuse(KernelStage::Refine, "new")?.domain(),
16 KnotVector::new(surface.knots_v.clone(), surface.degree_v).or_refuse(KernelStage::Refine, "new")?.domain(),
17 ))
18}
19
20fn stable_face_normal(face: &FaceRecord, u: f64, v: f64) -> Result<Vec3, KernelRefusal> {
24 OffsetEvaluator::new(
25 "offset_surface",
26 &face.surface,
27 OffsetNormal::FaceStable {
28 same_sense: face.same_sense,
29 },
30 )
31 .normal(u, v)
32 .or_refuse(KernelStage::Refine, "normal")
33}
34
35fn greville_parameters(knots: &KnotVector) -> Vec<f64> {
36 let mut parameters = (0..knots.control_point_count())
37 .map(|index| {
38 knots.knots[index + 1..=index + knots.degree]
39 .iter()
40 .sum::<f64>()
41 / knots.degree as f64
42 })
43 .collect::<Vec<_>>();
44 let domain = knots.domain();
45 parameters[0] = domain[0];
46 *parameters.last_mut().unwrap() = domain[1];
47 parameters
48}
49
50fn collocation_matrix(knots: &KnotVector, parameters: &[f64], weights: &[f64]) -> Vec<Vec<f64>> {
54 parameters
55 .iter()
56 .map(|parameter| {
57 let mut row = vec![0.0; knots.control_point_count()];
58 let span = knots.find_span(*parameter);
59 for (offset, value) in knots
60 .basis_functions(span, *parameter)
61 .into_iter()
62 .enumerate()
63 {
64 let index = span - knots.degree + offset;
65 row[index] = value * weights[index];
66 }
67 let denominator: f64 = row.iter().sum();
68 if denominator.abs() > 0.0 {
69 for value in &mut row {
70 *value /= denominator;
71 }
72 }
73 row
74 })
75 .collect()
76}
77
78fn separable_weights(weights: &[Vec<f64>]) -> Option<(Vec<f64>, Vec<f64>)> {
82 let first_row = weights.first()?;
83 let anchor = *first_row.first()?;
84 if anchor.abs() <= 1e-12 {
85 return None;
86 }
87 let a: Vec<f64> = weights.iter().map(|row| row[0]).collect();
88 let b: Vec<f64> = first_row.iter().map(|w| w / anchor).collect();
89 for (i, row) in weights.iter().enumerate() {
90 for (j, &w) in row.iter().enumerate() {
91 if (w - a[i] * b[j]).abs() > 1e-10 * (1.0 + w.abs()) {
92 return None;
93 }
94 }
95 }
96 Some((a, b))
97}
98
99fn interpolate_tensor(
105 knot_u: &KnotVector,
106 knot_v: &KnotVector,
107 parameters_u: &[f64],
108 parameters_v: &[f64],
109 samples: &[Vec<Vec3>],
110 weights: &[Vec<f64>],
111) -> Result<Vec<Vec<Vec4>>, KernelRefusal> {
112 let count_u = parameters_u.len();
113 let count_v = parameters_v.len();
114 if let Some((weights_u, weights_v)) = separable_weights(weights) {
115 let matrix_u = collocation_matrix(knot_u, parameters_u, &weights_u);
116 let matrix_v = collocation_matrix(knot_v, parameters_v, &weights_v);
117 let mut intermediate = vec![vec![Vec3::default(); count_v]; count_u];
118 for column in 0..count_v {
119 let solve_axis = |axis: fn(Vec3) -> f64| {
120 solve_dense(
121 matrix_u.clone(),
122 samples.iter().map(|row| axis(row[column])).collect(),
123 )
124 };
125 let x = solve_axis(|point| point.x).or_refuse(KernelStage::Refine, "solve_axis")?;
126 let y = solve_axis(|point| point.y).or_refuse(KernelStage::Refine, "solve_axis")?;
127 let z = solve_axis(|point| point.z).or_refuse(KernelStage::Refine, "solve_axis")?;
128 for row in 0..count_u {
129 intermediate[row][column] = Vec3::new(x[row], y[row], z[row]);
130 }
131 }
132 let mut controls = vec![vec![Vec4::from_point(Vec3::default(), 1.0); count_v]; count_u];
133 for row in 0..count_u {
134 let solve_axis = |axis: fn(Vec3) -> f64| {
135 solve_dense(
136 matrix_v.clone(),
137 intermediate[row].iter().copied().map(axis).collect(),
138 )
139 };
140 let x = solve_axis(|point| point.x).or_refuse(KernelStage::Refine, "solve_axis")?;
141 let y = solve_axis(|point| point.y).or_refuse(KernelStage::Refine, "solve_axis")?;
142 let z = solve_axis(|point| point.z).or_refuse(KernelStage::Refine, "solve_axis")?;
143 for column in 0..count_v {
144 controls[row][column] = Vec4::from_point(
145 Vec3::new(x[column], y[column], z[column]),
146 weights[row][column],
147 );
148 }
149 }
150 return Ok(controls);
151 }
152
153 let unknowns = count_u * count_v;
156 let mut matrix = vec![vec![0.0; unknowns]; unknowns];
157 for (k, &u) in parameters_u.iter().enumerate() {
158 let span_u = knot_u.find_span(u);
159 let basis_u = knot_u.basis_functions(span_u, u);
160 for (l, &v) in parameters_v.iter().enumerate() {
161 let span_v = knot_v.find_span(v);
162 let basis_v = knot_v.basis_functions(span_v, v);
163 let row = &mut matrix[k * count_v + l];
164 let mut denominator = 0.0;
165 for (du, value_u) in basis_u.iter().enumerate() {
166 let i = span_u - knot_u.degree + du;
167 for (dv, value_v) in basis_v.iter().enumerate() {
168 let j = span_v - knot_v.degree + dv;
169 let entry = value_u * value_v * weights[i][j];
170 row[i * count_v + j] = entry;
171 denominator += entry;
172 }
173 }
174 if denominator.abs() > 0.0 {
175 for value in row.iter_mut() {
176 *value /= denominator;
177 }
178 }
179 }
180 }
181 let solve_axis = |axis: fn(Vec3) -> f64| {
182 solve_dense(
183 matrix.clone(),
184 samples
185 .iter()
186 .flat_map(|row| row.iter().copied().map(axis))
187 .collect(),
188 )
189 };
190 let x = solve_axis(|point| point.x).or_refuse(KernelStage::Refine, "solve_axis")?;
191 let y = solve_axis(|point| point.y).or_refuse(KernelStage::Refine, "solve_axis")?;
192 let z = solve_axis(|point| point.z).or_refuse(KernelStage::Refine, "solve_axis")?;
193 let mut controls = vec![vec![Vec4::from_point(Vec3::default(), 1.0); count_v]; count_u];
194 for row in 0..count_u {
195 for column in 0..count_v {
196 let index = row * count_v + column;
197 controls[row][column] = Vec4::from_point(
198 Vec3::new(x[index], y[index], z[index]),
199 weights[row][column],
200 );
201 }
202 }
203 Ok(controls)
204}
205
206#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
216pub enum OffsetSurfaceLane {
217 Affine,
221 Analytic,
227 #[default]
231 Fit,
232 Reparameterised,
238}
239
240#[derive(Clone, Debug)]
243pub struct MeasuredOffsetSurface {
244 pub surface: NurbsSurface,
245 pub lane: OffsetSurfaceLane,
246 pub fit: Option<MeasuredTolerance>,
254}
255
256#[derive(Clone, Copy, Debug, PartialEq)]
268pub struct CarrierExtension {
269 pub u_min: f64,
270 pub u_max: f64,
271 pub v_min: f64,
272 pub v_max: f64,
273}
274
275impl CarrierExtension {
276 pub const NONE: CarrierExtension = CarrierExtension {
277 u_min: 0.0,
278 u_max: 0.0,
279 v_min: 0.0,
280 v_max: 0.0,
281 };
282
283 pub fn uniform(amount: f64) -> CarrierExtension {
284 CarrierExtension {
285 u_min: amount,
286 u_max: amount,
287 v_min: amount,
288 v_max: amount,
289 }
290 }
291
292 pub fn is_active(&self) -> bool {
293 self.u_min > 0.0 || self.u_max > 0.0 || self.v_min > 0.0 || self.v_max > 0.0
294 }
295
296 fn extends_v(&self) -> bool {
297 self.v_min > 0.0 || self.v_max > 0.0
298 }
299}
300
301fn trim_domain_fractions(
305 face: &FaceRecord,
306 [u0, u1]: [f64; 2],
307 [v0, v1]: [f64; 2],
308) -> Result<([f64; 2], [f64; 2]), KernelRefusal> {
309 let mut low = Vec2 {
310 x: f64::INFINITY,
311 y: f64::INFINITY,
312 };
313 let mut high = Vec2 {
314 x: f64::NEG_INFINITY,
315 y: f64::NEG_INFINITY,
316 };
317 for coedge in face
318 .loops
319 .iter()
320 .flat_map(|loop_record| &loop_record.coedges)
321 {
322 let [p0, p1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
323 for sample in 0..=24 {
324 let uv = coedge
325 .pcurve
326 .evaluate(p0 + (p1 - p0) * sample as f64 / 24.0).or_refuse(KernelStage::Refine, "evaluate")?;
327 low.x = low.x.min(uv.x);
328 low.y = low.y.min(uv.y);
329 high.x = high.x.max(uv.x);
330 high.y = high.y.max(uv.y);
331 }
332 }
333 let fraction = |value: f64, start: f64, end: f64| {
334 let span = end - start;
335 if span.abs() <= f64::EPSILON || !value.is_finite() {
336 return None;
337 }
338 Some(((value - start) / span).clamp(0.0, 1.0))
339 };
340 let u = match (fraction(low.x, u0, u1), fraction(high.x, u0, u1)) {
341 (Some(a), Some(b)) if b - a > 1e-6 => [a, b],
342 _ => [0.0, 1.0],
343 };
344 let v = match (fraction(low.y, v0, v1), fraction(high.y, v0, v1)) {
345 (Some(a), Some(b)) if b - a > 1e-6 => [a, b],
346 _ => [0.0, 1.0],
347 };
348 Ok((u, v))
349}
350
351fn domain_end_moves(grow_min: f64, grow_max: f64, [ta, tb]: [f64; 2]) -> (f64, f64) {
361 let span = tb - ta;
362 if span <= 1e-6 {
363 return (grow_min, grow_max);
364 }
365 let rate = (grow_min + grow_max) / span;
366 let back = grow_min + ta * rate;
367 let forward = (1.0 - ta) * rate - grow_min;
368 (back.max(0.0), forward.max(0.0))
369}
370
371pub fn offset_surface(
375 face: &FaceRecord,
376 distance: f64,
377 planar_extension: f64,
378) -> Result<NurbsSurface, KernelRefusal> {
379 offset_surface_with_lane(face, distance, &CarrierExtension::uniform(planar_extension))
380 .map(|(surface, _)| surface)
381}
382
383pub fn offset_surface_measured(
392 face: &FaceRecord,
393 distance: f64,
394 planar_extension: f64,
395 band: f64,
396) -> Result<MeasuredOffsetSurface, KernelRefusal> {
397 offset_surface_measured_sided(
398 face,
399 distance,
400 &CarrierExtension::uniform(planar_extension),
401 band,
402 )
403}
404
405pub fn offset_surface_measured_sided(
407 face: &FaceRecord,
408 distance: f64,
409 extension: &CarrierExtension,
410 band: f64,
411) -> Result<MeasuredOffsetSurface, KernelRefusal> {
412 let (surface, lane, extended) = offset_surface_constructed(face, distance, extension)?;
413 let fit = match lane {
414 OffsetSurfaceLane::Affine => Some(MeasuredTolerance::exact(band)),
415 OffsetSurfaceLane::Analytic | OffsetSurfaceLane::Fit => {
420 Some(measure_surface_fit_against_pointwise_offset(
421 extended.as_ref().unwrap_or(&face.surface),
422 face.same_sense,
423 &surface,
424 distance,
425 band,
426 )?)
427 }
428 OffsetSurfaceLane::Reparameterised => None,
429 };
430 Ok(MeasuredOffsetSurface { surface, lane, fit })
431}
432
433fn sphere_offset_surface(face: &FaceRecord, distance: f64) -> Result<Option<NurbsSurface>, KernelRefusal> {
462 if std::env::var_os("BREP_NO_ANALYTIC_OFFSET").is_some() {
466 return Ok(None);
467 }
468 let source = &face.surface;
469 let Some((centre, radius)) = source.analytic().and_then(|shape| shape.sphere_geometry()) else {
470 return Ok(None);
471 };
472 let ([u0, u1], [v0, v1]) = domains(source)?;
473 let Ok(sample) = OffsetEvaluator::new(
480 "offset_surface",
481 source,
482 OffsetNormal::FaceStable {
483 same_sense: face.same_sense,
484 },
485 )
486 .at((u0 + u1) / 2.0, (v0 + v1) / 2.0, -distance)
487 else {
488 return Ok(None);
489 };
490 let source_radial = sample.source.sub(centre);
491 let offset_radial = sample.point.sub(centre);
492 let offset_radius = offset_radial.length();
493 let scale = radius.max(1.0);
494 if radius <= 1e-12
495 || offset_radius <= 1e-9 * scale
496 || offset_radial.dot(source_radial) <= 0.0
499 || ((offset_radius - radius).abs() - distance.abs()).abs() > 1e-9 * scale
502 {
503 return Ok(None);
504 }
505 let factor = offset_radius / radius;
506 let Ok(controls) = source
509 .control_points
510 .iter()
511 .map(|row| {
512 row.iter()
513 .map(|control| {
514 Ok(Vec4::from_point(
515 centre.add(control.point().or_refuse(KernelStage::Refine, "point")?.sub(centre).scale(factor)),
516 control.w,
517 ))
518 })
519 .collect::<Result<Vec<_>, KernelRefusal>>()
520 })
521 .collect::<Result<Vec<Vec<_>>, KernelRefusal>>()
522 else {
523 return Ok(None);
524 };
525 let Ok(surface) = NurbsSurface::new(
526 source.degree_u,
527 source.degree_v,
528 source.knots_u.clone(),
529 source.knots_v.clone(),
530 controls,
531 ) else {
532 return Ok(None);
533 };
534 match measure_surface_fit_against_pointwise_offset(
538 source,
539 face.same_sense,
540 &surface,
541 distance,
542 1e-9 * scale,
543 ) {
544 Ok(measured) if measured.within_band() => Ok(Some(surface)),
545 _ => Ok(None),
546 }
547}
548
549fn revolved_arc_offset_surface(
582 face: &FaceRecord,
583 distance: f64,
584) -> Result<Option<NurbsSurface>, KernelRefusal> {
585 if std::env::var_os("BREP_NO_ANALYTIC_OFFSET").is_some() {
586 return Ok(None);
587 }
588 let source = &face.surface;
589 match source.analytic() {
590 Some(crate::AnalyticSurface::Revolution { .. } | crate::AnalyticSurface::Torus { .. }) => {}
591 _ => return Ok(None),
592 }
593 let rows = &source.control_points;
594 let Some(first_row) = rows.first() else {
595 return Ok(None);
596 };
597 if rows.iter().flatten().any(|control| !(control.w > 0.0)) {
598 return Ok(None);
599 }
600 let Ok(generatrix) = NurbsCurve::new(source.degree_v, source.knots_v.clone(), first_row.clone())
601 else {
602 return Ok(None);
603 };
604 let [t0, t1] = generatrix.domain().or_refuse(KernelStage::Refine, "domain")?;
605 let at = |fraction: f64| generatrix.evaluate(t0 + (t1 - t0) * fraction);
606 let (a, b, e) = (at(0.0).or_refuse(KernelStage::Refine, "at")?, at(1.0 / 3.0).or_refuse(KernelStage::Refine, "at")?, at(2.0 / 3.0).or_refuse(KernelStage::Refine, "at")?);
608 let (ab, ae) = (b.sub(a), e.sub(a));
609 let normal = ab.cross(ae);
610 let normal_length_squared = normal.dot(normal);
611 if normal_length_squared <= 1e-24 {
612 return Ok(None);
613 }
614 let centre = a.add(
615 ae.cross(normal)
616 .scale(ab.dot(ab))
617 .add(normal.cross(ab).scale(ae.dot(ae)))
618 .scale(0.5 / normal_length_squared),
619 );
620 let radius = a.sub(centre).length();
621 let scale = (centre.length() + radius).max(1.0);
622 let band = 1e-9 * scale;
623 let unit_normal = normal.scale(1.0 / normal_length_squared.sqrt());
624 let samples = 16 * generatrix.control_points.len();
625 for index in 0..=samples {
626 let point = at(index as f64 / samples as f64).or_refuse(KernelStage::Refine, "at")?;
627 let radial = point.sub(centre);
628 if (radial.length() - radius).abs() > band || radial.dot(unit_normal).abs() > band {
629 return Ok(None);
630 }
631 }
632 let controls = first_row
635 .iter()
636 .map(|control| control.point().or_refuse(KernelStage::Refine, "point"))
637 .collect::<Result<Vec<_>, KernelRefusal>>()?;
638 let origin = controls[0];
639 let mut best = (0usize, 0usize, 0.0f64);
640 for first in 1..controls.len() {
641 for second in first + 1..controls.len() {
642 let area = controls[first]
643 .sub(origin)
644 .cross(controls[second].sub(origin))
645 .length();
646 if area > best.2 {
647 best = (first, second, area);
648 }
649 }
650 }
651 let (first, second, area) = best;
652 if area <= 1e-9 * scale * scale {
653 return Ok(None);
654 }
655 let (along_first, along_second) = (controls[first].sub(origin), controls[second].sub(origin));
656 let offset_from_origin = centre.sub(origin);
657 let (g11, g12, g22) = (
658 along_first.dot(along_first),
659 along_first.dot(along_second),
660 along_second.dot(along_second),
661 );
662 let determinant = g11 * g22 - g12 * g12;
663 let (r1, r2) = (along_first.dot(offset_from_origin), along_second.dot(offset_from_origin));
664 let s = (r1 * g22 - r2 * g12) / determinant;
665 let t = (r2 * g11 - r1 * g12) / determinant;
666 if origin
667 .add(along_first.scale(s))
668 .add(along_second.scale(t))
669 .sub(centre)
670 .length()
671 > band
672 {
673 return Ok(None);
674 }
675 let ([u0, _], [v0, v1]) = domains(source)?;
678 let Ok(sample) = OffsetEvaluator::new(
679 "offset_surface",
680 source,
681 OffsetNormal::FaceStable {
682 same_sense: face.same_sense,
683 },
684 )
685 .at(u0, (v0 + v1) / 2.0, -distance)
686 else {
687 return Ok(None);
688 };
689 let source_radial = sample.source.sub(centre);
690 let offset_radial = sample.point.sub(centre);
691 let offset_radius = offset_radial.length();
692 if (source_radial.length() - radius).abs() > band
693 || offset_radius <= band
694 || offset_radial.dot(source_radial) <= 0.0
695 || ((offset_radius - radius).abs() - distance.abs()).abs() > band
696 {
697 return Ok(None);
698 }
699 let factor = offset_radius / radius;
700 let Ok(offset_rows) = rows
701 .iter()
702 .map(|row| {
703 let points = row.iter().map(|control| control.point().or_refuse(KernelStage::Refine, "point")).collect::<Result<Vec<_>, KernelRefusal>>()?;
704 let row_origin = points[0];
705 let row_centre = row_origin
706 .add(points[first].sub(row_origin).scale(s))
707 .add(points[second].sub(row_origin).scale(t));
708 Ok(points
709 .iter()
710 .zip(row)
711 .map(|(point, control)| {
712 Vec4::from_point(row_centre.add(point.sub(row_centre).scale(factor)), control.w)
713 })
714 .collect::<Vec<_>>())
715 })
716 .collect::<Result<Vec<Vec<_>>, KernelRefusal>>()
717 else {
718 return Ok(None);
719 };
720 let Ok(surface) = NurbsSurface::new(
721 source.degree_u,
722 source.degree_v,
723 source.knots_u.clone(),
724 source.knots_v.clone(),
725 offset_rows,
726 ) else {
727 return Ok(None);
728 };
729 match measure_surface_fit_against_pointwise_offset(
730 source,
731 face.same_sense,
732 &surface,
733 distance,
734 band,
735 ) {
736 Ok(measured) if measured.within_band() => Ok(Some(surface)),
737 _ => Ok(None),
738 }
739}
740
741pub(crate) fn offset_surface_with_lane(
742 face: &FaceRecord,
743 distance: f64,
744 extension: &CarrierExtension,
745) -> Result<(NurbsSurface, OffsetSurfaceLane), KernelRefusal> {
746 offset_surface_constructed(face, distance, extension).map(|(surface, lane, _)| (surface, lane))
747}
748
749fn offset_surface_constructed(
753 face: &FaceRecord,
754 distance: f64,
755 extension: &CarrierExtension,
756) -> Result<(NurbsSurface, OffsetSurfaceLane, Option<NurbsSurface>), KernelRefusal> {
757 let source = &face.surface;
758 let extension_active = extension.is_active();
759 if source.is_affine().or_refuse(KernelStage::Refine, "is_affine")? {
760 let ([u0, u1], [v0, v1]) = domains(source)?;
761 let normal = stable_face_normal(face, (u0 + u1) / 2.0, (v0 + v1) / 2.0)?;
762 let shift = normal.scale(-distance);
763 let mut points = source
764 .control_points
765 .iter()
766 .map(|row| {
767 row.iter()
768 .map(|control| Ok(control.point().or_refuse(KernelStage::Refine, "point")?.add(shift)))
769 .collect::<Result<Vec<_>, KernelRefusal>>()
770 })
771 .collect::<Result<Vec<_>, KernelRefusal>>()?;
772 if extension_active {
773 let p00 = points[0][0];
774 let p01 = points[0][1];
775 let p10 = points[1][0];
776 let direction_u = p10.sub(p00).normalized().or_refuse(KernelStage::Refine, "normalized")?;
777 let direction_v = p01.sub(p00).normalized().or_refuse(KernelStage::Refine, "normalized")?;
778 let (trim_u, trim_v) = trim_domain_fractions(face, [u0, u1], [v0, v1])?;
784 let (back_u, forward_u) = domain_end_moves(extension.u_min, extension.u_max, trim_u);
785 let (back_v, forward_v) = domain_end_moves(extension.v_min, extension.v_max, trim_v);
786 points[0][0] = p00
787 .sub(direction_u.scale(back_u))
788 .sub(direction_v.scale(back_v));
789 points[0][1] = p01
790 .sub(direction_u.scale(back_u))
791 .add(direction_v.scale(forward_v));
792 points[1][0] = p10
793 .add(direction_u.scale(forward_u))
794 .sub(direction_v.scale(back_v));
795 points[1][1] = points[1][1]
796 .add(direction_u.scale(forward_u))
797 .add(direction_v.scale(forward_v));
798 }
799 let controls = points
800 .into_iter()
801 .enumerate()
802 .map(|(row, points)| {
803 points
804 .into_iter()
805 .enumerate()
806 .map(|(column, point)| {
807 Vec4::from_point(point, source.control_points[row][column].w)
808 })
809 .collect()
810 })
811 .collect();
812 let lane = if extension_active {
816 OffsetSurfaceLane::Reparameterised
817 } else {
818 OffsetSurfaceLane::Affine
819 };
820 return Ok((
821 NurbsSurface::new(
822 source.degree_u,
823 source.degree_v,
824 source.knots_u.clone(),
825 source.knots_v.clone(),
826 controls,
827 ).or_refuse(KernelStage::Refine, "new")?,
828 lane,
829 None,
830 ));
831 }
832
833 if let Some(surface) = sphere_offset_surface(face, distance)? {
843 return Ok((surface, OffsetSurfaceLane::Analytic, None));
844 }
845 if let Some(surface) = revolved_arc_offset_surface(face, distance)? {
848 return Ok((surface, OffsetSurfaceLane::Analytic, None));
849 }
850
851 let knot_u = KnotVector::new(source.knots_u.clone(), source.degree_u).or_refuse(KernelStage::Refine, "new")?;
852 let knot_v = KnotVector::new(source.knots_v.clone(), source.degree_v).or_refuse(KernelStage::Refine, "new")?;
853 let parameters_u = greville_parameters(&knot_u);
854 let parameters_v = greville_parameters(&knot_v);
855 let evaluator = OffsetEvaluator::new(
861 "offset_surface",
862 source,
863 OffsetNormal::FaceStable {
864 same_sense: face.same_sense,
865 },
866 );
867 let mut samples = Vec::new();
868 for &u in ¶meters_u {
869 let mut row = Vec::new();
870 for &v in ¶meters_v {
871 row.push(evaluator.at(u, v, -distance).or_refuse(KernelStage::Refine, "at")?.point);
872 }
873 samples.push(row);
874 }
875 let mut reparameterised = false;
890 let mut extended_source: Option<NurbsSurface> = None;
891 if parameters_v.len() == 2 && parameters_u.len() >= 2 {
898 let centroid = |column: usize| {
899 let mut sum = Vec3::default();
900 for row in &samples {
901 sum = sum.add(row[column]);
902 }
903 sum.scale(1.0 / samples.len() as f64)
904 };
905 let near_centroid = centroid(0);
906 let far_centroid = centroid(1);
907 let mut inverted = true;
908 let mut pinch_fraction = 0.0f64;
909 let mut near_mean = 0.0f64;
910 let mut far_mean = 0.0f64;
911 for row in &samples {
912 let near_radial = row[0].sub(near_centroid);
913 let far_radial = row[1].sub(far_centroid);
914 let near_len = near_radial.length();
915 let far_len = far_radial.length();
916 if near_len <= 1e-9 || far_len <= 1e-9 {
917 inverted = false;
918 break;
919 }
920 if near_radial.dot(far_radial) >= 0.0 {
921 inverted = false;
922 break;
923 }
924 pinch_fraction += near_len / (near_len + far_len) / samples.len() as f64;
925 near_mean += near_len / samples.len() as f64;
926 far_mean += far_len / samples.len() as f64;
927 }
928 if inverted {
929 let retrim_far = far_mean <= near_mean;
930 let (_, trim_v) = trim_domain_fractions(face, knot_u.domain(), knot_v.domain())?;
954 let trim_reaches_the_pinch = if retrim_far {
955 trim_v[1] > pinch_fraction + 1e-9
956 } else {
957 trim_v[0] < pinch_fraction - 1e-9
958 };
959 if trim_reaches_the_pinch {
960 reparameterised = true;
963 for row in &mut samples {
964 let near = row[0];
965 let far = row[1];
966 let pinch = near.add(far.sub(near).scale(pinch_fraction));
967 if retrim_far {
968 row[1] = pinch;
969 } else {
970 row[0] = pinch;
971 }
972 }
973 }
974 }
975 if extension.extends_v() && !inverted {
995 let mut min_ruling = f64::MAX;
996 let mut back_allowance = f64::MAX;
997 let mut forward_allowance = f64::MAX;
998 let mut extendable = true;
999 for row in &samples {
1000 let ruling = row[1].sub(row[0]);
1001 let length = ruling.length();
1002 min_ruling = min_ruling.min(length);
1003 let near_radial = row[0].sub(near_centroid).length();
1007 let far_radial = row[1].sub(far_centroid).length();
1008 if (far_radial - near_radial).abs() > 1e-9 {
1009 let apex_at = near_radial / (near_radial - far_radial);
1010 if (-1e-9..=1.0 + 1e-9).contains(&apex_at) {
1011 extendable = false;
1013 break;
1014 }
1015 if apex_at < 0.0 {
1016 back_allowance = back_allowance.min(0.9 * -apex_at);
1017 } else {
1018 forward_allowance = forward_allowance.min(0.9 * (apex_at - 1.0));
1019 }
1020 }
1021 }
1022 let mut source_back = f64::MAX;
1030 let mut source_forward = f64::MAX;
1031 if extendable {
1032 let source_centroid = |column: usize| {
1033 let mut sum = Vec3::default();
1034 for row in &source.control_points {
1035 if let Ok(point) = row[column].point() {
1036 sum = sum.add(point);
1037 }
1038 }
1039 sum.scale(1.0 / source.control_points.len() as f64)
1040 };
1041 let (near_source, far_source) = (source_centroid(0), source_centroid(1));
1042 for row in &source.control_points {
1043 let (near, far) = (row[0].point().or_refuse(KernelStage::Refine, "point")?, row[1].point().or_refuse(KernelStage::Refine, "point")?);
1044 let near_radial = near.sub(near_source).length();
1045 let far_radial = far.sub(far_source).length();
1046 if (far_radial - near_radial).abs() > 1e-9 {
1047 let apex_at = near_radial / (near_radial - far_radial);
1048 if (-1e-9..=1.0 + 1e-9).contains(&apex_at) {
1049 extendable = false;
1050 break;
1051 }
1052 if apex_at < 0.0 {
1053 source_back = source_back.min(0.9 * -apex_at);
1054 } else {
1055 source_forward = source_forward.min(0.9 * (apex_at - 1.0));
1056 }
1057 }
1058 }
1059 }
1060 let (back_allowance, forward_allowance) =
1061 (back_allowance.min(source_back), forward_allowance.min(source_forward));
1062 if extendable && min_ruling > 1e-9 {
1063 let (_, trim_v) = trim_domain_fractions(face, knot_u.domain(), knot_v.domain())?;
1070 let (back_world, forward_world) =
1071 domain_end_moves(extension.v_min, extension.v_max, trim_v);
1072 let (requested_back, requested_forward) = (back_world / min_ruling, forward_world / min_ruling);
1073 let apex_end = analytic_offset_apex(source, &samples, offset_fit_contract(source)).and_then(
1085 |(at, apex)| {
1086 if at > 1.0 && at - 1.0 <= requested_forward && at - 1.0 <= source_forward {
1087 Some((false, at - 1.0, apex))
1088 } else if at < 0.0 && -at <= requested_back && -at <= source_back {
1089 Some((true, -at, apex))
1090 } else {
1091 None
1092 }
1093 },
1094 );
1095 let back = match apex_end {
1096 Some((true, reach, _)) => reach,
1097 _ => requested_back.min(back_allowance),
1098 };
1099 let forward = match apex_end {
1100 Some((false, reach, _)) => reach,
1101 _ => requested_forward.min(forward_allowance),
1102 };
1103 let stretched = samples
1104 .iter()
1105 .map(|row| {
1106 let ruling = row[1].sub(row[0]);
1107 let (near, far) = (row[0].sub(ruling.scale(back)), row[1].add(ruling.scale(forward)));
1108 match apex_end {
1109 Some((true, _, apex)) => vec![apex, far],
1110 Some((false, _, apex)) => vec![near, apex],
1111 None => vec![near, far],
1112 }
1113 })
1114 .collect::<Vec<_>>();
1115 let extended = extend_along_rulings(source, back, forward)?;
1116 let extended_offsets = OffsetEvaluator::new(
1117 "offset_surface",
1118 &extended,
1119 OffsetNormal::FaceStable {
1120 same_sense: face.same_sense,
1121 },
1122 );
1123 let [v0, v1] = knot_v.domain();
1124 let mut developability = 0.0f64;
1125 for (row, &u) in stretched.iter().zip(¶meters_u) {
1126 for fraction in [0.0, 0.5, 1.0] {
1127 let exact = extended_offsets.at(u, v0 + (v1 - v0) * fraction, -distance).or_refuse(KernelStage::Refine, "at")?.point;
1128 let along = row[0].add(row[1].sub(row[0]).scale(fraction));
1129 developability = developability.max(exact.sub(along).length());
1130 }
1131 }
1132 let contract = offset_fit_contract(source);
1133 let stretch_kept = developability <= contract;
1134 samples = if stretch_kept {
1135 stretched
1136 } else {
1137 parameters_u
1138 .iter()
1139 .map(|&u| {
1140 parameters_v
1141 .iter()
1142 .map(|&v| Ok(extended_offsets.at(u, v, -distance).or_refuse(KernelStage::Refine, "at")?.point))
1143 .collect::<Result<Vec<_>, KernelRefusal>>()
1144 })
1145 .collect::<Result<Vec<_>, KernelRefusal>>()?
1146 };
1147 if let Ok(path) = std::env::var("BREP_OFFSET_FIT_TRACE") {
1148 use std::io::Write;
1149 let line = format!(
1150 "extension deg=({},{}) net=({},{}) d={distance:.6} back={back:.6} forward={forward:.6} \
1151 developability={developability:.3e} contract={contract:.3e} lane={} thread={} args={}\n",
1152 source.degree_u,
1153 source.degree_v,
1154 source.control_points.len(),
1155 source.control_points[0].len(),
1156 if stretch_kept { "stretch" } else { "pointwise" },
1157 std::thread::current().name().unwrap_or("-"),
1158 std::env::args().skip(1).collect::<Vec<_>>().join(" "),
1159 );
1160 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
1161 let _ = file.write_all(line.as_bytes());
1162 }
1163 }
1164 extended_source = Some(extended);
1165 }
1166 }
1167 }
1168 let reference = extended_source.as_ref().unwrap_or(source);
1169 let weights = reference
1170 .control_points
1171 .iter()
1172 .map(|row| row.iter().map(|point| point.w).collect::<Vec<_>>())
1173 .collect::<Vec<_>>();
1174 let fitted = NurbsSurface::new(
1175 source.degree_u,
1176 source.degree_v,
1177 source.knots_u.clone(),
1178 source.knots_v.clone(),
1179 interpolate_tensor(
1180 &knot_u,
1181 &knot_v,
1182 ¶meters_u,
1183 ¶meters_v,
1184 &samples,
1185 &weights,
1186 )?,
1187 ).or_refuse(KernelStage::Refine, "new")?;
1188 if reparameterised {
1189 return Ok((fitted, OffsetSurfaceLane::Reparameterised, None));
1190 }
1191 let reference_offsets = OffsetEvaluator::new(
1192 "offset_surface",
1193 reference,
1194 OffsetNormal::FaceStable {
1195 same_sense: face.same_sense,
1196 },
1197 );
1198 Ok((
1199 refine_offset_fit(reference, &reference_offsets, distance, fitted)?,
1200 OffsetSurfaceLane::Fit,
1201 extended_source,
1202 ))
1203}
1204
1205
1206fn analytic_offset_apex(source: &NurbsSurface, samples: &[Vec<Vec3>], contract: f64) -> Option<(f64, Vec3)> {
1215 let Some(crate::AnalyticSurface::RuledRevolution { frame, .. }) = crate::analytic_surface::recognize(source) else {
1216 return None;
1217 };
1218 let radial = |point: Vec3| {
1219 let offset = point.sub(frame.origin);
1220 offset.sub(frame.axis.scale(offset.dot(frame.axis)))
1221 };
1222 let mut crossings = Vec::with_capacity(samples.len());
1223 for row in samples {
1224 let (near, far) = (radial(row[0]), radial(row[1]));
1225 let step = far.sub(near);
1226 let at = -near.dot(step) / step.dot(step);
1227 if !at.is_finite() {
1228 return None;
1229 }
1230 crossings.push((at, row[0].add(row[1].sub(row[0]).scale(at))));
1231 }
1232 let count = crossings.len() as f64;
1235 let at = crossings.iter().map(|(at, _)| at).sum::<f64>() / count;
1236 let first = crossings.first()?.1;
1237 let spread = crossings.iter().fold(Vec3::default(), |sum, (_, point)| sum.add(point.sub(first)));
1238 let centre = first.add(spread.scale(1.0 / count));
1239 let apex = frame.origin.add(frame.axis.scale(centre.sub(frame.origin).dot(frame.axis)));
1240 crossings
1243 .iter()
1244 .all(|(_, point)| radial(*point).length() <= contract && point.sub(apex).length() <= contract)
1245 .then_some((at, apex))
1246}
1247
1248fn extend_along_rulings(surface: &NurbsSurface, back: f64, forward: f64) -> Result<NurbsSurface, KernelRefusal> {
1255 let rows = surface
1256 .control_points
1257 .iter()
1258 .map(|row| {
1259 let step = row[1].add(row[0].scale(-1.0));
1260 vec![row[0].add(step.scale(-back)), row[1].add(step.scale(forward))]
1261 })
1262 .collect::<Vec<_>>();
1263 if rows.iter().flatten().any(|point| point.w <= 1e-12) {
1264 return Err(KernelRefusal::unsupported(KernelStage::Refine, "offset_ruled_extension_weight", format!(
1265 "offset_surface: extending a ruled face by ({back:.6}, {forward:.6}) of its rulings \
1266 carries a weight through zero; the extended face is not this surface"
1267 )));
1268 }
1269 NurbsSurface::new(surface.degree_u, 1, surface.knots_u.clone(), surface.knots_v.clone(), rows)
1270 .or_refuse(KernelStage::Refine, "new")
1271}
1272
1273fn offset_fit_contract(source: &NurbsSurface) -> f64 {
1276 let scale = crate::model_scale(
1277 source
1278 .control_points
1279 .iter()
1280 .flatten()
1281 .filter_map(|point| point.point().ok()),
1282 );
1283 crate::KernelTolerances::for_scale(scale, 1e-7).intersection_fit
1284}
1285
1286const OFFSET_FIT_REFINEMENTS: usize = 6;
1288
1289const OFFSET_FIT_MAX_CONTROLS_SEPARABLE: usize = 16_384;
1292const OFFSET_FIT_MAX_CONTROLS_TENSOR: usize = 900;
1293
1294#[derive(Clone, Copy, Debug)]
1299struct OffsetFitDeviation {
1300 along_u: f64,
1301 along_v: f64,
1302 between: f64,
1303}
1304
1305impl OffsetFitDeviation {
1306 fn worst(&self) -> f64 {
1307 self.along_u.max(self.along_v).max(self.between)
1308 }
1309}
1310
1311fn greville_midpoints(parameters: &[f64]) -> Vec<f64> {
1312 parameters
1313 .windows(2)
1314 .filter(|pair| pair[1] - pair[0] > 1e-12)
1315 .map(|pair| 0.5 * (pair[0] + pair[1]))
1316 .collect()
1317}
1318
1319fn offset_fit_deviation(
1320 fitted: &NurbsSurface,
1321 evaluator: &OffsetEvaluator,
1322 distance: f64,
1323) -> Result<OffsetFitDeviation, KernelRefusal> {
1324 let parameters_u = greville_parameters(&KnotVector::new(fitted.knots_u.clone(), fitted.degree_u).or_refuse(KernelStage::Refine, "new")?);
1325 let parameters_v = greville_parameters(&KnotVector::new(fitted.knots_v.clone(), fitted.degree_v).or_refuse(KernelStage::Refine, "new")?);
1326 let (middle_u, middle_v) = (greville_midpoints(¶meters_u), greville_midpoints(¶meters_v));
1327 let gap = |u: f64, v: f64| -> Result<f64, KernelRefusal> {
1328 Ok(fitted.evaluate(u, v).or_refuse(KernelStage::Refine, "evaluate")?.sub(evaluator.at(u, v, -distance).or_refuse(KernelStage::Refine, "at")?.point).length())
1329 };
1330 let mut deviation = OffsetFitDeviation {
1331 along_u: 0.0,
1332 along_v: 0.0,
1333 between: 0.0,
1334 };
1335 for &u in &middle_u {
1336 for &v in ¶meters_v {
1337 deviation.along_u = deviation.along_u.max(gap(u, v)?);
1338 }
1339 for &v in &middle_v {
1340 deviation.between = deviation.between.max(gap(u, v)?);
1341 }
1342 }
1343 for &u in ¶meters_u {
1344 for &v in &middle_v {
1345 deviation.along_v = deviation.along_v.max(gap(u, v)?);
1346 }
1347 }
1348 Ok(deviation)
1349}
1350
1351fn raise_linear_directions(
1361 surface: &NurbsSurface,
1362 in_u: bool,
1363 in_v: bool,
1364) -> Result<Option<NurbsSurface>, KernelRefusal> {
1365 fn raise(knots: &[f64], row: &[Vec4]) -> Option<(Vec<f64>, Vec<Vec4>)> {
1366 let count = row.len();
1368 if knots.len() != count + 2 {
1369 return None;
1370 }
1371 let breaks = &knots[1..=count];
1372 if breaks.windows(2).any(|pair| pair[1] - pair[0] <= 1e-12) {
1373 return None;
1374 }
1375 let mut raised_knots = vec![breaks[0]; 4];
1376 for &interior in &breaks[1..count - 1] {
1377 raised_knots.extend([interior; 3]);
1378 }
1379 raised_knots.extend([breaks[count - 1]; 4]);
1380 let mut points = Vec::with_capacity(3 * (count - 1) + 1);
1381 for pair in row.windows(2) {
1382 let step = pair[1].add(pair[0].scale(-1.0));
1383 points.push(pair[0]);
1384 points.push(pair[0].add(step.scale(1.0 / 3.0)));
1385 points.push(pair[0].add(step.scale(2.0 / 3.0)));
1386 }
1387 points.push(row[count - 1]);
1388 Some((raised_knots, points))
1389 }
1390 let mut knots_u = surface.knots_u.clone();
1391 let mut knots_v = surface.knots_v.clone();
1392 let mut degree_u = surface.degree_u;
1393 let mut degree_v = surface.degree_v;
1394 let mut net = surface.control_points.clone();
1395 if in_v {
1396 if degree_v != 1 {
1397 return Ok(None);
1398 }
1399 let mut rows = Vec::with_capacity(net.len());
1400 for row in &net {
1401 let Some((knots, points)) = raise(&surface.knots_v, row) else {
1402 return Ok(None);
1403 };
1404 knots_v = knots;
1405 rows.push(points);
1406 }
1407 net = rows;
1408 degree_v = 3;
1409 }
1410 if in_u {
1411 if degree_u != 1 {
1412 return Ok(None);
1413 }
1414 let count_v = net[0].len();
1415 let mut columns = Vec::with_capacity(count_v);
1416 for column in 0..count_v {
1417 let row = net.iter().map(|row| row[column]).collect::<Vec<_>>();
1418 let Some((knots, points)) = raise(&surface.knots_u, &row) else {
1419 return Ok(None);
1420 };
1421 knots_u = knots;
1422 columns.push(points);
1423 }
1424 let count_u = columns[0].len();
1425 net = (0..count_u)
1426 .map(|row| columns.iter().map(|column| column[row]).collect())
1427 .collect();
1428 degree_u = 3;
1429 }
1430 Ok(Some(NurbsSurface::new(degree_u, degree_v, knots_u, knots_v, net).or_refuse(KernelStage::Refine, "new")?))
1431}
1432
1433fn halve_knot_spans(surface: &NurbsSurface, in_u: bool, in_v: bool) -> Result<NurbsSurface, KernelRefusal> {
1437 fn midpoints(knots: &[f64], degree: usize) -> Vec<f64> {
1438 let last = knots.len() - 1 - degree;
1439 knots[degree..=last]
1440 .windows(2)
1441 .filter(|pair| pair[1] - pair[0] > 1e-12)
1442 .map(|pair| 0.5 * (pair[0] + pair[1]))
1443 .collect()
1444 }
1445 let refine = |degree: usize, knots: &[f64], net: Vec<Vec<Vec4>>| -> Result<(Vec<f64>, Vec<Vec<Vec4>>), KernelRefusal> {
1446 let inserted = midpoints(knots, degree);
1447 let mut refined_knots = knots.to_vec();
1448 let mut rows = Vec::with_capacity(net.len());
1449 for row in net {
1450 let mut curve = NurbsCurve::new(degree, knots.to_vec(), row).or_refuse(KernelStage::Refine, "new")?;
1451 for ¶meter in &inserted {
1452 curve = curve.insert_knot(parameter, 1).or_refuse(KernelStage::Refine, "insert_knot")?;
1453 }
1454 refined_knots = curve.knots.clone();
1455 rows.push(curve.control_points);
1456 }
1457 Ok((refined_knots, rows))
1458 };
1459 let mut knots_u = surface.knots_u.clone();
1460 let mut knots_v = surface.knots_v.clone();
1461 let mut net = surface.control_points.clone();
1462 if in_v {
1463 let (knots, rows) = refine(surface.degree_v, &knots_v, net)?;
1464 knots_v = knots;
1465 net = rows;
1466 }
1467 if in_u {
1468 let count_v = net[0].len();
1470 let columns = (0..count_v)
1471 .map(|column| net.iter().map(|row| row[column]).collect::<Vec<_>>())
1472 .collect::<Vec<_>>();
1473 let (knots, refined) = refine(surface.degree_u, &knots_u, columns)?;
1474 knots_u = knots;
1475 let count_u = refined[0].len();
1476 net = (0..count_u)
1477 .map(|row| refined.iter().map(|column| column[row]).collect())
1478 .collect();
1479 }
1480 NurbsSurface::new(surface.degree_u, surface.degree_v, knots_u, knots_v, net).or_refuse(KernelStage::Refine, "new")
1481}
1482
1483fn refine_offset_fit(
1519 source: &NurbsSurface,
1520 evaluator: &OffsetEvaluator,
1521 distance: f64,
1522 fitted: NurbsSurface,
1523) -> Result<NurbsSurface, KernelRefusal> {
1524 let scale = crate::model_scale(
1525 source
1526 .control_points
1527 .iter()
1528 .flatten()
1529 .filter_map(|point| point.point().ok()),
1530 );
1531 let bar = crate::KernelTolerances::for_scale(scale, 1e-7).intersection_fit;
1532 let trace = std::env::var("BREP_OFFSET_FIT_TRACE").ok();
1533 let started = web_time::Instant::now();
1534 let enabled = std::env::var("BREP_OFFSET_FIT_REFINE").as_deref() != Ok("0");
1535 let Ok(initial) = offset_fit_deviation(&fitted, evaluator, distance) else {
1536 return Ok(fitted);
1537 };
1538 let mut best = fitted;
1539 let mut best_deviation = initial;
1540 let mut refined_source = source.clone();
1541 let mut rounds = 0usize;
1542 let mut stop = "under bar";
1543 if enabled {
1544 while best_deviation.worst() > bar {
1545 if rounds == OFFSET_FIT_REFINEMENTS {
1546 stop = "refinement limit";
1547 break;
1548 }
1549 let (along_u, along_v) = (best_deviation.along_u > bar, best_deviation.along_v > bar);
1550 let (in_u, in_v) = if along_u || along_v {
1551 (along_u, along_v)
1552 } else {
1553 (true, true)
1554 };
1555 let (raise_u, raise_v) = (in_u && refined_source.degree_u == 1, in_v && refined_source.degree_v == 1);
1556 let raised = if raise_u || raise_v {
1557 raise_linear_directions(&refined_source, raise_u, raise_v)?
1558 } else {
1559 None
1560 };
1561 let (halve_u, halve_v) = match raised {
1562 Some(_) => (in_u && !raise_u, in_v && !raise_v),
1563 None => (in_u, in_v),
1564 };
1565 let raised = raised.unwrap_or_else(|| refined_source.clone());
1566 let candidate_source = if halve_u || halve_v {
1567 halve_knot_spans(&raised, halve_u, halve_v)?
1568 } else {
1569 raised
1570 };
1571 let weights = candidate_source
1572 .control_points
1573 .iter()
1574 .map(|row| row.iter().map(|point| point.w).collect::<Vec<_>>())
1575 .collect::<Vec<_>>();
1576 let controls = weights.len() * weights[0].len();
1577 let ceiling = if separable_weights(&weights).is_some() {
1578 OFFSET_FIT_MAX_CONTROLS_SEPARABLE
1579 } else {
1580 OFFSET_FIT_MAX_CONTROLS_TENSOR
1581 };
1582 if controls > ceiling {
1583 stop = "net ceiling";
1584 break;
1585 }
1586 let knot_u = KnotVector::new(candidate_source.knots_u.clone(), candidate_source.degree_u).or_refuse(KernelStage::Refine, "new")?;
1587 let knot_v = KnotVector::new(candidate_source.knots_v.clone(), candidate_source.degree_v).or_refuse(KernelStage::Refine, "new")?;
1588 let parameters_u = greville_parameters(&knot_u);
1589 let parameters_v = greville_parameters(&knot_v);
1590 let samples = parameters_u
1591 .iter()
1592 .map(|&u| {
1593 parameters_v
1594 .iter()
1595 .map(|&v| Ok(evaluator.at(u, v, -distance).or_refuse(KernelStage::Refine, "at")?.point))
1596 .collect::<Result<Vec<_>, KernelRefusal>>()
1597 })
1598 .collect::<Result<Vec<_>, KernelRefusal>>();
1599 let Ok(samples) = samples else {
1600 stop = "unevaluable sample";
1601 break;
1602 };
1603 let candidate = NurbsSurface::new(
1604 candidate_source.degree_u,
1605 candidate_source.degree_v,
1606 candidate_source.knots_u.clone(),
1607 candidate_source.knots_v.clone(),
1608 interpolate_tensor(&knot_u, &knot_v, ¶meters_u, ¶meters_v, &samples, &weights)?,
1609 ).or_refuse(KernelStage::Refine, "new")?;
1610 let Ok(deviation) = offset_fit_deviation(&candidate, evaluator, distance) else {
1611 stop = "unmeasurable round";
1612 break;
1613 };
1614 rounds += 1;
1615 if deviation.worst() >= best_deviation.worst() {
1616 stop = "stopped improving";
1617 break;
1618 }
1619 best = candidate;
1620 best_deviation = deviation;
1621 refined_source = candidate_source;
1622 }
1623 } else if best_deviation.worst() > bar {
1624 stop = "refinement disabled";
1625 }
1626 let band = offset_construction_band(scale);
1631 let refused = enabled && best_deviation.worst() > band;
1632 if let Some(path) = trace {
1633 use std::io::Write;
1634 let caller = std::env::args().skip(1).collect::<Vec<_>>().join(" ");
1635 let thread = std::thread::current().name().unwrap_or("-").to_string();
1636 let line = format!(
1637 "fit deg=({},{}) net=({},{})->({},{}) d={distance:.6} scale={scale:.4} bar={bar:.3e} \
1638 initial=(u {:.3e}, v {:.3e}, uv {:.3e}) final={:.3e} rounds={rounds} stop={stop} \
1639 refused={refused} seconds={:.4} thread={thread} args={caller}\n",
1640 source.degree_u,
1641 source.degree_v,
1642 source.control_points.len(),
1643 source.control_points[0].len(),
1644 best.control_points.len(),
1645 best.control_points[0].len(),
1646 initial.along_u,
1647 initial.along_v,
1648 initial.between,
1649 best_deviation.worst(),
1650 started.elapsed().as_secs_f64(),
1651 );
1652 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
1653 let _ = file.write_all(line.as_bytes());
1654 }
1655 }
1656 if refused {
1657 return Err(KernelRefusal::non_convergence(KernelStage::Refine, "offset_carrier_fit", format!(
1658 "offset_surface: the fitted offset carrier stays {:.3e} off its pointwise offset at \
1659 distance {distance} after {rounds} refinement round(s) ({stop}), past the offset \
1660 construction band {band:.3e}",
1661 best_deviation.worst()
1662 )));
1663 }
1664 Ok(best)
1665}
1666
1667pub(crate) fn unify_offset_sheet_bases(
1677 first: &NurbsSurface,
1678 second: &NurbsSurface,
1679) -> Result<(NurbsSurface, NurbsSurface), KernelRefusal> {
1680 fn raise_to(surface: &NurbsSurface, degree_u: usize, degree_v: usize) -> Result<NurbsSurface, KernelRefusal> {
1681 let raise_u = surface.degree_u == 1 && degree_u == 3;
1682 let raise_v = surface.degree_v == 1 && degree_v == 3;
1683 if !raise_u && !raise_v {
1684 return Ok(surface.clone());
1685 }
1686 raise_linear_directions(surface, raise_u, raise_v)?
1687 .ok_or_else(|| {
1688 KernelRefusal::internal(
1689 KernelStage::Refine,
1690 "offset_raise_linear",
1691 "offset sheets: a linear direction could not be raised",
1692 )
1693 })
1694 }
1695 let degree_u = first.degree_u.max(second.degree_u);
1696 let degree_v = first.degree_v.max(second.degree_v);
1697 let mut first = raise_to(first, degree_u, degree_v)?;
1698 let mut second = raise_to(second, degree_u, degree_v)?;
1699 if first.degree_u != second.degree_u || first.degree_v != second.degree_v {
1700 return Err(KernelRefusal::internal(KernelStage::Refine, "offset_common_basis", format!(
1701 "offset sheets: degrees ({},{}) and ({},{}) have no common basis",
1702 first.degree_u, first.degree_v, second.degree_u, second.degree_v
1703 )));
1704 }
1705 fn missing(from: &[f64], into: &[f64]) -> Vec<f64> {
1707 let mut extra = Vec::new();
1708 let mut index = 0usize;
1709 for &knot in from {
1710 while index < into.len() && into[index] < knot - 1e-12 {
1711 index += 1;
1712 }
1713 if index < into.len() && (into[index] - knot).abs() <= 1e-12 {
1714 index += 1;
1715 } else {
1716 extra.push(knot);
1717 }
1718 }
1719 extra
1720 }
1721 fn insert(surface: &NurbsSurface, knots: &[f64], along_u: bool) -> Result<NurbsSurface, KernelRefusal> {
1722 if knots.is_empty() {
1723 return Ok(surface.clone());
1724 }
1725 let (degree, vector) = if along_u {
1726 (surface.degree_u, &surface.knots_u)
1727 } else {
1728 (surface.degree_v, &surface.knots_v)
1729 };
1730 let lines = if along_u {
1731 (0..surface.control_points[0].len())
1732 .map(|column| surface.control_points.iter().map(|row| row[column]).collect::<Vec<_>>())
1733 .collect::<Vec<_>>()
1734 } else {
1735 surface.control_points.clone()
1736 };
1737 let mut refined_knots = vector.clone();
1738 let mut refined = Vec::with_capacity(lines.len());
1739 for line in lines {
1740 let mut curve = NurbsCurve::new(degree, vector.clone(), line).or_refuse(KernelStage::Refine, "new")?;
1741 for &knot in knots {
1742 curve = curve.insert_knot(knot, 1).or_refuse(KernelStage::Refine, "insert_knot")?;
1743 }
1744 refined_knots = curve.knots.clone();
1745 refined.push(curve.control_points);
1746 }
1747 let net = if along_u {
1748 (0..refined[0].len())
1749 .map(|row| refined.iter().map(|column| column[row]).collect())
1750 .collect()
1751 } else {
1752 refined
1753 };
1754 let (knots_u, knots_v) = if along_u {
1755 (refined_knots, surface.knots_v.clone())
1756 } else {
1757 (surface.knots_u.clone(), refined_knots)
1758 };
1759 NurbsSurface::new(surface.degree_u, surface.degree_v, knots_u, knots_v, net).or_refuse(KernelStage::Refine, "new")
1760 }
1761 let (to_first_u, to_second_u) = (
1762 missing(&second.knots_u, &first.knots_u),
1763 missing(&first.knots_u, &second.knots_u),
1764 );
1765 first = insert(&first, &to_first_u, true)?;
1766 second = insert(&second, &to_second_u, true)?;
1767 let (to_first_v, to_second_v) = (
1768 missing(&second.knots_v, &first.knots_v),
1769 missing(&first.knots_v, &second.knots_v),
1770 );
1771 first = insert(&first, &to_first_v, false)?;
1772 second = insert(&second, &to_second_v, false)?;
1773 Ok((first, second))
1774}
1775
1776fn carrier_edge_curve(
1798 surface: &NurbsSurface,
1799 pcurve: &NurbsCurve,
1800 points: &[Vec3],
1801 parameters: &[f64],
1802 fit_tolerance: f64,
1803) -> Result<(NurbsCurve, f64, f64), KernelRefusal> {
1804 let image = if std::env::var("BREP_CARRIER_POLYLINE").as_deref() == Ok("1") {
1806 Err("BREP_CARRIER_POLYLINE set".to_string())
1807 } else {
1808 crate::image_curve::image_curve(surface, pcurve, fit_tolerance, "offset_face_carrier")
1809 };
1810 match image {
1811 Ok(image) if image.t0 <= image.t1 => Ok((image.curve, image.t0, image.t1)),
1812 Ok(image) => {
1813 let [start, end] = image.curve.domain().or_refuse(KernelStage::Refine, "domain")?;
1816 let curve = image.curve.reversed().or_refuse(KernelStage::Refine, "reversed")?;
1817 Ok((curve, start + end - image.t0, start + end - image.t1))
1818 }
1819 Err(_) => {
1820 let curve = interpolate_curve(points, 1, parameters).or_refuse(KernelStage::Refine, "interpolate_curve")?;
1821 let [t0, t1] = curve.domain().or_refuse(KernelStage::Refine, "domain")?;
1822 Ok((curve, t0, t1))
1823 }
1824 }
1825}
1826
1827fn mapped_pcurve_polyline(
1828 surface: &NurbsSurface,
1829 pcurve: &NurbsCurve,
1830 degenerate: bool,
1831) -> Result<(Vec<Vec3>, Vec<f64>), KernelRefusal> {
1832 let [start, end] = pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
1833 let evaluate = |fraction: f64| -> Result<Vec3, KernelRefusal> {
1834 let uv = pcurve.evaluate(start + (end - start) * fraction).or_refuse(KernelStage::Refine, "evaluate")?;
1835 surface.evaluate(uv.x, uv.y).or_refuse(KernelStage::Refine, "evaluate")
1836 };
1837 let first = evaluate(0.0)?;
1838 let last = evaluate(1.0)?;
1839 if degenerate {
1840 return Ok((vec![first, last], vec![0.0, 1.0]));
1841 }
1842 fn append(
1843 evaluate: &impl Fn(f64) -> Result<Vec3, KernelRefusal>,
1844 a_fraction: f64,
1845 a: Vec3,
1846 b_fraction: f64,
1847 b: Vec3,
1848 depth: usize,
1849 parameters: &mut Vec<f64>,
1850 points: &mut Vec<Vec3>,
1851 ) -> Result<(), KernelRefusal> {
1852 let fractions =
1853 [0.25, 0.5, 0.75].map(|local| a_fraction + (b_fraction - a_fraction) * local);
1854 let samples = fractions
1855 .map(evaluate)
1856 .into_iter()
1857 .collect::<Result<Vec<_>, KernelRefusal>>()?;
1858 let deviation = samples
1859 .iter()
1860 .enumerate()
1861 .map(|(index, point)| {
1862 point
1863 .sub(a.add(b.sub(a).scale((index + 1) as f64 * 0.25)))
1864 .length()
1865 })
1866 .fold(0.0, f64::max);
1867 if deviation <= 5e-4 || depth >= 10 {
1868 parameters.push(b_fraction);
1869 points.push(b);
1870 return Ok(());
1871 }
1872 append(
1873 evaluate,
1874 a_fraction,
1875 a,
1876 fractions[1],
1877 samples[1],
1878 depth + 1,
1879 parameters,
1880 points,
1881 )?;
1882 append(
1883 evaluate,
1884 fractions[1],
1885 samples[1],
1886 b_fraction,
1887 b,
1888 depth + 1,
1889 parameters,
1890 points,
1891 )
1892 }
1893 let mut parameters = vec![0.0];
1894 let mut points = vec![first];
1895 append(
1896 &evaluate,
1897 0.0,
1898 first,
1899 1.0,
1900 last,
1901 0,
1902 &mut parameters,
1903 &mut points,
1904 )?;
1905 Ok((points, parameters))
1906}
1907
1908#[derive(Clone, Debug)]
1922pub struct CarrierDeviation {
1923 pub band: f64,
1926 pub lane: OffsetSurfaceLane,
1928 pub surface: Option<MeasuredTolerance>,
1930 pub edges: Vec<(u64, MeasuredTolerance)>,
1935 pub vertices: Vec<(u64, f64)>,
1939}
1940
1941impl CarrierDeviation {
1942 pub fn worst(&self) -> Option<MeasuredTolerance> {
1945 MeasuredTolerance::worst(
1946 self.surface
1947 .into_iter()
1948 .chain(self.edges.iter().map(|(_, measured)| *measured))
1949 .chain(
1950 self.vertices
1951 .iter()
1952 .map(|(_, gap)| MeasuredTolerance::new(*gap, self.band)),
1953 ),
1954 )
1955 }
1956
1957 pub fn exceedances(&self) -> Vec<String> {
1960 let mut out = Vec::new();
1961 if let Some(surface) = self.surface {
1962 if surface.exceeds_band() {
1963 out.push(format!("carrier surface fit {}", surface.describe()));
1964 }
1965 }
1966 for (id, measured) in &self.edges {
1967 if measured.exceeds_band() {
1968 out.push(format!("edge {id} {}", measured.describe()));
1969 }
1970 }
1971 for (id, gap) in &self.vertices {
1972 let measured = MeasuredTolerance::new(*gap, self.band);
1973 if measured.exceeds_band() {
1974 out.push(format!("vertex {id} {}", measured.describe()));
1975 }
1976 }
1977 out
1978 }
1979}
1980
1981#[derive(Clone, Debug, Serialize)]
1982pub struct OffsetFaceCarrier {
1983 pub vertices: Vec<VertexRecord>,
1984 pub edges: Vec<EdgeRecord>,
1985 pub face: FaceRecord,
1986 #[serde(skip)]
1994 pub deviation: Option<CarrierDeviation>,
1995}
1996
1997fn claim_vertex_image(
1998 source_id: u64,
1999 point: Vec3,
2000 vertex_images: &mut HashMap<u64, u64>,
2001 vertices: &mut Vec<VertexRecord>,
2002 next_id: &mut u64,
2003) -> u64 {
2004 if let Some(id) = vertex_images.get(&source_id) {
2005 return *id;
2006 }
2007 let id = *next_id;
2008 *next_id += 1;
2009 vertices.push(VertexRecord { id, point });
2010 vertex_images.insert(source_id, id);
2011 id
2012}
2013
2014pub fn offset_face_carrier(
2015 solid: &BrepSolid,
2016 face_id: u64,
2017 distance: f64,
2018 planar_extension: f64,
2019) -> Result<OffsetFaceCarrier, KernelRefusal> {
2020 offset_face_carrier_impl(
2021 solid,
2022 face_id,
2023 distance,
2024 &CarrierExtension::uniform(planar_extension),
2025 false,
2026 )
2027}
2028
2029pub fn offset_face_carrier_sided(
2031 solid: &BrepSolid,
2032 face_id: u64,
2033 distance: f64,
2034 extension: &CarrierExtension,
2035) -> Result<OffsetFaceCarrier, KernelRefusal> {
2036 offset_face_carrier_impl(solid, face_id, distance, extension, false)
2037}
2038
2039pub fn offset_face_carrier_measured(
2059 solid: &BrepSolid,
2060 face_id: u64,
2061 distance: f64,
2062 planar_extension: f64,
2063) -> Result<OffsetFaceCarrier, KernelRefusal> {
2064 offset_face_carrier_impl(
2065 solid,
2066 face_id,
2067 distance,
2068 &CarrierExtension::uniform(planar_extension),
2069 true,
2070 )
2071}
2072
2073fn measure_carrier(
2084 surface: &NurbsSurface,
2085 vertices: &[VertexRecord],
2086 edges: &[EdgeRecord],
2087 loops: &[LoopRecord],
2088 lane: OffsetSurfaceLane,
2089 surface_fit: Option<MeasuredTolerance>,
2090 band: f64,
2091) -> Result<CarrierDeviation, KernelRefusal> {
2092 let edge_by_id: HashMap<u64, &EdgeRecord> = edges.iter().map(|edge| (edge.id, edge)).collect();
2093 let mut per_edge: HashMap<u64, MeasuredTolerance> = HashMap::default();
2097 for loop_record in loops {
2098 for coedge in &loop_record.coedges {
2099 let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
2100 continue;
2101 };
2102 let measured = measure_edge_against_pcurve_image(
2103 surface,
2104 &coedge.pcurve,
2105 edge,
2106 coedge.forward,
2107 band,
2108 )?;
2109 per_edge
2110 .entry(edge.id)
2111 .and_modify(|existing| *existing = existing.worse_of(measured))
2112 .or_insert(measured);
2113 }
2114 }
2115
2116 let mut measured_edges: Vec<(u64, MeasuredTolerance)> = per_edge.into_iter().collect();
2117 measured_edges.sort_by_key(|(id, _)| *id);
2118 let deviation_of: HashMap<u64, f64> = measured_edges
2119 .iter()
2120 .map(|(id, measured)| (*id, measured.deviation()))
2121 .collect();
2122
2123 let mut ends_at: HashMap<u64, Vec<(&EdgeRecord, f64)>> = HashMap::default();
2127 for edge in edges {
2128 ends_at
2129 .entry(edge.start_vertex_id)
2130 .or_default()
2131 .push((edge, edge.t0));
2132 ends_at
2133 .entry(edge.end_vertex_id)
2134 .or_default()
2135 .push((edge, edge.t1));
2136 }
2137
2138 let mut measured_vertices = Vec::with_capacity(vertices.len());
2139 for vertex in vertices {
2140 let mut gaps = Vec::new();
2141 let mut incident = Vec::new();
2142 for (edge, parameter) in ends_at.get(&vertex.id).into_iter().flatten() {
2143 gaps.push(vertex_endpoint_gap(
2144 vertex.point,
2145 edge.curve.evaluate(*parameter).or_refuse(KernelStage::Refine, "evaluate")?,
2146 ));
2147 incident.push(deviation_of.get(&edge.id).copied().unwrap_or(0.0));
2148 }
2149 measured_vertices.push((vertex.id, vertex_tolerance_from_edges(gaps, incident)));
2150 }
2151
2152 Ok(CarrierDeviation {
2153 band,
2154 lane,
2155 surface: surface_fit,
2156 edges: measured_edges,
2157 vertices: measured_vertices,
2158 })
2159}
2160
2161fn offset_face_carrier_impl(
2162 solid: &BrepSolid,
2163 face_id: u64,
2164 distance: f64,
2165 extension: &CarrierExtension,
2166 measure: bool,
2167) -> Result<OffsetFaceCarrier, KernelRefusal> {
2168 let source = solid
2169 .shells
2170 .iter()
2171 .flat_map(|shell| &shell.faces)
2172 .find(|face| face.id == face_id)
2173 .ok_or_else(|| format!("offset_face_carrier: missing face {face_id}")).or_refuse(KernelStage::Refine, "offset_missing_face")?;
2174 let band = offset_construction_band(solid_model_scale(solid));
2182 let fit_tolerance =
2189 crate::KernelTolerances::for_scale(solid_model_scale(solid), 1e-7).intersection_fit;
2190 let (surface, lane, surface_fit) = if measure {
2191 let measured = offset_surface_measured_sided(source, distance, extension, band)?;
2192 (measured.surface, measured.lane, measured.fit)
2193 } else {
2194 let (surface, lane) = offset_surface_with_lane(source, distance, extension)?;
2195 (surface, lane, None)
2196 };
2197 let source_edges = solid
2198 .edges
2199 .iter()
2200 .map(|edge| (edge.id, edge))
2201 .collect::<HashMap<_, _>>();
2202 let source_vertices = solid
2203 .vertices
2204 .iter()
2205 .map(|vertex| (vertex.id, vertex))
2206 .collect::<HashMap<_, _>>();
2207 let mut vertices = Vec::new();
2208 let mut vertex_images = HashMap::default();
2209 let mut edges = Vec::new();
2210 let mut edge_images = HashMap::default();
2211 let mut loops = Vec::new();
2212 let mut next_id = 1u64;
2213
2214 for source_loop in &source.loops {
2215 let mut coedges = Vec::new();
2216 for source_coedge in &source_loop.coedges {
2217 let source_edge = source_edges
2218 .get(&source_coedge.edge_id)
2219 .ok_or_else(|| "offset_face_carrier: missing source edge".to_string()).or_refuse(KernelStage::Refine, "offset_missing_edge")?;
2220 let (source_start, source_end) = if source_coedge.forward {
2221 (source_edge.start_vertex_id, source_edge.end_vertex_id)
2222 } else {
2223 (source_edge.end_vertex_id, source_edge.start_vertex_id)
2224 };
2225 if !source_vertices.contains_key(&source_start)
2226 || !source_vertices.contains_key(&source_end)
2227 {
2228 return Err(KernelRefusal::internal(KernelStage::Refine, "offset_missing_vertex", "offset_face_carrier: missing source vertex"));
2229 }
2230 let (points, parameters) =
2248 mapped_pcurve_polyline(&surface, &source_coedge.pcurve, false)?;
2249 let collapse_tolerance = 1e-6f64.max(distance.abs() * 1e-2);
2250 let image_collapsed = points
2251 .iter()
2252 .all(|point| point.sub(points[0]).length() <= collapse_tolerance);
2253 let (edge_id, forward) =
2254 if let Some((edge_id, edge_start_vertex_id, creator_forward)) =
2255 edge_images.get(&source_edge.id)
2256 {
2257 if source_start == source_end {
2258 (*edge_id, source_coedge.forward == *creator_forward)
2281 } else {
2282 (
2285 *edge_id,
2286 vertex_images.get(&source_start) == Some(edge_start_vertex_id),
2287 )
2288 }
2289 } else {
2290 let (curve, t0, t1) = if image_collapsed {
2291 let curve = NurbsCurve::new(
2292 1,
2293 vec![0.0, 0.0, 1.0, 1.0],
2294 vec![
2295 Vec4::from_point(points[0], 1.0),
2296 Vec4::from_point(points[0], 1.0),
2297 ],
2298 ).or_refuse(KernelStage::Refine, "new")?;
2299 (curve, 0.0, 1.0)
2300 } else {
2301 carrier_edge_curve(
2302 &surface,
2303 &source_coedge.pcurve,
2304 &points,
2305 ¶meters,
2306 fit_tolerance,
2307 )?
2308 };
2309 let start_vertex_id = claim_vertex_image(
2310 source_start,
2311 points[0],
2312 &mut vertex_images,
2313 &mut vertices,
2314 &mut next_id,
2315 );
2316 let end_vertex_id = claim_vertex_image(
2317 source_end,
2318 points[points.len() - 1],
2319 &mut vertex_images,
2320 &mut vertices,
2321 &mut next_id,
2322 );
2323 let id = next_id;
2324 next_id += 1;
2325 edges.push(EdgeRecord {
2326 id,
2327 curve,
2328 t0,
2329 t1,
2330 start_vertex_id,
2331 end_vertex_id,
2332 degenerate: image_collapsed,
2346 name: source_edge
2350 .name
2351 .as_ref()
2352 .map(|name| format!("{name}_Offset")),
2353 });
2354 edge_images.insert(
2355 source_edge.id,
2356 (id, start_vertex_id, source_coedge.forward),
2357 );
2358 (id, true)
2359 };
2360 let id = next_id;
2361 next_id += 1;
2362 coedges.push(CoedgeRecord {
2363 id,
2364 edge_id,
2365 forward,
2366 pcurve: source_coedge.pcurve.clone(),
2367 });
2368 }
2369 let id = next_id;
2370 next_id += 1;
2371 loops.push(LoopRecord { id, coedges });
2372 }
2373 let deviation = if measure {
2374 Some(measure_carrier(
2375 &surface,
2376 &vertices,
2377 &edges,
2378 &loops,
2379 lane,
2380 surface_fit,
2381 band,
2382 )?)
2383 } else {
2384 None
2385 };
2386 Ok(OffsetFaceCarrier {
2387 vertices,
2388 edges,
2389 face: FaceRecord {
2390 id: next_id,
2391 surface,
2392 same_sense: source.same_sense,
2393 loops,
2394 name: source.name.as_ref().map(|name| format!("{name}_Offset")),
2395 },
2396 deviation,
2397 })
2398}
2399