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