mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 01:18:12 +08:00
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:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
45edeb2a2b
commit
f8e72be492
@@ -519,6 +519,17 @@ impl Document {
|
||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::poly_line(points, *style)), *transform), *insert_index)?;
|
||||
Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }], update_thumbnails_upstream(path)].concat())
|
||||
}
|
||||
Operation::AddSpline {
|
||||
path,
|
||||
insert_index,
|
||||
points,
|
||||
transform,
|
||||
style,
|
||||
} => {
|
||||
let points: Vec<glam::DVec2> = points.iter().map(|&it| it.into()).collect();
|
||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::spline(points, *style)), *transform), *insert_index)?;
|
||||
Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }], update_thumbnails_upstream(path)].concat())
|
||||
}
|
||||
Operation::DeleteLayer { path } => {
|
||||
fn aggregate_deletions(folder: &Folder, path: &mut Vec<LayerId>, responses: &mut Vec<DocumentResponse>) {
|
||||
for (id, layer) in folder.layer_ids.iter().zip(folder.layers()) {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,13 @@ pub enum Operation {
|
||||
points: Vec<(f64, f64)>,
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddSpline {
|
||||
path: Vec<LayerId>,
|
||||
transform: [f64; 6],
|
||||
insert_index: isize,
|
||||
points: Vec<(f64, f64)>,
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddNgon {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
|
||||
Reference in New Issue
Block a user