geop_core_geometry/nurb_surface/
join.rs1use geop_core_math::{
4 geop_error::{GeopError, GeopResult},
5 vector::Vector,
6};
7
8use super::NurbSurface3D;
9use crate::knot_insertion::is_clamped_at;
10
11impl NurbSurface3D {
12 pub fn joined(&self, next: &Self, along_u: bool) -> GeopResult<Self> {
27 if !along_u {
28 return Ok(self
29 .transposed()
30 .joined(&next.transposed(), true)?
31 .transposed());
32 }
33 let (a, b) = (self, next);
34 let fail = |why: String| Err(GeopError::new(format!("NurbSurface::joined: {why}")));
35 if a.degree_u != b.degree_u || a.degree_v != b.degree_v {
36 return fail(format!(
37 "degrees {}x{} and {}x{} differ",
38 a.degree_u, a.degree_v, b.degree_u, b.degree_v
39 ));
40 }
41 let across = a.knot_vector_v.len() == b.knot_vector_v.len()
42 && (a.knot_vector_v.iter().zip(&b.knot_vector_v)).all(|(x, y)| x.could_be_equal(*y));
43 if !across {
44 return fail(format!(
45 "the knots across the join differ: {:?} and {:?}",
46 a.knot_vector_v, b.knot_vector_v
47 ));
48 }
49 let p = a.degree_u;
50 if !is_clamped_at(&a.knot_vector_u, p, false) || !is_clamped_at(&b.knot_vector_u, p, true) {
51 return fail(format!(
52 "the knot vectors {:?} and {:?} are not clamped where the surfaces meet",
53 a.knot_vector_u, b.knot_vector_u
54 ));
55 }
56 let nv = a.num_v;
57 let last = &a.control_points[(a.num_u - 1) * nv..];
58 let first = &b.control_points[..nv];
59 let scale = last[0][3].div(first[0][3])?;
60 let scaled: Vec<Vector<4>> = (b.control_points.iter())
61 .map(|cp| cp.prod_scalar(scale))
62 .collect();
63 if !(last.iter().zip(&scaled[..nv])).all(|(p, q)| p.could_be_equal(q)) {
64 return fail(format!(
65 "the surfaces do not meet: one ends at {last:?}, the other starts at {:?}",
66 &scaled[..nv]
67 ));
68 }
69 let meet: Vec<Vector<4>> = (last.iter().zip(&scaled[..nv]))
70 .map(|(p, q)| Vector::from_array(std::array::from_fn(|k| p[k].intersect(q[k]))))
71 .collect();
72 let (_, a1) = a.domain_u();
73 let (b0, _) = b.domain_u();
74 let shift = a1.sub(b0);
75 let mut knots: Vec<_> = a.knot_vector_u[..a.knot_vector_u.len() - 1].to_vec();
76 knots.extend(b.knot_vector_u[p + 1..].iter().map(|k| k.add(shift)));
77 let mut points = a.control_points[..(a.num_u - 1) * nv].to_vec();
78 points.extend(meet);
79 points.extend_from_slice(&scaled[nv..]);
80 let across: Vec<_> = (a.knot_vector_v.iter().zip(&b.knot_vector_v))
81 .map(|(x, y)| x.union(*y))
82 .collect();
83 NurbSurface3D::try_new(p, a.degree_v, points, knots, across)
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use geop_core_math::{scalars::Scalar, vector::Vector3};
90
91 use crate::nurb_surface::NurbSurface3D;
92
93 fn p(x: f64, y: f64, z: f64) -> Vector3 {
94 Vector3::from_array([x, y, z].map(Scalar::from_f64))
95 }
96
97 #[test]
100 fn joins_two_squares_along_either_direction() {
101 let square = |x: f64| {
102 NurbSurface3D::bilinear(
103 p(x, 0., 0.),
104 p(x + 1., 0., 0.),
105 p(x + 1., 1., 0.),
106 p(x, 1., 0.),
107 )
108 .unwrap()
109 };
110 let joined = square(0.0).joined(&square(1.0), true).unwrap();
111 let (u0, u1) = joined.domain_u();
112 assert_eq!(u1.sub(u0).to_f64(), 2.0);
113 let at = joined
114 .evaluate(Scalar::from_f64(1.5), Scalar::from_f64(0.5))
115 .unwrap();
116 assert!((at[0].to_f64() - 1.5).abs() < 1e-12 && (at[1].to_f64() - 0.5).abs() < 1e-12);
117 let turned = |s: &NurbSurface3D| s.transposed();
118 let joined = turned(&square(0.0))
119 .joined(&turned(&square(1.0)), false)
120 .unwrap();
121 let at = joined
122 .evaluate(Scalar::from_f64(0.5), Scalar::from_f64(1.5))
123 .unwrap();
124 assert!((at[0].to_f64() - 1.5).abs() < 1e-12, "{at:?}");
125 assert!(square(0.0).joined(&square(2.0), true).is_err(), "apart");
126 }
127}