geop_core_geometry/nurb_curve/
derivative.rs1use geop_core_math::{
2 geop_error::{GeopError, GeopResult},
3 scalars::Scalar,
4 vector::Vector,
5};
6
7use super::NurbCurve;
8use crate::aabb::compute_aabb;
9
10impl<S: Scalar, const D: usize> NurbCurve<S, D> {
11 pub fn derivative(&self) -> GeopResult<NurbCurve<S, D>> {
23 let p = self.degree;
24 if p == 0 {
25 return Err(GeopError::new(
26 "derivative is undefined for a degree-0 curve",
27 ));
28 }
29
30 let n = self.control_points.len() - 1;
31 let u = &self.knot_vector;
32 let p_s = S::from_i64(p as i64);
33
34 let mut deriv_pts: Vec<Vector<S, D>> = Vec::with_capacity(n);
35 for i in 0..n {
36 let den = u[i + p + 1].sub(u[i + 1]);
37 let mut d = Vector::<S, D>::zero();
38 if den.definitely_not_equal(S::ZERO) {
39 let scale = p_s.div(den).unwrap_or(S::ZERO);
40 for c in 0..D {
41 d[c] = self.control_points[i + 1][c]
42 .sub(self.control_points[i][c])
43 .mul(scale);
44 }
45 }
46 deriv_pts.push(d);
47 }
48
49 let deriv_knots: Vec<S> = u[1..=n + p].to_vec();
50
51 let aabb = compute_aabb(&deriv_pts);
61
62 Ok(NurbCurve {
63 degree: p - 1,
64 control_points: deriv_pts,
65 knot_vector: deriv_knots,
66 aabb,
67 })
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use crate::nurb_curve::NurbCurve;
74 use geop_core_math::for_all_scalars;
75 use geop_core_math::{scalars::Scalar, vector::Vector4};
76
77 fn pt<S: Scalar>(x: f64, y: f64, z: f64, w: f64) -> Vector4<S> {
78 Vector4::from_array([
79 S::from_f64(x),
80 S::from_f64(y),
81 S::from_f64(z),
82 S::from_f64(w),
83 ])
84 }
85
86 fn check_degree_zero_errors<S: Scalar>() {
87 let f = S::from_f64;
88 let c = NurbCurve::try_new(
89 1,
90 vec![pt(0., 0., 0., 1.), pt(1., 0., 0., 1.)],
91 vec![f(0.), f(0.), f(1.), f(1.)],
92 )
93 .unwrap();
94 let d = c.derivative().unwrap();
95 assert!(d.derivative().is_err());
96 }
97 #[test]
98 fn degree_zero_errors() {
99 for_all_scalars!(check_degree_zero_errors);
100 }
101
102 fn check_structural_properties<S: Scalar>() {
103 let f = S::from_f64;
104 let c = NurbCurve::try_new(
105 3,
106 vec![
107 pt(0., 0., 0., 1.),
108 pt(1., 1., 0., 1.),
109 pt(2., 1., 0., 1.),
110 pt(3., 0., 0., 1.),
111 ],
112 vec![f(0.), f(0.), f(0.), f(0.), f(1.), f(1.), f(1.), f(1.)],
113 )
114 .unwrap();
115
116 let d1 = c.derivative().unwrap();
117 assert_eq!(d1.degree, 2);
118 assert_eq!(d1.control_points.len(), 3);
119 assert_eq!(
120 d1.knot_vector.len(),
121 d1.control_points.len() + d1.degree + 1
122 );
123
124 let d2 = d1.derivative().unwrap();
125 assert_eq!(d2.degree, 1);
126 assert_eq!(d2.control_points.len(), 2);
127 assert_eq!(
128 d2.knot_vector.len(),
129 d2.control_points.len() + d2.degree + 1
130 );
131
132 let d3 = d2.derivative().unwrap();
133 assert_eq!(d3.degree, 0);
134 assert_eq!(d3.control_points.len(), 1);
135 assert_eq!(
136 d3.knot_vector.len(),
137 d3.control_points.len() + d3.degree + 1
138 );
139 }
140 #[test]
141 fn structural_properties() {
142 for_all_scalars!(check_structural_properties);
143 }
144
145 fn check_quadratic_derivative_control_points<S: Scalar>() {
146 let f = S::from_f64;
147 let c = NurbCurve::try_new(
148 2,
149 vec![pt(0., 0., 0., 1.), pt(0.5, 0.5, 0., 1.), pt(1., 0., 0., 1.)],
150 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
151 )
152 .unwrap();
153
154 let d = c.derivative().unwrap();
155 assert_eq!(d.degree, 1);
156 assert_eq!(d.control_points.len(), 2);
157
158 assert!(d.control_points[0][0].could_be_equal(S::ONE));
159 assert!(d.control_points[0][1].could_be_equal(S::ONE));
160 assert!(d.control_points[0][2].could_be_equal(S::ZERO));
161 assert!(d.control_points[0][3].could_be_equal(S::ZERO));
162
163 assert!(d.control_points[1][0].could_be_equal(S::ONE));
164 assert!(d.control_points[1][1].could_be_equal(S::ONE.neg()));
165 assert!(d.control_points[1][2].could_be_equal(S::ZERO));
166 assert!(d.control_points[1][3].could_be_equal(S::ZERO));
167 }
168 #[test]
169 fn quadratic_derivative_control_points() {
170 for_all_scalars!(check_quadratic_derivative_control_points);
171 }
172
173 fn check_knot_vector_is_trimmed<S: Scalar>() {
174 let f = S::from_f64;
175 let c = NurbCurve::try_new(
176 2,
177 vec![pt(0., 0., 0., 1.), pt(0.5, 0.5, 0., 1.), pt(1., 0., 0., 1.)],
178 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
179 )
180 .unwrap();
181
182 let d = c.derivative().unwrap();
183 let expected = [0., 0., 1., 1.];
184 for (got, &exp) in d.knot_vector.iter().zip(expected.iter()) {
185 assert!(
186 got.could_be_equal(f(exp)),
187 "expected {exp}, got {}",
188 got.to_f64()
189 );
190 }
191 }
192 #[test]
193 fn knot_vector_is_trimmed() {
194 for_all_scalars!(check_knot_vector_is_trimmed);
195 }
196
197 fn check_weighted_derivative_is_evaluatable<S: Scalar>() {
198 let f = S::from_f64;
199 let c = NurbCurve::try_new(
200 1,
201 vec![pt(0., 0., 0., 1.), pt(1., 0., 0., 2.)],
202 vec![f(0.), f(0.), f(1.), f(1.)],
203 )
204 .unwrap();
205
206 let d = c.derivative().unwrap();
207 assert_eq!(d.degree, 0);
208
209 assert!(d.control_points[0][0].could_be_equal(S::ONE));
210 assert!(d.control_points[0][3].could_be_equal(S::ONE));
211
212 let a_prime = d.evaluate(f(0.5)).unwrap();
213 assert!(a_prime[0].could_be_equal(S::ONE));
214 assert!(a_prime[1].could_be_equal(S::ZERO));
215 }
216 #[test]
217 fn weighted_derivative_is_evaluatable() {
218 for_all_scalars!(check_weighted_derivative_is_evaluatable);
219 }
220}