1use crate::coons::homogeneous_distance;
101use crate::coons::compat::{self, CompatWork};
102use crate::coons::{corner_band, WEIGHT_CLOSURE_BAND};
103use crate::{
104 intersect_curves, model_scale, solve_dense, KnotVector, NurbsCurve, NurbsSurface, Vec3, Vec4,
105 KNOT_IDENTITY_TOL,
106};
107
108#[path = "gordon/periodic.rs"]
109mod periodic;
110use periodic::direction_cardinals;
111
112
113const MAX_SKIN_DEGREE: usize = 3;
117
118const CURVE_SAMPLES: usize = 32;
121
122const APPROACH_SAMPLES: usize = 96;
125
126const CROSSING_ITERATIONS: usize = 60;
129
130#[derive(Clone, Debug, PartialEq)]
135pub enum GordonRefusal {
136 TooFewCurves { family: char, count: usize },
138 InvalidBand { band: f64 },
140 DegenerateCurve {
142 family: char,
143 curve: usize,
144 extent: f64,
145 band: f64,
146 },
147 ClosedCurve {
150 family: char,
151 curve: usize,
152 gap: f64,
153 band: f64,
154 },
155 CurvesDoNotMeet {
157 u_curve: usize,
158 v_curve: usize,
159 gap: f64,
160 band: f64,
161 },
162 MultipleCrossings {
165 u_curve: usize,
166 v_curve: usize,
167 crossings: Vec<(f64, f64)>,
168 band: f64,
169 },
170 CrossingOrder {
175 family: char,
176 curve: usize,
177 parameters: Vec<f64>,
178 },
179 CoincidentCrossings {
182 family: char,
183 first: usize,
184 second: usize,
185 separation: f64,
186 },
187 Overhang {
192 family: char,
193 curve: usize,
194 end: usize,
195 parameter: f64,
196 gap: f64,
197 band: f64,
198 },
199 InconsistentCrossing {
205 u_curve: usize,
206 v_curve: usize,
207 gap: f64,
208 band: f64,
209 u_parameter_offset: f64,
210 v_parameter_offset: f64,
211 },
212 WeightClosure {
216 u_curve: usize,
217 v_curve: usize,
218 ratio: f64,
219 band: f64,
220 },
221 NonPositiveWeight {
224 row: usize,
225 column: usize,
226 weight: f64,
227 },
228 Incompatible { family: char, detail: String },
231 Geometry(String),
233}
234
235impl std::fmt::Display for GordonRefusal {
236 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 match self {
238 Self::TooFewCurves { family, count } => write!(
239 formatter,
240 "gordon: the {family} family has {count} curve(s) — a network needs at least two \
241 per family"
242 ),
243 Self::InvalidBand { band } => write!(
244 formatter,
245 "gordon: crossing band {band:.3e} is not a positive finite distance"
246 ),
247 Self::DegenerateCurve {
248 family,
249 curve,
250 extent,
251 band,
252 } => write!(
253 formatter,
254 "gordon: {family}-curve {curve} is degenerate — extent {extent:.3e} is inside the \
255 band {band:.3e}"
256 ),
257 Self::ClosedCurve {
258 family,
259 curve,
260 gap,
261 band,
262 } => write!(
263 formatter,
264 "gordon: {family}-curve {curve} is closed — its ends are {gap:.3e} apart, inside \
265 the band {band:.3e}; its family mixes open and closed curves"
266 ),
267 Self::CurvesDoNotMeet {
268 u_curve,
269 v_curve,
270 gap,
271 band,
272 } => write!(
273 formatter,
274 "gordon: u-curve {u_curve} and v-curve {v_curve} do not meet — closest approach \
275 {gap:.3e} exceeds the crossing band {band:.3e}"
276 ),
277 Self::MultipleCrossings {
278 u_curve,
279 v_curve,
280 crossings,
281 band,
282 } => write!(
283 formatter,
284 "gordon: u-curve {u_curve} and v-curve {v_curve} meet {} times within the band \
285 {band:.3e} (at {crossings:?}) — a network pair must cross once",
286 crossings.len()
287 ),
288 Self::CrossingOrder {
289 family,
290 curve,
291 parameters,
292 } => write!(
293 formatter,
294 "gordon: along {family}-curve {curve} the crossings run out of order \
295 ({parameters:?}) — the families do not form a grid"
296 ),
297 Self::CoincidentCrossings {
298 family,
299 first,
300 second,
301 separation,
302 } => write!(
303 formatter,
304 "gordon: {family}-curves {first} and {second} cross the other family at the same \
305 parameter ({separation:.3e} apart)"
306 ),
307 Self::Overhang {
308 family,
309 curve,
310 end,
311 parameter,
312 gap,
313 band,
314 } => write!(
315 formatter,
316 "gordon: {family}-curve {curve} runs past the network boundary at its {} — the \
317 boundary crossing sits at parameter {parameter:.6}, {gap:.3e} along the curve \
318 (band {band:.3e})",
319 if *end == 0 { "start" } else { "end" }
320 ),
321 Self::InconsistentCrossing {
322 u_curve,
323 v_curve,
324 gap,
325 band,
326 u_parameter_offset,
327 v_parameter_offset,
328 } => write!(
329 formatter,
330 "gordon: u-curve {u_curve} and v-curve {v_curve} are {gap:.3e} apart at the \
331 families' shared crossing parameters (band {band:.3e}); their own crossing is \
332 offset by {u_parameter_offset:.3e} in u and {v_parameter_offset:.3e} in v"
333 ),
334 Self::WeightClosure {
335 u_curve,
336 v_curve,
337 ratio,
338 band,
339 } => write!(
340 formatter,
341 "gordon: rational weights do not reconcile at u-curve {u_curve} x v-curve \
342 {v_curve} — ratio {ratio:.12} (band {band:.3e} about 1)"
343 ),
344 Self::NonPositiveWeight {
345 row,
346 column,
347 weight,
348 } => write!(
349 formatter,
350 "gordon: blended control point ({row}, {column}) has non-positive weight \
351 {weight:.3e} — the surface would pass through infinity"
352 ),
353 Self::Incompatible { family, detail } => {
354 write!(formatter, "gordon: {family} family incompatible — {detail}")
355 }
356 Self::Geometry(message) => formatter.write_str(message),
357 }
358 }
359}
360
361impl From<GordonRefusal> for String {
363 fn from(refusal: GordonRefusal) -> Self {
364 refusal.to_string()
365 }
366}
367
368#[derive(Clone, Debug)]
374pub struct GordonReport {
375 pub periodic_u: bool,
377 pub periodic_v: bool,
379 pub u_order: Vec<usize>,
381 pub v_order: Vec<usize>,
383 pub u_reversed: Vec<bool>,
385 pub v_reversed: Vec<bool>,
387 pub u_parameters: Vec<f64>,
390 pub v_parameters: Vec<f64>,
392 pub crossing_band: f64,
394 pub closest_approach: f64,
396 pub crossing_gap: f64,
403 pub parameter_spread: f64,
405 pub u_weight_scales: Vec<f64>,
409 pub v_weight_scales: Vec<f64>,
411 pub weight_closure: f64,
413 pub u_work: Vec<CompatWork>,
415 pub v_work: Vec<CompatWork>,
417 pub skin_degree_u: usize,
419 pub skin_degree_v: usize,
421 pub degree_u: usize,
422 pub degree_v: usize,
423 pub control_rows: usize,
424 pub control_columns: usize,
425 pub min_weight: f64,
427 pub interpolation_bound: f64,
441 pub sampled_interpolation_residual: f64,
445}
446
447pub fn gordon_surface(
454 u_curves: &[NurbsCurve],
455 v_curves: &[NurbsCurve],
456) -> Result<NurbsSurface, GordonRefusal> {
457 gordon_surface_reported(u_curves, v_curves, None).map(|(surface, _)| surface)
458}
459
460pub fn gordon_surface_reported(
469 u_curves: &[NurbsCurve],
470 v_curves: &[NurbsCurve],
471 crossing_band: Option<f64>,
472) -> Result<(NurbsSurface, GordonReport), GordonRefusal> {
473 let network = prepare(u_curves, v_curves, crossing_band)?;
474 build(network)
475}
476
477struct Family {
479 periodic: bool,
480 order: Vec<usize>,
482 curves: Vec<NurbsCurve>,
484 reversed: Vec<bool>,
486 weight_scales: Vec<f64>,
488 work: Vec<CompatWork>,
490 parameters: Vec<f64>,
493}
494
495struct Network {
497 u: Family,
498 v: Family,
499 crossings: Vec<Vec<Vec4>>,
502 band: f64,
503 closest_approach: f64,
504 crossing_gap: f64,
505 parameter_spread: f64,
506 weight_closure: f64,
507}
508
509fn samples(curve: &NurbsCurve, count: usize) -> Result<Vec<Vec3>, String> {
511 let [start, end] = curve.domain()?;
512 (0..=count)
513 .map(|index| curve.evaluate(start + (end - start) * index as f64 / count as f64))
514 .collect()
515}
516
517fn geometry(message: String) -> GordonRefusal {
518 GordonRefusal::Geometry(message)
519}
520
521fn homogeneous_midpoint(first: Vec4, second: Vec4) -> Vec4 {
527 first.add(second).scale(0.5)
528}
529
530fn polish_crossing(
539 first: &NurbsCurve,
540 second: &NurbsCurve,
541 seed_s: f64,
542 seed_t: f64,
543) -> Result<(f64, f64, f64), String> {
544 let mut s = seed_s.clamp(0.0, 1.0);
545 let mut t = seed_t.clamp(0.0, 1.0);
546 let mut gap = first.evaluate(s)?.sub(second.evaluate(t)?).length();
547 for _ in 0..CROSSING_ITERATIONS {
548 let a = first.derivatives(s, 2)?;
549 let b = second.derivatives(t, 2)?;
550 let residual = a[0].sub(b[0]);
551 let gradient_s = a[1].dot(residual);
552 let gradient_t = -b[1].dot(residual);
553 let h_ss = a[1].dot(a[1]) + a[2].dot(residual);
554 let h_st = -a[1].dot(b[1]);
555 let h_tt = b[1].dot(b[1]) - b[2].dot(residual);
556 let determinant = h_ss * h_tt - h_st * h_st;
557 let (mut step_s, mut step_t) = if determinant.abs() > f64::MIN_POSITIVE {
558 (
559 (-gradient_s * h_tt + gradient_t * h_st) / determinant,
560 (-gradient_t * h_ss + gradient_s * h_st) / determinant,
561 )
562 } else {
563 (0.0, 0.0)
564 };
565 let pinned_s = (s <= 0.0 && step_s < 0.0) || (s >= 1.0 && step_s > 0.0);
566 let pinned_t = (t <= 0.0 && step_t < 0.0) || (t >= 1.0 && step_t > 0.0);
567 if pinned_s && !pinned_t {
568 step_s = 0.0;
569 step_t = if h_tt.abs() > f64::MIN_POSITIVE { -gradient_t / h_tt } else { 0.0 };
570 } else if pinned_t && !pinned_s {
571 step_t = 0.0;
572 step_s = if h_ss.abs() > f64::MIN_POSITIVE { -gradient_s / h_ss } else { 0.0 };
573 } else if pinned_s && pinned_t {
574 break;
575 }
576 let next_s = (s + step_s).clamp(0.0, 1.0);
577 let next_t = (t + step_t).clamp(0.0, 1.0);
578 if next_s == s && next_t == t {
579 break;
580 }
581 let next_gap = first.evaluate(next_s)?.sub(second.evaluate(next_t)?).length();
582 if next_gap > gap {
583 break;
584 }
585 s = next_s;
586 t = next_t;
587 gap = next_gap;
588 }
589 Ok((s, t, gap))
590}
591
592fn closest_approach(first: &NurbsCurve, second: &NurbsCurve) -> Result<(f64, f64, f64), String> {
595 let first_points = samples(first, APPROACH_SAMPLES)?;
596 let second_points = samples(second, APPROACH_SAMPLES)?;
597 let mut best = (0usize, 0usize, f64::INFINITY);
598 for (i, a) in first_points.iter().enumerate() {
599 for (j, b) in second_points.iter().enumerate() {
600 let distance = a.sub(*b).length();
601 if distance < best.2 {
602 best = (i, j, distance);
603 }
604 }
605 }
606 polish_crossing(
607 first,
608 second,
609 best.0 as f64 / APPROACH_SAMPLES as f64,
610 best.1 as f64 / APPROACH_SAMPLES as f64,
611 )
612}
613
614fn median(values: &[f64]) -> f64 {
617 let mut sorted = values.to_vec();
618 sorted.sort_by(f64::total_cmp);
619 let middle = sorted.len() / 2;
620 if sorted.len() % 2 == 1 {
621 sorted[middle]
622 } else if sorted[middle - 1] == sorted[middle] {
623 sorted[middle]
624 } else {
625 0.5 * (sorted[middle - 1] + sorted[middle])
626 }
627}
628
629fn orientation(
634 family: char,
635 curve: usize,
636 sequence: &[f64],
637 crossing_family: char,
638 crossing_order: &[usize],
639) -> Result<bool, GordonRefusal> {
640 for (index, pair) in sequence.windows(2).enumerate() {
641 let separation = (pair[1] - pair[0]).abs();
642 if separation <= KNOT_IDENTITY_TOL {
643 return Err(GordonRefusal::CoincidentCrossings {
644 family: crossing_family,
645 first: crossing_order[index],
646 second: crossing_order[index + 1],
647 separation,
648 });
649 }
650 }
651 if sequence.windows(2).all(|pair| pair[1] > pair[0]) {
652 Ok(false)
653 } else if sequence.windows(2).all(|pair| pair[1] < pair[0]) {
654 Ok(true)
655 } else {
656 Err(GordonRefusal::CrossingOrder {
657 family,
658 curve,
659 parameters: sequence.to_vec(),
660 })
661 }
662}
663
664fn prepare(
667 u_input: &[NurbsCurve],
668 v_input: &[NurbsCurve],
669 crossing_band: Option<f64>,
670) -> Result<Network, GordonRefusal> {
671 for (family, curves) in [('u', u_input), ('v', v_input)] {
672 if curves.len() < 2 {
673 return Err(GordonRefusal::TooFewCurves {
674 family,
675 count: curves.len(),
676 });
677 }
678 }
679 if let Some(band) = crossing_band {
680 if !(band > 0.0 && band.is_finite()) {
681 return Err(GordonRefusal::InvalidBand { band });
682 }
683 }
684
685 let (u_curves, u_work) = normalise_domains(u_input)?;
687 let (v_curves, v_work) = normalise_domains(v_input)?;
688 let mut points: Vec<Vec3> = Vec::new();
689 for curve in u_curves.iter().chain(&v_curves) {
690 points.extend(samples(curve, CURVE_SAMPLES).map_err(geometry)?);
691 }
692 let band = crossing_band.unwrap_or_else(|| corner_band(model_scale(points.iter().copied())));
693 let u_open = u_curves.iter().map(|c| is_closed(c, band)).collect::<Result<Vec<_>, _>>()?.iter().all(|&c| !c);
694 let v_open = v_curves.iter().map(|c| is_closed(c, band)).collect::<Result<Vec<_>, _>>()?.iter().all(|&c| !c);
695 let periodic_u = judge_curves('u', &u_curves, band, v_open)?;
696 let periodic_v = judge_curves('v', &v_curves, band, u_open)?;
697
698 let crossings = find_crossings(&u_curves, &v_curves, band)?;
699 let order = order_network(&crossings, periodic_u, periodic_v)?;
700
701 let f = oriented(&u_curves, &order.u_order, &order.u_reversed)?;
704 let g = oriented(&v_curves, &order.v_order, &order.v_reversed)?;
705 let (rows, columns) = (f.len(), g.len());
706 let mut own_s = vec![vec![0.0f64; columns]; rows];
708 let mut own_t = vec![vec![0.0f64; columns]; rows];
709 for a in 0..rows {
710 for b in 0..columns {
711 let (j, i) = (order.u_order[a], order.v_order[b]);
712 let (s, t) = (crossings.s[j][i], crossings.t[j][i]);
713 let seed_s = if order.u_reversed[j] { 1.0 - s } else { s };
714 let seed_t = if order.v_reversed[i] { 1.0 - t } else { t };
715 let (polished_s, polished_t, _) =
716 polish_crossing(&f[a], &g[b], seed_s, seed_t).map_err(geometry)?;
717 own_s[a][b] = seam_parameter(if point_curve(&f[a]) { seed_s } else { polished_s }, periodic_u);
718 own_t[a][b] = seam_parameter(if point_curve(&g[b]) { seed_t } else { polished_t }, periodic_v);
719 }
720 }
721
722 let u_parameters = shared_parameters(columns, periodic_u, |b| (0..rows).map(|a| own_s[a][b]).collect());
725 let v_parameters = shared_parameters(rows, periodic_v, |a| (0..columns).map(|b| own_t[a][b]).collect());
726 for (family, parameters, network_order) in [
727 ('v', &u_parameters, &order.v_order),
728 ('u', &v_parameters, &order.u_order),
729 ] {
730 for (index, pair) in parameters.windows(2).enumerate() {
731 let separation = pair[1] - pair[0];
732 if separation <= KNOT_IDENTITY_TOL {
733 return Err(GordonRefusal::CoincidentCrossings {
734 family,
735 first: network_order[index],
736 second: network_order[index + 1],
737 separation,
738 });
739 }
740 }
741 }
742 let shared = SharedCrossings {
743 periodic_u,
744 periodic_v,
745 f: &f,
746 g: &g,
747 u_parameters: &u_parameters,
748 v_parameters: &v_parameters,
749 own_s: &own_s,
750 own_t: &own_t,
751 u_order: &order.u_order,
752 v_order: &order.v_order,
753 };
754 let (crossing_gap, parameter_spread) = judge_shared_crossings(&shared, band)?;
755 let (f_scales, g_scales, weight_closure) = reconcile_weights(&shared)?;
756 let f_scaled = scaled(&f, &f_scales)?;
757 let g_scaled = scaled(&g, &g_scales)?;
758 let grid = crossing_grid(&f_scaled, &g_scaled, &u_parameters, &v_parameters, periodic_u, periodic_v)?;
759
760 let mut u_weight_scales = vec![1.0f64; rows];
762 for (a, &j) in order.u_order.iter().enumerate() {
763 u_weight_scales[j] = f_scales[a];
764 }
765 let mut v_weight_scales = vec![1.0f64; columns];
766 for (b, &i) in order.v_order.iter().enumerate() {
767 v_weight_scales[i] = g_scales[b];
768 }
769 Ok(Network {
772 u: Family {
773 periodic: periodic_u,
774 order: order.u_order,
775 curves: f_scaled,
776 reversed: order.u_reversed,
777 weight_scales: u_weight_scales,
778 work: u_work,
779 parameters: u_parameters,
780 },
781 v: Family {
782 periodic: periodic_v,
783 order: order.v_order,
784 curves: g_scaled,
785 reversed: order.v_reversed,
786 weight_scales: v_weight_scales,
787 work: v_work,
788 parameters: v_parameters,
789 },
790 crossings: grid,
791 band,
792 closest_approach: crossings.closest,
793 crossing_gap,
794 parameter_spread,
795 weight_closure,
796 })
797}
798
799fn normalise_domains(
801 curves: &[NurbsCurve],
802) -> Result<(Vec<NurbsCurve>, Vec<CompatWork>), GordonRefusal> {
803 let mut normalised = Vec::with_capacity(curves.len());
804 let mut work = vec![CompatWork::default(); curves.len()];
805 for (index, curve) in curves.iter().enumerate() {
806 let (moved, remapped) = compat::normalise_domain(curve).map_err(geometry)?;
807 work[index].domain_remapped = remapped;
808 normalised.push(moved);
809 }
810 Ok((normalised, work))
811}
812
813fn is_closed(curve: &NurbsCurve, band: f64) -> Result<bool, GordonRefusal> {
815 Ok(curve.evaluate(0.0).map_err(geometry)?
816 .sub(curve.evaluate(1.0).map_err(geometry)?).length() <= band)
817}
818
819fn seam_parameter(parameter: f64, periodic: bool) -> f64 {
820 if periodic && (1.0 - parameter).abs() <= KNOT_IDENTITY_TOL { 0.0 } else { parameter }
821}
822
823fn point_curve(curve: &NurbsCurve) -> bool {
827 let first = curve.control_points[0];
828 let origin = Vec3::new(first.x / first.w, first.y / first.w, first.z / first.w);
829 curve.control_points.iter().all(|p| {
830 let q = Vec3::new(p.x / p.w, p.y / p.w, p.z / p.w);
831 q.sub(origin).length() <= 128.0 * f64::EPSILON * origin.length().max(1.0)
832 })
833}
834
835fn judge_curves(family: char, curves: &[NurbsCurve], band: f64, allow_poles: bool) -> Result<bool, GordonRefusal> {
836 let closed = curves.iter().map(|c| is_closed(c, band)).collect::<Result<Vec<_>, _>>()?;
837 let periodic = closed.iter().all(|&value| value);
838 let allow_poles = allow_poles && periodic && curves.iter().any(|c| !point_curve(c));
839 for (index, curve) in curves.iter().enumerate() {
840 let points = samples(curve, CURVE_SAMPLES).map_err(geometry)?;
841 let spread = points
842 .iter()
843 .map(|point| point.sub(points[0]).length())
844 .fold(0.0f64, f64::max);
845 let extent = model_scale(points.iter().copied()).min(spread);
846 if extent <= band && !(allow_poles && point_curve(curve)) {
847 return Err(GordonRefusal::DegenerateCurve {
848 family,
849 curve: index,
850 extent,
851 band,
852 });
853 }
854 let gap = points[0].sub(points[points.len() - 1]).length();
855 if gap <= band && !periodic {
856 return Err(GordonRefusal::ClosedCurve {
857 family,
858 curve: index,
859 gap,
860 band,
861 });
862 }
863 }
864 Ok(periodic)
865}
866
867struct Crossings {
869 s: Vec<Vec<f64>>,
872 t: Vec<Vec<f64>>,
874 closest: f64,
876}
877
878fn find_crossings(
881 u_curves: &[NurbsCurve],
882 v_curves: &[NurbsCurve],
883 band: f64,
884) -> Result<Crossings, GordonRefusal> {
885 let mut crossings = Crossings {
886 s: vec![vec![0.0f64; v_curves.len()]; u_curves.len()],
887 t: vec![vec![0.0f64; v_curves.len()]; u_curves.len()],
888 closest: 0.0,
889 };
890 for (j, u_curve) in u_curves.iter().enumerate() {
891 for (i, v_curve) in v_curves.iter().enumerate() {
892 if point_curve(u_curve) || point_curve(v_curve) {
893 let (s, t, gap) = closest_approach(u_curve, v_curve).map_err(geometry)?;
894 if gap > band {
895 return Err(GordonRefusal::CurvesDoNotMeet { u_curve: j, v_curve: i, gap, band });
896 }
897 crossings.s[j][i] = s;
898 crossings.t[j][i] = t;
899 crossings.closest = crossings.closest.max(gap);
900 continue;
901 }
902 let periodic_u = is_closed(u_curve, band)?;
903 let periodic_v = is_closed(v_curve, band)?;
904 let mut hits = intersect_curves(u_curve, v_curve, band).map_err(geometry)?;
905 if periodic_u || periodic_v {
906 for hit in &mut hits {
907 hit.s = seam_parameter(hit.s, periodic_u);
908 hit.t = seam_parameter(hit.t, periodic_v);
909 }
910 hits.sort_by(|a, b| a.s.total_cmp(&b.s).then(a.t.total_cmp(&b.t)));
911 hits.dedup_by(|a, b| (a.s - b.s).abs() <= KNOT_IDENTITY_TOL
912 && (a.t - b.t).abs() <= KNOT_IDENTITY_TOL);
913 }
914 let (s, t, gap) = match hits.len() {
915 1 => polish_crossing(u_curve, v_curve, hits[0].s, hits[0].t).map_err(geometry)?,
916 0 => {
917 let approach = closest_approach(u_curve, v_curve).map_err(geometry)?;
918 if approach.2 > band {
919 return Err(GordonRefusal::CurvesDoNotMeet {
920 u_curve: j,
921 v_curve: i,
922 gap: approach.2,
923 band,
924 });
925 }
926 approach
927 }
928 _ => {
929 return Err(GordonRefusal::MultipleCrossings {
930 u_curve: j,
931 v_curve: i,
932 crossings: hits.iter().map(|hit| (hit.s, hit.t)).collect(),
933 band,
934 })
935 }
936 };
937 crossings.s[j][i] = seam_parameter(s, periodic_u);
938 crossings.t[j][i] = seam_parameter(t, periodic_v);
939 crossings.closest = crossings.closest.max(gap);
940 }
941 }
942 if let Some(row) = u_curves.iter().position(|c| !point_curve(c)) {
943 for (j, curve) in u_curves.iter().enumerate() {
944 if point_curve(curve) { crossings.s[j] = crossings.s[row].clone(); }
945 }
946 }
947 if let Some(column) = v_curves.iter().position(|c| !point_curve(c)) {
948 for (i, curve) in v_curves.iter().enumerate() {
949 if point_curve(curve) {
950 for row in &mut crossings.t { row[i] = row[column]; }
951 }
952 }
953 }
954 Ok(crossings)
955}
956
957struct NetworkOrder {
959 u_order: Vec<usize>,
961 v_order: Vec<usize>,
962 u_reversed: Vec<bool>,
964 v_reversed: Vec<bool>,
965}
966
967fn order_network(crossings: &Crossings, periodic_u: bool, periodic_v: bool) -> Result<NetworkOrder, GordonRefusal> {
971 let (s, t) = (&crossings.s, &crossings.t);
972 let (rows, columns) = (s.len(), s[0].len());
973 let mut v_order: Vec<usize> = (0..columns).collect();
974 v_order.sort_by(|&a, &b| s[0][a].total_cmp(&s[0][b]));
975 let mut u_order: Vec<usize> = (0..rows).collect();
976 u_order.sort_by(|&a, &b| t[a][0].total_cmp(&t[b][0]));
977 let mut u_reversed = vec![false; rows];
978 for (j, reversed) in u_reversed.iter_mut().enumerate() {
979 let sequence: Vec<f64> = v_order.iter().map(|&i| s[j][i]).collect();
980 *reversed = network_orientation('u', j, &sequence, 'v', &v_order, periodic_u)?;
981 }
982 let mut v_reversed = vec![false; columns];
983 for (i, reversed) in v_reversed.iter_mut().enumerate() {
984 let sequence: Vec<f64> = u_order.iter().map(|&j| t[j][i]).collect();
985 *reversed = network_orientation('v', i, &sequence, 'u', &u_order, periodic_v)?;
986 }
987 Ok(NetworkOrder {
988 u_order,
989 v_order,
990 u_reversed,
991 v_reversed,
992 })
993}
994
995fn network_orientation(family: char, curve: usize, sequence: &[f64],
997 crossing_family: char, order: &[usize], periodic: bool) -> Result<bool, GordonRefusal> {
998 if periodic {
999 if sequence.windows(2).all(|p| p[1] - p[0] > KNOT_IDENTITY_TOL) {
1000 return Ok(false);
1001 }
1002 let reversed: Vec<f64> = sequence.iter().map(|&t| seam_parameter(1.0 - t, true)).collect();
1003 if reversed.windows(2).all(|p| p[1] - p[0] > KNOT_IDENTITY_TOL) {
1004 return Ok(true);
1005 }
1006 }
1007 orientation(family, curve, sequence, crossing_family, order)
1008}
1009
1010fn oriented(
1012 curves: &[NurbsCurve],
1013 order: &[usize],
1014 reversed: &[bool],
1015) -> Result<Vec<NurbsCurve>, GordonRefusal> {
1016 order
1017 .iter()
1018 .map(|&index| {
1019 if reversed[index] {
1020 curves[index].reversed().map_err(geometry)
1021 } else {
1022 Ok(curves[index].clone())
1023 }
1024 })
1025 .collect()
1026}
1027
1028fn shared_parameters(count: usize, periodic: bool, own: impl Fn(usize) -> Vec<f64>) -> Vec<f64> {
1031 (0..count)
1032 .map(|index| {
1033 if periodic {
1034 median(&own(index))
1035 } else if index == 0 {
1036 0.0
1037 } else if index == count - 1 {
1038 1.0
1039 } else {
1040 median(&own(index))
1041 }
1042 })
1043 .collect()
1044}
1045
1046struct SharedCrossings<'a> {
1048 periodic_u: bool,
1049 periodic_v: bool,
1050 f: &'a [NurbsCurve],
1051 g: &'a [NurbsCurve],
1052 u_parameters: &'a [f64],
1053 v_parameters: &'a [f64],
1054 own_s: &'a [Vec<f64>],
1055 own_t: &'a [Vec<f64>],
1056 u_order: &'a [usize],
1057 v_order: &'a [usize],
1058}
1059
1060fn judge_shared_crossings(
1065 shared: &SharedCrossings<'_>,
1066 band: f64,
1067) -> Result<(f64, f64), GordonRefusal> {
1068 let (rows, columns) = (shared.f.len(), shared.g.len());
1069 let mut crossing_gap = 0.0f64;
1070 let mut spread = 0.0f64;
1071 let mut worst: Option<GordonRefusal> = None;
1072 for a in 0..rows {
1073 for b in 0..columns {
1074 let own_s = shared.own_s[a][b];
1075 let own_t = shared.own_t[a][b];
1076 let on_u = shared.f[a].evaluate(shared.u_parameters[b]).map_err(geometry)?;
1077 let on_v = shared.g[b].evaluate(shared.v_parameters[a]).map_err(geometry)?;
1078 let gap = on_u.sub(on_v).length();
1079 let u_offset = (own_s - shared.u_parameters[b]).abs();
1080 let v_offset = (own_t - shared.v_parameters[a]).abs();
1081 if !shared.periodic_u && (b == 0 || b == columns - 1) {
1082 let along = on_u.sub(shared.f[a].evaluate(own_s).map_err(geometry)?).length();
1083 if along > band {
1084 return Err(GordonRefusal::Overhang {
1085 family: 'u',
1086 curve: shared.u_order[a],
1087 end: usize::from(b == columns - 1),
1088 parameter: own_s,
1089 gap: along,
1090 band,
1091 });
1092 }
1093 }
1094 if !shared.periodic_v && (a == 0 || a == rows - 1) {
1095 let along = on_v.sub(shared.g[b].evaluate(own_t).map_err(geometry)?).length();
1096 if along > band {
1097 return Err(GordonRefusal::Overhang {
1098 family: 'v',
1099 curve: shared.v_order[b],
1100 end: usize::from(a == rows - 1),
1101 parameter: own_t,
1102 gap: along,
1103 band,
1104 });
1105 }
1106 }
1107 if gap > band && gap > crossing_gap {
1108 worst = Some(GordonRefusal::InconsistentCrossing {
1109 u_curve: shared.u_order[a],
1110 v_curve: shared.v_order[b],
1111 gap,
1112 band,
1113 u_parameter_offset: u_offset,
1114 v_parameter_offset: v_offset,
1115 });
1116 }
1117 crossing_gap = crossing_gap.max(gap);
1118 spread = spread.max(u_offset).max(v_offset);
1119 }
1120 }
1121 match worst {
1122 Some(refusal) => Err(refusal),
1123 None => Ok((crossing_gap, spread)),
1124 }
1125}
1126
1127fn reconcile_weights(
1136 shared: &SharedCrossings<'_>,
1137) -> Result<(Vec<f64>, Vec<f64>, f64), GordonRefusal> {
1138 let (rows, columns) = (shared.f.len(), shared.g.len());
1139 let mut f_scales = vec![1.0f64; rows];
1140 let mut g_scales = vec![1.0f64; columns];
1141 let polynomial = shared
1142 .f
1143 .iter()
1144 .chain(shared.g)
1145 .all(|curve| curve.control_points.iter().all(|point| point.w == 1.0));
1146 if polynomial {
1147 return Ok((f_scales, g_scales, 1.0));
1148 }
1149 let mut f_weight = vec![vec![1.0f64; columns]; rows];
1150 let mut g_weight = vec![vec![1.0f64; columns]; rows];
1151 for a in 0..rows {
1152 for b in 0..columns {
1153 f_weight[a][b] = shared.f[a]
1154 .evaluate_homogeneous(shared.u_parameters[b])
1155 .map_err(geometry)?
1156 .w;
1157 g_weight[a][b] = shared.g[b]
1158 .evaluate_homogeneous(shared.v_parameters[a])
1159 .map_err(geometry)?
1160 .w;
1161 }
1162 }
1163 for b in 0..columns {
1164 g_scales[b] = f_weight[0][b] / g_weight[0][b];
1165 }
1166 for a in 1..rows {
1167 f_scales[a] = g_scales[0] * g_weight[a][0] / f_weight[a][0];
1168 }
1169 let mut weight_closure = 1.0f64;
1170 for a in 0..rows {
1171 for b in 0..columns {
1172 let ratio = f_scales[a] * f_weight[a][b] / (g_scales[b] * g_weight[a][b]);
1173 if (ratio - 1.0).abs() > (weight_closure - 1.0).abs() {
1174 weight_closure = ratio;
1175 }
1176 if (ratio - 1.0).abs() > WEIGHT_CLOSURE_BAND {
1177 return Err(GordonRefusal::WeightClosure {
1178 u_curve: shared.u_order[a],
1179 v_curve: shared.v_order[b],
1180 ratio,
1181 band: WEIGHT_CLOSURE_BAND,
1182 });
1183 }
1184 }
1185 }
1186 Ok((f_scales, g_scales, weight_closure))
1187}
1188
1189fn scaled(curves: &[NurbsCurve], scales: &[f64]) -> Result<Vec<NurbsCurve>, GordonRefusal> {
1192 curves
1193 .iter()
1194 .zip(scales)
1195 .map(|(curve, &factor)| {
1196 if factor == 1.0 {
1197 return Ok(curve.clone());
1198 }
1199 NurbsCurve::new(
1200 curve.degree,
1201 curve.knots.clone(),
1202 curve.control_points.iter().map(|point| point.scale(factor)).collect(),
1203 )
1204 .map_err(geometry)
1205 })
1206 .collect()
1207}
1208
1209fn crossing_grid(
1220 f: &[NurbsCurve],
1221 g: &[NurbsCurve],
1222 u_parameters: &[f64],
1223 v_parameters: &[f64],
1224 periodic_u: bool,
1225 periodic_v: bool,
1226) -> Result<Vec<Vec<Vec4>>, GordonRefusal> {
1227 let (last_row, last_column) = (f.len() - 1, g.len() - 1);
1228 let mut grid = Vec::with_capacity(f.len());
1229 for (a, u_curve) in f.iter().enumerate() {
1230 let mut row = Vec::with_capacity(g.len());
1231 for (b, v_curve) in g.iter().enumerate() {
1232 let on_u = u_curve.evaluate_homogeneous(u_parameters[b]).map_err(geometry)?;
1233 let on_v = v_curve.evaluate_homogeneous(v_parameters[a]).map_err(geometry)?;
1234 let boundary_u = !periodic_v && (a == 0 || a == last_row);
1235 let boundary_v = !periodic_u && (b == 0 || b == last_column);
1236 row.push(match (boundary_u, boundary_v) {
1237 (true, false) => on_v,
1238 (false, true) => on_u,
1239 _ => homogeneous_midpoint(on_u, on_v),
1240 });
1241 }
1242 grid.push(row);
1243 }
1244 Ok(grid)
1245}
1246
1247fn cardinal_functions(parameters: &[f64]) -> Result<(usize, Vec<NurbsCurve>), String> {
1258 let count = parameters.len();
1259 let degree = MAX_SKIN_DEGREE.min(count - 1);
1260 let mut knots = vec![0.0f64; count + degree + 1];
1261 for knot in &mut knots[count..] {
1262 *knot = 1.0;
1263 }
1264 for j in 1..count - degree {
1265 knots[j + degree] = parameters[j..j + degree].iter().sum::<f64>() / degree as f64;
1266 }
1267 let knot_vector = KnotVector::new(knots.clone(), degree)?;
1268 let mut matrix = vec![vec![0.0f64; count]; count];
1269 for (row, ¶meter) in parameters.iter().enumerate() {
1270 let span = knot_vector.find_span(parameter);
1271 for (offset, value) in knot_vector
1272 .basis_functions(span, parameter)
1273 .into_iter()
1274 .enumerate()
1275 {
1276 matrix[row][span - degree + offset] = value;
1277 }
1278 }
1279 let mut functions = Vec::with_capacity(count);
1280 for j in 0..count {
1281 let mut rhs = vec![0.0f64; count];
1282 rhs[j] = 1.0;
1283 let mut coefficients = solve_dense(matrix.clone(), rhs)
1284 .map_err(|error| format!("cardinal collocation at {parameters:?}: {error}"))?;
1285 coefficients[0] = if j == 0 { 1.0 } else { 0.0 };
1286 coefficients[count - 1] = if j == count - 1 { 1.0 } else { 0.0 };
1287 functions.push(NurbsCurve::new(
1288 degree,
1289 knots.clone(),
1290 coefficients
1291 .into_iter()
1292 .map(|value| Vec4 { x: value, y: 0.0, z: 0.0, w: 1.0 })
1293 .collect(),
1294 )?);
1295 }
1296 Ok((degree, functions))
1297}
1298
1299fn compatible_direction(
1303 family: char,
1304 curves: &[NurbsCurve],
1305 cardinals: &[NurbsCurve],
1306) -> Result<(Vec<NurbsCurve>, Vec<Vec<f64>>, Vec<CompatWork>), GordonRefusal> {
1307 let combined: Vec<NurbsCurve> = curves.iter().chain(cardinals).cloned().collect();
1308 let (compatible, work) = compat::make_family_compatible(&combined, 1)
1309 .map_err(|detail| GordonRefusal::Incompatible { family, detail })?;
1310 let coefficients = compatible[curves.len()..]
1311 .iter()
1312 .map(|function| function.control_points.iter().map(|point| point.x).collect())
1313 .collect();
1314 Ok((
1315 compatible[..curves.len()].to_vec(),
1316 coefficients,
1317 work[..curves.len()].to_vec(),
1318 ))
1319}
1320
1321fn build(network: Network) -> Result<(NurbsSurface, GordonReport), GordonRefusal> {
1323 let Network {
1324 mut u,
1325 mut v,
1326 crossings,
1327 band,
1328 closest_approach,
1329 crossing_gap,
1330 parameter_spread,
1331 weight_closure,
1332 } = network;
1333 let rows_in_network = u.curves.len();
1334 let columns_in_network = v.curves.len();
1335
1336 let (skin_degree_u, alphas) = direction_cardinals(&u.parameters, u.periodic).map_err(|detail| {
1339 GordonRefusal::Incompatible {
1340 family: 'v',
1341 detail,
1342 }
1343 })?;
1344 let (skin_degree_v, betas) = direction_cardinals(&v.parameters, v.periodic).map_err(|detail| {
1345 GordonRefusal::Incompatible {
1346 family: 'u',
1347 detail,
1348 }
1349 })?;
1350 let (alphas, betas) = if u.periodic || v.periodic {
1351 (periodic::weight_cardinals('u', &u.curves, alphas, &u.parameters)?,
1352 periodic::weight_cardinals('v', &v.curves, betas, &v.parameters)?)
1353 } else {
1354 (alphas, betas)
1355 };
1356 let (f, alpha, f_work) = compatible_direction('u', &u.curves, &alphas)?;
1357 let (g, beta, g_work) = compatible_direction('v', &v.curves, &betas)?;
1358 for (a, &j) in u.order.iter().enumerate() {
1359 u.work[j].elevated_by = f_work[a].elevated_by;
1360 u.work[j].knots_inserted = f_work[a].knots_inserted;
1361 }
1362 for (b, &i) in v.order.iter().enumerate() {
1363 v.work[i].elevated_by = g_work[b].elevated_by;
1364 v.work[i].knots_inserted = g_work[b].knots_inserted;
1365 }
1366
1367 let rows = f[0].control_points.len();
1368 let columns = g[0].control_points.len();
1369 let zero = Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
1370 let mut net = vec![vec![zero; columns]; rows];
1371 let crossing_rows: Vec<Vec<Vec4>> = (0..rows)
1373 .map(|k| {
1374 (0..rows_in_network)
1375 .map(|a| {
1376 (0..columns_in_network).fold(zero, |sum, b| {
1377 sum.add(crossings[a][b].scale(alpha[b][k]))
1378 })
1379 })
1380 .collect()
1381 })
1382 .collect();
1383 for (k, net_row) in net.iter_mut().enumerate() {
1384 for (l, cell) in net_row.iter_mut().enumerate() {
1385 let mut sum = zero;
1386 for a in 0..rows_in_network {
1387 sum = sum.add(
1389 f[a].control_points[k]
1390 .add(crossing_rows[k][a].scale(-1.0))
1391 .scale(beta[a][l]),
1392 );
1393 }
1394 for b in 0..columns_in_network {
1395 sum = sum.add(g[b].control_points[l].scale(alpha[b][k]));
1396 }
1397 *cell = sum;
1398 }
1399 }
1400 if !u.periodic && !v.periodic {
1404 let (last_row, last_column) = (rows - 1, columns - 1);
1405 let (f_first, f_last) = (&f[0], &f[rows_in_network - 1]);
1406 let (g_first, g_last) = (&g[0], &g[columns_in_network - 1]);
1407 for (k, net_row) in net.iter_mut().enumerate() {
1408 net_row[0] = f_first.control_points[k];
1409 net_row[last_column] = f_last.control_points[k];
1410 }
1411 for l in 0..columns {
1412 net[0][l] = g_first.control_points[l];
1413 net[last_row][l] = g_last.control_points[l];
1414 }
1415 net[0][0] = homogeneous_midpoint(f_first.control_points[0], g_first.control_points[0]);
1416 net[last_row][0] =
1417 homogeneous_midpoint(f_first.control_points[last_row], g_last.control_points[0]);
1418 net[0][last_column] =
1419 homogeneous_midpoint(f_last.control_points[0], g_first.control_points[last_column]);
1420 net[last_row][last_column] = homogeneous_midpoint(
1421 f_last.control_points[last_row],
1422 g_last.control_points[last_column],
1423 );
1424 }
1425
1426 let mut min_weight = f64::INFINITY;
1427 let mut worst = (0usize, 0usize);
1428 for (row, net_row) in net.iter().enumerate() {
1429 for (column, cell) in net_row.iter().enumerate() {
1430 if !(cell.w >= min_weight) {
1431 min_weight = cell.w;
1432 worst = (row, column);
1433 }
1434 }
1435 }
1436 if !(min_weight > 0.0) || !min_weight.is_finite() {
1437 return Err(GordonRefusal::NonPositiveWeight {
1438 row: worst.0,
1439 column: worst.1,
1440 weight: min_weight,
1441 });
1442 }
1443
1444 let surface = NurbsSurface::new(
1445 f[0].degree,
1446 g[0].degree,
1447 f[0].knots.clone(),
1448 g[0].knots.clone(),
1449 net,
1450 )
1451 .map_err(geometry)?;
1452
1453 let mut interpolation_bound = 0.0f64;
1457 let mut sampled = 0.0f64;
1458 for (a, curve) in f.iter().enumerate() {
1459 let iso = surface.iso_curve_v(v.parameters[a]).map_err(geometry)?;
1460 for (on_surface, on_curve) in iso.control_points.iter().zip(&curve.control_points) {
1461 interpolation_bound =
1462 interpolation_bound.max(homogeneous_distance(*on_surface, *on_curve));
1463 }
1464 for index in 0..=CURVE_SAMPLES {
1465 let parameter = index as f64 / CURVE_SAMPLES as f64;
1466 let on_surface = surface.evaluate(parameter, v.parameters[a]).map_err(geometry)?;
1467 let on_curve = u.curves[a].evaluate(parameter).map_err(geometry)?;
1468 sampled = sampled.max(on_surface.sub(on_curve).length());
1469 }
1470 }
1471 for (b, curve) in g.iter().enumerate() {
1472 let iso = surface.iso_curve_u(u.parameters[b]).map_err(geometry)?;
1473 for (on_surface, on_curve) in iso.control_points.iter().zip(&curve.control_points) {
1474 interpolation_bound =
1475 interpolation_bound.max(homogeneous_distance(*on_surface, *on_curve));
1476 }
1477 for index in 0..=CURVE_SAMPLES {
1478 let parameter = index as f64 / CURVE_SAMPLES as f64;
1479 let on_surface = surface.evaluate(u.parameters[b], parameter).map_err(geometry)?;
1480 let on_curve = v.curves[b].evaluate(parameter).map_err(geometry)?;
1481 sampled = sampled.max(on_surface.sub(on_curve).length());
1482 }
1483 }
1484
1485 let report = GordonReport {
1486 periodic_u: u.periodic,
1487 periodic_v: v.periodic,
1488 u_order: u.order,
1489 v_order: v.order,
1490 u_reversed: u.reversed,
1491 v_reversed: v.reversed,
1492 u_parameters: u.parameters,
1493 v_parameters: v.parameters,
1494 crossing_band: band,
1495 closest_approach,
1496 crossing_gap,
1497 parameter_spread,
1498 u_weight_scales: u.weight_scales,
1499 v_weight_scales: v.weight_scales,
1500 weight_closure,
1501 u_work: u.work,
1502 v_work: v.work,
1503 skin_degree_u,
1504 skin_degree_v,
1505 degree_u: surface.degree_u,
1506 degree_v: surface.degree_v,
1507 control_rows: rows,
1508 control_columns: columns,
1509 min_weight,
1510 interpolation_bound,
1511 sampled_interpolation_residual: sampled,
1512 };
1513 Ok((surface, report))
1514}