Skip to content

virtualritz/uniform-cubic-splines

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

56 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

uniform-cubic-splines

Uniform cubic spline interpolation & inversion.

Documentation Crate

This crate supports the following types of splines:

Curve widget with 1D Catmull-Rom spline

Curve widget with a 1D Catmull-Rom spline with non-uniform knot spacing and knot multiplicity using this crate for interpolation (drawn using tiny-skia).

The crate uses generics to allow interpolation of any type for which certain traits are defined.

I.e. you can use this crate to interpolate splines in 1D, 2D, 3D, etc.

[dependencies]
uniform-cubic-splines = { version = "0.4" }

Example

Using a combination of spline_inverse() and spline() it is possible to compute a full spline-with-nonuniform-abscissæ:

use uniform_cubic_splines::prelude::*;

// We want to evaluate the spline at knot value 0.3.
let x = 0.3;

// The first and last points are never interpolated.
let knot_spacing = [0.0, 0.0, 0.1, 0.3, 1.0, 1.0];
let knots        = [0.0, 0.0, 1.3, 4.2, 3.2, 3.2];

let v = spline_inverse::<CatmullRom, _>(x, &knot_spacing).unwrap();
let y = spline::<CatmullRom, _, _>(v, &knots).unwrap();

assert!((y - 4.2).abs() < 1e-6);

Basis Conversion

The convert module converts control values between bases without changing the underlying curve. convert_segment() operates on a single 4-CV window; convert_spline() handles full knot vectors and returns a Vec whose length matches the output basis's stride requirement. Any basis except Linear may be used as the output.

use uniform_cubic_splines::{
    basis::{Bspline, Bezier},
    convert::convert_spline,
};

let bspline_cvs = [0.0_f64, 1.0, 2.0, 3.0, 2.5, 1.5, 0.5];
let bezier_cvs = convert_spline::<Bspline, Bezier, _>(&bspline_cvs);

Features

  • monotonic_check -- The spline_inverse()/spline_inverse_with() code will check if the knot vector is monotonic (enabled by default). Performance impact: Disabling this feature can improve spline_inverse performance by ~5-10% by eliminating monotonicity validation overhead. Only disable if you can guarantee monotonic input, as non-monotonic knots will produce undefined results.
  • alloc -- Enables convert::convert_spline(), which returns a Vec of converted control values (enabled by default). Disable for pure no_std use without a global allocator; convert_segment() remains available.

Using with Math Libraries

The Spline trait can be implemented for vector types from external math libraries. See the tests/nalgebra_integration.rs file for a complete example of implementing the trait for nalgebra::Vector3<f32> and nalgebra::Point3<f32>.

use nalgebra::Vector3;
use uniform_cubic_splines::{spline, CatmullRom};

// After implementing the Spline trait for Vector3<f32>
let knots = vec![
    Vector3::new(0.0, 0.0, 0.0),
    Vector3::new(1.0, 1.0, 1.0),
    Vector3::new(2.0, 0.0, 2.0),
    Vector3::new(3.0, -1.0, 3.0),
];

let result = spline::<CatmullRom, _, _>(0.5, &knots).unwrap();

Note that vector types from math libraries need custom Spline implementations because:

  • The interpolation parameter x must be a scalar (f32/f64)
  • Vectors don't implement the Float trait
  • Component-wise interpolation requires custom logic

See tests/nalgebra_integration.rs for an example of implementing the Spline trait for vector types using wrapper structs.

f16 & f128 Support

This crate supports f16 and f128 types on a nightly toolchain if you use this repository as an overlay in your Cargo.toml.

[patch.crates-io]
uniform-cubic-splines = {
    git = "https://github.com/virtualritz/uniform-cubic-splines.git"
}

This will be supported without an overlay once this PR on num-traits is merged and published.

Background

The code was originally a Rust port of the implementation found in the Open Shading Language C++ source. However, it has since diverged significantly with numerous optimizations and improvements:

  • Optimized spline_inverse: Uses binary search for segment location (O(log n) instead of O(n)) for splines with >12 segments.
  • Improved segment range detection: Evaluates actual spline values at segment boundaries instead of just checking control points.

If you come from a background of computer graphics/shading languages used in offline rendering this crate should feel like home.

Performance

The implementation has been heavily optimized beyond the original OSL C++ source.

Key Optimizations

  • Binary search in spline_inverse: For splines with >12 segments, uses binary search (O(log n)) instead of linear search (O(n)), providing up to 7x speedup for 1,000+ segments.
  • Adaptive root-finding: Illinois modification of Regula Falsi with automatic fallback to bisection for robust convergence.
  • Optimized polynomial evaluation: Uses Horner's method for efficient cubic polynomial evaluation.

Architecture Changes in v0.5

  • Trait-based design: The new Spline trait allows specialized implementations for different types.
  • Error handling: Functions now return Result<T, SplineError> instead of panicking on invalid inputs.

Benchmarks

Performance for scalar types (f32 and f64):

Points f32 spline() f32 spline_inverse() f64 spline() f64 spline_inverse()
10 2.4 ns 19 ns 2.3 ns 18 ns
50 2.4 ns 39 ns 2.4 ns 39 ns
100 2.4 ns 62 ns 2.4 ns 72 ns
500 2.4 ns 265 ns 2.6 ns 263 ns

Benchmarks taken on Ubuntu with rustc 1.83.0-nightly on an AMD Ryzen 7 6800H laptop.

About

Uniform cubic spline evaluation and inversion in Rust

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages