Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions iOverlay/src/core/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub(crate) enum VisitState {
pub struct BooleanExtractionBuffer {
pub(crate) points: Vec<IntPoint>,
pub(crate) visited: Vec<VisitState>,
pub(crate) contour_visited: Option<Vec<VisitState>>,
}

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

self.find_contour(&start_data, direction, visited_state, buffer);
self.find_contour(
&start_data,
direction,
visited_state,
&mut buffer.visited,
&mut buffer.points,
);
let (is_valid, is_modified) = buffer.points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
Expand Down Expand Up @@ -176,35 +183,29 @@ impl OverlayGraph<'_> {
start_data: &StartPathData,
clockwise: bool,
visited_state: VisitState,
buffer: &mut BooleanExtractionBuffer,
visited: &mut Vec<VisitState>,
points: &mut Vec<IntPoint>,
) {
let mut link_id = start_data.link_id;
let mut node_id = start_data.node_id;
let last_node_id = start_data.last_node_id;

buffer.visited.visit_edge(link_id, visited_state);
buffer.points.clear();
buffer.points.push(start_data.begin);
visited.visit_edge(link_id, visited_state);
points.clear();
points.push(start_data.begin);

// Find a closed tour
while node_id != last_node_id {
link_id = GraphUtil::next_link(
self.links,
self.nodes,
link_id,
node_id,
clockwise,
&buffer.visited,
);
link_id = GraphUtil::next_link(self.links, self.nodes, link_id, node_id, clockwise, &visited);

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

buffer.visited.visit_edge(link_id, visited_state);
visited.visit_edge(link_id, visited_state);
}
}

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

self.find_contour(&start_data, direction, visited_state, buffer);
self.find_contour(
&start_data,
direction,
visited_state,
&mut buffer.visited,
&mut buffer.points,
);
let (is_valid, _) = buffer.points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
Expand Down
186 changes: 167 additions & 19 deletions iOverlay/src/core/extract_ogc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use crate::core::overlay_rule::OverlayRule;
use crate::geom::v_segment::VSegment;
use alloc::vec;
use alloc::vec::Vec;
use i_shape::int::shape::IntShapes;
use i_float::int::point::IntPoint;
use i_shape::int::shape::{IntShape, IntShapes};
use i_shape::util::reserve::Reserve;

impl OverlayGraph<'_> {
Expand All @@ -20,10 +21,21 @@ impl OverlayGraph<'_> {
) -> IntShapes {
let is_main_dir_cw = self.options.output_direction == ContourDirection::Clockwise;

let mut contour_visited = if let Some(mut visited) = buffer.contour_visited.take() {
let target_len = buffer.visited.len();
if visited.len() != target_len {
visited.resize(target_len, VisitState::Skipped);
}
visited.fill(VisitState::Skipped);
visited
} else {
vec![VisitState::Skipped; buffer.visited.len()]
};

let mut shapes = Vec::new();

buffer.points.reserve_capacity(buffer.visited.len());
let mut avg_holes_count = 0;
let mut hole_count_hint = 0;

let mut link_index = 0;
while link_index < buffer.visited.len() {
Expand Down Expand Up @@ -58,26 +70,24 @@ impl OverlayGraph<'_> {
visited_state,
&mut buffer.visited,
);
avg_holes_count += 1;
hole_count_hint += 1;
continue;
}

self.find_contour(&start_data, traversal_direction, visited_state, buffer);
let (is_valid, _) = buffer.points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
);

if !is_valid {
if let Some(shape) = self.collect_shape(
&start_data,
traversal_direction,
&mut buffer.visited,
&mut contour_visited,
&mut buffer.points,
) {
shapes.push(shape);
} else {
link_index += 1;
continue;
}

let contour = buffer.points.as_slice().to_vec();
shapes.push(vec![contour]);
};
}

if avg_holes_count > 0 {
if hole_count_hint > 0 {
// Keep only hole edges; skip everything else for the second pass.
for state in buffer.visited.iter_mut() {
*state = match *state {
Expand All @@ -86,8 +96,8 @@ impl OverlayGraph<'_> {
};
}

let mut holes = Vec::with_capacity(avg_holes_count);
let mut anchors = Vec::with_capacity(avg_holes_count);
let mut holes = Vec::with_capacity(hole_count_hint);
let mut anchors = Vec::with_capacity(hole_count_hint);
let mut anchors_already_sorted = true;
link_index = 0;

Expand All @@ -112,7 +122,14 @@ impl OverlayGraph<'_> {

let start_data = StartPathData::new(is_main_dir_cw, link, left_top_link);

self.find_contour(&start_data, is_main_dir_cw, VisitState::HullVisited, buffer);
self.find_contour(
&start_data,
is_main_dir_cw,
VisitState::HullVisited,
&mut buffer.visited,
&mut buffer.points,
);

let (is_valid, is_modified) = buffer.points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
Expand Down Expand Up @@ -157,6 +174,8 @@ impl OverlayGraph<'_> {
shapes.join_sorted_holes(holes, anchors, is_main_dir_cw);
}

buffer.contour_visited = Some(contour_visited);

shapes
}

Expand Down Expand Up @@ -192,4 +211,133 @@ impl OverlayGraph<'_> {
visited.visit_edge(link_id, visited_state);
}
}

fn collect_shape(
&self,
start_data: &StartPathData,
clockwise: bool,
global_visited: &mut Vec<VisitState>,
contour_visited: &mut Vec<VisitState>,
points: &mut Vec<IntPoint>,
) -> Option<IntShape> {
let mut link_id = start_data.link_id;
let mut node_id = start_data.node_id;
let last_node_id = start_data.last_node_id;

// First, mark all edges that belong to the contour.

let mut end_link_id = start_data.link_id;

global_visited.visit_edge(link_id, VisitState::HullVisited);
contour_visited.visit_edge(link_id, VisitState::Unvisited);

let mut original_contour_len = 1;

// Find a closed tour
while node_id != last_node_id {
link_id = GraphUtil::next_link(
self.links,
self.nodes,
link_id,
node_id,
clockwise,
global_visited,
);

let link = unsafe {
// Safety: `link_id` is always derived from a previous in-bounds index or
// from `find_left_top_link`, so it remains in `0..self.links.len()`.
self.links.get_unchecked(link_id)
};

node_id = if link.a.id == node_id {
link.b.id
} else {
link.a.id
};
end_link_id = end_link_id.max(link_id);
contour_visited.visit_edge(link_id, VisitState::Unvisited);
global_visited.visit_edge(link_id, VisitState::HullVisited);
original_contour_len += 1;
}

// Revisit the contour in reverse;
// all links escape current contour are skipped in `contour_visited`.

points.reserve_capacity(original_contour_len);
self.find_contour(
&start_data,
!clockwise,
VisitState::HullVisited,
contour_visited,
points,
);

let (is_valid, _) = points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
);

let contour_len = points.len();

let mut shape = if is_valid {
let mut shape = vec![];
let contour = points.as_slice().to_vec();
shape.push(contour);
Some(shape)
} else {
None
};

if contour_len < original_contour_len {
// contour has self touches
let mut link_index = start_data.link_id;
while link_index <= end_link_id {
if contour_visited.is_visited(link_index) {
link_index += 1;
continue;
}

let left_top_link = unsafe {
// Safety: `link_index` walks 0..buffer.visited.len(), and buffer.visited.len() <= self.links.len().
GraphUtil::find_left_top_link(self.links, self.nodes, link_index, contour_visited)
};

let link = unsafe {
// Safety: `left_top_link` originates from `find_left_top_link`, which only returns
// indices in 0..self.links.len(), so this lookup cannot go out of bounds.
self.links.get_unchecked(left_top_link)
};

// Self-touch splits can only produce holes inside this contour.

let hole_start_data = StartPathData::new(clockwise, link, left_top_link);
self.find_contour(
&hole_start_data,
clockwise,
VisitState::HoleVisited,
contour_visited,
points,
);

// Hole have to belong to this shape.
if let Some(shape) = shape.as_mut() {
let (is_valid, _) = points.validate(
self.options.min_output_area,
self.options.preserve_output_collinear,
);

if !is_valid {
link_index += 1;
continue;
}

let contour = points.as_slice().to_vec();
shape.push(contour);
}
}
}

shape
}
}
Loading