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:
Priyanshu
2025-08-16 13:39:25 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 99984fc2d6
commit d22b2ca927
60 changed files with 2126 additions and 453 deletions
@@ -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;