mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 21:48:13 +08:00
Refactor the Centroid node and Subpath struct and methods to use Kurbo, eliminating all remaining usages of Bezier-rs (#3036)
* define Subpath struct in gcore and refactor node-graph * Refactor few methods * refactoring worked! * refactor centoid area and length * remove unused * cleanup * fix pathseg_points function * fix tranforming segments * fix segment intersection * refactor to_path_segments fn in gpath-bool crate * refactor gcraft * add bezier-rs dep * Code review the editor directory * use path-bool for solving roots * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
99984fc2d6
commit
d22b2ca927
@@ -21,14 +21,16 @@ use crate::messages::tool::common_functionality::shape_editor::{
|
||||
};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate, make_path_editable_is_allowed};
|
||||
use bezier_rs::{Bezier, BezierHandles, TValue};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::algorithms::util::pathseg_tangent;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType};
|
||||
use kurbo::{DEFAULT_ACCURACY, ParamCurve, ParamCurveNearest, PathSeg, Rect};
|
||||
use std::vec;
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
@@ -494,7 +496,7 @@ pub enum PointSelectState {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SlidingSegmentData {
|
||||
segment_id: SegmentId,
|
||||
bezier: Bezier,
|
||||
bezier: PathSeg,
|
||||
start: PointId,
|
||||
}
|
||||
|
||||
@@ -840,13 +842,12 @@ impl PathToolData {
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
} else {
|
||||
let start_pos = segment.bezier().start;
|
||||
let end_pos = segment.bezier().end;
|
||||
let points = pathseg_points(segment.pathseg());
|
||||
|
||||
let [pos1, pos2] = match segment.bezier().handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end],
|
||||
BezierHandles::Quadratic { handle } => [handle, end_pos],
|
||||
BezierHandles::Linear => [start_pos + (end_pos - start_pos) / 3., end_pos + (start_pos - end_pos) / 3.],
|
||||
let [pos1, pos2] = match (points.p1, points.p2) {
|
||||
(Some(p1), Some(p2)) => [p1, p2],
|
||||
(Some(p1), None) | (None, Some(p1)) => [p1, points.p3],
|
||||
(None, None) => [points.p0 + (points.p3 - points.p0) / 3., points.p3 + (points.p0 - points.p3) / 3.],
|
||||
};
|
||||
self.molding_info = Some((pos1, pos2));
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
@@ -1215,7 +1216,7 @@ impl PathToolData {
|
||||
let Some(point_id) = anchor.as_anchor() else { return false };
|
||||
|
||||
let mut connected_segments = [None, None];
|
||||
for (segment, bezier, start, end) in vector.segment_bezier_iter() {
|
||||
for (segment, bezier, start, end) in vector.segment_iter() {
|
||||
if start == point_id || end == point_id {
|
||||
match (connected_segments[0], connected_segments[1]) {
|
||||
(None, None) => connected_segments[0] = Some(SlidingSegmentData { segment_id: segment, bezier, start }),
|
||||
@@ -1255,13 +1256,13 @@ impl PathToolData {
|
||||
|
||||
let segments = sliding_point_info.connected_segments;
|
||||
|
||||
let t1 = segments[0].bezier.project(layer_pos);
|
||||
let position1 = segments[0].bezier.evaluate(TValue::Parametric(t1));
|
||||
let t1 = segments[0].bezier.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||
let position1 = point_to_dvec2(segments[0].bezier.eval(t1));
|
||||
|
||||
let t2 = segments[1].bezier.project(layer_pos);
|
||||
let position2 = segments[1].bezier.evaluate(TValue::Parametric(t2));
|
||||
let t2 = segments[1].bezier.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||
let position2 = point_to_dvec2(segments[1].bezier.eval(t2));
|
||||
|
||||
let (closer_segment, farther_segment, t_value, new_position) = if position2.distance(layer_pos) < position1.distance(layer_pos) {
|
||||
let (closer_segment, further_segment, t_value, new_position) = if position2.distance(layer_pos) < position1.distance(layer_pos) {
|
||||
(segments[1], segments[0], t2, position2)
|
||||
} else {
|
||||
(segments[0], segments[1], t1, position1)
|
||||
@@ -1276,50 +1277,59 @@ impl PathToolData {
|
||||
shape_editor.move_anchor(anchor, &vector, delta, layer, None, responses);
|
||||
|
||||
// Make a split at the t_value
|
||||
let [first, second] = closer_segment.bezier.split(TValue::Parametric(t_value));
|
||||
let closer_segment_other_point = if anchor == closer_segment.start { closer_segment.bezier.end } else { closer_segment.bezier.start };
|
||||
let first = closer_segment.bezier.subsegment(0_f64..t_value);
|
||||
let second = closer_segment.bezier.subsegment(t_value..1.);
|
||||
|
||||
let (split_segment, other_segment) = if first.start == closer_segment_other_point { (first, second) } else { (second, first) };
|
||||
let closer_segment_other_point = if anchor == closer_segment.start {
|
||||
closer_segment.bezier.end()
|
||||
} else {
|
||||
closer_segment.bezier.start()
|
||||
};
|
||||
|
||||
let (split_segment, other_segment) = if first.start() == closer_segment_other_point { (first, second) } else { (second, first) };
|
||||
let split_segment_points = pathseg_points(split_segment);
|
||||
|
||||
// Primary handle maps to primary handle and secondary maps to secondary
|
||||
let closer_primary_handle = HandleId::primary(closer_segment.segment_id);
|
||||
let Some(handle_position) = split_segment.handle_start() else { return };
|
||||
let relative_position1 = handle_position - split_segment.start;
|
||||
let Some(handle_position) = split_segment_points.p1 else { return };
|
||||
let relative_position1 = handle_position - split_segment_points.p0;
|
||||
let modification_type = closer_primary_handle.set_relative_position(relative_position1);
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
let closer_secondary_handle = HandleId::end(closer_segment.segment_id);
|
||||
let Some(handle_position) = split_segment.handle_end() else { return };
|
||||
let relative_position2 = handle_position - split_segment.end;
|
||||
let Some(handle_position) = split_segment_points.p2 else { return };
|
||||
let relative_position2 = handle_position - split_segment_points.p3;
|
||||
let modification_type = closer_secondary_handle.set_relative_position(relative_position2);
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
let end_handle_direction = if anchor == closer_segment.start { -relative_position1 } else { -relative_position2 };
|
||||
|
||||
let (farther_other_point, start_handle, end_handle, start_handle_pos) = if anchor == farther_segment.start {
|
||||
let further_segment_points = pathseg_points(further_segment.bezier);
|
||||
|
||||
let (further_other_point, start_handle, end_handle, start_handle_pos) = if anchor == further_segment.start {
|
||||
(
|
||||
farther_segment.bezier.end,
|
||||
HandleId::end(farther_segment.segment_id),
|
||||
HandleId::primary(farther_segment.segment_id),
|
||||
farther_segment.bezier.handle_end(),
|
||||
further_segment_points.p3,
|
||||
HandleId::end(further_segment.segment_id),
|
||||
HandleId::primary(further_segment.segment_id),
|
||||
further_segment_points.p2,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
farther_segment.bezier.start,
|
||||
HandleId::primary(farther_segment.segment_id),
|
||||
HandleId::end(farther_segment.segment_id),
|
||||
farther_segment.bezier.handle_start(),
|
||||
further_segment_points.p0,
|
||||
HandleId::primary(further_segment.segment_id),
|
||||
HandleId::end(further_segment.segment_id),
|
||||
further_segment_points.p1,
|
||||
)
|
||||
};
|
||||
let Some(start_handle_position) = start_handle_pos else { return };
|
||||
let start_handle_direction = start_handle_position - farther_other_point;
|
||||
let start_handle_direction = start_handle_position - further_other_point;
|
||||
|
||||
// Get normalized direction vectors, if cubic handle is zero then we consider corresponding tangent
|
||||
let d1 = start_handle_direction.try_normalize().unwrap_or({
|
||||
if anchor == farther_segment.start {
|
||||
-farther_segment.bezier.tangent(TValue::Parametric(0.99))
|
||||
if anchor == further_segment.start {
|
||||
-pathseg_tangent(further_segment.bezier, 1.)
|
||||
} else {
|
||||
farther_segment.bezier.tangent(TValue::Parametric(0.01))
|
||||
pathseg_tangent(further_segment.bezier, 0.)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1328,7 +1338,7 @@ impl PathToolData {
|
||||
let min_len1 = start_handle_direction.length() * 0.4;
|
||||
let min_len2 = end_handle_direction.length() * 0.4;
|
||||
|
||||
let (relative_pos1, relative_pos2) = find_two_param_best_approximate(farther_other_point, new_position, d1, d2, min_len1, min_len2, farther_segment.bezier, other_segment);
|
||||
let (relative_pos1, relative_pos2) = find_two_param_best_approximate(further_other_point, new_position, d1, d2, min_len1, min_len2, further_segment.bezier, other_segment);
|
||||
|
||||
// Now set those handles to these handle lengths keeping the directions d1, d2
|
||||
let modification_type = start_handle.set_relative_position(relative_pos1);
|
||||
@@ -1737,13 +1747,13 @@ impl Fsm for PathToolFsmState {
|
||||
if tool_options.path_editing_mode.segment_editing_mode && !tool_data.segment_editing_modifier {
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);
|
||||
|
||||
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
|
||||
overlay_context.outline_overlay_bezier(closest_segment.pathseg(), transform);
|
||||
|
||||
// Draw the anchors again
|
||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||
if display_anchors {
|
||||
let start_pos = transform.transform_point2(closest_segment.bezier().start);
|
||||
let end_pos = transform.transform_point2(closest_segment.bezier().end);
|
||||
let start_pos = transform.transform_point2(point_to_dvec2(closest_segment.pathseg().start()));
|
||||
let end_pos = transform.transform_point2(point_to_dvec2(closest_segment.pathseg().end()));
|
||||
let start_id = closest_segment.points()[0];
|
||||
let end_id = closest_segment.points()[1];
|
||||
if let Some(shape_state) = shape_editor.selected_shape_state.get_mut(&closest_segment.layer()) {
|
||||
@@ -1820,7 +1830,7 @@ impl Fsm for PathToolFsmState {
|
||||
let (points_inside, segments_inside) = match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
let previous_mouse = document.metadata().document_to_viewport.transform_point2(tool_data.previous_mouse_position);
|
||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
||||
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y);
|
||||
shape_editor.get_inside_points_and_segments(
|
||||
&document.network_interface,
|
||||
SelectionShape::Box(bbox),
|
||||
@@ -1865,7 +1875,7 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
|
||||
for (segment, bezier, _, _) in vector.segment_bezier_iter() {
|
||||
for (segment, bezier, _, _) in vector.segment_iter() {
|
||||
if segments.contains(&segment) {
|
||||
overlay_context.outline_overlay_bezier(bezier, transform);
|
||||
}
|
||||
@@ -2241,7 +2251,8 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
||||
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y);
|
||||
|
||||
shape_editor.select_all_in_shape(
|
||||
&document.network_interface,
|
||||
SelectionShape::Box(bbox),
|
||||
@@ -2337,7 +2348,8 @@ impl Fsm for PathToolFsmState {
|
||||
} else {
|
||||
match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
||||
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y);
|
||||
|
||||
shape_editor.select_all_in_shape(
|
||||
&document.network_interface,
|
||||
SelectionShape::Box(bbox),
|
||||
@@ -2697,16 +2709,13 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
// Create new segment ids and add the segments into the existing vector content
|
||||
let mut segments_map = HashMap::new();
|
||||
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
|
||||
for (segment_id, bezier, start, end) in new_vector.segment_iter() {
|
||||
let new_segment_id = SegmentId::generate();
|
||||
|
||||
segments_map.insert(segment_id, new_segment_id);
|
||||
|
||||
let handles = match bezier.handles {
|
||||
BezierHandles::Linear => [None, None],
|
||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
||||
};
|
||||
let points = pathseg_points(bezier);
|
||||
let handles = [points.p1, points.p2];
|
||||
|
||||
let points = [points_map[&start], points_map[&end]];
|
||||
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
|
||||
@@ -2802,7 +2811,7 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
let mut segments_map = HashMap::new();
|
||||
|
||||
for (segment_id, bezier, start, end) in old_vector.segment_bezier_iter() {
|
||||
for (segment_id, bezier, start, end) in old_vector.segment_iter() {
|
||||
let both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));
|
||||
|
||||
let segment_selected = layer_selection_state.is_segment_selected(segment_id);
|
||||
@@ -2811,11 +2820,8 @@ impl Fsm for PathToolFsmState {
|
||||
let new_id = SegmentId::generate();
|
||||
segments_map.insert(segment_id, new_id);
|
||||
|
||||
let handles = match bezier.handles {
|
||||
BezierHandles::Linear => [None, None],
|
||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
||||
};
|
||||
let points = pathseg_points(bezier);
|
||||
let handles = [points.p1, points.p2];
|
||||
|
||||
let points = [points_map[&start], points_map[&end]];
|
||||
let modification_type = VectorModificationType::InsertSegment { id: new_id, points, handles };
|
||||
|
||||
@@ -11,11 +11,12 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, closest_point, should_extend};
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
|
||||
use graphene_std::vector::{NoHashBuilder, PointId, SegmentId, StrokeId, Vector, VectorModificationType};
|
||||
use kurbo::{CubicBez, PathSeg};
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct PenTool {
|
||||
@@ -1257,7 +1258,7 @@ impl PenToolData {
|
||||
let vector = document.network_interface.compute_modified_vector(layer);
|
||||
let (handle_start, in_segment) = if let Some(vector) = &vector {
|
||||
vector
|
||||
.segment_bezier_iter()
|
||||
.segment_iter()
|
||||
.find_map(|(segment_id, bezier, start, end)| {
|
||||
let is_end = point == end;
|
||||
let is_start = point == start;
|
||||
@@ -1265,15 +1266,11 @@ impl PenToolData {
|
||||
return None;
|
||||
}
|
||||
|
||||
let handle = match bezier.handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end, .. } => {
|
||||
if is_start {
|
||||
handle_start
|
||||
} else {
|
||||
handle_end
|
||||
}
|
||||
}
|
||||
BezierHandles::Quadratic { handle } => handle,
|
||||
let points = pathseg_points(bezier);
|
||||
let handle = match (points.p1, points.p2) {
|
||||
(Some(p1), Some(_)) if is_start => p1,
|
||||
(Some(_), Some(p2)) if !is_start => p2,
|
||||
(Some(p1), None) | (None, Some(p1)) => p1,
|
||||
_ => return None,
|
||||
};
|
||||
Some((segment_id, is_end, handle))
|
||||
@@ -1599,9 +1596,8 @@ impl Fsm for PenToolFsmState {
|
||||
let handle_start = tool_data.latest_point().map(|point| transform.transform_point2(point.handle_start));
|
||||
|
||||
if let (Some((start, handle_start)), Some(handle_end)) = (tool_data.latest_point().map(|point| (point.pos, point.handle_start)), tool_data.handle_end) {
|
||||
let handles = BezierHandles::Cubic { handle_start, handle_end };
|
||||
let end = tool_data.next_point;
|
||||
let bezier = Bezier { start, handles, end };
|
||||
let bezier = PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(end)));
|
||||
if (end - start).length_squared() > f64::EPSILON {
|
||||
// Draw the curve for the currently-being-placed segment
|
||||
overlay_context.outline_bezier(bezier, transform);
|
||||
@@ -1723,7 +1719,7 @@ impl Fsm for PenToolFsmState {
|
||||
// We have the point. Join the 2 vertices and check if any path is closed.
|
||||
if let Some(end) = closest_point {
|
||||
let segment_id = SegmentId::generate();
|
||||
vector.push(segment_id, start, end, BezierHandles::Cubic { handle_start, handle_end }, StrokeId::ZERO);
|
||||
vector.push(segment_id, start, end, (Some(handle_start), Some(handle_end)), StrokeId::ZERO);
|
||||
|
||||
let grouped_segments = vector.auto_join_paths();
|
||||
let closed_paths = grouped_segments.iter().filter(|path| path.is_closed() && path.contains(segment_id));
|
||||
|
||||
@@ -20,12 +20,12 @@ use crate::messages::tool::common_functionality::shape_editor::SelectionShapeTyp
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage};
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DMat2;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::renderer::Rect;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use std::fmt;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user