diff --git a/editor/src/messages/clipboard/clipboard_message_handler.rs b/editor/src/messages/clipboard/clipboard_message_handler.rs index 65122d80b3..30394313c2 100644 --- a/editor/src/messages/clipboard/clipboard_message_handler.rs +++ b/editor/src/messages/clipboard/clipboard_message_handler.rs @@ -13,9 +13,10 @@ use graph_craft::document::NodeId; use graphene_std::Color; use graphene_std::raster::Image; use graphene_std::subpath::BezierHandles; -use graphene_std::vector::misc::HandleId; +use graphene_std::vector::misc::{HandleId, point_to_dvec2, segment_to_handles}; use graphene_std::vector::{PointId, SegmentId, VectorModificationType}; use graphite_proc_macros::{ExtractField, message_handler_data}; +use kurbo::ParamCurve; use std::sync::Arc; const CLIPBOARD_PREFIX: &str = "graphite: "; @@ -425,7 +426,7 @@ impl MessageHandler> for Clipboard // Create new segment ids and add the segments into the existing Vector path let mut segments_map = HashMap::new(); - for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() { + for (segment_id, segment, start, end) in new_vector.segment_iter() { let (Some(&start_point), Some(&end_point)) = (points_map.get(&start), points_map.get(&end)) else { warn!("Skipping pasted vector segment with an unknown endpoint"); continue; @@ -434,10 +435,12 @@ impl MessageHandler> for Clipboard let new_segment_id = SegmentId::generate(); segments_map.insert(segment_id, new_segment_id); - let handles = match bezier.handles { + let segment_start = point_to_dvec2(segment.start()); + let segment_end = point_to_dvec2(segment.end()); + let handles = match segment_to_handles(&segment) { 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)], + BezierHandles::Quadratic { handle } => [Some(handle - segment_start), None], + BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - segment_start), Some(handle_end - segment_end)], }; let points = [start_point, end_point]; diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index 17ea05b22a..e26b4a73b0 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -6,9 +6,10 @@ pub use crate::messages::portfolio::document::utility_types::text_metrics::text_ use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState}; use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler; use glam::{DAffine2, DVec2}; -use graphene_std::subpath::{Bezier, BezierHandles}; -use graphene_std::vector::misc::ManipulatorPointId; +use graphene_std::subpath::BezierHandles; +use graphene_std::vector::misc::{ManipulatorPointId, point_to_dvec2, segment_to_handles}; use graphene_std::vector::{PointId, SegmentId, Vector}; +use kurbo::{Affine, ParamCurve, PathSeg}; use std::collections::HashMap; #[cfg(target_family = "wasm")] use wasm_bindgen::JsCast; @@ -59,7 +60,7 @@ pub fn selected_segments_for_layer(vector: &Vector, state: &SelectedLayerState) .collect::>(); // Adding segments which are are connected to selected anchors - for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() { + for (segment_id, _, start, end) in vector.segment_iter() { if selected_anchors.contains(&start) || selected_anchors.contains(&end) { selected_segments.push(segment_id); } @@ -67,23 +68,25 @@ pub fn selected_segments_for_layer(vector: &Vector, state: &SelectedLayerState) selected_segments } -fn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAffine2, is_selected: impl Fn(ManipulatorPointId) -> bool, overlay_context: &mut OverlayContext) { - let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); +fn overlay_bezier_handles(segment: PathSeg, segment_id: SegmentId, transform: DAffine2, is_selected: impl Fn(ManipulatorPointId) -> bool, overlay_context: &mut OverlayContext) { + let segment = Affine::new(transform.to_cols_array()) * segment; + let segment_start = point_to_dvec2(segment.start()); + let segment_end = point_to_dvec2(segment.end()); let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; - match bezier.handles { - BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { - overlay_context.line(handle, bezier.start, None, None); - overlay_context.line(handle, bezier.end, None, None); + match segment_to_handles(&segment) { + BezierHandles::Quadratic { handle } if not_under_anchor(handle, segment_start) && not_under_anchor(handle, segment_end) => { + overlay_context.line(handle, segment_start, None, None); + overlay_context.line(handle, segment_end, None, None); overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); } BezierHandles::Cubic { handle_start, handle_end } => { - if not_under_anchor(handle_start, bezier.start) { - overlay_context.line(handle_start, bezier.start, None, None); + if not_under_anchor(handle_start, segment_start) { + overlay_context.line(handle_start, segment_start, None, None); overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); } - if not_under_anchor(handle_end, bezier.end) { - overlay_context.line(handle_end, bezier.end, None, None); + if not_under_anchor(handle_end, segment_end) { + overlay_context.line(handle_end, segment_end, None, None); overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None); } } @@ -92,7 +95,7 @@ fn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAff } fn overlay_bezier_handle_specific_point( - bezier: Bezier, + segment: PathSeg, segment_id: SegmentId, (start, end): (PointId, PointId), point_to_render: PointId, @@ -100,24 +103,26 @@ fn overlay_bezier_handle_specific_point( is_selected: impl Fn(ManipulatorPointId) -> bool, overlay_context: &mut OverlayContext, ) { - let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); + let segment = Affine::new(transform.to_cols_array()) * segment; + let segment_start = point_to_dvec2(segment.start()); + let segment_end = point_to_dvec2(segment.end()); let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; - match bezier.handles { + match segment_to_handles(&segment) { BezierHandles::Quadratic { handle } => { - if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) { - let end = if start == point_to_render { bezier.start } else { bezier.end }; - overlay_context.line(handle, end, None, None); + if not_under_anchor(handle, segment_start) && not_under_anchor(handle, segment_end) { + let anchor = if start == point_to_render { segment_start } else { segment_end }; + overlay_context.line(handle, anchor, None, None); overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); } } BezierHandles::Cubic { handle_start, handle_end } => { - if not_under_anchor(handle_start, bezier.start) && (point_to_render == start) { - overlay_context.line(handle_start, bezier.start, None, None); + if not_under_anchor(handle_start, segment_start) && (point_to_render == start) { + overlay_context.line(handle_start, segment_start, None, None); overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); } - if not_under_anchor(handle_end, bezier.end) && (point_to_render == end) { - overlay_context.line(handle_end, bezier.end, None, None); + if not_under_anchor(handle_end, segment_end) && (point_to_render == end) { + overlay_context.line(handle_end, segment_end, None, None); overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None); } } @@ -152,23 +157,23 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle match draw_handles { DrawHandles::All => { - vector.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| { - overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context); + vector.segment_iter().for_each(|(segment_id, segment, _start, _end)| { + overlay_bezier_handles(segment, segment_id, transform, is_selected, overlay_context); }); } DrawHandles::SelectedAnchors(ref selected_segments) => { let Some(focused_segments) = selected_segments.get(&layer) else { continue }; vector - .segment_bezier_iter() + .segment_iter() .filter(|(segment_id, ..)| focused_segments.contains(segment_id)) - .for_each(|(segment_id, bezier, _start, _end)| { - overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context); + .for_each(|(segment_id, segment, _start, _end)| { + overlay_bezier_handles(segment, segment_id, transform, is_selected, overlay_context); }); - for (segment_id, bezier, start, end) in vector.segment_bezier_iter() { + for (segment_id, segment, start, end) in vector.segment_iter() { if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) { - overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context); + overlay_bezier_handle_specific_point(segment, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context); } } } @@ -176,14 +181,14 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle let Some(segment_endpoints) = segment_endpoints_by_layer.get(&layer) else { continue }; vector - .segment_bezier_iter() + .segment_iter() .filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id)) - .for_each(|(segment_id, bezier, start, end)| { + .for_each(|(segment_id, segment, start, end)| { if segment_endpoints.get(&segment_id).unwrap().len() == 1 { let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0]; - overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context); + overlay_bezier_handle_specific_point(segment, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context); } else { - overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context); + overlay_bezier_handles(segment, segment_id, transform, is_selected, overlay_context); } }); } diff --git a/editor/src/messages/portfolio/document/utility_types/transformation.rs b/editor/src/messages/portfolio/document/utility_types/transformation.rs index bf6068afce..ea6cc54f03 100644 --- a/editor/src/messages/portfolio/document/utility_types/transformation.rs +++ b/editor/src/messages/portfolio/document/utility_types/transformation.rs @@ -93,7 +93,7 @@ impl OriginalTransforms { let mut selected_points = selected_points.clone(); - for (segment_id, _, start, end) in vector.segment_bezier_iter() { + for (segment_id, _, start, end) in vector.segment_iter() { if selected_segments.contains(&segment_id) { selected_points.insert(ManipulatorPointId::Anchor(start)); selected_points.insert(ManipulatorPointId::Anchor(end)); diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 598e493935..67f5a0e6cb 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -168,7 +168,7 @@ pub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifie let transform = document.metadata().transform_to_document(layer); let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return }; - let segment = vector.segment_bezier_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont); + let segment = vector.segment_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont); let Some((segment, _, mut segment_start_point, mut segment_end_point)) = segment else { log::error!("Could not get the segment for second_endpoint."); return; diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index a20113fde5..fdceebbe7c 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -15,7 +15,7 @@ use glam::{DAffine2, DVec2}; use graphene_std::subpath::{BezierHandles, Subpath}; use graphene_std::subpath::{PathSegPoints, pathseg_points}; use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table; -use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2}; +use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2, segment_to_handles}; use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModificationType}; use kurbo::{Affine, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveNearest, PathSeg, Rect, Shape}; use std::f64::consts::TAU; @@ -251,7 +251,7 @@ impl ClosestSegment { // Transform to viewport space let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface); - // Split the Bezier at the parameter `t` + // Split the segment at the parameter `t` let first = self.bezier.subsegment(0_f64..self.t); let second = self.bezier.subsegment(self.t..1.); @@ -788,7 +788,7 @@ impl ShapeState { } if segments { - for (id, _, start, end) in vector.segment_bezier_iter() { + for (id, _, start, end) in vector.segment_iter() { if connected_points.contains(&start) || connected_points.contains(&end) { state.select_segment(id); } @@ -950,14 +950,14 @@ impl ShapeState { // Move the other handle for a quadratic bezier for segment in vector.end_connected(point) { - let Some((start, _end, bezier)) = vector.segment_points_from_id(segment) else { continue }; + let Some((start, _end, path_segment)) = vector.segment_points_from_id(segment) else { continue }; - if let BezierHandles::Quadratic { handle } = bezier.handles { + if let BezierHandles::Quadratic { handle } = segment_to_handles(&path_segment) { if selected.is_some_and(|selected| selected.is_point_selected(ManipulatorPointId::Anchor(start))) { continue; } - let relative_position = handle - bezier.start + delta; + let relative_position = handle - point_to_dvec2(path_segment.start()) + delta; let modification_type = VectorModificationType::SetPrimaryHandle { segment, relative_position }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); @@ -1284,7 +1284,7 @@ impl ShapeState { // Make a new set of anchor points which needs to be moved let mut affected_points = state.selected_points.clone(); - for (segment_id, _, start, end) in vector.segment_bezier_iter() { + for (segment_id, _, start, end) in vector.segment_iter() { if state.is_segment_selected(segment_id) { affected_points.insert(ManipulatorPointId::Anchor(start)); affected_points.insert(ManipulatorPointId::Anchor(end)); @@ -1570,7 +1570,7 @@ impl ShapeState { for (&layer, state) in &self.selected_shape_state { let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue }; - for (segment, _, start, end) in vector.segment_bezier_iter() { + for (segment, _, start, end) in vector.segment_iter() { if state.selected_segments.contains(&segment) { if start_transaction && !transaction_started { responses.add(DocumentMessage::AddTransaction); @@ -1785,20 +1785,23 @@ impl ShapeState { let viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); // Handles - for (segment_id, bezier, _, _) in vector.segment_bezier_iter() { - let bezier = bezier.apply_transformation(|point| viewspace.transform_point2(point)); + for (segment_id, segment, _, _) in vector.segment_iter() { + let segment = Affine::new(viewspace.to_cols_array()) * segment; + let handles = segment_to_handles(&segment); + let segment_start = point_to_dvec2(segment.start()); + let segment_end = point_to_dvec2(segment.end()); let valid = |handle: DVec2, control: DVec2| handle.distance_squared(control) > crate::consts::HIDE_HANDLE_DISTANCE.powi(2); - if let Some(primary_handle) = bezier.handle_start() - && valid(primary_handle, bezier.start) - && (bezier.handle_end().is_some() || valid(primary_handle, bezier.end)) + if let Some(primary_handle) = handles.start() + && valid(primary_handle, segment_start) + && (handles.end().is_some() || valid(primary_handle, segment_end)) && primary_handle.distance_squared(pos) <= closest_distance_squared { closest_distance_squared = primary_handle.distance_squared(pos); manipulator_point = Some(ManipulatorPointId::PrimaryHandle(segment_id)); } - if let Some(end_handle) = bezier.handle_end() - && valid(end_handle, bezier.end) + if let Some(end_handle) = handles.end() + && valid(end_handle, segment_end) && end_handle.distance_squared(pos) <= closest_distance_squared { closest_distance_squared = end_handle.distance_squared(pos); @@ -2071,9 +2074,9 @@ impl ShapeState { self.convert_manipulator_handles_to_colinear(&vector, point_id, responses, layer); } else { for handle in vector.all_connected(point_id) { - let Some(bezier) = vector.segment_from_id(handle.segment) else { continue }; + let Some(path_segment) = vector.segment_from_id(handle.segment) else { continue }; - match bezier.handles { + match segment_to_handles(&path_segment) { BezierHandles::Linear => {} BezierHandles::Quadratic { .. } => { let segment = handle.segment; @@ -2173,7 +2176,7 @@ impl ShapeState { let Some(vector) = network_interface.compute_modified_vector(layer) else { continue }; if !select_points && select_segments { vector - .segment_bezier_iter() + .segment_iter() .filter(|(segment, _, _, _)| segments.contains(segment)) .for_each(|(_, _, start, end)| match selection_change { SelectionChange::Shrink => { diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 319cf68f03..6f310ab306 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -30,7 +30,7 @@ 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, dvec2_to_point, point_to_dvec2}; +use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2, segment_to_handles}; use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType}; use kurbo::{DEFAULT_ACCURACY, ParamCurve, ParamCurveNearest, PathSeg, Rect}; use std::vec; @@ -1647,7 +1647,7 @@ impl Fsm for PathToolFsmState { let selected_state = shape_editor.selected_shape_state.entry(layer).or_default(); - for (segment, _, start, end) in vector.segment_bezier_iter() { + for (segment, _, start, end) in vector.segment_iter() { if selected_state.is_segment_selected(segment) { selected_state.select_point(ManipulatorPointId::Anchor(start)); selected_state.select_point(ManipulatorPointId::Anchor(end)); @@ -1700,7 +1700,7 @@ impl Fsm for PathToolFsmState { let selected_state = shape_editor.selected_shape_state.entry(layer).or_default(); - for (segment, _, start, end) in vector.segment_bezier_iter() { + for (segment, _, start, end) in vector.segment_iter() { let first_selected = selected_state.is_point_selected(ManipulatorPointId::Anchor(start)); let second_selected = selected_state.is_point_selected(ManipulatorPointId::Anchor(end)); if first_selected && second_selected { @@ -1764,7 +1764,7 @@ impl Fsm for PathToolFsmState { // The points which are part of only one segment will be rendered let mut selected_segments_by_point: HashMap> = HashMap::new(); - for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() { + for (segment_id, _, start, end) in vector.segment_iter() { if focused_segments.contains(&segment_id) { selected_segments_by_point.entry(start).or_default().push(segment_id); selected_segments_by_point.entry(end).or_default().push(segment_id); @@ -2764,7 +2764,7 @@ impl Fsm for PathToolFsmState { let mut selected_points_by_segment = HashSet::new(); old_vector - .segment_bezier_iter() + .segment_iter() .filter(|(segment, _, _, _)| layer_selection_state.is_segment_selected(*segment)) .for_each(|(_, _, start, end)| { selected_points_by_segment.insert(start); @@ -2781,7 +2781,7 @@ impl Fsm for PathToolFsmState { let find_index = |id: PointId| new_vector.point_domain.iter().enumerate().find(|(_, (point_id, _))| *point_id == id).map(|(index, _)| index); // Add segments which have selected ends - for ((segment_id, bezier, start, end), stroke) in old_vector.segment_bezier_iter().zip(old_vector.segment_domain.stroke().iter()) { + for ((segment_id, segment, start, end), stroke) in old_vector.segment_iter().zip(old_vector.segment_domain.stroke().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); @@ -2791,7 +2791,7 @@ impl Fsm for PathToolFsmState { error!("Point does not exist in point domain"); return PathToolFsmState::Ready; }; - new_vector.segment_domain.push(segment_id, start_index, end_index, bezier.handles, *stroke); + new_vector.segment_domain.push(segment_id, start_index, end_index, segment_to_handles(&segment), *stroke); } } @@ -2945,7 +2945,7 @@ impl Fsm for PathToolFsmState { // Add all the selected points let mut selected_points_by_segment = HashSet::new(); old_vector - .segment_bezier_iter() + .segment_iter() .filter(|(segment, _, _, _)| layer_selection_state.is_segment_selected(*segment)) .for_each(|(_, _, start, end)| { selected_points_by_segment.insert(start); @@ -3398,7 +3398,7 @@ fn calculate_adjacent_anchor_tangent(currently_dragged_handle: ManipulatorPointI 0 => { // Find non-shared segments let non_shared_segment: Vec<_> = vector - .segment_bezier_iter() + .segment_iter() .filter_map(|(segment_id, _, start, end)| { let touches_adjacent = start == adjacent_anchor || end == adjacent_anchor; let shares_with_dragged = start == dragged_handle_anchor || end == dragged_handle_anchor; diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 33d1508300..60438a5456 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -162,7 +162,7 @@ impl MessageHandler> for let selected_segments = shape_editor.selected_segments().collect::>(); let mut affected_points = shape_editor.selected_points().copied().collect::>(); - for (segment_id, _, start, end) in vector.segment_bezier_iter() { + for (segment_id, _, start, end) in vector.segment_iter() { if selected_segments.contains(&segment_id) { affected_points.push(ManipulatorPointId::Anchor(start)); affected_points.push(ManipulatorPointId::Anchor(end)); diff --git a/node-graph/libraries/vector-types/src/subpath/structs.rs b/node-graph/libraries/vector-types/src/subpath/structs.rs index d3c99e147a..ec9dd3aeec 100644 --- a/node-graph/libraries/vector-types/src/subpath/structs.rs +++ b/node-graph/libraries/vector-types/src/subpath/structs.rs @@ -1,7 +1,6 @@ -use crate::vector::algorithms::intersection::filtered_segment_intersections; -use crate::vector::misc::{dvec2_to_point, handles_to_segment}; +use crate::vector::misc::dvec2_to_point; use glam::{DAffine2, DVec2}; -use kurbo::{CubicBez, Line, PathSeg, QuadBez, Shape}; +use kurbo::{CubicBez, Line, PathSeg, QuadBez}; use std::fmt::{Debug, Formatter, Result}; use std::hash::Hash; @@ -163,187 +162,3 @@ impl BezierHandles { } } } - -/// Representation of a bezier curve with 2D points. -#[derive(Copy, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Bezier { - /// Start point of the bezier curve. - pub start: DVec2, - /// End point of the bezier curve. - pub end: DVec2, - /// Handles of the bezier curve. - pub handles: BezierHandles, -} - -impl Debug for Bezier { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let mut debug_struct = f.debug_struct("Bezier"); - let mut debug_struct_ref = debug_struct.field("start", &self.start); - debug_struct_ref = match self.handles { - BezierHandles::Linear => debug_struct_ref, - BezierHandles::Quadratic { handle } => debug_struct_ref.field("handle", &handle), - BezierHandles::Cubic { handle_start, handle_end } => debug_struct_ref.field("handle_start", &handle_start).field("handle_end", &handle_end), - }; - debug_struct_ref.field("end", &self.end).finish() - } -} - -/// Functionality for the getters and setters of the various points in a Bezier -impl Bezier { - /// Set the coordinates of the start point. - pub fn set_start(&mut self, s: DVec2) { - self.start = s; - } - - /// Set the coordinates of the end point. - pub fn set_end(&mut self, e: DVec2) { - self.end = e; - } - - /// Set the coordinates of the first handle point. This represents the only handle in a quadratic segment. If used on a linear segment, it will be changed to a quadratic. - pub fn set_handle_start(&mut self, h1: DVec2) { - match self.handles { - BezierHandles::Linear => { - self.handles = BezierHandles::Quadratic { handle: h1 }; - } - BezierHandles::Quadratic { ref mut handle } => { - *handle = h1; - } - BezierHandles::Cubic { ref mut handle_start, .. } => { - *handle_start = h1; - } - }; - } - - /// Set the coordinates of the second handle point. This will convert both linear and quadratic segments into cubic ones. For a linear segment, the first handle will be set to the start point. - pub fn set_handle_end(&mut self, h2: DVec2) { - match self.handles { - BezierHandles::Linear => { - self.handles = BezierHandles::Cubic { - handle_start: self.start, - handle_end: h2, - }; - } - BezierHandles::Quadratic { handle } => { - self.handles = BezierHandles::Cubic { handle_start: handle, handle_end: h2 }; - } - BezierHandles::Cubic { ref mut handle_end, .. } => { - *handle_end = h2; - } - }; - } - - /// Get the coordinates of the bezier segment's start point. - pub fn start(&self) -> DVec2 { - self.start - } - - /// Get the coordinates of the bezier segment's end point. - pub fn end(&self) -> DVec2 { - self.end - } - - /// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment. - pub fn handle_start(&self) -> Option { - self.handles.start() - } - - /// Get the coordinates of the second handle point. This will return `None` for a quadratic segment. - pub fn handle_end(&self) -> Option { - self.handles.end() - } - - /// Get an iterator over the coordinates of all points in a vector. - /// - For a linear segment, the order of the points will be: `start`, `end`. - /// - For a quadratic segment, the order of the points will be: `start`, `handle`, `end`. - /// - For a cubic segment, the order of the points will be: `start`, `handle_start`, `handle_end`, `end`. - pub fn get_points(&self) -> impl Iterator + use<> { - match self.handles { - BezierHandles::Linear => [self.start, self.end, DVec2::ZERO, DVec2::ZERO].into_iter().take(2), - BezierHandles::Quadratic { handle } => [self.start, handle, self.end, DVec2::ZERO].into_iter().take(3), - BezierHandles::Cubic { handle_start, handle_end } => [self.start, handle_start, handle_end, self.end].into_iter().take(4), - } - } - - // TODO: Consider removing this function - /// Create a linear bezier using the provided coordinates as the start and end points. - pub fn from_linear_coordinates(x1: f64, y1: f64, x2: f64, y2: f64) -> Self { - Bezier { - start: DVec2::new(x1, y1), - handles: BezierHandles::Linear, - end: DVec2::new(x2, y2), - } - } - - /// Create a linear bezier using the provided DVec2s as the start and end points. - pub fn from_linear_dvec2(p1: DVec2, p2: DVec2) -> Self { - Bezier { - start: p1, - handles: BezierHandles::Linear, - end: p2, - } - } - - // TODO: Consider removing this function - /// Create a quadratic bezier using the provided coordinates as the start, handle, and end points. - pub fn from_quadratic_coordinates(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Self { - Bezier { - start: DVec2::new(x1, y1), - handles: BezierHandles::Quadratic { handle: DVec2::new(x2, y2) }, - end: DVec2::new(x3, y3), - } - } - - /// Create a quadratic bezier using the provided DVec2s as the start, handle, and end points. - pub fn from_quadratic_dvec2(p1: DVec2, p2: DVec2, p3: DVec2) -> Self { - Bezier { - start: p1, - handles: BezierHandles::Quadratic { handle: p2 }, - end: p3, - } - } - - // TODO: Consider removing this function - /// Create a cubic bezier using the provided coordinates as the start, handles, and end points. - #[allow(clippy::too_many_arguments)] - pub fn from_cubic_coordinates(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Self { - Bezier { - start: DVec2::new(x1, y1), - handles: BezierHandles::Cubic { - handle_start: DVec2::new(x2, y2), - handle_end: DVec2::new(x3, y3), - }, - end: DVec2::new(x4, y4), - } - } - - /// Create a cubic bezier using the provided DVec2s as the start, handles, and end points. - pub fn from_cubic_dvec2(p1: DVec2, p2: DVec2, p3: DVec2, p4: DVec2) -> Self { - Bezier { - start: p1, - handles: BezierHandles::Cubic { handle_start: p2, handle_end: p3 }, - end: p4, - } - } - - /// Returns a Bezier curve that results from applying the transformation function to each point in the Bezier. - pub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Bezier { - Self { - start: transformation_function(self.start), - end: transformation_function(self.end), - handles: self.handles.apply_transformation(transformation_function), - } - } - - pub fn intersections(&self, other: &Bezier, accuracy: Option, minimum_separation: Option) -> Vec { - let this = handles_to_segment(self.start, self.handles, self.end); - let other = handles_to_segment(other.start, other.handles, other.end); - filtered_segment_intersections(this, other, accuracy, minimum_separation) - } - - pub fn winding(&self, point: DVec2) -> i32 { - let this = handles_to_segment(self.start, self.handles, self.end); - this.winding(dvec2_to_point(point)) - } -} diff --git a/node-graph/libraries/vector-types/src/vector/misc.rs b/node-graph/libraries/vector-types/src/vector/misc.rs index b84ad29170..b5918ad454 100644 --- a/node-graph/libraries/vector-types/src/vector/misc.rs +++ b/node-graph/libraries/vector-types/src/vector/misc.rs @@ -449,8 +449,8 @@ impl ManipulatorPointId { pub fn get_position(&self, vector: &Vector) -> Option { match self { ManipulatorPointId::Anchor(id) => vector.point_domain.position_from_id(*id), - ManipulatorPointId::PrimaryHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_start()), - ManipulatorPointId::EndHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_end()), + ManipulatorPointId::PrimaryHandle(id) => vector.segment_from_id(*id).and_then(|segment| segment_to_handles(&segment).start()), + ManipulatorPointId::EndHandle(id) => vector.segment_from_id(*id).and_then(|segment| segment_to_handles(&segment).end()), } } diff --git a/node-graph/libraries/vector-types/src/vector/vector_attributes.rs b/node-graph/libraries/vector-types/src/vector/vector_attributes.rs index 8ffa23b0b5..2f150cc7b5 100644 --- a/node-graph/libraries/vector-types/src/vector/vector_attributes.rs +++ b/node-graph/libraries/vector-types/src/vector/vector_attributes.rs @@ -1,4 +1,4 @@ -use crate::subpath::{Bezier, BezierHandles, Identifier, ManipulatorGroup, Subpath}; +use crate::subpath::{BezierHandles, Identifier, ManipulatorGroup, Subpath}; use crate::vector::misc::{HandleId, Tangent, dvec2_to_point}; use crate::vector::vector_types::Vector; use dyn_any::DynAny; @@ -881,33 +881,26 @@ impl Vector { } } - /// Construct a [`Bezier`] curve spanning from the resolved position of the start and end points with the specified handles. - fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: BezierHandles) -> Bezier { - let start = self.point_domain.positions()[start]; - let end = self.point_domain.positions()[end]; - Bezier { start, end, handles } + /// Tries to convert a segment with the specified id to a [`PathSeg`], returning None if the id is invalid. + pub fn segment_from_id(&self, id: SegmentId) -> Option { + self.segment_points_from_id(id).map(|(_, _, segment)| segment) } - /// Tries to convert a segment with the specified id to a [`Bezier`], returning None if the id is invalid. - pub fn segment_from_id(&self, id: SegmentId) -> Option { - self.segment_points_from_id(id).map(|(_, _, bezier)| bezier) - } - - /// Tries to convert a segment with the specified id to the start and end points and a [`Bezier`], returning None if the id is invalid. - pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, Bezier)> { + /// Tries to convert a segment with the specified id to the start and end points and a [`PathSeg`], returning None if the id is invalid. + pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, PathSeg)> { Some(self.segment_points_from_index(self.segment_domain.id_to_index(id)?)) } - /// Tries to convert a segment with the specified index to the start and end points and a [`Bezier`]. - pub fn segment_points_from_index(&self, index: usize) -> (PointId, PointId, Bezier) { + /// Converts a segment with the specified index to the start and end points and a [`PathSeg`]. + pub fn segment_points_from_index(&self, index: usize) -> (PointId, PointId, PathSeg) { let start = self.segment_domain.start_point[index]; let end = self.segment_domain.end_point[index]; let start_id = self.point_domain.ids()[start]; let end_id = self.point_domain.ids()[end]; - (start_id, end_id, self.segment_to_bezier_with_index(start, end, self.segment_domain.handles[index])) + (start_id, end_id, self.path_segment_from_index(start, end, self.segment_domain.handles[index])) } - /// Iterator over all of the [`Bezier`] following the order that they are stored in the segment domain, skipping invalid segments. + /// Iterator over all of the [`PathSeg`]s following the order that they are stored in the segment domain, skipping invalid segments. pub fn segment_iter(&self) -> impl Iterator { let to_segment = |(((&handles, &id), &start), &end)| (id, self.path_segment_from_index(start, end, handles), self.point_domain.ids()[start], self.point_domain.ids()[end]); @@ -920,18 +913,6 @@ impl Vector { .map(to_segment) } - /// Iterator over all of the [`Bezier`] following the order that they are stored in the segment domain, skipping invalid segments. - pub fn segment_bezier_iter(&self) -> impl Iterator + '_ { - let to_bezier = |(((&handles, &id), &start), &end)| (id, self.segment_to_bezier_with_index(start, end, handles), self.point_domain.ids()[start], self.point_domain.ids()[end]); - self.segment_domain - .handles - .iter() - .zip(&self.segment_domain.id) - .zip(self.segment_domain.start_point()) - .zip(self.segment_domain.end_point()) - .map(to_bezier) - } - pub fn auto_join_paths(&self) -> Vec { let segments = self.segment_domain.iter().map(|(id, start, end, _)| HalfEdge::new(id, start, end, false)); @@ -1003,7 +984,7 @@ impl Vector { } } - /// Construct a [`Bezier`] curve from an iterator of segments with (handles, start point, end point) independently of discontinuities. + /// Construct a [`Subpath`] from an iterator of segments with (handles, start point, end point) independently of discontinuities. pub fn subpath_from_segments_ignore_discontinuities(&self, segments: impl Iterator) -> Option> { let mut first_point = None; let mut manipulators_list = Vec::new(); @@ -1055,7 +1036,7 @@ impl Vector { } } - /// Construct a [`Bezier`] curve for stroke. + /// Construct a [`Subpath`] for each stroke path. pub fn stroke_bezier_paths(&self) -> impl Iterator> { self.build_stroke_path_iter().map(|(manipulators_list, closed)| Subpath::new(manipulators_list, closed)) } diff --git a/node-graph/libraries/vector-types/src/vector/vector_modification.rs b/node-graph/libraries/vector-types/src/vector/vector_modification.rs index ce7a1110d9..542ab4719b 100644 --- a/node-graph/libraries/vector-types/src/vector/vector_modification.rs +++ b/node-graph/libraries/vector-types/src/vector/vector_modification.rs @@ -1,10 +1,10 @@ use super::*; use crate::subpath::BezierHandles; -use crate::vector::misc::{HandleId, HandleType, point_to_dvec2}; +use crate::vector::misc::{HandleId, HandleType, point_to_dvec2, segment_to_handles}; use core_types::uuid::generate_uuid; use dyn_any::DynAny; use glam::DVec2; -use kurbo::{BezPath, PathEl, Point}; +use kurbo::{BezPath, ParamCurve, PathEl, Point}; use serde::de::{SeqAccess, Visitor}; use serde::ser::SerializeSeq; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -225,8 +225,14 @@ impl SegmentModification { remove: HashSet::new(), start_point: vector.segment_domain.ids().iter().zip(vector.segment_domain.start_point()).map(point_id).collect(), end_point: vector.segment_domain.ids().iter().zip(vector.segment_domain.end_point()).map(point_id).collect(), - handle_primary: vector.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_start().map(|handle| handle - b.start))).collect(), - handle_end: vector.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_end().map(|handle| handle - b.end))).collect(), + handle_primary: vector + .segment_iter() + .map(|(id, segment, _, _)| (id, segment_to_handles(&segment).start().map(|handle| handle - point_to_dvec2(segment.start())))) + .collect(), + handle_end: vector + .segment_iter() + .map(|(id, segment, _, _)| (id, segment_to_handles(&segment).end().map(|handle| handle - point_to_dvec2(segment.end())))) + .collect(), stroke: vector.segment_domain.ids().iter().copied().zip(vector.segment_domain.stroke().iter().cloned()).collect(), } } @@ -808,7 +814,8 @@ impl HandleExt for HandleId { mod tests { use super::*; - use crate::subpath::{Bezier, ManipulatorGroup, Subpath}; + use crate::subpath::{ManipulatorGroup, Subpath}; + use kurbo::{PathSeg, QuadBez}; #[test] fn modify_new() { @@ -856,12 +863,12 @@ mod tests { assert_eq!(vector.point_domain.positions()[0], DVec2::X); assert_eq!(vector.point_domain.positions()[9], DVec2::new(11., 0.)); assert_eq!( - vector.segment_bezier_iter().nth(8).unwrap().1, - Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(11., 0.)) + vector.segment_iter().nth(8).unwrap().1, + PathSeg::Quad(QuadBez::new(Point::new(0., 0.), Point::new(5., 10.), Point::new(11., 0.))) ); assert_eq!( - vector.segment_bezier_iter().nth(9).unwrap().1, - Bezier::from_quadratic_dvec2(DVec2::new(11., 0.), DVec2::new(16., 10.), DVec2::new(20., 0.)) + vector.segment_iter().nth(9).unwrap().1, + PathSeg::Quad(QuadBez::new(Point::new(11., 0.), Point::new(16., 10.), Point::new(20., 0.))) ); } } diff --git a/node-graph/libraries/vector-types/src/vector/vector_types.rs b/node-graph/libraries/vector-types/src/vector/vector_types.rs index 73fe19f614..6b8fe9882b 100644 --- a/node-graph/libraries/vector-types/src/vector/vector_types.rs +++ b/node-graph/libraries/vector-types/src/vector/vector_types.rs @@ -338,8 +338,8 @@ impl Vector { /// Returns the number of linear segments connected to the given point. pub fn connected_linear_segments(&self, point_id: PointId) -> usize { - self.segment_bezier_iter() - .filter(|(_, bez, start, end)| (*start == point_id || *end == point_id) && matches!(bez.handles, BezierHandles::Linear)) + self.segment_iter() + .filter(|(_, segment, start, end)| (*start == point_id || *end == point_id) && matches!(segment, kurbo::PathSeg::Line(_))) .count() } diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 3099521252..9eae06f1b8 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -399,6 +399,8 @@ fn grid( #[cfg(test)] mod tests { use super::*; + use kurbo::ParamCurve; + use vector_types::vector::misc::point_to_dvec2; fn item(value: T) -> Item { Item::new_from_element(value) @@ -413,14 +415,11 @@ mod tests { // Works properly let grid = grid((), (), item(GridType::Isometric), item(10.), item(5_u32), item(5_u32), item((30., 30.).into()), item(true)); assert_eq!(grid.element().point_domain.ids().len(), 5 * 5); - assert_eq!(grid.element().segment_bezier_iter().count(), 4 * 5 + 4 * 9); - for (_, bezier, _, _) in grid.element().segment_bezier_iter() { - assert_eq!(bezier.handles, subpath::BezierHandles::Linear); - assert!( - ((bezier.start - bezier.end).length() - 10.).abs() < 1e-5, - "Length of {} should be 10", - (bezier.start - bezier.end).length() - ); + assert_eq!(grid.element().segment_iter().count(), 4 * 5 + 4 * 9); + for (_, segment, _, _) in grid.element().segment_iter() { + assert!(matches!(segment, kurbo::PathSeg::Line(_))); + let span = point_to_dvec2(segment.start()) - point_to_dvec2(segment.end()); + assert!((span.length() - 10.).abs() < 1e-5, "Length of {} should be 10", span.length()); } } @@ -428,10 +427,10 @@ mod tests { fn skew_isometric_grid_test() { let grid = grid((), (), item(GridType::Isometric), item(10.), item(5_u32), item(5_u32), item((40., 30.).into()), item(true)); assert_eq!(grid.element().point_domain.ids().len(), 5 * 5); - assert_eq!(grid.element().segment_bezier_iter().count(), 4 * 5 + 4 * 9); - for (_, bezier, _, _) in grid.element().segment_bezier_iter() { - assert_eq!(bezier.handles, subpath::BezierHandles::Linear); - let vector = bezier.start - bezier.end; + assert_eq!(grid.element().segment_iter().count(), 4 * 5 + 4 * 9); + for (_, segment, _, _) in grid.element().segment_iter() { + assert!(matches!(segment, kurbo::PathSeg::Line(_))); + let vector = point_to_dvec2(segment.start()) - point_to_dvec2(segment.end()); let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.; assert!([90., 150., 40.].into_iter().any(|target| (target - angle).abs() < 1e-10), "unexpected angle of {angle}") } diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index bbc65de14e..617b6ea787 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -696,16 +696,6 @@ pub mod extrude_algorithms { use vector_types::vector::StrokeId; use vector_types::vector::misc::ExtrudeJoiningAlgorithm; - /// Convert [`vector_types::subpath::Bezier`] to [`kurbo::PathSeg`]. - fn bezier_to_path_seg(bezier: vector_types::subpath::Bezier) -> kurbo::PathSeg { - let [start, end] = [(bezier.start().x, bezier.start().y), (bezier.end().x, bezier.end().y)]; - match bezier.handles { - BezierHandles::Linear => kurbo::Line::new(start, end).into(), - BezierHandles::Quadratic { handle } => kurbo::QuadBez::new(start, (handle.x, handle.y), end).into(), - BezierHandles::Cubic { handle_start, handle_end } => kurbo::CubicBez::new(start, (handle_start.x, handle_start.y), (handle_end.x, handle_end.y), end).into(), - } - } - /// Convert [`kurbo::CubicBez`] to [`vector_types::subpath::BezierHandles`]. fn cubic_to_handles(cubic_bez: kurbo::CubicBez) -> BezierHandles { BezierHandles::Cubic { @@ -736,9 +726,9 @@ pub mod extrude_algorithms { let mut next_segment = vector.segment_domain.next_id(); for segment_index in 0..segment_count { - let (_, _, bezier) = vector.segment_points_from_index(segment_index); + let (_, _, segment) = vector.segment_points_from_index(segment_index); let mut start_index = vector.segment_domain.start_point()[segment_index]; - let pathseg = bezier_to_path_seg(bezier).to_cubic(); + let pathseg = segment.to_cubic(); let mut start_t = 0.; for split_t in find_splits(pathseg, direction) {