Layer and grid snapping systems (#1521)

* Grid overlays

* Rectangle tool basic snapping

* Fix bezier demos

* Fix bézier crate tests

* Constrained snapping for circle & shape tool

* Line tool snapping

* Pen tool snapping

* Path tool snapping

* Snapping whilst dragging layers (not constrained)

* Constrained drag

* Resize snapping

* Normal and tangent

* Cleanup

* Grid snapping

* Grid snapping

* Fix imports

* Fix bug in artboard tool

* Fix hang on 0 size grid spacing

* Fix NaN when scaling

* Polishing

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2024-01-13 14:32:10 +00:00
committed by GitHub
co-authored by Keavon Chambers
parent 78a1bb17cd
commit 456ca170a4
40 changed files with 2170 additions and 475 deletions
+33 -35
View File
@@ -93,37 +93,31 @@ pub fn compute_abc_for_cubic_through_points(start_point: DVec2, point_on_curve:
/// Return the index and the value of the closest point in the LUT compared to the provided point.
pub fn get_closest_point_in_lut(lut: &[DVec2], point: DVec2) -> (usize, f64) {
lut.iter()
.enumerate()
.map(|(i, p)| (i, point.distance_squared(*p)))
.min_by(|x, y| (x.1).partial_cmp(&(y.1)).unwrap())
.unwrap()
lut.iter().enumerate().map(|(i, p)| (i, point.distance_squared(*p))).min_by(|x, y| (x.1).total_cmp(&(y.1))).unwrap()
}
// TODO: Use an `Option` return type instead of a `Vec`
/// Find the roots of the linear equation `ax + b`.
pub fn solve_linear(a: f64, b: f64) -> Vec<f64> {
let mut roots = Vec::new();
pub fn solve_linear(a: f64, b: f64) -> [Option<f64>; 3] {
// There exist roots when `a` is not 0
if a.abs() > MAX_ABSOLUTE_DIFFERENCE {
roots.push(-b / a);
[Some(-b / a), None, None]
} else {
[None; 3]
}
roots
}
// TODO: Use an `impl Iterator` return type instead of a `Vec`
/// Find the roots of the linear equation `ax^2 + bx + c`.
/// Precompute the `discriminant` (`b^2 - 4ac`) and `two_times_a` arguments prior to calling this function for efficiency purposes.
pub fn solve_quadratic(discriminant: f64, two_times_a: f64, b: f64, c: f64) -> Vec<f64> {
let mut roots = Vec::new();
pub fn solve_quadratic(discriminant: f64, two_times_a: f64, b: f64, c: f64) -> [Option<f64>; 3] {
let mut roots = [None; 3];
if two_times_a.abs() <= STRICT_MAX_ABSOLUTE_DIFFERENCE {
roots = solve_linear(b, c);
} else if discriminant.abs() <= STRICT_MAX_ABSOLUTE_DIFFERENCE {
roots.push(-b / (two_times_a));
roots[0] = Some(-b / (two_times_a));
} else if discriminant > 0. {
let root_discriminant = discriminant.sqrt();
roots.push((-b + root_discriminant) / (two_times_a));
roots.push((-b - root_discriminant) / (two_times_a));
roots[0] = Some((-b + root_discriminant) / (two_times_a));
roots[1] = Some((-b - root_discriminant) / (two_times_a));
}
roots
}
@@ -139,8 +133,8 @@ fn cube_root(f: f64) -> f64 {
// TODO: Use an `impl Iterator` return type instead of a `Vec`
/// Solve a cubic of the form `x^3 + px + q`, derivation from: <https://trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm>.
pub fn solve_reformatted_cubic(discriminant: f64, a: f64, p: f64, q: f64) -> Vec<f64> {
let mut roots = Vec::new();
pub fn solve_reformatted_cubic(discriminant: f64, a: f64, p: f64, q: f64) -> [Option<f64>; 3] {
let mut roots = [None; 3];
if discriminant.abs() <= STRICT_MAX_ABSOLUTE_DIFFERENCE {
// When discriminant is 0 (check for approximation because of floating point errors), all roots are real, and 2 are repeated
// filter out repeated roots (ie. roots whose distance is less than some epsilon)
@@ -149,15 +143,15 @@ pub fn solve_reformatted_cubic(discriminant: f64, a: f64, p: f64, q: f64) -> Vec
let root_1 = 2. * cube_root(-q_divided_by_2) - a_divided_by_3;
let root_2 = cube_root(q_divided_by_2) - a_divided_by_3;
if (root_1 - root_2).abs() > MIN_SEPARATION_VALUE {
roots.push(root_1);
roots[0] = Some(root_1);
}
roots.push(root_2);
roots[1] = Some(root_2);
} else if discriminant > 0. {
// When discriminant > 0, there is one real and two imaginary roots
let q_divided_by_2 = q / 2.;
let square_root_discriminant = discriminant.powf(1. / 2.);
roots.push(cube_root(-q_divided_by_2 + square_root_discriminant) - cube_root(q_divided_by_2 + square_root_discriminant) - a / 3.);
roots[0] = Some(cube_root(-q_divided_by_2 + square_root_discriminant) - cube_root(q_divided_by_2 + square_root_discriminant) - a / 3.);
} else {
// Otherwise, discriminant < 0 and there are three real roots
let p_divided_by_3 = p / 3.;
@@ -166,16 +160,16 @@ pub fn solve_reformatted_cubic(discriminant: f64, a: f64, p: f64, q: f64) -> Vec
let phi = (-q / (2. * cube_root_r.powi(3))).acos();
let two_times_cube_root_r = 2. * cube_root_r;
roots.push(two_times_cube_root_r * (phi / 3.).cos() - a_divided_by_3);
roots.push(two_times_cube_root_r * ((phi + 2. * PI) / 3.).cos() - a_divided_by_3);
roots.push(two_times_cube_root_r * ((phi + 4. * PI) / 3.).cos() - a_divided_by_3);
roots[0] = Some(two_times_cube_root_r * (phi / 3.).cos() - a_divided_by_3);
roots[1] = Some(two_times_cube_root_r * ((phi + 2. * PI) / 3.).cos() - a_divided_by_3);
roots[2] = Some(two_times_cube_root_r * ((phi + 4. * PI) / 3.).cos() - a_divided_by_3);
}
roots
}
// TODO: Use an `impl Iterator` return type instead of a `Vec`
/// Solve a cubic of the form `ax^3 + bx^2 + ct + d`.
pub fn solve_cubic(a: f64, b: f64, c: f64, d: f64) -> Vec<f64> {
pub fn solve_cubic(a: f64, b: f64, c: f64, d: f64) -> [Option<f64>; 3] {
if a.abs() <= STRICT_MAX_ABSOLUTE_DIFFERENCE {
if b.abs() <= STRICT_MAX_ABSOLUTE_DIFFERENCE {
// If both a and b are approximately 0, treat as a linear problem
@@ -327,43 +321,47 @@ mod tests {
a.len() == b.len() && a.into_iter().zip(b).all(|(a, b)| f64_compare(a, b, max_abs_diff))
}
fn collect_roots(roots: [Option<f64>; 3]) -> Vec<f64> {
roots.into_iter().flatten().collect()
}
#[test]
fn test_solve_linear() {
// Line that is on the x-axis
assert!(solve_linear(0., 0.).is_empty());
assert!(collect_roots(solve_linear(0., 0.)).is_empty());
// Line that is parallel to but not on the x-axis
assert!(solve_linear(0., 1.).is_empty());
assert!(collect_roots(solve_linear(0., 1.)).is_empty());
// Line with a non-zero slope
assert!(solve_linear(2., -8.) == vec![4.]);
assert!(collect_roots(solve_linear(2., -8.)) == vec![4.]);
}
#[test]
fn test_solve_cubic() {
// discriminant == 0
let roots1 = solve_cubic(1., 0., 0., 0.);
let roots1 = collect_roots(solve_cubic(1., 0., 0., 0.));
assert!(roots1 == vec![0.]);
let roots2 = solve_cubic(1., 3., 0., -4.);
let roots2 = collect_roots(solve_cubic(1., 3., 0., -4.));
assert!(roots2 == vec![1., -2.]);
// p == 0
let roots3 = solve_cubic(1., 0., 0., -1.);
let roots3 = collect_roots(solve_cubic(1., 0., 0., -1.));
assert!(roots3 == vec![1.]);
// discriminant > 0
let roots4 = solve_cubic(1., 3., 0., 2.);
let roots4 = collect_roots(solve_cubic(1., 3., 0., 2.));
assert!(f64_compare_vector(roots4, vec![-3.196], MAX_ABSOLUTE_DIFFERENCE));
// discriminant < 0
let roots5 = solve_cubic(1., 3., 0., -1.);
let roots5 = collect_roots(solve_cubic(1., 3., 0., -1.));
assert!(f64_compare_vector(roots5, vec![0.532, -2.879, -0.653], MAX_ABSOLUTE_DIFFERENCE));
// quadratic
let roots6 = solve_cubic(0., 3., 0., -3.);
let roots6 = collect_roots(solve_cubic(0., 3., 0., -3.));
assert!(roots6 == vec![1., -1.]);
// linear
let roots7 = solve_cubic(0., 0., 1., -1.);
let roots7 = collect_roots(solve_cubic(0., 0., 1., -1.));
assert!(roots7 == vec![1.]);
}