1use crate::coons::compat::{self, CompatWork};
85use crate::coons::{corner_band, WEIGHT_CLOSURE_BAND};
86use crate::{
87 intersect_curves, model_scale, solve_dense, KnotVector, NurbsCurve, NurbsSurface, Vec3, Vec4,
88 KNOT_IDENTITY_TOL,
89};
90
91
92const MAX_SKIN_DEGREE: usize = 3;
96
97const CURVE_SAMPLES: usize = 32;
100
101const APPROACH_SAMPLES: usize = 96;
104
105const CROSSING_ITERATIONS: usize = 60;
108
109#[derive(Clone, Debug, PartialEq)]
114pub enum GordonRefusal {
115 TooFewCurves { family: char, count: usize },
117 InvalidBand { band: f64 },
119 DegenerateCurve {
121 family: char,
122 curve: usize,
123 extent: f64,
124 band: f64,
125 },
126 ClosedCurve {
130 family: char,
131 curve: usize,
132 gap: f64,
133 band: f64,
134 },
135 CurvesDoNotMeet {
137 u_curve: usize,
138 v_curve: usize,
139 gap: f64,
140 band: f64,
141 },
142 MultipleCrossings {
145 u_curve: usize,
146 v_curve: usize,
147 crossings: Vec<(f64, f64)>,
148 band: f64,
149 },
150 CrossingOrder {
155 family: char,
156 curve: usize,
157 parameters: Vec<f64>,
158 },
159 CoincidentCrossings {
162 family: char,
163 first: usize,
164 second: usize,
165 separation: f64,
166 },
167 Overhang {
172 family: char,
173 curve: usize,
174 end: usize,
175 parameter: f64,
176 gap: f64,
177 band: f64,
178 },
179 InconsistentCrossing {
185 u_curve: usize,
186 v_curve: usize,
187 gap: f64,
188 band: f64,
189 u_parameter_offset: f64,
190 v_parameter_offset: f64,
191 },
192 WeightClosure {
196 u_curve: usize,
197 v_curve: usize,
198 ratio: f64,
199 band: f64,
200 },
201 NonPositiveWeight {
204 row: usize,
205 column: usize,
206 weight: f64,
207 },
208 Incompatible { family: char, detail: String },
211 Geometry(String),
213}
214
215impl std::fmt::Display for GordonRefusal {
216 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 match self {
218 Self::TooFewCurves { family, count } => write!(
219 formatter,
220 "gordon: the {family} family has {count} curve(s) — a network needs at least two \
221 per family"
222 ),
223 Self::InvalidBand { band } => write!(
224 formatter,
225 "gordon: crossing band {band:.3e} is not a positive finite distance"
226 ),
227 Self::DegenerateCurve {
228 family,
229 curve,
230 extent,
231 band,
232 } => write!(
233 formatter,
234 "gordon: {family}-curve {curve} is degenerate — extent {extent:.3e} is inside the \
235 band {band:.3e}"
236 ),
237 Self::ClosedCurve {
238 family,
239 curve,
240 gap,
241 band,
242 } => write!(
243 formatter,
244 "gordon: {family}-curve {curve} is closed — its ends are {gap:.3e} apart, inside \
245 the band {band:.3e}; periodic networks are not supported"
246 ),
247 Self::CurvesDoNotMeet {
248 u_curve,
249 v_curve,
250 gap,
251 band,
252 } => write!(
253 formatter,
254 "gordon: u-curve {u_curve} and v-curve {v_curve} do not meet — closest approach \
255 {gap:.3e} exceeds the crossing band {band:.3e}"
256 ),
257 Self::MultipleCrossings {
258 u_curve,
259 v_curve,
260 crossings,
261 band,
262 } => write!(
263 formatter,
264 "gordon: u-curve {u_curve} and v-curve {v_curve} meet {} times within the band \
265 {band:.3e} (at {crossings:?}) — a network pair must cross once",
266 crossings.len()
267 ),
268 Self::CrossingOrder {
269 family,
270 curve,
271 parameters,
272 } => write!(
273 formatter,
274 "gordon: along {family}-curve {curve} the crossings run out of order \
275 ({parameters:?}) — the families do not form a grid"
276 ),
277 Self::CoincidentCrossings {
278 family,
279 first,
280 second,
281 separation,
282 } => write!(
283 formatter,
284 "gordon: {family}-curves {first} and {second} cross the other family at the same \
285 parameter ({separation:.3e} apart)"
286 ),
287 Self::Overhang {
288 family,
289 curve,
290 end,
291 parameter,
292 gap,
293 band,
294 } => write!(
295 formatter,
296 "gordon: {family}-curve {curve} runs past the network boundary at its {} — the \
297 boundary crossing sits at parameter {parameter:.6}, {gap:.3e} along the curve \
298 (band {band:.3e})",
299 if *end == 0 { "start" } else { "end" }
300 ),
301 Self::InconsistentCrossing {
302 u_curve,
303 v_curve,
304 gap,
305 band,
306 u_parameter_offset,
307 v_parameter_offset,
308 } => write!(
309 formatter,
310 "gordon: u-curve {u_curve} and v-curve {v_curve} are {gap:.3e} apart at the \
311 families' shared crossing parameters (band {band:.3e}); their own crossing is \
312 offset by {u_parameter_offset:.3e} in u and {v_parameter_offset:.3e} in v"
313 ),
314 Self::WeightClosure {
315 u_curve,
316 v_curve,
317 ratio,
318 band,
319 } => write!(
320 formatter,
321 "gordon: rational weights do not reconcile at u-curve {u_curve} x v-curve \
322 {v_curve} — ratio {ratio:.12} (band {band:.3e} about 1)"
323 ),
324 Self::NonPositiveWeight {
325 row,
326 column,
327 weight,
328 } => write!(
329 formatter,
330 "gordon: blended control point ({row}, {column}) has non-positive weight \
331 {weight:.3e} — the surface would pass through infinity"
332 ),
333 Self::Incompatible { family, detail } => {
334 write!(formatter, "gordon: {family} family incompatible — {detail}")
335 }
336 Self::Geometry(message) => formatter.write_str(message),
337 }
338 }
339}
340
341impl From<GordonRefusal> for String {
343 fn from(refusal: GordonRefusal) -> Self {
344 refusal.to_string()
345 }
346}
347
348#[derive(Clone, Debug)]
354pub struct GordonReport {
355 pub u_order: Vec<usize>,
357 pub v_order: Vec<usize>,
359 pub u_reversed: Vec<bool>,
361 pub v_reversed: Vec<bool>,
363 pub u_parameters: Vec<f64>,
366 pub v_parameters: Vec<f64>,
368 pub crossing_band: f64,
370 pub closest_approach: f64,
372 pub crossing_gap: f64,
379 pub parameter_spread: f64,
381 pub u_weight_scales: Vec<f64>,
385 pub v_weight_scales: Vec<f64>,
387 pub weight_closure: f64,
389 pub u_work: Vec<CompatWork>,
391 pub v_work: Vec<CompatWork>,
393 pub skin_degree_u: usize,
395 pub skin_degree_v: usize,
397 pub degree_u: usize,
398 pub degree_v: usize,
399 pub control_rows: usize,
400 pub control_columns: usize,
401 pub min_weight: f64,
403 pub interpolation_bound: f64,
417 pub sampled_interpolation_residual: f64,
421}
422
423pub fn gordon_surface(
430 u_curves: &[NurbsCurve],
431 v_curves: &[NurbsCurve],
432) -> Result<NurbsSurface, GordonRefusal> {
433 gordon_surface_reported(u_curves, v_curves, None).map(|(surface, _)| surface)
434}
435
436pub fn gordon_surface_reported(
445 u_curves: &[NurbsCurve],
446 v_curves: &[NurbsCurve],
447 crossing_band: Option<f64>,
448) -> Result<(NurbsSurface, GordonReport), GordonRefusal> {
449 let network = prepare(u_curves, v_curves, crossing_band)?;
450 build(network)
451}
452
453struct Family {
455 order: Vec<usize>,
457 curves: Vec<NurbsCurve>,
459 reversed: Vec<bool>,
461 weight_scales: Vec<f64>,
463 work: Vec<CompatWork>,
465 parameters: Vec<f64>,
468}
469
470struct Network {
472 u: Family,
473 v: Family,
474 crossings: Vec<Vec<Vec4>>,
477 band: f64,
478 closest_approach: f64,
479 crossing_gap: f64,
480 parameter_spread: f64,
481 weight_closure: f64,
482}
483
484fn samples(curve: &NurbsCurve, count: usize) -> Result<Vec<Vec3>, String> {
486 let [start, end] = curve.domain()?;
487 (0..=count)
488 .map(|index| curve.evaluate(start + (end - start) * index as f64 / count as f64))
489 .collect()
490}
491
492fn geometry(message: String) -> GordonRefusal {
493 GordonRefusal::Geometry(message)
494}
495
496fn homogeneous_distance(first: Vec4, second: Vec4) -> f64 {
498 ((first.x - second.x).powi(2)
499 + (first.y - second.y).powi(2)
500 + (first.z - second.z).powi(2)
501 + (first.w - second.w).powi(2))
502 .sqrt()
503}
504
505fn homogeneous_midpoint(first: Vec4, second: Vec4) -> Vec4 {
508 first.add(second).scale(0.5)
509}
510
511fn polish_crossing(
520 first: &NurbsCurve,
521 second: &NurbsCurve,
522 seed_s: f64,
523 seed_t: f64,
524) -> Result<(f64, f64, f64), String> {
525 let mut s = seed_s.clamp(0.0, 1.0);
526 let mut t = seed_t.clamp(0.0, 1.0);
527 let mut gap = first.evaluate(s)?.sub(second.evaluate(t)?).length();
528 for _ in 0..CROSSING_ITERATIONS {
529 let a = first.derivatives(s, 2)?;
530 let b = second.derivatives(t, 2)?;
531 let residual = a[0].sub(b[0]);
532 let gradient_s = a[1].dot(residual);
533 let gradient_t = -b[1].dot(residual);
534 let h_ss = a[1].dot(a[1]) + a[2].dot(residual);
535 let h_st = -a[1].dot(b[1]);
536 let h_tt = b[1].dot(b[1]) - b[2].dot(residual);
537 let determinant = h_ss * h_tt - h_st * h_st;
538 let (mut step_s, mut step_t) = if determinant.abs() > f64::MIN_POSITIVE {
539 (
540 (-gradient_s * h_tt + gradient_t * h_st) / determinant,
541 (-gradient_t * h_ss + gradient_s * h_st) / determinant,
542 )
543 } else {
544 (0.0, 0.0)
545 };
546 let pinned_s = (s <= 0.0 && step_s < 0.0) || (s >= 1.0 && step_s > 0.0);
547 let pinned_t = (t <= 0.0 && step_t < 0.0) || (t >= 1.0 && step_t > 0.0);
548 if pinned_s && !pinned_t {
549 step_s = 0.0;
550 step_t = if h_tt.abs() > f64::MIN_POSITIVE { -gradient_t / h_tt } else { 0.0 };
551 } else if pinned_t && !pinned_s {
552 step_t = 0.0;
553 step_s = if h_ss.abs() > f64::MIN_POSITIVE { -gradient_s / h_ss } else { 0.0 };
554 } else if pinned_s && pinned_t {
555 break;
556 }
557 let next_s = (s + step_s).clamp(0.0, 1.0);
558 let next_t = (t + step_t).clamp(0.0, 1.0);
559 if next_s == s && next_t == t {
560 break;
561 }
562 let next_gap = first.evaluate(next_s)?.sub(second.evaluate(next_t)?).length();
563 if next_gap > gap {
564 break;
565 }
566 s = next_s;
567 t = next_t;
568 gap = next_gap;
569 }
570 Ok((s, t, gap))
571}
572
573fn closest_approach(first: &NurbsCurve, second: &NurbsCurve) -> Result<(f64, f64, f64), String> {
576 let first_points = samples(first, APPROACH_SAMPLES)?;
577 let second_points = samples(second, APPROACH_SAMPLES)?;
578 let mut best = (0usize, 0usize, f64::INFINITY);
579 for (i, a) in first_points.iter().enumerate() {
580 for (j, b) in second_points.iter().enumerate() {
581 let distance = a.sub(*b).length();
582 if distance < best.2 {
583 best = (i, j, distance);
584 }
585 }
586 }
587 polish_crossing(
588 first,
589 second,
590 best.0 as f64 / APPROACH_SAMPLES as f64,
591 best.1 as f64 / APPROACH_SAMPLES as f64,
592 )
593}
594
595fn median(values: &[f64]) -> f64 {
598 let mut sorted = values.to_vec();
599 sorted.sort_by(f64::total_cmp);
600 let middle = sorted.len() / 2;
601 if sorted.len() % 2 == 1 {
602 sorted[middle]
603 } else if sorted[middle - 1] == sorted[middle] {
604 sorted[middle]
605 } else {
606 0.5 * (sorted[middle - 1] + sorted[middle])
607 }
608}
609
610fn orientation(
615 family: char,
616 curve: usize,
617 sequence: &[f64],
618 crossing_family: char,
619 crossing_order: &[usize],
620) -> Result<bool, GordonRefusal> {
621 for (index, pair) in sequence.windows(2).enumerate() {
622 let separation = (pair[1] - pair[0]).abs();
623 if separation <= KNOT_IDENTITY_TOL {
624 return Err(GordonRefusal::CoincidentCrossings {
625 family: crossing_family,
626 first: crossing_order[index],
627 second: crossing_order[index + 1],
628 separation,
629 });
630 }
631 }
632 if sequence.windows(2).all(|pair| pair[1] > pair[0]) {
633 Ok(false)
634 } else if sequence.windows(2).all(|pair| pair[1] < pair[0]) {
635 Ok(true)
636 } else {
637 Err(GordonRefusal::CrossingOrder {
638 family,
639 curve,
640 parameters: sequence.to_vec(),
641 })
642 }
643}
644
645fn prepare(
648 u_input: &[NurbsCurve],
649 v_input: &[NurbsCurve],
650 crossing_band: Option<f64>,
651) -> Result<Network, GordonRefusal> {
652 for (family, curves) in [('u', u_input), ('v', v_input)] {
653 if curves.len() < 2 {
654 return Err(GordonRefusal::TooFewCurves {
655 family,
656 count: curves.len(),
657 });
658 }
659 }
660 if let Some(band) = crossing_band {
661 if !(band > 0.0 && band.is_finite()) {
662 return Err(GordonRefusal::InvalidBand { band });
663 }
664 }
665
666 let (u_curves, u_work) = normalise_domains(u_input)?;
668 let (v_curves, v_work) = normalise_domains(v_input)?;
669 let mut points: Vec<Vec3> = Vec::new();
670 for curve in u_curves.iter().chain(&v_curves) {
671 points.extend(samples(curve, CURVE_SAMPLES).map_err(geometry)?);
672 }
673 let band = crossing_band.unwrap_or_else(|| corner_band(model_scale(points.iter().copied())));
674 judge_curves('u', &u_curves, band)?;
675 judge_curves('v', &v_curves, band)?;
676
677 let crossings = find_crossings(&u_curves, &v_curves, band)?;
678 let order = order_network(&crossings)?;
679
680 let f = oriented(&u_curves, &order.u_order, &order.u_reversed)?;
683 let g = oriented(&v_curves, &order.v_order, &order.v_reversed)?;
684 let (rows, columns) = (f.len(), g.len());
685 let mut own_s = vec![vec![0.0f64; columns]; rows];
687 let mut own_t = vec![vec![0.0f64; columns]; rows];
688 for a in 0..rows {
689 for b in 0..columns {
690 let (j, i) = (order.u_order[a], order.v_order[b]);
691 let (s, t) = (crossings.s[j][i], crossings.t[j][i]);
692 let seed_s = if order.u_reversed[j] { 1.0 - s } else { s };
693 let seed_t = if order.v_reversed[i] { 1.0 - t } else { t };
694 let (polished_s, polished_t, _) =
695 polish_crossing(&f[a], &g[b], seed_s, seed_t).map_err(geometry)?;
696 own_s[a][b] = polished_s;
697 own_t[a][b] = polished_t;
698 }
699 }
700
701 let u_parameters = shared_parameters(columns, |b| (0..rows).map(|a| own_s[a][b]).collect());
704 let v_parameters = shared_parameters(rows, |a| (0..columns).map(|b| own_t[a][b]).collect());
705 for (family, parameters, network_order) in [
706 ('v', &u_parameters, &order.v_order),
707 ('u', &v_parameters, &order.u_order),
708 ] {
709 for (index, pair) in parameters.windows(2).enumerate() {
710 let separation = pair[1] - pair[0];
711 if separation <= KNOT_IDENTITY_TOL {
712 return Err(GordonRefusal::CoincidentCrossings {
713 family,
714 first: network_order[index],
715 second: network_order[index + 1],
716 separation,
717 });
718 }
719 }
720 }
721 let shared = SharedCrossings {
722 f: &f,
723 g: &g,
724 u_parameters: &u_parameters,
725 v_parameters: &v_parameters,
726 own_s: &own_s,
727 own_t: &own_t,
728 u_order: &order.u_order,
729 v_order: &order.v_order,
730 };
731 let (crossing_gap, parameter_spread) = judge_shared_crossings(&shared, band)?;
732 let (f_scales, g_scales, weight_closure) = reconcile_weights(&shared)?;
733 let f_scaled = scaled(&f, &f_scales)?;
734 let g_scaled = scaled(&g, &g_scales)?;
735 let grid = crossing_grid(&f_scaled, &g_scaled, &u_parameters, &v_parameters)?;
736
737 let mut u_weight_scales = vec![1.0f64; rows];
739 for (a, &j) in order.u_order.iter().enumerate() {
740 u_weight_scales[j] = f_scales[a];
741 }
742 let mut v_weight_scales = vec![1.0f64; columns];
743 for (b, &i) in order.v_order.iter().enumerate() {
744 v_weight_scales[i] = g_scales[b];
745 }
746 Ok(Network {
749 u: Family {
750 order: order.u_order,
751 curves: f_scaled,
752 reversed: order.u_reversed,
753 weight_scales: u_weight_scales,
754 work: u_work,
755 parameters: u_parameters,
756 },
757 v: Family {
758 order: order.v_order,
759 curves: g_scaled,
760 reversed: order.v_reversed,
761 weight_scales: v_weight_scales,
762 work: v_work,
763 parameters: v_parameters,
764 },
765 crossings: grid,
766 band,
767 closest_approach: crossings.closest,
768 crossing_gap,
769 parameter_spread,
770 weight_closure,
771 })
772}
773
774fn normalise_domains(
776 curves: &[NurbsCurve],
777) -> Result<(Vec<NurbsCurve>, Vec<CompatWork>), GordonRefusal> {
778 let mut normalised = Vec::with_capacity(curves.len());
779 let mut work = vec![CompatWork::default(); curves.len()];
780 for (index, curve) in curves.iter().enumerate() {
781 let (moved, remapped) = compat::normalise_domain(curve).map_err(geometry)?;
782 work[index].domain_remapped = remapped;
783 normalised.push(moved);
784 }
785 Ok((normalised, work))
786}
787
788fn judge_curves(family: char, curves: &[NurbsCurve], band: f64) -> Result<(), GordonRefusal> {
790 for (index, curve) in curves.iter().enumerate() {
791 let points = samples(curve, CURVE_SAMPLES).map_err(geometry)?;
792 let spread = points
793 .iter()
794 .map(|point| point.sub(points[0]).length())
795 .fold(0.0f64, f64::max);
796 let extent = model_scale(points.iter().copied()).min(spread);
797 if extent <= band {
798 return Err(GordonRefusal::DegenerateCurve {
799 family,
800 curve: index,
801 extent,
802 band,
803 });
804 }
805 let gap = points[0].sub(points[points.len() - 1]).length();
806 if gap <= band {
807 return Err(GordonRefusal::ClosedCurve {
808 family,
809 curve: index,
810 gap,
811 band,
812 });
813 }
814 }
815 Ok(())
816}
817
818struct Crossings {
820 s: Vec<Vec<f64>>,
823 t: Vec<Vec<f64>>,
825 closest: f64,
827}
828
829fn find_crossings(
832 u_curves: &[NurbsCurve],
833 v_curves: &[NurbsCurve],
834 band: f64,
835) -> Result<Crossings, GordonRefusal> {
836 let mut crossings = Crossings {
837 s: vec![vec![0.0f64; v_curves.len()]; u_curves.len()],
838 t: vec![vec![0.0f64; v_curves.len()]; u_curves.len()],
839 closest: 0.0,
840 };
841 for (j, u_curve) in u_curves.iter().enumerate() {
842 for (i, v_curve) in v_curves.iter().enumerate() {
843 let hits = intersect_curves(u_curve, v_curve, band).map_err(geometry)?;
844 let (s, t, gap) = match hits.len() {
845 1 => polish_crossing(u_curve, v_curve, hits[0].s, hits[0].t).map_err(geometry)?,
846 0 => {
847 let approach = closest_approach(u_curve, v_curve).map_err(geometry)?;
848 if approach.2 > band {
849 return Err(GordonRefusal::CurvesDoNotMeet {
850 u_curve: j,
851 v_curve: i,
852 gap: approach.2,
853 band,
854 });
855 }
856 approach
857 }
858 _ => {
859 return Err(GordonRefusal::MultipleCrossings {
860 u_curve: j,
861 v_curve: i,
862 crossings: hits.iter().map(|hit| (hit.s, hit.t)).collect(),
863 band,
864 })
865 }
866 };
867 crossings.s[j][i] = s;
868 crossings.t[j][i] = t;
869 crossings.closest = crossings.closest.max(gap);
870 }
871 }
872 Ok(crossings)
873}
874
875struct NetworkOrder {
877 u_order: Vec<usize>,
879 v_order: Vec<usize>,
880 u_reversed: Vec<bool>,
882 v_reversed: Vec<bool>,
883}
884
885fn order_network(crossings: &Crossings) -> Result<NetworkOrder, GordonRefusal> {
889 let (s, t) = (&crossings.s, &crossings.t);
890 let (rows, columns) = (s.len(), s[0].len());
891 let mut v_order: Vec<usize> = (0..columns).collect();
892 v_order.sort_by(|&a, &b| s[0][a].total_cmp(&s[0][b]));
893 let mut u_order: Vec<usize> = (0..rows).collect();
894 u_order.sort_by(|&a, &b| t[a][0].total_cmp(&t[b][0]));
895 let mut u_reversed = vec![false; rows];
896 for (j, reversed) in u_reversed.iter_mut().enumerate() {
897 let sequence: Vec<f64> = v_order.iter().map(|&i| s[j][i]).collect();
898 *reversed = orientation('u', j, &sequence, 'v', &v_order)?;
899 }
900 let mut v_reversed = vec![false; columns];
901 for (i, reversed) in v_reversed.iter_mut().enumerate() {
902 let sequence: Vec<f64> = u_order.iter().map(|&j| t[j][i]).collect();
903 *reversed = orientation('v', i, &sequence, 'u', &u_order)?;
904 }
905 Ok(NetworkOrder {
906 u_order,
907 v_order,
908 u_reversed,
909 v_reversed,
910 })
911}
912
913fn oriented(
915 curves: &[NurbsCurve],
916 order: &[usize],
917 reversed: &[bool],
918) -> Result<Vec<NurbsCurve>, GordonRefusal> {
919 order
920 .iter()
921 .map(|&index| {
922 if reversed[index] {
923 curves[index].reversed().map_err(geometry)
924 } else {
925 Ok(curves[index].clone())
926 }
927 })
928 .collect()
929}
930
931fn shared_parameters(count: usize, own: impl Fn(usize) -> Vec<f64>) -> Vec<f64> {
934 (0..count)
935 .map(|index| {
936 if index == 0 {
937 0.0
938 } else if index == count - 1 {
939 1.0
940 } else {
941 median(&own(index))
942 }
943 })
944 .collect()
945}
946
947struct SharedCrossings<'a> {
949 f: &'a [NurbsCurve],
950 g: &'a [NurbsCurve],
951 u_parameters: &'a [f64],
952 v_parameters: &'a [f64],
953 own_s: &'a [Vec<f64>],
954 own_t: &'a [Vec<f64>],
955 u_order: &'a [usize],
956 v_order: &'a [usize],
957}
958
959fn judge_shared_crossings(
964 shared: &SharedCrossings<'_>,
965 band: f64,
966) -> Result<(f64, f64), GordonRefusal> {
967 let (rows, columns) = (shared.f.len(), shared.g.len());
968 let mut crossing_gap = 0.0f64;
969 let mut spread = 0.0f64;
970 let mut worst: Option<GordonRefusal> = None;
971 for a in 0..rows {
972 for b in 0..columns {
973 let own_s = shared.own_s[a][b];
974 let own_t = shared.own_t[a][b];
975 let on_u = shared.f[a].evaluate(shared.u_parameters[b]).map_err(geometry)?;
976 let on_v = shared.g[b].evaluate(shared.v_parameters[a]).map_err(geometry)?;
977 let gap = on_u.sub(on_v).length();
978 let u_offset = (own_s - shared.u_parameters[b]).abs();
979 let v_offset = (own_t - shared.v_parameters[a]).abs();
980 if b == 0 || b == columns - 1 {
981 let along = on_u.sub(shared.f[a].evaluate(own_s).map_err(geometry)?).length();
982 if along > band {
983 return Err(GordonRefusal::Overhang {
984 family: 'u',
985 curve: shared.u_order[a],
986 end: usize::from(b == columns - 1),
987 parameter: own_s,
988 gap: along,
989 band,
990 });
991 }
992 }
993 if a == 0 || a == rows - 1 {
994 let along = on_v.sub(shared.g[b].evaluate(own_t).map_err(geometry)?).length();
995 if along > band {
996 return Err(GordonRefusal::Overhang {
997 family: 'v',
998 curve: shared.v_order[b],
999 end: usize::from(a == rows - 1),
1000 parameter: own_t,
1001 gap: along,
1002 band,
1003 });
1004 }
1005 }
1006 if gap > band && gap > crossing_gap {
1007 worst = Some(GordonRefusal::InconsistentCrossing {
1008 u_curve: shared.u_order[a],
1009 v_curve: shared.v_order[b],
1010 gap,
1011 band,
1012 u_parameter_offset: u_offset,
1013 v_parameter_offset: v_offset,
1014 });
1015 }
1016 crossing_gap = crossing_gap.max(gap);
1017 spread = spread.max(u_offset).max(v_offset);
1018 }
1019 }
1020 match worst {
1021 Some(refusal) => Err(refusal),
1022 None => Ok((crossing_gap, spread)),
1023 }
1024}
1025
1026fn reconcile_weights(
1035 shared: &SharedCrossings<'_>,
1036) -> Result<(Vec<f64>, Vec<f64>, f64), GordonRefusal> {
1037 let (rows, columns) = (shared.f.len(), shared.g.len());
1038 let mut f_scales = vec![1.0f64; rows];
1039 let mut g_scales = vec![1.0f64; columns];
1040 let polynomial = shared
1041 .f
1042 .iter()
1043 .chain(shared.g)
1044 .all(|curve| curve.control_points.iter().all(|point| point.w == 1.0));
1045 if polynomial {
1046 return Ok((f_scales, g_scales, 1.0));
1047 }
1048 let mut f_weight = vec![vec![1.0f64; columns]; rows];
1049 let mut g_weight = vec![vec![1.0f64; columns]; rows];
1050 for a in 0..rows {
1051 for b in 0..columns {
1052 f_weight[a][b] = shared.f[a]
1053 .evaluate_homogeneous(shared.u_parameters[b])
1054 .map_err(geometry)?
1055 .w;
1056 g_weight[a][b] = shared.g[b]
1057 .evaluate_homogeneous(shared.v_parameters[a])
1058 .map_err(geometry)?
1059 .w;
1060 }
1061 }
1062 for b in 0..columns {
1063 g_scales[b] = f_weight[0][b] / g_weight[0][b];
1064 }
1065 for a in 1..rows {
1066 f_scales[a] = g_scales[0] * g_weight[a][0] / f_weight[a][0];
1067 }
1068 let mut weight_closure = 1.0f64;
1069 for a in 0..rows {
1070 for b in 0..columns {
1071 let ratio = f_scales[a] * f_weight[a][b] / (g_scales[b] * g_weight[a][b]);
1072 if (ratio - 1.0).abs() > (weight_closure - 1.0).abs() {
1073 weight_closure = ratio;
1074 }
1075 if (ratio - 1.0).abs() > WEIGHT_CLOSURE_BAND {
1076 return Err(GordonRefusal::WeightClosure {
1077 u_curve: shared.u_order[a],
1078 v_curve: shared.v_order[b],
1079 ratio,
1080 band: WEIGHT_CLOSURE_BAND,
1081 });
1082 }
1083 }
1084 }
1085 Ok((f_scales, g_scales, weight_closure))
1086}
1087
1088fn scaled(curves: &[NurbsCurve], scales: &[f64]) -> Result<Vec<NurbsCurve>, GordonRefusal> {
1091 curves
1092 .iter()
1093 .zip(scales)
1094 .map(|(curve, &factor)| {
1095 if factor == 1.0 {
1096 return Ok(curve.clone());
1097 }
1098 NurbsCurve::new(
1099 curve.degree,
1100 curve.knots.clone(),
1101 curve.control_points.iter().map(|point| point.scale(factor)).collect(),
1102 )
1103 .map_err(geometry)
1104 })
1105 .collect()
1106}
1107
1108fn crossing_grid(
1119 f: &[NurbsCurve],
1120 g: &[NurbsCurve],
1121 u_parameters: &[f64],
1122 v_parameters: &[f64],
1123) -> Result<Vec<Vec<Vec4>>, GordonRefusal> {
1124 let (last_row, last_column) = (f.len() - 1, g.len() - 1);
1125 let mut grid = Vec::with_capacity(f.len());
1126 for (a, u_curve) in f.iter().enumerate() {
1127 let mut row = Vec::with_capacity(g.len());
1128 for (b, v_curve) in g.iter().enumerate() {
1129 let on_u = u_curve.evaluate_homogeneous(u_parameters[b]).map_err(geometry)?;
1130 let on_v = v_curve.evaluate_homogeneous(v_parameters[a]).map_err(geometry)?;
1131 let boundary_u = a == 0 || a == last_row;
1132 let boundary_v = b == 0 || b == last_column;
1133 row.push(match (boundary_u, boundary_v) {
1134 (true, false) => on_v,
1135 (false, true) => on_u,
1136 _ => homogeneous_midpoint(on_u, on_v),
1137 });
1138 }
1139 grid.push(row);
1140 }
1141 Ok(grid)
1142}
1143
1144fn cardinal_functions(parameters: &[f64]) -> Result<(usize, Vec<NurbsCurve>), String> {
1155 let count = parameters.len();
1156 let degree = MAX_SKIN_DEGREE.min(count - 1);
1157 let mut knots = vec![0.0f64; count + degree + 1];
1158 for knot in &mut knots[count..] {
1159 *knot = 1.0;
1160 }
1161 for j in 1..count - degree {
1162 knots[j + degree] = parameters[j..j + degree].iter().sum::<f64>() / degree as f64;
1163 }
1164 let knot_vector = KnotVector::new(knots.clone(), degree)?;
1165 let mut matrix = vec![vec![0.0f64; count]; count];
1166 for (row, ¶meter) in parameters.iter().enumerate() {
1167 let span = knot_vector.find_span(parameter);
1168 for (offset, value) in knot_vector
1169 .basis_functions(span, parameter)
1170 .into_iter()
1171 .enumerate()
1172 {
1173 matrix[row][span - degree + offset] = value;
1174 }
1175 }
1176 let mut functions = Vec::with_capacity(count);
1177 for j in 0..count {
1178 let mut rhs = vec![0.0f64; count];
1179 rhs[j] = 1.0;
1180 let mut coefficients = solve_dense(matrix.clone(), rhs)
1181 .map_err(|error| format!("cardinal collocation at {parameters:?}: {error}"))?;
1182 coefficients[0] = if j == 0 { 1.0 } else { 0.0 };
1183 coefficients[count - 1] = if j == count - 1 { 1.0 } else { 0.0 };
1184 functions.push(NurbsCurve::new(
1185 degree,
1186 knots.clone(),
1187 coefficients
1188 .into_iter()
1189 .map(|value| Vec4 { x: value, y: 0.0, z: 0.0, w: 1.0 })
1190 .collect(),
1191 )?);
1192 }
1193 Ok((degree, functions))
1194}
1195
1196fn compatible_direction(
1200 family: char,
1201 curves: &[NurbsCurve],
1202 cardinals: &[NurbsCurve],
1203) -> Result<(Vec<NurbsCurve>, Vec<Vec<f64>>, Vec<CompatWork>), GordonRefusal> {
1204 let combined: Vec<NurbsCurve> = curves.iter().chain(cardinals).cloned().collect();
1205 let (compatible, work) = compat::make_family_compatible(&combined, 1)
1206 .map_err(|detail| GordonRefusal::Incompatible { family, detail })?;
1207 let coefficients = compatible[curves.len()..]
1208 .iter()
1209 .map(|function| function.control_points.iter().map(|point| point.x).collect())
1210 .collect();
1211 Ok((
1212 compatible[..curves.len()].to_vec(),
1213 coefficients,
1214 work[..curves.len()].to_vec(),
1215 ))
1216}
1217
1218fn build(network: Network) -> Result<(NurbsSurface, GordonReport), GordonRefusal> {
1220 let Network {
1221 mut u,
1222 mut v,
1223 crossings,
1224 band,
1225 closest_approach,
1226 crossing_gap,
1227 parameter_spread,
1228 weight_closure,
1229 } = network;
1230 let rows_in_network = u.curves.len();
1231 let columns_in_network = v.curves.len();
1232
1233 let (skin_degree_u, alphas) = cardinal_functions(&u.parameters).map_err(|detail| {
1236 GordonRefusal::Incompatible {
1237 family: 'v',
1238 detail,
1239 }
1240 })?;
1241 let (skin_degree_v, betas) = cardinal_functions(&v.parameters).map_err(|detail| {
1242 GordonRefusal::Incompatible {
1243 family: 'u',
1244 detail,
1245 }
1246 })?;
1247 let (f, alpha, f_work) = compatible_direction('u', &u.curves, &alphas)?;
1248 let (g, beta, g_work) = compatible_direction('v', &v.curves, &betas)?;
1249 for (a, &j) in u.order.iter().enumerate() {
1250 u.work[j].elevated_by = f_work[a].elevated_by;
1251 u.work[j].knots_inserted = f_work[a].knots_inserted;
1252 }
1253 for (b, &i) in v.order.iter().enumerate() {
1254 v.work[i].elevated_by = g_work[b].elevated_by;
1255 v.work[i].knots_inserted = g_work[b].knots_inserted;
1256 }
1257
1258 let rows = f[0].control_points.len();
1259 let columns = g[0].control_points.len();
1260 let zero = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
1261 let mut net = vec![vec![zero; columns]; rows];
1262 let crossing_rows: Vec<Vec<Vec4>> = (0..rows)
1264 .map(|k| {
1265 (0..rows_in_network)
1266 .map(|a| {
1267 (0..columns_in_network).fold(zero, |sum, b| {
1268 sum.add(crossings[a][b].scale(alpha[b][k]))
1269 })
1270 })
1271 .collect()
1272 })
1273 .collect();
1274 for (k, net_row) in net.iter_mut().enumerate() {
1275 for (l, cell) in net_row.iter_mut().enumerate() {
1276 let mut sum = zero;
1277 for a in 0..rows_in_network {
1278 sum = sum.add(
1280 f[a].control_points[k]
1281 .add(crossing_rows[k][a].scale(-1.0))
1282 .scale(beta[a][l]),
1283 );
1284 }
1285 for b in 0..columns_in_network {
1286 sum = sum.add(g[b].control_points[l].scale(alpha[b][k]));
1287 }
1288 *cell = sum;
1289 }
1290 }
1291 let (last_row, last_column) = (rows - 1, columns - 1);
1295 let (f_first, f_last) = (&f[0], &f[rows_in_network - 1]);
1296 let (g_first, g_last) = (&g[0], &g[columns_in_network - 1]);
1297 for (k, net_row) in net.iter_mut().enumerate() {
1298 net_row[0] = f_first.control_points[k];
1299 net_row[last_column] = f_last.control_points[k];
1300 }
1301 for l in 0..columns {
1302 net[0][l] = g_first.control_points[l];
1303 net[last_row][l] = g_last.control_points[l];
1304 }
1305 net[0][0] = homogeneous_midpoint(f_first.control_points[0], g_first.control_points[0]);
1306 net[last_row][0] =
1307 homogeneous_midpoint(f_first.control_points[last_row], g_last.control_points[0]);
1308 net[0][last_column] =
1309 homogeneous_midpoint(f_last.control_points[0], g_first.control_points[last_column]);
1310 net[last_row][last_column] = homogeneous_midpoint(
1311 f_last.control_points[last_row],
1312 g_last.control_points[last_column],
1313 );
1314
1315 let mut min_weight = f64::INFINITY;
1316 let mut worst = (0usize, 0usize);
1317 for (row, net_row) in net.iter().enumerate() {
1318 for (column, cell) in net_row.iter().enumerate() {
1319 if !(cell.w >= min_weight) {
1320 min_weight = cell.w;
1321 worst = (row, column);
1322 }
1323 }
1324 }
1325 if !(min_weight > 0.0) || !min_weight.is_finite() {
1326 return Err(GordonRefusal::NonPositiveWeight {
1327 row: worst.0,
1328 column: worst.1,
1329 weight: min_weight,
1330 });
1331 }
1332
1333 let surface = NurbsSurface::new(
1334 f[0].degree,
1335 g[0].degree,
1336 f[0].knots.clone(),
1337 g[0].knots.clone(),
1338 net,
1339 )
1340 .map_err(geometry)?;
1341
1342 let mut interpolation_bound = 0.0f64;
1346 let mut sampled = 0.0f64;
1347 for (a, curve) in f.iter().enumerate() {
1348 let iso = surface.iso_curve_v(v.parameters[a]).map_err(geometry)?;
1349 for (on_surface, on_curve) in iso.control_points.iter().zip(&curve.control_points) {
1350 interpolation_bound =
1351 interpolation_bound.max(homogeneous_distance(*on_surface, *on_curve));
1352 }
1353 for index in 0..=CURVE_SAMPLES {
1354 let parameter = index as f64 / CURVE_SAMPLES as f64;
1355 let on_surface = surface.evaluate(parameter, v.parameters[a]).map_err(geometry)?;
1356 let on_curve = u.curves[a].evaluate(parameter).map_err(geometry)?;
1357 sampled = sampled.max(on_surface.sub(on_curve).length());
1358 }
1359 }
1360 for (b, curve) in g.iter().enumerate() {
1361 let iso = surface.iso_curve_u(u.parameters[b]).map_err(geometry)?;
1362 for (on_surface, on_curve) in iso.control_points.iter().zip(&curve.control_points) {
1363 interpolation_bound =
1364 interpolation_bound.max(homogeneous_distance(*on_surface, *on_curve));
1365 }
1366 for index in 0..=CURVE_SAMPLES {
1367 let parameter = index as f64 / CURVE_SAMPLES as f64;
1368 let on_surface = surface.evaluate(u.parameters[b], parameter).map_err(geometry)?;
1369 let on_curve = v.curves[b].evaluate(parameter).map_err(geometry)?;
1370 sampled = sampled.max(on_surface.sub(on_curve).length());
1371 }
1372 }
1373
1374 let report = GordonReport {
1375 u_order: u.order,
1376 v_order: v.order,
1377 u_reversed: u.reversed,
1378 v_reversed: v.reversed,
1379 u_parameters: u.parameters,
1380 v_parameters: v.parameters,
1381 crossing_band: band,
1382 closest_approach,
1383 crossing_gap,
1384 parameter_spread,
1385 u_weight_scales: u.weight_scales,
1386 v_weight_scales: v.weight_scales,
1387 weight_closure,
1388 u_work: u.work,
1389 v_work: v.work,
1390 skin_degree_u,
1391 skin_degree_v,
1392 degree_u: surface.degree_u,
1393 degree_v: surface.degree_v,
1394 control_rows: rows,
1395 control_columns: columns,
1396 min_weight,
1397 interpolation_bound,
1398 sampled_interpolation_residual: sampled,
1399 };
1400 Ok((surface, report))
1401}