hadronized / splines

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

tangents of 0.0 returns unexpected value. #34

Closed rukai closed 5 years ago

rukai commented 5 years ago

Hey, thanks for the quick implementation of Interpolation::StrokeBezier! This example program:

extern crate splines;

use splines::{Interpolation, Key, Spline};

fn main() {
  let keys = vec![
      Key::new(0.0, 1.0, Interpolation::StrokeBezier(0.0, 0.0)),
      Key::new(5.0, 1.0, Interpolation::StrokeBezier(0.0, 0.0))
    ];
  let spline = Spline::from_vec(keys);

  println!("value at 0: {:?}", spline.clamped_sample(0.0));
  println!("value at 1: {:?}", spline.clamped_sample(1.0));
  println!("value at 2: {:?}", spline.clamped_sample(2.0));
  println!("value at 3: {:?}", spline.clamped_sample(3.0));
  println!("value at 4: {:?}", spline.clamped_sample(4.0));
  println!("value at 5: {:?}", spline.clamped_sample(5.0));
}

Outputs:

value at 0: Some(1.0)
value at 1: Some(0.7120000000000002)
value at 2: Some(0.856)
value at 3: Some(1.1440000000000001)
value at 4: Some(1.288)
value at 3: Some(1.0)

I would expect it to output Some(1.0) for every input value.

This is based on some gltf data blender is giving me, used to interpolate the scaling of bones. However the exported animation should not scale the bones hence why I assume that this case should always output 1.0. But maybe I need to be handling this case manually in my code and it is not a problem of the splines crate? What do you think?

hadronized commented 5 years ago

Maybe try the second key with Interpolation::StrokeBezier(5., 0.).

rukai commented 5 years ago

I tried it and it gives the same output.

For reference, the example I posted is a simplified version of my gltf animation interpolation code:

                    let mut points = vec!();
                    for (input, outputs) in channel.inputs.iter().zip(scales.chunks(3)) {
                        points.push(Key::new(*input, outputs[1], SplineInterpolation::StrokeBezier(outputs[0], outputs[2])));
                        println!("spline vertex: {:?}, input tangent: {:?}, output tangent: {:?}", outputs[1], outputs[0], outputs[2]);
                    }
                    let spline = Spline::from_vec(points);
                    if let Some(result) = spline.clamped_sample(seconds) {
                        scale = result;
                    }
                    else {
                        error!("Failed to interpolate scale spline");
                    }
hadronized commented 5 years ago

Ok so I thought about your issue and I think I got why. Interpolation::Bezier(..) and Interpolation::StrokeBezier(..) works in the same dimension as the value carried by the key, not the key itself. What it means is that if you have a Spline<f32, f32>, you will never get a very interesting Bézier interpolation here (it’s a 1D Bézier interpolation). The T argument of the Spline allows you to modulate the speed at which you sample keys. So if you want a 2D spline interpolation, and hence have a better Bézier, you need Spline<f32, [f32; 2]>, for instance (or whatever algebra crate type you need). If you don’t know which value take for T, I typically use the same as the x coordinate of the point. Adding a point then looks like spline.add(Key::new(p[0], p, interpolation)).

I’ll change spline-editor as linked above to show what stroke Bézier curves look like and I might add some hints in the documentation about the T parameter.

rukai commented 5 years ago

gltf specifies the input value so using the x component of the output doesnt really make sense for me... https://github.com/rukai/canon_collision/blob/65b800ce02e2b62d8130212bb9cd0ab3d0ca33bf/canon_collision/src/wgpu/animation.rs#L78

rukai commented 5 years ago

Using the branch in your PR the example code I originally posted now outputs:

value at 0: Some(1.0)
value at 1: Some(0.5200000000000001)
value at 2: Some(0.28)
value at 3: Some(0.28)
value at 4: Some(0.5200000000000001)
value at 3: Some(1.0)

I still think it should be outputing 1.0 for all values.

hadronized commented 5 years ago

So I don’t really know what happens for Bézier in 1D but I think it cannot work the way you expect it to work. Try to use 2D Bézier as I did here.

rukai commented 5 years ago

Ok, so this is my example in 2D (although I am personally using 3D)

use splines::{Interpolation, Key, Spline};

fn main() {
  let keys = vec![
      Key::new(0.0, cg::Vector2::new(1.0, 1.0), Interpolation::StrokeBezier(cg::Vector2::new(0.0, 0.0), cg::Vector2::new(0.0, 0.0))),
      Key::new(5.0, cg::Vector2::new(1.0, 1.0), Interpolation::StrokeBezier(cg::Vector2::new(0.0, 0.0), cg::Vector2::new(0.0, 0.0))),
    ];
  let spline = Spline::from_vec(keys);

  println!("value at 0: {:?}", spline.clamped_sample(0.0));
  println!("value at 1: {:?}", spline.clamped_sample(1.0));
  println!("value at 2: {:?}", spline.clamped_sample(2.0));
  println!("value at 3: {:?}", spline.clamped_sample(3.0));
  println!("value at 4: {:?}", spline.clamped_sample(4.0));
  println!("value at 5: {:?}", spline.clamped_sample(5.0));
}

this outputs

value at 0: Some(Vector2 [1.0, 1.0])
value at 1: Some(Vector2 [0.5200000000000001, 0.5200000000000001])
value at 2: Some(Vector2 [0.28, 0.28])
value at 3: Some(Vector2 [0.28, 0.28])
value at 4: Some(Vector2 [0.5200000000000001, 0.5200000000000001])
value at 5: Some(Vector2 [1.0, 1.0])

Again, it is quite possible I am misinterpreting the gltf data or I'm meant to handle tangents of 0 as a special case in my code.

rukai commented 5 years ago

I think this is the three.js implementation https://github.com/mrdoob/three.js/blob/25a59dc9a96e5b51c26fd260faa3ea526ddae644/examples/jsm/loaders/GLTFLoader.js#L1087

Oh huh... Just realized gltf specifies the spline implementation at the bottom of the spec https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation Totally missed that!

rukai commented 5 years ago

I've implemented it myself https://github.com/rukai/canon_collision/blob/7fc9af477b074bc5f05dbd88a42896d022a229f8/canon_collision/src/wgpu/animation.rs#L91 Now that I understand how it works, its pretty simple, so I'd rather just re-implement it in my project to avoid an extra dependency.

Thanks for your time implementing extra features in splines! I will close this issue as the problem was that I was trying to use splines for something that it didn't support. Feel free to reopen it if you want to implement the type of spline that gltf uses.

hadronized commented 5 years ago

The one implemented in splines for cubic Bézier is this one, if that might help.