use crate::algebra::ScalarT;
use std::fmt::Debug;
use std::ops::Index;
#[derive(Clone, Debug, PartialEq)]
pub struct KnotVec<N: ScalarT> {
knots: Vec<N>,
}
impl<N: ScalarT> KnotVec<N> {
pub fn new(knots: Vec<N>) -> Option<Self> {
if knots.len() >= 2 && knots.is_sorted() && &knots[0] != knots.last().unwrap() {
Some(KnotVec { knots })
} else {
None
}
}
pub fn len(&self) -> usize {
self.knots.len()
}
pub fn is_clamped(&self, degree: usize) -> bool {
if self.knots.len() < 2 * (degree + 1) {
false
} else {
let start_knot = self.knots[0];
for i_knot in &self.knots[1..degree] {
if *i_knot != start_knot {
return false;
}
}
let end_knot = self.knots.last().unwrap();
for e_knot in &self.knots[self.knots.len() - degree - 1..self.knots.len() - 1] {
if e_knot != end_knot {
return false;
}
}
true
}
}
pub fn is_empty(&self) -> bool {
false
}
pub fn min_u(&self) -> N {
self.knots[0]
}
pub fn max_u(&self) -> N {
*self
.knots
.last()
.expect("last() should always succeed, because there should be >=2 knots")
}
pub fn clamp(&self, u: N) -> N {
if u < self.min_u() {
self.min_u()
} else if u > self.max_u() {
self.max_u()
} else {
u
}
}
pub fn find_span(&self, u: N) -> usize {
debug_assert!(
u >= self.min_u(),
"parameter u={:?} is below the required range {:?} <= u <= {:?}",
u,
self.min_u(),
self.max_u()
);
debug_assert!(
u <= self.max_u(),
"parameter u={:?} is above the required range {:?} <= u <= {:?}",
u,
self.min_u(),
self.max_u()
);
if u == self.max_u() {
self.knots
.iter()
.enumerate()
.rev()
.find(|&item| item.1 < &u)
.unwrap()
.0
} else {
let mut low: usize = 0;
let mut high: usize = self.len() - 1;
let mut mid: usize = (low + high) / 2;
while u < self.knots[mid] || u >= self.knots[mid + 1] {
if u < self.knots[mid] {
high = mid;
} else {
low = mid;
}
mid = (low + high) / 2;
}
mid
}
}
}
impl<N: ScalarT> Index<usize> for KnotVec<N> {
type Output = N;
fn index(&self, i: usize) -> &Self::Output {
&self.knots[i]
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn new() {
let knots = KnotVec::new(vec![0.0, 0.0, 0.5, 1.0, 1.0]).unwrap();
assert_eq!(knots.len(), 5);
assert_eq!(knots.is_empty(), false);
assert_eq!(knots[0], 0.0);
assert_eq!(knots[1], 0.0);
assert_eq!(knots[2], 0.5);
assert_eq!(knots[3], 1.0);
assert_eq!(knots[4], 1.0);
assert_eq!(knots.min_u(), 0.0);
assert_eq!(knots.max_u(), 1.0);
}
#[test]
fn is_clamped() {
let knots1 = KnotVec::new(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]).unwrap();
assert!(knots1.is_clamped(2));
assert!(!knots1.is_clamped(3));
let knots2 = KnotVec::new(vec![0.0, 0.0, 0.0, 1.0, 1.0]).unwrap();
assert!(knots2.is_clamped(1));
assert!(!knots2.is_clamped(2));
assert!(!knots2.is_clamped(100));
}
#[test]
fn clamp() {
let knots = KnotVec::new(vec![0.0, 0.0, 1.0, 1.0]).unwrap();
assert_eq!(knots.clamp(-1.0), 0.0);
assert_eq!(knots.clamp(0.5), 0.5);
assert_eq!(knots.clamp(1.2), 1.0);
}
#[test]
fn less_than_two_knots() {
assert_eq!(KnotVec::new(vec![0.0]), None);
}
#[test]
fn badly_ordered_knots() {
assert_eq!(KnotVec::new(vec![1.0, 0.0]), None);
}
#[test]
fn degenerate_knots() {
assert_eq!(KnotVec::new(vec![0.0, 0.0, 0.0]), None);
}
#[test]
fn find_span() {
let knots = KnotVec::new(vec![0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 5.0, 5.0]).unwrap();
assert_eq!(knots.find_span(0.0), 1);
assert_eq!(knots.find_span(3.001), 4);
assert_eq!(knots.find_span(4.0), 6);
assert_eq!(knots.find_span(5.0), 6);
}
#[test]
#[should_panic(expected = "parameter u=0.5 is below the required range 1.0 <= u <= 5.0")]
fn find_span_panic_when_u_is_too_low() {
let knots = KnotVec::new(vec![1.0, 1.0, 1.0, 5.0, 5.0, 5.0]).unwrap();
knots.find_span(0.5);
}
#[test]
#[should_panic(expected = "parameter u=5.5 is above the required range 1.0 <= u <= 5.0")]
fn find_span_panic_when_u_is_too_high() {
let knots = KnotVec::new(vec![1.0, 1.0, 1.0, 5.0, 5.0, 5.0]).unwrap();
knots.find_span(5.5);
}
prop_compose! {
fn arb_knotvec(min_len: usize)
(len in min_len..128)
(mut ks in proptest::collection::vec(any::<f32>(), len)) -> KnotVec<f32>
{
ks.sort_by(|a, b| a.partial_cmp(b).unwrap());
if ks.last().unwrap() == &ks[0] {
ks.push(ks.last().unwrap() + 1.0);
}
KnotVec::new(ks).unwrap()
}
}
prop_compose! {
fn arb_knotvec_and_param()
(knotvec in arb_knotvec(2))
(u in knotvec.min_u()..knotvec.max_u(), knotvec in Just(knotvec)) -> (f32, KnotVec<f32>)
{
(u, knotvec)
}
}
proptest! {
#[test]
fn knot_span_contains_knot((u, knotvec) in arb_knotvec_and_param()) {
let i: usize = knotvec.find_span(u);
assert!(knotvec[i] <= u);
if u < knotvec.max_u() {
assert!(knotvec[i+1] >= u);
} else {
assert!(knotvec[i+1] > u);
}
}
}
}