use crate::{
curve::{knots::Knots, parameters::Parameters, CurveError},
types::VecD,
};
fn inputCheck(degree: usize, segments: usize) -> Result<(), CurveError> {
match degree {
0 => Err(CurveError::DegreeTooLow { p: degree, limit: 0 }),
degree if degree > segments => Err(CurveError::DegreeAndSegmentsMismatch { p: degree, n: segments }),
_ => Ok(()),
}
}
pub fn uniform(degree: usize, segments: usize) -> Result<Knots, CurveError> {
let p = degree;
let n = segments;
inputCheck(p, n)?;
let internal_knot_count = n - p;
let mut U = VecD::zeros(p + n + 2);
for i in 1..=internal_knot_count {
U[degree + i] = i as f64 / (internal_knot_count + 1) as f64
}
for i in n + 1..U.len() {
U[i] = 1.;
}
Ok(Knots::new(degree, U))
}
pub fn averaging(degree: usize, segments: usize, parameters: &Parameters) -> Result<Knots, CurveError> {
let p = degree;
let n = segments;
inputCheck(p, n)?;
let internal_knot_count = n - p;
let U_bar = parameters.vector();
let mut U = VecD::zeros(p + n + 2);
for j in 1..=internal_knot_count {
let mut parameter_sum = 0.;
let lim = j + p - 1;
for i in j..=lim {
parameter_sum += U_bar[i];
}
U[p + j] = parameter_sum / p as f64;
}
for j in n + 1..U.len() {
U[j] = 1.;
}
Ok(Knots::new(degree, U))
}
pub fn de_boor(degree: usize, segments: usize, parameters: &Parameters) -> Result<Knots, CurveError> {
let p = degree;
let n = segments;
inputCheck(p, n)?;
let U_bar = parameters.vector();
let internal_knot_count = n - p;
let internal_knot_spans = internal_knot_count + 1;
let d = (n + 1) as f64 / (internal_knot_spans) as f64;
let mut U = VecD::zeros(p + n + 2);
for j in 1..=internal_knot_count {
let jd = j as f64 * d;
let i = jd as usize;
let alpha = jd - i as f64;
U[degree + j] = (1f64 - alpha) * U_bar[i - 1] + alpha * U_bar[i];
}
for j in n + 1..U.len() {
U[j] = 1.;
}
Ok(Knots::new(degree, U))
}
#[cfg(test)]
mod tests {
use nalgebra::dvector;
use crate::curve::parameters::methods::equally_spaced;
use super::*;
mod uniform {
use rstest::rstest;
use super::*;
#[test]
fn degree_0_errors() {
let degree = 0;
let segments = degree + 1;
assert!(uniform(degree, segments).is_err());
}
#[test]
fn degree_1() {
assert_eq!(uniform(1, 4).unwrap().Uk[0], dvector![0., 0., 0.25, 0.5, 0.75, 1., 1.]);
}
#[test]
fn degree_2() {
assert_eq!(uniform(2, 4).unwrap().Uk[0], dvector![0., 0., 0., 1. / 3., 2. / 3., 1., 1., 1.]);
}
#[test]
fn degree_3() {
assert_eq!(uniform(3, 4).unwrap().Uk[0], dvector![0., 0., 0., 0., 0.5, 1., 1., 1., 1.]);
}
#[test]
fn it_creates_knot_vectors_delta0() {
for i in 1..3 {
assert_eq!(uniform(i, i).unwrap().Uk[0], VecD::from_vec([vec![0.; i + 1], vec![1.; i + 1]].concat()));
}
}
#[test]
fn it_creates_knot_vectors_delta1() {
for i in 1..3 {
assert_eq!(
uniform(i, i + 1).unwrap().Uk[0],
VecD::from_vec([vec![0f64; i + 1], vec![0.5f64], vec![1f64; i + 1]].concat())
);
}
}
#[test]
fn it_creates_knot_vectors_delta2() {
for i in 1..3 {
assert_eq!(
uniform(i, i + 2).unwrap().Uk[0],
VecD::from_vec([vec![0f64; i + 1], vec![1f64 / 3f64, 2f64 / 3f64], vec![1f64; i + 1]].concat())
);
}
}
#[rstest(degree, case(1), case(2), case(3))]
fn segments_eq_degree(degree: usize) {
let segments = degree;
let head = vec![0.0; degree + 1];
let tail = vec![1.0; degree + 1];
assert_eq!(uniform(degree, segments).unwrap().Uk[0], VecD::from_vec([head, tail].concat()));
}
#[rstest(degree, case(1), case(2), case(3))]
fn segments_eq_degree_plus_1(degree: usize) {
let segments = degree + 1;
let head = vec![0.0; degree + 1];
let internal = vec![0.5];
let tail = vec![1.0; degree + 1];
assert_eq!(uniform(degree, segments).unwrap().Uk[0], VecD::from_vec([head, internal, tail].concat()));
}
}
mod averaging {
use rstest::rstest;
use crate::curve::knots::methods::averaging;
use super::*;
#[test]
fn degree_0_errors() {
let degree = 0;
let segments = degree + 1; let params = equally_spaced(segments);
assert!(averaging(degree, segments, ¶ms).is_err());
}
#[test]
fn degree_1() {
let segments = 4; let params = equally_spaced(segments);
assert_eq!(averaging(1, segments, ¶ms).unwrap().Uk[0], dvector![0., 0., 0.25, 0.5, 0.75, 1., 1.]);
}
#[test]
fn degree_2() {
let segments = 4; let params = equally_spaced(segments);
assert_eq!(averaging(2, segments, ¶ms).unwrap().Uk[0], dvector![0., 0., 0., 0.375, 0.625, 1., 1., 1.]);
}
#[test]
fn degree_3() {
let segments = 4; let params = equally_spaced(4);
assert_eq!(averaging(3, segments, ¶ms).unwrap().Uk[0], dvector![0., 0., 0., 0., 0.5, 1., 1., 1., 1.]);
}
#[rstest(degree, case(1), case(2), case(3))]
fn segments_eq_degree(degree: usize) {
let segments = degree; let params = equally_spaced(4);
let head = vec![0.0; degree + 1];
let tail = vec![1.0; degree + 1];
assert_eq!(averaging(degree, segments, ¶ms).unwrap().Uk[0], VecD::from_vec([head, tail].concat()));
}
#[rstest(degree, case(1), case(2), case(3))]
fn segments_eq_degree_plus_1(degree: usize) {
let segments = degree + 1; let params = equally_spaced(segments);
let head = vec![0.0; degree + 1];
let internal = vec![0.5];
let tail = vec![1.0; degree + 1];
assert_eq!(
averaging(degree, segments, ¶ms).unwrap().Uk[0],
VecD::from_vec([head, internal, tail].concat())
);
}
}
mod de_boor {
use approx::assert_relative_eq;
use nalgebra::dvector;
use rstest::rstest;
use super::*;
#[test]
fn degree_0_errors() {
let degree = 0;
let segments = degree + 1; let params = equally_spaced(segments);
assert!(de_boor(degree, segments, ¶ms).is_err());
}
#[test]
fn degree_1() {
let segments = 4; let params = equally_spaced(segments);
assert_eq!(de_boor(1, segments, ¶ms).unwrap().Uk[0], dvector![0., 0., 0.0625, 0.375, 0.6875, 1., 1.]);
}
#[test]
fn degree_2() {
let segments = 4; let params = equally_spaced(segments);
assert_relative_eq!(
de_boor(2, segments, ¶ms).unwrap().Uk[0],
dvector![0., 0., 0., 1. / 6., 1. / 3. + 0.25, 1., 1., 1.],
epsilon = f64::EPSILON.sqrt()
);
}
#[test]
fn degree_3() {
let segments = 4; let params = equally_spaced(4);
assert_eq!(de_boor(3, segments, ¶ms).unwrap().Uk[0], dvector![0., 0., 0., 0., 0.375, 1., 1., 1., 1.]);
}
#[rstest(degree, case(1), case(2), case(3))]
fn segments_eq_degree(degree: usize) {
let segments = degree; let params = equally_spaced(4);
let head = vec![0.0; degree + 1];
let tail = vec![1.0; degree + 1];
assert_eq!(de_boor(degree, segments, ¶ms).unwrap().Uk[0], VecD::from_vec([head, tail].concat()));
}
#[rstest(degree, internal_knot, case(1, 1. / 4.), case(2, 2. / 6.), case(3, 3. / 8.))]
fn segments_eq_degree_plus_1(degree: usize, internal_knot: f64) {
let segments = degree + 1; let params = equally_spaced(segments);
let head = vec![0.0; degree + 1];
let internal = vec![internal_knot];
let tail = vec![1.0; degree + 1];
assert_eq!(
de_boor(degree, segments, ¶ms).unwrap().Uk[0],
VecD::from_vec([head, internal, tail].concat())
);
}
}
}