mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 03:08:12 +08:00
Add anchor sliding along adjacent segments in the Path tool (#2682)
* Improved comments * Add point sliding with approximate t value * Add similarity calculation * Numerical approach to fit the curve * Reliable point sliding for cubic segments * Fix formatting and clean comments * Fix cubic with one handle logic * Cancel on right click and escape * Two parameter optimization * Esc/ Right click cancellation * Code review * Fix dynamic hints * Revert selected_points_counts and fix comments * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
eda858b6e2
commit
781271782c
@@ -107,11 +107,7 @@ impl SelectedLayerState {
|
||||
}
|
||||
|
||||
pub fn selected_points_count(&self) -> usize {
|
||||
let count = self.selected_points.iter().fold(0, |acc, point| {
|
||||
let is_ignored = (point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors);
|
||||
acc + if is_ignored { 0 } else { 1 }
|
||||
});
|
||||
count
|
||||
self.selected_points.len()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_text;
|
||||
use crate::messages::tool::tool_messages::path_tool::PathOverlayMode;
|
||||
use bezier_rs::Bezier;
|
||||
use glam::DVec2;
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::text::{FontCache, load_face};
|
||||
@@ -196,3 +197,99 @@ pub fn is_visible_point(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates similarity metric between new bezier curve and two old beziers by using sampled points.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn log_optimization(a: f64, b: f64, p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec2, points1: &[DVec2], n: usize) -> f64 {
|
||||
let start_handle_length = a.exp();
|
||||
let end_handle_length = b.exp();
|
||||
|
||||
// Compute the handle positions of new bezier curve
|
||||
let c1 = p1 + d1 * start_handle_length;
|
||||
let c2 = p3 + d2 * end_handle_length;
|
||||
|
||||
let new_curve = Bezier::from_cubic_coordinates(p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p3.x, p3.y);
|
||||
|
||||
// Sample 2*n points from new curve and get the L2 metric between all of points
|
||||
let points = new_curve.compute_lookup_table(Some(2 * n), None).collect::<Vec<_>>();
|
||||
|
||||
let dist = points1.iter().zip(points.iter()).map(|(p1, p2)| (p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sum::<f64>();
|
||||
|
||||
dist / (2 * n) as f64
|
||||
}
|
||||
|
||||
/// Calculates optimal handle lengths with adam optimization.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec2, min_len1: f64, min_len2: f64, farther_segment: Bezier, other_segment: Bezier) -> (DVec2, DVec2) {
|
||||
let h = 1e-6;
|
||||
let tol = 1e-6;
|
||||
let max_iter = 200;
|
||||
|
||||
let mut a = (5_f64).ln();
|
||||
let mut b = (5_f64).ln();
|
||||
|
||||
let mut m_a = 0.;
|
||||
let mut v_a = 0.;
|
||||
let mut m_b = 0.;
|
||||
let mut v_b = 0.;
|
||||
|
||||
let initial_alpha = 0.05;
|
||||
let decay_rate: f64 = 0.99;
|
||||
|
||||
let beta1 = 0.9;
|
||||
let beta2 = 0.999;
|
||||
let epsilon = 1e-8;
|
||||
|
||||
let n = 20;
|
||||
|
||||
let farther_segment = if farther_segment.start.distance(p1) >= f64::EPSILON {
|
||||
farther_segment.reverse()
|
||||
} else {
|
||||
farther_segment
|
||||
};
|
||||
|
||||
let other_segment = if other_segment.end.distance(p3) >= f64::EPSILON { other_segment.reverse() } else { other_segment };
|
||||
|
||||
// Now we sample points proportional to the lengths of the beziers
|
||||
let l1 = farther_segment.length(None);
|
||||
let l2 = other_segment.length(None);
|
||||
let ratio = l1 / (l1 + l2);
|
||||
let n_points1 = ((2 * n) as f64 * ratio).floor() as usize;
|
||||
let mut points1 = farther_segment.compute_lookup_table(Some(n_points1), None).collect::<Vec<_>>();
|
||||
let mut points2 = other_segment.compute_lookup_table(Some(n), None).collect::<Vec<_>>();
|
||||
points1.append(&mut points2);
|
||||
|
||||
let f = |a: f64, b: f64| -> f64 { log_optimization(a, b, p1, p3, d1, d2, &points1, n) };
|
||||
|
||||
for t in 1..=max_iter {
|
||||
let dfa = (f(a + h, b) - f(a - h, b)) / (2. * h);
|
||||
let dfb = (f(a, b + h) - f(a, b - h)) / (2. * h);
|
||||
|
||||
m_a = beta1 * m_a + (1. - beta1) * dfa;
|
||||
m_b = beta1 * m_b + (1. - beta1) * dfb;
|
||||
|
||||
v_a = beta2 * v_a + (1. - beta2) * dfa * dfa;
|
||||
v_b = beta2 * v_b + (1. - beta2) * dfb * dfb;
|
||||
|
||||
let m_a_hat = m_a / (1. - beta1.powi(t));
|
||||
let v_a_hat = v_a / (1. - beta2.powi(t));
|
||||
let m_b_hat = m_b / (1. - beta1.powi(t));
|
||||
let v_b_hat = v_b / (1. - beta2.powi(t));
|
||||
|
||||
let alpha_t = initial_alpha * decay_rate.powi(t);
|
||||
|
||||
// Update log-lengths
|
||||
a -= alpha_t * m_a_hat / (v_a_hat.sqrt() + epsilon);
|
||||
b -= alpha_t * m_b_hat / (v_b_hat.sqrt() + epsilon);
|
||||
|
||||
// Convergence check
|
||||
if dfa.abs() < tol && dfb.abs() < tol {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let len1 = a.exp().max(min_len1);
|
||||
let len2 = b.exp().max(min_len2);
|
||||
|
||||
(d1 * len1, d2 * len2)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user