@@ -4,7 +4,7 @@ use crate::vector::vector_types::Vector;
44use dyn_any:: DynAny ;
55use glam:: { DAffine2 , DVec2 } ;
66use kurbo:: { CubicBez , Line , PathSeg , QuadBez } ;
7- use std:: collections:: HashMap ;
7+ use std:: collections:: { HashMap , HashSet } ;
88use std:: hash:: { Hash , Hasher } ;
99use std:: iter:: zip;
1010
@@ -678,6 +678,44 @@ impl FoundSubpath {
678678 pub fn contains ( & self , segment_id : SegmentId ) -> bool {
679679 self . edges . iter ( ) . any ( |s| s. id == segment_id)
680680 }
681+
682+ pub fn to_bezpath ( & self , vector : & Vector ) -> kurbo:: BezPath {
683+ let mut bezpath = kurbo:: BezPath :: new ( ) ;
684+
685+ if let Some ( first_edge) = self . edges . first ( ) {
686+ let start_pos = vector. point_domain . positions ( ) [ first_edge. start ] ;
687+ bezpath. move_to ( dvec2_to_point ( start_pos) ) ;
688+ }
689+
690+ for edge in & self . edges {
691+ let segment_index = vector. segment_domain . ids ( ) . iter ( ) . position ( |& id| id == edge. id ) . expect ( "Segment ID must exist" ) ;
692+
693+ let mut handles = vector. segment_domain . handles ( ) [ segment_index] ;
694+ if edge. reverse {
695+ handles = handles. reversed ( ) ;
696+ }
697+
698+ let end_pos = vector. point_domain . positions ( ) [ edge. end ] ;
699+
700+ match handles {
701+ BezierHandles :: Linear => {
702+ bezpath. line_to ( dvec2_to_point ( end_pos) ) ;
703+ }
704+ BezierHandles :: Quadratic { handle } => {
705+ bezpath. quad_to ( dvec2_to_point ( handle) , dvec2_to_point ( end_pos) ) ;
706+ }
707+ BezierHandles :: Cubic { handle_start, handle_end } => {
708+ bezpath. curve_to ( dvec2_to_point ( handle_start) , dvec2_to_point ( handle_end) , dvec2_to_point ( end_pos) ) ;
709+ }
710+ }
711+ }
712+
713+ if self . is_closed ( ) {
714+ bezpath. close_path ( ) ;
715+ }
716+
717+ bezpath
718+ }
681719}
682720
683721impl Vector {
@@ -985,6 +1023,134 @@ impl Vector {
9851023 self . segment_domain . map_ids ( & id_map) ;
9861024 self . region_domain . map_ids ( & id_map) ;
9871025 }
1026+
1027+ pub fn is_branching_mesh ( & self ) -> bool {
1028+ for point_index in 0 ..self . point_domain . len ( ) {
1029+ let connection_count = self . segment_domain . connected_count ( point_index) ;
1030+
1031+ if connection_count > 2 {
1032+ return true ;
1033+ }
1034+ }
1035+
1036+ false
1037+ }
1038+
1039+ /// Find all minimal closed regions (faces) in a branching mesh vector.
1040+ pub fn find_closed_regions ( & self ) -> Vec < FoundSubpath > {
1041+ let mut regions = Vec :: new ( ) ;
1042+ let mut used_half_edges = HashSet :: new ( ) ;
1043+
1044+ // Build adjacency list sorted by angle for proper face traversal
1045+ let mut adjacency: HashMap < usize , Vec < ( SegmentId , usize , bool ) > > = HashMap :: new ( ) ;
1046+
1047+ for ( segment_id, start, end, _) in self . segment_domain . iter ( ) {
1048+ adjacency. entry ( start) . or_default ( ) . push ( ( segment_id, end, false ) ) ;
1049+ adjacency. entry ( end) . or_default ( ) . push ( ( segment_id, start, true ) ) ;
1050+ }
1051+
1052+ // Sort neighbors by angle to enable finding the "rightmost" path
1053+ for ( point_idx, neighbors) in adjacency. iter_mut ( ) {
1054+ let point_pos = self . point_domain . positions ( ) [ * point_idx] ;
1055+ neighbors. sort_by ( |a, b| {
1056+ let pos_a = self . point_domain . positions ( ) [ a. 1 ] ;
1057+ let pos_b = self . point_domain . positions ( ) [ b. 1 ] ;
1058+ let angle_a = ( pos_a - point_pos) . y . atan2 ( ( pos_a - point_pos) . x ) ;
1059+ let angle_b = ( pos_b - point_pos) . y . atan2 ( ( pos_b - point_pos) . x ) ;
1060+ angle_a. partial_cmp ( & angle_b) . unwrap_or ( std:: cmp:: Ordering :: Equal )
1061+ } ) ;
1062+ }
1063+
1064+ for ( segment_id, start, end, _) in self . segment_domain . iter ( ) {
1065+ for & reversed in & [ false , true ] {
1066+ let ( from, to) = if reversed { ( end, start) } else { ( start, end) } ;
1067+ let half_edge_key = ( segment_id, reversed) ;
1068+
1069+ if used_half_edges. contains ( & half_edge_key) {
1070+ continue ;
1071+ }
1072+
1073+ if let Some ( face) = self . find_minimal_face_from_edge ( segment_id, from, to, reversed, & adjacency, & mut used_half_edges) {
1074+ regions. push ( face) ;
1075+ }
1076+ }
1077+ }
1078+
1079+ regions
1080+ }
1081+
1082+ /// Helper to find a minimal face (smallest cycle) starting from a half-edge
1083+ fn find_minimal_face_from_edge (
1084+ & self ,
1085+ start_segment : SegmentId ,
1086+ from_point : usize ,
1087+ to_point : usize ,
1088+ start_reversed : bool ,
1089+ adjacency : & HashMap < usize , Vec < ( SegmentId , usize , bool ) > > ,
1090+ used_half_edges : & mut HashSet < ( SegmentId , bool ) > ,
1091+ ) -> Option < FoundSubpath > {
1092+ let mut path = vec ! [ HalfEdge :: new( start_segment, from_point, to_point, start_reversed) ] ;
1093+ let mut current = to_point;
1094+ let target = from_point;
1095+ let mut prev_segment = start_segment;
1096+
1097+ let mut iteration = 0 ;
1098+ let max_iterations = adjacency. len ( ) * 2 ;
1099+
1100+ // Follow the "rightmost" edge at each vertex to find minimal face
1101+ loop {
1102+ iteration += 1 ;
1103+
1104+ if iteration > max_iterations {
1105+ return None ;
1106+ }
1107+
1108+ let neighbors = adjacency. get ( & current) ?;
1109+ // Find the next edge in counterclockwise order (rightmost turn)
1110+ let prev_direction = self . point_domain . positions ( ) [ current] - self . point_domain . positions ( ) [ path. last ( ) ?. start ] ;
1111+
1112+ let angle_between = |v1 : DVec2 , v2 : DVec2 | -> f64 {
1113+ let angle = v2. y . atan2 ( v2. x ) - v1. y . atan2 ( v1. x ) ;
1114+ if angle < 0.0 { angle + 2.0 * std:: f64:: consts:: PI } else { angle }
1115+ } ;
1116+
1117+ let next = neighbors. iter ( ) . filter ( |( seg, _next, _rev) | * seg != prev_segment) . min_by ( |a, b| {
1118+ let dir_a = self . point_domain . positions ( ) [ a. 1 ] - self . point_domain . positions ( ) [ current] ;
1119+ let dir_b = self . point_domain . positions ( ) [ b. 1 ] - self . point_domain . positions ( ) [ current] ;
1120+ let angle_a = angle_between ( prev_direction, dir_a) ;
1121+ let angle_b = angle_between ( prev_direction, dir_b) ;
1122+ angle_a. partial_cmp ( & angle_b) . unwrap_or ( std:: cmp:: Ordering :: Equal )
1123+ } ) ?;
1124+
1125+ let ( next_segment, next_point, next_reversed) = * next;
1126+
1127+ if next_point == target {
1128+ // Completed the cycle
1129+ path. push ( HalfEdge :: new ( next_segment, current, next_point, next_reversed) ) ;
1130+
1131+ // Mark all half-edges as used
1132+ for edge in & path {
1133+ used_half_edges. insert ( ( edge. id , edge. reverse ) ) ;
1134+ }
1135+
1136+ return Some ( FoundSubpath :: new ( path) ) ;
1137+ }
1138+
1139+ // Check if we've created a cycle (might not be back to start)
1140+ if path. iter ( ) . any ( |e| e. end == next_point && e. id != next_segment) {
1141+ return None ;
1142+ }
1143+
1144+ path. push ( HalfEdge :: new ( next_segment, current, next_point, next_reversed) ) ;
1145+ prev_segment = next_segment;
1146+ current = next_point;
1147+
1148+ // Prevent infinite loops
1149+ if path. len ( ) > adjacency. len ( ) {
1150+ return None ;
1151+ }
1152+ }
1153+ }
9881154}
9891155
9901156#[ derive( Clone , Copy , PartialEq , Eq , Debug , Default ) ]
0 commit comments