hadronized / splines

Interpolation made easy.
https://crates.io/crates/splines
Other
162 stars 34 forks source link

Support slerp #52

Closed iwikal closed 1 week ago

iwikal commented 4 years ago

Normalized lerps are nice and all, but what if my keyframes are far enough apart that the non-constant velocity becomes noticable? I would like for there to be a convenient way to get something more slerpy-looking. Besides somehow adding support for using real slerps in quaternion splines, I had an idea of a method on Spline that would make it easier to still get the performance benefits of nlerps:

  /// Sample a spline at a regular interval and construct a new spline with these samples as contol
  /// points, and the supplied interpolation variant.
  pub fn resampled(&self, interval: T, interpolation: Interpolation<T, V>) -> Self {
    self.clone().resample()
  }

  pub fn into_resampled(mut self, interval: T, interpolation: Interpolation<T, V>) -> Self {
    self.resample();
    self
  }

  pub fn resample(&mut self, interval: T, interpolation: Interpolation<T, V>) {
    todo!()
  }

I'm sure a regular interval is not the most optimal resampling strategy, but it's the easiest to implement. A more sophisticated strategy would try to minimize the number of control points while staying within some allowable error margin.

iwikal commented 4 years ago

One possible way to support slerp without having to touch a lot of the splines internals would be to make use of this resampling, and to introduce another public trait:

pub trait Slerp<T> {
  fn slerp(other: Self, t: T) -> Self;
}

Quaternions would implement this trait. We also add a wrapper struct around the quats that forwards Interpolate::lerp to Slerp::slerp.

struct SlerpWrapper<V>(V);

impl<T, V> Interpolate<T> for SlerpWrapper<V>
  where V: Slerp<T>
{
  fn lerp(a: Self, b: Self, t: T) -> Self {
    a.0.slerp(b.0, t)
  }
}

Then we can add another constructor for Spline that resamples using this wrapper, then unwraps it again:

pub fn resample_quats<T, V>(keys: Vec<Key<T, V>>, interval: T, interpolation: Interpolation<T, V>) -> Self
  where V: Slerp<T>
{
  let mut spline = Self::from_iter(keys.into_iter().map(
    |Key { t, interpolation, value }| Key { t, interpolation, value: SlerpWrapper(value) }
  );
  spline.resample(interval, interpolation);
  Self::from_iter(spline.0.into_iter().map(
    |Key { t, interpolation, value: SlerpWrapper(value) }| Key { t, interpolation, value }
  )
}
iwikal commented 4 years ago

I would also be completely fine with not having that interpolation argument. Linear interpolation is probably the most common thing you want after resampling, since it's the cheapest.

hadronized commented 4 years ago

Hm, I think slerp should be an implementation detail of Interpolate, right? Maybe the bounds need to be adapted, indeed. I’ll have a deeper look at your issue this night — I’m also following what people are doing in bevy. Thanks for opening! <3

iwikal commented 4 years ago

I guess there's nothing in the core of splines itself that specifies nlerp to be the default impl for Quat, but that's how it works in the provided impl for cgmath::Quat, and how I did it in my impl-glam PR for consistency. A thought: maybe it should be the opposite to what I just proposed here. Maybe the default behavior should be the accurate, albeit slow implementation, and if people want the faster, less general-purpose solution, they opt into that?

iwikal commented 4 years ago

An unintended but perhaps useful side effect of the resampling feature is that it can be used for other kinds of splines too. I don't know the performance characteristics of the algorithms splines supports, but if there's an expensive one, it can also be approximated by resampling it to shorter linear segments.

hadronized commented 1 week ago

I’m moving the project to sourcehut. Feel free to reopen there if you want to — issues / mailing list. Thank you.