I have made the following definition of a spline data structure:
#[derive(Clone, Debug)]
pub struct BSpline
{
knots: Vec<f32>,
control_points: Vec<Vec3>,
/// Internal helper variable. Used to optimize repetetitive samplings on close
/// $t$ values.
last_t_index: usize,
order: usize,
}
impl BSpline
{
pub fn sample(&self, t: f32) -> Vec3
{
debug_assert!(self.control_points.len() >= self.order);
let t = f32::clamp(t, self.knots[0], *self.knots.last().unwrap());
let mut t_index = self.last_t_index;
while !(t >= self.knots[t_index] && t <= self.knots[t_index + 1])
{
t_index = (t_index + 1) % self.knots.len();
}
// TODO(low): find a better way than this hack.
let ptr = &self.last_t_index as *const usize as *mut usize;
unsafe {
ptr.write(usize::min(t_index, self.order - 1));
};
// Create the cache to compute the pyramid of intermediate evaluations.
let order = self.order;
let degree = order - 1;
let mut evaluation = Vec::new();
for i in 0..order
{
evaluation.push(self.control_points[t_index - degree + i]);
}
for l in 0..degree
{
for j in ((l + 1)..=degree).rev()
{
let alpha = (t - self.knots[t_index - degree + j])
/ (self.knots[t_index - l + j] - self.knots[t_index - degree + j]);
evaluation[j] = (1.0 - alpha) * evaluation[j - 1] + alpha * evaluation[j];
}
}
evaluation[degree]
}
}
Note the absolutely awful hack I needed to do to make the sample method take a reference rather than a mutable reference. Which of course makes the entire data structure unsound.
The reasons the hack:
I need to compute a lot of differential operators on the spline, which are defined generically, defining these operators to take FnMut instead of Fn leads to a lot of superflous cloning which is problematic.
I need this to be fast, I don't want the overhead from a mutex or even from an atomic boolean check.
UnsafeCells are not clonable.
The code is intended to be used in single threaded code only, and in that case this should work ok, but it's still a red flag. Is there something I can do to improve it?
Make last_t_index
a Cell<usize>
. It provides interior mutability, is Clone
-able, and has no runtime overhead. It does this by requiring that the .get()
and .set()
methods copy the value, but for usize
that is of no concern.