mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Add feat refit segments on shift + delete
This commit is contained in:
@@ -8,11 +8,10 @@ use crate::messages::portfolio::document::utility_types::misc::{PathSnapSource,
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;
|
||||
use crate::messages::tool::common_functionality::utility_functions::is_visible_point;
|
||||
use crate::messages::tool::common_functionality::utility_functions::{find_refit_handle_lengths, is_visible_point};
|
||||
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
|
||||
use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document;
|
||||
use graphene_std::vector::{HandleId, SegmentId};
|
||||
use graphene_std::vector::{ManipulatorPointId, PointId, VectorData, VectorModificationType};
|
||||
|
||||
@@ -1174,11 +1173,7 @@ impl ShapeState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_bezier_from_segment_id(segment: SegmentId, vector_data: &VectorData) -> Option<SegmentId> {
|
||||
vector_data.segment_bezier_iter().find(|(id, _, _, _)| *id == segment).map(|(_, bezier, _, _)| bezier)
|
||||
}
|
||||
|
||||
fn dissolve_anchor(anchor: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) -> Option<[(HandleId, PointId); 2]> {
|
||||
fn dissolve_anchor(anchor: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) -> Option<[(HandleId, PointId, Bezier); 2]> {
|
||||
// Delete point
|
||||
let modification_type = VectorModificationType::RemovePoint { id: anchor };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
@@ -1201,19 +1196,15 @@ impl ShapeState {
|
||||
let [Some(start), Some(end)] = opposites.map(|opposite| opposite.to_manipulator_point().get_anchor(vector_data)) else {
|
||||
return None;
|
||||
};
|
||||
let Some(bezier1) = vector_data.segment_bezier_iter().find(|(id, _, _, _)| *id == segment).map(|(_, bezier, _, _)| bezier) else {
|
||||
return None;
|
||||
};
|
||||
let Some(bezier2) = vector_data.segment_bezier_iter().find(|(id, _, _, _)| *id == segment).map(|(_, bezier, _, _)| bezier) else {
|
||||
return None;
|
||||
};
|
||||
// Here we should also return the beziers of the connected handles
|
||||
|
||||
Some([(handles[0], start), (handles[1], end)])
|
||||
let get_bezier = |segment_id: SegmentId| -> Option<Bezier> { vector_data.segment_bezier_iter().find(|(id, _, _, _)| *id == segment_id).map(|(_, bezier, _, _)| bezier) };
|
||||
let beziers = opposites.map(|opposite| get_bezier(opposite.segment));
|
||||
|
||||
Some([(handles[0], start, beziers[0]?), (handles[1], end, beziers[1]?)])
|
||||
}
|
||||
|
||||
/// Dissolve the selected points.
|
||||
pub fn delete_selected_points(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
pub fn delete_selected_points(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, refit: bool) {
|
||||
for (&layer, state) in &mut self.selected_shape_state {
|
||||
let mut missing_anchors = HashMap::new();
|
||||
let mut deleted_anchors = HashSet::new();
|
||||
@@ -1248,21 +1239,46 @@ impl ShapeState {
|
||||
}
|
||||
|
||||
let mut visited = Vec::new();
|
||||
while let Some((anchor, handles)) = missing_anchors.keys().next().copied().and_then(|id| missing_anchors.remove_entry(&id)) {
|
||||
while let Some((anchor, connected_info)) = missing_anchors.keys().next().copied().and_then(|id| missing_anchors.remove_entry(&id)) {
|
||||
visited.push(anchor);
|
||||
|
||||
// If the adjacent point is just this point then skip
|
||||
let mut handles = handles.map(|handle| (handle.1 != anchor).then_some(handle));
|
||||
let mut handles = connected_info.map(|handle| (handle.1 != anchor).then_some(handle));
|
||||
|
||||
// If the adjacent points are themselves being deleted, then repeatedly visit the newest agacent points.
|
||||
for handle in &mut handles {
|
||||
while let Some((point, connected)) = (*handle).and_then(|(_, point)| missing_anchors.remove_entry(&point)) {
|
||||
visited.push(point);
|
||||
let [handle1, handle2] = &mut handles;
|
||||
|
||||
*handle = connected.into_iter().find(|(_, point)| !visited.contains(point));
|
||||
// Store Beziers for fitting later
|
||||
let mut beziers_start = Vec::new();
|
||||
let mut beziers_end = Vec::new();
|
||||
if let Some((_, _, bezier)) = *handle1 {
|
||||
beziers_start.push(bezier);
|
||||
}
|
||||
while let Some((point, connected)) = (*handle1).and_then(|(_, point, _)| missing_anchors.remove_entry(&point)) {
|
||||
visited.push(point);
|
||||
|
||||
if let Some(new_handle) = connected.into_iter().find(|(_, point, _)| !visited.contains(point)) {
|
||||
*handle1 = Some(new_handle);
|
||||
beziers_start.push(new_handle.2);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((_, _, bezier)) = *handle2 {
|
||||
beziers_end.push(bezier);
|
||||
}
|
||||
while let Some((point, connected)) = (*handle2).and_then(|(_, point, _)| missing_anchors.remove_entry(&point)) {
|
||||
visited.push(point);
|
||||
|
||||
if let Some(new_handle) = connected.into_iter().find(|(_, point, _)| !visited.contains(point)) {
|
||||
*handle2 = Some(new_handle);
|
||||
beziers_end.push(new_handle.2);
|
||||
}
|
||||
}
|
||||
|
||||
beziers_start.reverse();
|
||||
let mut combined = beziers_start.clone();
|
||||
combined.extend(beziers_end);
|
||||
|
||||
let [Some(start), Some(end)] = handles else { continue };
|
||||
|
||||
// Avoid reconnecting to points that are being deleted (this can happen if a whole loop is deleted)
|
||||
@@ -1271,7 +1287,7 @@ impl ShapeState {
|
||||
}
|
||||
|
||||
// Grab the handles from the opposite side of the segment(s) being deleted and make it relative to the anchor
|
||||
let [handle_start, handle_end] = [start, end].map(|(handle, _)| {
|
||||
let [handle_start, handle_end] = [start, end].map(|(handle, _, _)| {
|
||||
let handle = handle.opposite();
|
||||
let handle_position = handle.to_manipulator_point().get_position(&vector_data);
|
||||
let relative_position = handle
|
||||
@@ -1281,12 +1297,36 @@ impl ShapeState {
|
||||
handle_position.and_then(|handle| relative_position.map(|relative| handle - relative)).unwrap_or_default()
|
||||
});
|
||||
|
||||
let [handle1, handle2] = if refit {
|
||||
let handle_start_unit = handle_start.try_normalize().unwrap_or_default();
|
||||
let handle_end_unit = handle_end.try_normalize().unwrap_or_default();
|
||||
|
||||
let p1 = start
|
||||
.0
|
||||
.opposite()
|
||||
.to_manipulator_point()
|
||||
.get_anchor(&vector_data)
|
||||
.and_then(|anchor| vector_data.point_domain.position_from_id(anchor))
|
||||
.unwrap_or_default();
|
||||
|
||||
let p3 = end
|
||||
.0
|
||||
.opposite()
|
||||
.to_manipulator_point()
|
||||
.get_anchor(&vector_data)
|
||||
.and_then(|anchor| vector_data.point_domain.position_from_id(anchor))
|
||||
.unwrap_or_default();
|
||||
find_refit_handle_lengths(p1, p3, combined, handle_start_unit, handle_end_unit)
|
||||
} else {
|
||||
[handle_start, handle_end]
|
||||
};
|
||||
|
||||
let segment = start.0.segment;
|
||||
|
||||
let modification_type = VectorModificationType::InsertSegment {
|
||||
id: segment,
|
||||
points: [start.1, end.1],
|
||||
handles: [Some(handle_start), Some(handle_end)],
|
||||
handles: [Some(handle1), Some(handle2)],
|
||||
};
|
||||
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
@@ -1317,15 +1357,6 @@ impl ShapeState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_point_and_refit(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
// Here define the logic of what happens exactly when sone selected points are deleted
|
||||
for (&layer, state) in &mut self.selected_shape_state {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn break_path_at_selected_point(&self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
for (&layer, state) in &self.selected_shape_state {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
|
||||
@@ -439,16 +439,48 @@ pub fn log_optimization(a: f64, b: f64, p1: DVec2, p3: DVec2, d1: DVec2, d2: DVe
|
||||
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 points = new_curve.compute_lookup_table(Some(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
|
||||
dist / (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 n = 40;
|
||||
|
||||
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 = ((n) as f64 * ratio).floor() as usize;
|
||||
let n_points2 = n - n_points1;
|
||||
let mut points1 = farther_segment.compute_lookup_table(Some(2), None).collect::<Vec<_>>();
|
||||
let points2 = other_segment.compute_lookup_table(Some(n_points2), None).collect::<Vec<_>>();
|
||||
if points2.len() >= 2 {
|
||||
points1.extend_from_slice(&points2[1..]);
|
||||
}
|
||||
|
||||
let f = |a: f64, b: f64| -> f64 { log_optimization(a, b, p1, p3, d1, d2, &points1, n) };
|
||||
|
||||
let (a, b) = adam_optimizer(f);
|
||||
|
||||
let len1 = a.exp().max(min_len1);
|
||||
let len2 = b.exp().max(min_len2);
|
||||
|
||||
(d1 * len1, d2 * len2)
|
||||
}
|
||||
|
||||
pub fn adam_optimizer(f: impl Fn(f64, f64) -> f64) -> (f64, f64) {
|
||||
let h = 1e-6;
|
||||
let tol = 1e-6;
|
||||
let max_iter = 200;
|
||||
@@ -468,30 +500,9 @@ pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec
|
||||
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);
|
||||
let dfa: f64 = (f(a + h, b) - f(a - h, b)) / (2. * h);
|
||||
let dfb: f64 = (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;
|
||||
@@ -515,9 +526,46 @@ pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let len1 = a.exp().max(min_len1);
|
||||
let len2 = b.exp().max(min_len2);
|
||||
|
||||
(d1 * len1, d2 * len2)
|
||||
(a, b)
|
||||
}
|
||||
|
||||
pub fn find_refit_handle_lengths(p1: DVec2, p3: DVec2, beziers: Vec<Bezier>, d1: DVec2, d2: DVec2) -> [DVec2; 2] {
|
||||
let n = 40;
|
||||
|
||||
let points_per_bez = n / beziers.len();
|
||||
|
||||
let points = if points_per_bez < 1 {
|
||||
beziers.iter().map(|bezier| bezier.start()).collect::<Vec<_>>()
|
||||
} else {
|
||||
let mut points = Vec::new();
|
||||
for bezier in &beziers {
|
||||
let lookup = bezier.compute_lookup_table(Some(points_per_bez), None).collect::<Vec<_>>();
|
||||
points.extend_from_slice(&lookup[..lookup.len() - 1]);
|
||||
}
|
||||
points
|
||||
};
|
||||
|
||||
let limit = points.len();
|
||||
|
||||
let f = |a: f64, b: f64| -> f64 {
|
||||
let start_handle_len = a.exp();
|
||||
let end_handle_len = b.exp();
|
||||
|
||||
let c1 = p1 + d1 * start_handle_len;
|
||||
let c2 = p3 + d2 * end_handle_len;
|
||||
|
||||
let new_curve = Bezier::from_cubic_coordinates(p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p3.x, p3.y);
|
||||
|
||||
let new_points = new_curve.compute_lookup_table(Some(limit), None);
|
||||
let dist = points.iter().zip(new_points).map(|(p1, p2)| (p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sum::<f64>();
|
||||
|
||||
dist / (limit) as f64
|
||||
};
|
||||
|
||||
let (a, b) = adam_optimizer(f);
|
||||
|
||||
let len1 = a.exp();
|
||||
let len2 = b.exp();
|
||||
|
||||
[d1 * len1, d2 * len2]
|
||||
}
|
||||
|
||||
@@ -1919,7 +1919,8 @@ impl Fsm for PathToolFsmState {
|
||||
(_, PathToolMessage::Delete) => {
|
||||
// Delete the selected points and clean up overlays
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
shape_editor.delete_selected_points(document, responses);
|
||||
// shape_editor.delete_selected_points(document, responses);
|
||||
shape_editor.delete_selected_points(document, responses, false);
|
||||
responses.add(PathToolMessage::SelectionChanged);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
@@ -1940,7 +1941,7 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
(_, PathToolMessage::DeleteAndRefit) => {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
shape_editor.delete_point_and_refit(document, responses);
|
||||
shape_editor.delete_selected_points(document, responses, true);
|
||||
responses.add(PathToolMessage::SelectionChanged);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
|
||||
Reference in New Issue
Block a user