Skip to content

Commit adb4c69

Browse files
Merge pull request #59 from iShape-Rust/fix/ogc_self_touch
Fix/ogc self touch
2 parents e35f194 + 24dd18e commit adb4c69

3 files changed

Lines changed: 490 additions & 39 deletions

File tree

iOverlay/src/core/extract.rs

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub(crate) enum VisitState {
3131
pub struct BooleanExtractionBuffer {
3232
pub(crate) points: Vec<IntPoint>,
3333
pub(crate) visited: Vec<VisitState>,
34+
pub(crate) contour_visited: Option<Vec<VisitState>>,
3435
}
3536

3637
impl OverlayGraph<'_> {
@@ -120,7 +121,13 @@ impl OverlayGraph<'_> {
120121
let direction = is_hole == clockwise;
121122
let start_data = StartPathData::new(direction, link, left_top_link);
122123

123-
self.find_contour(&start_data, direction, visited_state, buffer);
124+
self.find_contour(
125+
&start_data,
126+
direction,
127+
visited_state,
128+
&mut buffer.visited,
129+
&mut buffer.points,
130+
);
124131
let (is_valid, is_modified) = buffer.points.validate(
125132
self.options.min_output_area,
126133
self.options.preserve_output_collinear,
@@ -176,35 +183,29 @@ impl OverlayGraph<'_> {
176183
start_data: &StartPathData,
177184
clockwise: bool,
178185
visited_state: VisitState,
179-
buffer: &mut BooleanExtractionBuffer,
186+
visited: &mut Vec<VisitState>,
187+
points: &mut Vec<IntPoint>,
180188
) {
181189
let mut link_id = start_data.link_id;
182190
let mut node_id = start_data.node_id;
183191
let last_node_id = start_data.last_node_id;
184192

185-
buffer.visited.visit_edge(link_id, visited_state);
186-
buffer.points.clear();
187-
buffer.points.push(start_data.begin);
193+
visited.visit_edge(link_id, visited_state);
194+
points.clear();
195+
points.push(start_data.begin);
188196

189197
// Find a closed tour
190198
while node_id != last_node_id {
191-
link_id = GraphUtil::next_link(
192-
self.links,
193-
self.nodes,
194-
link_id,
195-
node_id,
196-
clockwise,
197-
&buffer.visited,
198-
);
199+
link_id = GraphUtil::next_link(self.links, self.nodes, link_id, node_id, clockwise, &visited);
199200

200201
let link = unsafe {
201202
// Safety: `link_id` is always derived from a previous in-bounds index or
202203
// from `find_left_top_link`, so it remains in `0..self.links.len()`.
203204
self.links.get_unchecked(link_id)
204205
};
205-
node_id = buffer.points.push_node_and_get_other(link, node_id);
206+
node_id = points.push_node_and_get_other(link, node_id);
206207

207-
buffer.visited.visit_edge(link_id, visited_state);
208+
visited.visit_edge(link_id, visited_state);
208209
}
209210
}
210211

@@ -242,7 +243,13 @@ impl OverlayGraph<'_> {
242243
let direction = is_hole == clockwise;
243244
let start_data = StartPathData::new(direction, link, left_top_link);
244245

245-
self.find_contour(&start_data, direction, visited_state, buffer);
246+
self.find_contour(
247+
&start_data,
248+
direction,
249+
visited_state,
250+
&mut buffer.visited,
251+
&mut buffer.points,
252+
);
246253
let (is_valid, _) = buffer.points.validate(
247254
self.options.min_output_area,
248255
self.options.preserve_output_collinear,

iOverlay/src/core/extract_ogc.rs

Lines changed: 167 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use crate::core::overlay_rule::OverlayRule;
99
use crate::geom::v_segment::VSegment;
1010
use alloc::vec;
1111
use 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};
1314
use i_shape::util::reserve::Reserve;
1415

1516
impl 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

Comments
 (0)