@@ -15,6 +15,7 @@ use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
1515use glam:: { DAffine2 , DVec2 } ;
1616use graphene_std:: vector:: { HandleExt , HandleId , SegmentId } ;
1717use graphene_std:: vector:: { ManipulatorPointId , PointId , VectorData , VectorModificationType } ;
18+ use std:: f64:: consts:: TAU ;
1819
1920#[ derive( Debug , Copy , Clone , PartialEq , Eq ) ]
2021pub enum SelectionChange {
@@ -892,16 +893,20 @@ impl ShapeState {
892893 ManipulatorPointId :: Anchor ( point) => self . move_anchor ( point, & vector_data, delta, layer, None , responses) ,
893894 ManipulatorPointId :: PrimaryHandle ( segment) => {
894895 self . move_primary ( segment, delta, layer, responses) ;
895- if let Some ( handles) = point. get_handle_pair ( & vector_data) {
896- let modification_type = VectorModificationType :: SetG1Continuous { handles, enabled : false } ;
897- responses. add ( GraphOperationMessage :: Vector { layer, modification_type } ) ;
896+ if let Some ( handle) = point. as_handle ( ) {
897+ if let Some ( handles) = vector_data. colinear_manipulators . iter ( ) . find ( |handles| handles[ 0 ] == handle || handles[ 1 ] == handle) {
898+ let modification_type = VectorModificationType :: SetG1Continuous { handles : * handles, enabled : false } ;
899+ responses. add ( GraphOperationMessage :: Vector { layer, modification_type } ) ;
900+ }
898901 }
899902 }
900903 ManipulatorPointId :: EndHandle ( segment) => {
901904 self . move_end ( segment, delta, layer, responses) ;
902- if let Some ( handles) = point. get_handle_pair ( & vector_data) {
903- let modification_type = VectorModificationType :: SetG1Continuous { handles, enabled : false } ;
904- responses. add ( GraphOperationMessage :: Vector { layer, modification_type } ) ;
905+ if let Some ( handle) = point. as_handle ( ) {
906+ if let Some ( handles) = vector_data. colinear_manipulators . iter ( ) . find ( |handles| handles[ 0 ] == handle || handles[ 1 ] == handle) {
907+ let modification_type = VectorModificationType :: SetG1Continuous { handles : * handles, enabled : false } ;
908+ responses. add ( GraphOperationMessage :: Vector { layer, modification_type } ) ;
909+ }
905910 }
906911 }
907912 }
@@ -1027,6 +1032,9 @@ impl ShapeState {
10271032 /// If only one handle is selected, the other handle will be moved to match the angle of the selected handle.
10281033 /// If both or neither handles are selected, the angle of both handles will be averaged from their current angles, weighted by their lengths.
10291034 /// Assumes all selected manipulators have handles that are already not colinear.
1035+ ///
1036+ /// For vector meshes, the non-colinear handle which is nearest in the direction of 180° angle separation becomes colinear with current handle.
1037+ /// If there is no such handle, nothing happens.
10301038 pub fn convert_selected_manipulators_to_colinear_handles ( & self , responses : & mut VecDeque < Message > , document : & DocumentMessageHandler ) {
10311039 let mut skip_set = HashSet :: new ( ) ;
10321040
@@ -1037,7 +1045,55 @@ impl ShapeState {
10371045 let transform = document. metadata ( ) . transform_to_document_if_feeds ( layer, & document. network_interface ) ;
10381046
10391047 for & point in layer_state. selected_points . iter ( ) {
1040- let Some ( handles) = point. get_handle_pair ( & vector_data) else { continue } ;
1048+ // Skip a point which has more than 2 segments connected (vector meshes)
1049+ if let ManipulatorPointId :: Anchor ( anchor) = point {
1050+ if vector_data. all_connected ( anchor) . count ( ) > 2 {
1051+ continue ;
1052+ }
1053+ }
1054+
1055+ // Here we take handles as the current handle and the most opposite non-colinear-handle
1056+
1057+ let is_handle_colinear = |handle : HandleId | -> bool { vector_data. colinear_manipulators . iter ( ) . any ( |& handles| handles[ 0 ] == handle || handles[ 1 ] == handle) } ;
1058+
1059+ let other_handles = if matches ! ( point, ManipulatorPointId :: Anchor ( _) ) {
1060+ point. get_handle_pair ( & vector_data)
1061+ } else {
1062+ point. get_all_connected_handles ( & vector_data) . and_then ( |handles| {
1063+ let mut non_colinear_handles = handles. iter ( ) . filter ( |& handle| !is_handle_colinear ( * handle) ) . clone ( ) . collect :: < Vec < _ > > ( ) ;
1064+
1065+ // Sort these by angle from the current handle
1066+ non_colinear_handles. sort_by ( |& handle_a, & handle_b| {
1067+ let anchor = point. get_anchor_position ( & vector_data) . expect ( "No anchor position for handle" ) ;
1068+ let orig_handle_pos = point. get_position ( & vector_data) . expect ( "No handle position" ) ;
1069+
1070+ let a_pos = handle_a. to_manipulator_point ( ) . get_position ( & vector_data) . expect ( "No handle position" ) ;
1071+ let b_pos = handle_b. to_manipulator_point ( ) . get_position ( & vector_data) . expect ( "No handle position" ) ;
1072+
1073+ let v_orig = ( orig_handle_pos - anchor) . normalize_or_zero ( ) ;
1074+
1075+ let v_a = ( a_pos - anchor) . normalize_or_zero ( ) ;
1076+ let v_b = ( b_pos - anchor) . normalize_or_zero ( ) ;
1077+
1078+ let angle_a = v_orig. angle_to ( v_a) . abs ( ) ;
1079+ let angle_b = v_orig. angle_to ( v_b) . abs ( ) ;
1080+
1081+ // Sort by descending angle (180° is furthest)
1082+ angle_b. partial_cmp ( & angle_a) . unwrap_or ( std:: cmp:: Ordering :: Equal )
1083+ } ) ;
1084+
1085+ let current = match point {
1086+ ManipulatorPointId :: EndHandle ( segment) => HandleId :: end ( segment) ,
1087+ ManipulatorPointId :: PrimaryHandle ( segment) => HandleId :: primary ( segment) ,
1088+ ManipulatorPointId :: Anchor ( _) => unreachable ! ( ) ,
1089+ } ;
1090+
1091+ non_colinear_handles. first ( ) . map ( |other| [ current, * * other] )
1092+ } )
1093+ } ;
1094+
1095+ let Some ( handles) = other_handles else { continue } ;
1096+
10411097 if skip_set. contains ( & handles) || skip_set. contains ( & [ handles[ 1 ] , handles[ 0 ] ] ) {
10421098 continue ;
10431099 } ;
@@ -1317,7 +1373,7 @@ impl ShapeState {
13171373 match point {
13181374 ManipulatorPointId :: Anchor ( anchor) => {
13191375 if let Some ( handles) = Self :: dissolve_anchor ( anchor, responses, layer, & vector_data) {
1320- if !vector_data. all_connected ( anchor) . any ( |a| selected_segments. contains ( & a. segment ) ) {
1376+ if !vector_data. all_connected ( anchor) . any ( |a| selected_segments. contains ( & a. segment ) ) && vector_data . all_connected ( anchor ) . count ( ) <= 2 {
13211377 missing_anchors. insert ( anchor, handles) ;
13221378 }
13231379 }
@@ -1496,21 +1552,21 @@ impl ShapeState {
14961552 /// Disable colinear handles colinear.
14971553 pub fn disable_colinear_handles_state_on_selected ( & self , network_interface : & NodeNetworkInterface , responses : & mut VecDeque < Message > ) {
14981554 for ( & layer, state) in & self . selected_shape_state {
1499- let Some ( vector_data) = network_interface. compute_modified_vector ( layer) else {
1500- continue ;
1501- } ;
1555+ let Some ( vector_data) = network_interface. compute_modified_vector ( layer) else { continue } ;
15021556
15031557 for & point in & state. selected_points {
15041558 if let ManipulatorPointId :: Anchor ( point) = point {
15051559 for connected in vector_data. all_connected ( point) {
1506- if let Some ( & handles) = vector_data. colinear_manipulators . iter ( ) . find ( |target| target. iter ( ) . any ( | & target| target == connected) ) {
1560+ if let Some ( & handles) = vector_data. colinear_manipulators . iter ( ) . find ( |target| target. contains ( & connected) ) {
15071561 let modification_type = VectorModificationType :: SetG1Continuous { handles, enabled : false } ;
15081562 responses. add ( GraphOperationMessage :: Vector { layer, modification_type } ) ;
15091563 }
15101564 }
1511- } else if let Some ( handles) = point. get_handle_pair ( & vector_data) {
1512- let modification_type = VectorModificationType :: SetG1Continuous { handles, enabled : false } ;
1513- responses. add ( GraphOperationMessage :: Vector { layer, modification_type } ) ;
1565+ } else if let Some ( handle) = point. as_handle ( ) {
1566+ if let Some ( handles) = vector_data. colinear_manipulators . iter ( ) . find ( |handles| handles[ 0 ] == handle || handles[ 1 ] == handle) {
1567+ let modification_type = VectorModificationType :: SetG1Continuous { handles : * handles, enabled : false } ;
1568+ responses. add ( GraphOperationMessage :: Vector { layer, modification_type } ) ;
1569+ }
15141570 }
15151571 }
15161572 }
@@ -1720,9 +1776,40 @@ impl ShapeState {
17201776 if point. as_anchor ( ) . is_some ( ) {
17211777 continue ;
17221778 }
1723- if let Some ( handles) = point. get_handle_pair ( & vector_data) {
1724- // handle[0] is selected, handle[1] is opposite / mirror handle
1725- handles_to_update. push ( ( layer, handles[ 0 ] . to_manipulator_point ( ) , handles[ 1 ] . to_manipulator_point ( ) ) ) ;
1779+
1780+ if let Some ( other_handles) = point. get_all_connected_handles ( & vector_data) {
1781+ // Find the next closest handle in the clockwise sense
1782+ let mut candidates = other_handles. clone ( ) ;
1783+ candidates. sort_by ( |& handle_a, & handle_b| {
1784+ let anchor = point. get_anchor_position ( & vector_data) . expect ( "No anchor position for handle" ) ;
1785+ let orig_handle_pos = point. get_position ( & vector_data) . expect ( "No handle position" ) ;
1786+
1787+ let a_pos = handle_a. to_manipulator_point ( ) . get_position ( & vector_data) . expect ( "No handle position" ) ;
1788+ let b_pos = handle_b. to_manipulator_point ( ) . get_position ( & vector_data) . expect ( "No handle position" ) ;
1789+
1790+ let v_orig = ( orig_handle_pos - anchor) . normalize_or_zero ( ) ;
1791+
1792+ let v_a = ( a_pos - anchor) . normalize_or_zero ( ) ;
1793+ let v_b = ( b_pos - anchor) . normalize_or_zero ( ) ;
1794+
1795+ let signed_angle = |base : DVec2 , to : DVec2 | -> f64 {
1796+ let angle = base. angle_to ( to) ;
1797+ let cross = base. perp_dot ( to) ;
1798+
1799+ if cross < 0. { TAU - angle } else { angle }
1800+ } ;
1801+
1802+ let angle_a = signed_angle ( v_orig, v_a) ;
1803+ let angle_b = signed_angle ( v_orig, v_b) ;
1804+
1805+ angle_a. partial_cmp ( & angle_b) . unwrap_or ( std:: cmp:: Ordering :: Equal )
1806+ } ) ;
1807+
1808+ if candidates. is_empty ( ) {
1809+ continue ;
1810+ }
1811+
1812+ handles_to_update. push ( ( layer, * point, candidates[ 0 ] . to_manipulator_point ( ) ) ) ;
17261813 }
17271814 }
17281815 }
0 commit comments