Skip to main content

geop_core_geometry/nurb_curve/
join.rs

1//! Two curves, one ending where the other starts, as one curve.
2
3use geop_core_math::{
4    geop_error::{GeopError, GeopResult},
5    vector::Vector,
6};
7
8use super::NurbCurve;
9use crate::knot_insertion::is_clamped_at;
10
11impl<const D: usize> NurbCurve<D> {
12    /// This curve and `next`, which starts where this one ends, as one
13    /// curve: the lower in degree raised to the other's, `next`'s knots
14    /// carried on from where this one's end, its homogeneous control points
15    /// scaled so its first weight is this one's last — the same rational
16    /// curve — and the two joined by a knot of full multiplicity, so the
17    /// joined curve is exactly the two pieces, however they meet there.
18    /// The control point where they meet is the one both pieces had there:
19    /// two enclosures of one point, so what they share.
20    ///
21    /// Fails for a knot vector not clamped where they meet, and for pieces
22    /// that do not meet: their end and start control points apart.
23    pub fn joined(&self, next: &Self) -> GeopResult<Self> {
24        let (mut a, mut b) = (self.clone(), next.clone());
25        while a.degree < b.degree {
26            a = a.elevate_degree()?;
27        }
28        while b.degree < a.degree {
29            b = b.elevate_degree()?;
30        }
31        let p = a.degree;
32        if !is_clamped_at(&a.knot_vector, p, false) || !is_clamped_at(&b.knot_vector, p, true) {
33            return Err(GeopError::new(format!(
34                "NurbCurve::joined: the knot vectors {:?} and {:?} are not clamped where the curves meet",
35                a.knot_vector, b.knot_vector
36            )));
37        }
38        let last = *a.control_points.last().expect("a curve has control points");
39        let first = b.control_points[0];
40        let scale = last[D - 1].div(first[D - 1])?;
41        let scaled: Vec<Vector<D>> = (b.control_points.iter())
42            .map(|cp| cp.prod_scalar(scale))
43            .collect();
44        if !scaled[0].could_be_equal(&last) {
45            return Err(GeopError::new(format!(
46                "NurbCurve::joined: the curves do not meet: one ends at {last:?}, the other starts at {:?}",
47                scaled[0]
48            )));
49        }
50        let meet = Vector::from_array(std::array::from_fn(|k| last[k].intersect(scaled[0][k])));
51        let (_, a1) = a.domain();
52        let (b0, _) = b.domain();
53        let shift = a1.sub(b0);
54        let mut knots: Vec<_> = a.knot_vector[..a.knot_vector.len() - 1].to_vec();
55        knots.extend(b.knot_vector[p + 1..].iter().map(|k| k.add(shift)));
56        let mut points = a.control_points;
57        *points.last_mut().expect("a curve has control points") = meet;
58        points.extend_from_slice(&scaled[1..]);
59        NurbCurve::try_new(p, points, knots)
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use geop_core_math::{scalars::Scalar, vector::Vector3};
66
67    use crate::nurb_curve::NurbCurve3D;
68
69    fn p(x: f64, y: f64, z: f64) -> Vector3 {
70        Vector3::from_array([x, y, z].map(Scalar::from_f64))
71    }
72
73    /// Two lines end to end joined: one curve through both, its middle at
74    /// the corner where they met.
75    #[test]
76    fn joins_two_lines() {
77        let a = NurbCurve3D::line(p(0., 0., 0.), p(1., 0., 0.)).unwrap();
78        let b = NurbCurve3D::line(p(1., 0., 0.), p(1., 2., 0.)).unwrap();
79        let joined = a.joined(&b).unwrap();
80        let (t0, t1) = joined.domain();
81        assert!(joined.evaluate(t0).unwrap().could_be_equal(&p(0., 0., 0.)));
82        assert!(joined.evaluate(t1).unwrap().could_be_equal(&p(1., 2., 0.)));
83        let (_, a1) = a.domain();
84        assert!(joined.evaluate(a1).unwrap().could_be_equal(&p(1., 0., 0.)));
85    }
86
87    /// Curves that do not meet are refused.
88    #[test]
89    fn refuses_curves_apart() {
90        let a = NurbCurve3D::line(p(0., 0., 0.), p(1., 0., 0.)).unwrap();
91        let b = NurbCurve3D::line(p(2., 0., 0.), p(3., 0., 0.)).unwrap();
92        assert!(a.joined(&b).is_err());
93    }
94}