Implement the Spline Tool (#512)

* Add Spline Tool

* Adapt to changes from master

* Apply review feedback

* Fixes

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Paul Kupper
2022-02-10 01:22:57 +01:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 45edeb2a2b
commit f8e72be492
17 changed files with 409 additions and 29 deletions
+78
View File
@@ -155,4 +155,82 @@ impl Shape {
closed: false,
}
}
/// Creates a smooth bezier spline that passes through all given points.
/// The algorithm used in this implementation is described here: https://www.particleincell.com/2012/bezier-splines/
pub fn spline(points: Vec<impl Into<glam::DVec2>>, style: PathStyle) -> Self {
let mut path = kurbo::BezPath::new();
// Creating a bezier spline is only necessary for 3 or more points.
// For 2 given points a line segment is created instead.
if points.len() > 2 {
let points: Vec<_> = points.into_iter().map(|v| v.into()).map(|v: DVec2| kurbo::Vec2 { x: v.x, y: v.y }).collect();
// Number of bezier segments
let n = points.len() - 1;
// Control points for each bezier segment
let mut p1 = vec![kurbo::Vec2::ZERO; n];
let mut p2 = vec![kurbo::Vec2::ZERO; n];
// Tri-diagonal matrix coefficients a, b and c (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
let mut a = vec![1.0; n];
a[0] = 0.0;
a[n - 1] = 2.0;
let mut b = vec![4.0; n];
b[0] = 2.0;
b[n - 1] = 7.0;
let mut c = vec![1.0; n];
c[n - 1] = 0.0;
let mut r: Vec<_> = (0..n).map(|i| 4.0 * points[i] + 2.0 * points[i + 1]).collect();
r[0] = points[0] + (2.0 * points[1]);
r[n - 1] = 8.0 * points[n - 1] + points[n];
// Solve with Thomas algorithm (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
for i in 1..n {
let m = a[i] / b[i - 1];
// TODO: Fix Clippy warning which makes the borrow checker angry
b[i] = b[i] - m * c[i - 1];
r[i] = r[i] - m * r[i - 1];
}
// Determine first control point for each segment
p1[n - 1] = r[n - 1] / b[n - 1];
for i in (0..n - 1).rev() {
p1[i] = (r[i] - c[i] * p1[i + 1]) / b[i];
}
// Determine second control point per segment from first
for i in 0..n - 1 {
p2[i] = 2.0 * points[i + 1] - p1[i + 1];
}
p2[n - 1] = 0.5 * (points[n] + p1[n - 1]);
// Create bezier path from given points and computed control points
points.into_iter().enumerate().for_each(|(i, p)| {
if i == 0 {
path.move_to(p.to_point())
} else {
path.curve_to(p1[i - 1].to_point(), p2[i - 1].to_point(), p.to_point())
}
});
} else {
points
.into_iter()
.map(|v| v.into())
.map(|v: DVec2| kurbo::Point { x: v.x, y: v.y })
.enumerate()
.for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) });
}
Self {
path,
style,
render_index: 0,
closed: false,
}
}
}