@@ -9,7 +9,8 @@ use crate::core::overlay_rule::OverlayRule;
99use crate :: geom:: v_segment:: VSegment ;
1010use alloc:: vec;
1111use alloc:: vec:: Vec ;
12- use i_shape:: int:: shape:: IntShapes ;
12+ use i_float:: int:: point:: IntPoint ;
13+ use i_shape:: int:: shape:: { IntShape , IntShapes } ;
1314use i_shape:: util:: reserve:: Reserve ;
1415
1516impl OverlayGraph < ' _ > {
@@ -20,10 +21,21 @@ impl OverlayGraph<'_> {
2021 ) -> IntShapes {
2122 let is_main_dir_cw = self . options . output_direction == ContourDirection :: Clockwise ;
2223
24+ let mut contour_visited = if let Some ( mut visited) = buffer. contour_visited . take ( ) {
25+ let target_len = buffer. visited . len ( ) ;
26+ if visited. len ( ) != target_len {
27+ visited. resize ( target_len, VisitState :: Skipped ) ;
28+ }
29+ visited. fill ( VisitState :: Skipped ) ;
30+ visited
31+ } else {
32+ vec ! [ VisitState :: Skipped ; buffer. visited. len( ) ]
33+ } ;
34+
2335 let mut shapes = Vec :: new ( ) ;
2436
2537 buffer. points . reserve_capacity ( buffer. visited . len ( ) ) ;
26- let mut avg_holes_count = 0 ;
38+ let mut hole_count_hint = 0 ;
2739
2840 let mut link_index = 0 ;
2941 while link_index < buffer. visited . len ( ) {
@@ -58,26 +70,24 @@ impl OverlayGraph<'_> {
5870 visited_state,
5971 & mut buffer. visited ,
6072 ) ;
61- avg_holes_count += 1 ;
73+ hole_count_hint += 1 ;
6274 continue ;
6375 }
6476
65- self . find_contour ( & start_data, traversal_direction, visited_state, buffer) ;
66- let ( is_valid, _) = buffer. points . validate (
67- self . options . min_output_area ,
68- self . options . preserve_output_collinear ,
69- ) ;
70-
71- if !is_valid {
77+ if let Some ( shape) = self . collect_shape (
78+ & start_data,
79+ traversal_direction,
80+ & mut buffer. visited ,
81+ & mut contour_visited,
82+ & mut buffer. points ,
83+ ) {
84+ shapes. push ( shape) ;
85+ } else {
7286 link_index += 1 ;
73- continue ;
74- }
75-
76- let contour = buffer. points . as_slice ( ) . to_vec ( ) ;
77- shapes. push ( vec ! [ contour] ) ;
87+ } ;
7888 }
7989
80- if avg_holes_count > 0 {
90+ if hole_count_hint > 0 {
8191 // Keep only hole edges; skip everything else for the second pass.
8292 for state in buffer. visited . iter_mut ( ) {
8393 * state = match * state {
@@ -86,8 +96,8 @@ impl OverlayGraph<'_> {
8696 } ;
8797 }
8898
89- let mut holes = Vec :: with_capacity ( avg_holes_count ) ;
90- let mut anchors = Vec :: with_capacity ( avg_holes_count ) ;
99+ let mut holes = Vec :: with_capacity ( hole_count_hint ) ;
100+ let mut anchors = Vec :: with_capacity ( hole_count_hint ) ;
91101 let mut anchors_already_sorted = true ;
92102 link_index = 0 ;
93103
@@ -112,7 +122,14 @@ impl OverlayGraph<'_> {
112122
113123 let start_data = StartPathData :: new ( is_main_dir_cw, link, left_top_link) ;
114124
115- self . find_contour ( & start_data, is_main_dir_cw, VisitState :: HullVisited , buffer) ;
125+ self . find_contour (
126+ & start_data,
127+ is_main_dir_cw,
128+ VisitState :: HullVisited ,
129+ & mut buffer. visited ,
130+ & mut buffer. points ,
131+ ) ;
132+
116133 let ( is_valid, is_modified) = buffer. points . validate (
117134 self . options . min_output_area ,
118135 self . options . preserve_output_collinear ,
@@ -157,6 +174,8 @@ impl OverlayGraph<'_> {
157174 shapes. join_sorted_holes ( holes, anchors, is_main_dir_cw) ;
158175 }
159176
177+ buffer. contour_visited = Some ( contour_visited) ;
178+
160179 shapes
161180 }
162181
@@ -192,4 +211,133 @@ impl OverlayGraph<'_> {
192211 visited. visit_edge ( link_id, visited_state) ;
193212 }
194213 }
214+
215+ fn collect_shape (
216+ & self ,
217+ start_data : & StartPathData ,
218+ clockwise : bool ,
219+ global_visited : & mut Vec < VisitState > ,
220+ contour_visited : & mut Vec < VisitState > ,
221+ points : & mut Vec < IntPoint > ,
222+ ) -> Option < IntShape > {
223+ let mut link_id = start_data. link_id ;
224+ let mut node_id = start_data. node_id ;
225+ let last_node_id = start_data. last_node_id ;
226+
227+ // First, mark all edges that belong to the contour.
228+
229+ let mut end_link_id = start_data. link_id ;
230+
231+ global_visited. visit_edge ( link_id, VisitState :: HullVisited ) ;
232+ contour_visited. visit_edge ( link_id, VisitState :: Unvisited ) ;
233+
234+ let mut original_contour_len = 1 ;
235+
236+ // Find a closed tour
237+ while node_id != last_node_id {
238+ link_id = GraphUtil :: next_link (
239+ self . links ,
240+ self . nodes ,
241+ link_id,
242+ node_id,
243+ clockwise,
244+ global_visited,
245+ ) ;
246+
247+ let link = unsafe {
248+ // Safety: `link_id` is always derived from a previous in-bounds index or
249+ // from `find_left_top_link`, so it remains in `0..self.links.len()`.
250+ self . links . get_unchecked ( link_id)
251+ } ;
252+
253+ node_id = if link. a . id == node_id {
254+ link. b . id
255+ } else {
256+ link. a . id
257+ } ;
258+ end_link_id = end_link_id. max ( link_id) ;
259+ contour_visited. visit_edge ( link_id, VisitState :: Unvisited ) ;
260+ global_visited. visit_edge ( link_id, VisitState :: HullVisited ) ;
261+ original_contour_len += 1 ;
262+ }
263+
264+ // Revisit the contour in reverse;
265+ // all links escape current contour are skipped in `contour_visited`.
266+
267+ points. reserve_capacity ( original_contour_len) ;
268+ self . find_contour (
269+ & start_data,
270+ !clockwise,
271+ VisitState :: HullVisited ,
272+ contour_visited,
273+ points,
274+ ) ;
275+
276+ let ( is_valid, _) = points. validate (
277+ self . options . min_output_area ,
278+ self . options . preserve_output_collinear ,
279+ ) ;
280+
281+ let contour_len = points. len ( ) ;
282+
283+ let mut shape = if is_valid {
284+ let mut shape = vec ! [ ] ;
285+ let contour = points. as_slice ( ) . to_vec ( ) ;
286+ shape. push ( contour) ;
287+ Some ( shape)
288+ } else {
289+ None
290+ } ;
291+
292+ if contour_len < original_contour_len {
293+ // contour has self touches
294+ let mut link_index = start_data. link_id ;
295+ while link_index <= end_link_id {
296+ if contour_visited. is_visited ( link_index) {
297+ link_index += 1 ;
298+ continue ;
299+ }
300+
301+ let left_top_link = unsafe {
302+ // Safety: `link_index` walks 0..buffer.visited.len(), and buffer.visited.len() <= self.links.len().
303+ GraphUtil :: find_left_top_link ( self . links , self . nodes , link_index, contour_visited)
304+ } ;
305+
306+ let link = unsafe {
307+ // Safety: `left_top_link` originates from `find_left_top_link`, which only returns
308+ // indices in 0..self.links.len(), so this lookup cannot go out of bounds.
309+ self . links . get_unchecked ( left_top_link)
310+ } ;
311+
312+ // Self-touch splits can only produce holes inside this contour.
313+
314+ let hole_start_data = StartPathData :: new ( clockwise, link, left_top_link) ;
315+ self . find_contour (
316+ & hole_start_data,
317+ clockwise,
318+ VisitState :: HoleVisited ,
319+ contour_visited,
320+ points,
321+ ) ;
322+
323+ // Hole have to belong to this shape.
324+ if let Some ( shape) = shape. as_mut ( ) {
325+ let ( is_valid, _) = points. validate (
326+ self . options . min_output_area ,
327+ self . options . preserve_output_collinear ,
328+ ) ;
329+
330+ if !is_valid {
331+ link_index += 1 ;
332+ continue ;
333+ }
334+
335+ let contour = points. as_slice ( ) . to_vec ( ) ;
336+ shape. push ( contour) ;
337+ }
338+ }
339+ }
340+
341+ shape
342+ }
195343}
0 commit comments