@@ -15,6 +15,7 @@ use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
15
15
use glam:: { DAffine2 , DVec2 } ;
16
16
use graphene_std:: vector:: { HandleExt , HandleId , SegmentId } ;
17
17
use graphene_std:: vector:: { ManipulatorPointId , PointId , VectorData , VectorModificationType } ;
18
+ use std:: f64:: consts:: TAU ;
18
19
19
20
#[ derive( Debug , Copy , Clone , PartialEq , Eq ) ]
20
21
pub enum SelectionChange {
@@ -892,16 +893,20 @@ impl ShapeState {
892
893
ManipulatorPointId :: Anchor ( point) => self . move_anchor ( point, & vector_data, delta, layer, None , responses) ,
893
894
ManipulatorPointId :: PrimaryHandle ( segment) => {
894
895
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
+ }
898
901
}
899
902
}
900
903
ManipulatorPointId :: EndHandle ( segment) => {
901
904
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
+ }
905
910
}
906
911
}
907
912
}
@@ -1027,6 +1032,9 @@ impl ShapeState {
1027
1032
/// If only one handle is selected, the other handle will be moved to match the angle of the selected handle.
1028
1033
/// If both or neither handles are selected, the angle of both handles will be averaged from their current angles, weighted by their lengths.
1029
1034
/// 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.
1030
1038
pub fn convert_selected_manipulators_to_colinear_handles ( & self , responses : & mut VecDeque < Message > , document : & DocumentMessageHandler ) {
1031
1039
let mut skip_set = HashSet :: new ( ) ;
1032
1040
@@ -1037,7 +1045,55 @@ impl ShapeState {
1037
1045
let transform = document. metadata ( ) . transform_to_document_if_feeds ( layer, & document. network_interface ) ;
1038
1046
1039
1047
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
+
1041
1097
if skip_set. contains ( & handles) || skip_set. contains ( & [ handles[ 1 ] , handles[ 0 ] ] ) {
1042
1098
continue ;
1043
1099
} ;
@@ -1317,7 +1373,7 @@ impl ShapeState {
1317
1373
match point {
1318
1374
ManipulatorPointId :: Anchor ( anchor) => {
1319
1375
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 {
1321
1377
missing_anchors. insert ( anchor, handles) ;
1322
1378
}
1323
1379
}
@@ -1496,21 +1552,21 @@ impl ShapeState {
1496
1552
/// Disable colinear handles colinear.
1497
1553
pub fn disable_colinear_handles_state_on_selected ( & self , network_interface : & NodeNetworkInterface , responses : & mut VecDeque < Message > ) {
1498
1554
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 } ;
1502
1556
1503
1557
for & point in & state. selected_points {
1504
1558
if let ManipulatorPointId :: Anchor ( point) = point {
1505
1559
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) ) {
1507
1561
let modification_type = VectorModificationType :: SetG1Continuous { handles, enabled : false } ;
1508
1562
responses. add ( GraphOperationMessage :: Vector { layer, modification_type } ) ;
1509
1563
}
1510
1564
}
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
+ }
1514
1570
}
1515
1571
}
1516
1572
}
@@ -1720,9 +1776,40 @@ impl ShapeState {
1720
1776
if point. as_anchor ( ) . is_some ( ) {
1721
1777
continue ;
1722
1778
}
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 ( ) ) ) ;
1726
1813
}
1727
1814
}
1728
1815
}
0 commit comments