1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
use crate::bind::solver::JoinHoles;
use crate::core::nearest_vector::NearestVector;
use crate::core::overlay::{ContourDirection, IntOverlayOptions};
use crate::segm::segment::SUBJ_TOP;
use crate::string::graph::StringGraph;
use crate::string::rule::StringRule;
use crate::string::split::{BinStore, Split};
use alloc::vec;
use alloc::vec::Vec;
use i_shape::int::path::{ContourExtension, IntPath};
use i_shape::int::shape::IntShapes;
impl StringGraph<'_> {
/// Extracts shapes from the graph based on the specified `StringRule`.
/// - `string_rule`: The rule used to determine how shapes are extracted.
/// # Shape Representation
/// The output is a `IntShapes`, where:
/// - The outer `Vec<IntShape>` represents a set of shapes.
/// - Each shape `Vec<IntContour>` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary.
/// - Each path `Vec<IntPoint>` is a sequence of points, forming a closed path.
///
/// Note: Outer boundary paths have a counterclockwise order, and holes have a clockwise order.
#[inline(always)]
pub fn extract_shapes(&self, string_rule: StringRule) -> IntShapes {
self.extract_shapes_custom(string_rule, Default::default())
}
/// Extracts shapes from the graph with a minimum area constraint.
/// - `string_rule`: The rule used to determine how shapes are extracted.
/// - `main_direction`: Winding direction for the **output** main (outer) contour. All hole contours will automatically use the opposite direction. Impact on **output** only!
/// - `min_area`: The minimum area that a shape must have to be included in the results. Shapes smaller than this will be excluded.
/// - Returns: A vector of `IntShape`, representing the geometric result of the applied overlay rule.
/// # Shape Representation
/// The output is a `IntShapes`, where:
/// - The outer `Vec<IntShape>` represents a set of shapes.
/// - Each shape `Vec<IntContour>` represents a collection of contours, where the first contour is the outer boundary, and all subsequent contours are holes in this boundary.
/// - Each path `Vec<IntPoint>` is a sequence of points, forming a closed path.
///
/// Note: Outer boundary paths have a **main_direction** order, and holes have an opposite to **main_direction** order.
pub fn extract_shapes_custom(&self, string_rule: StringRule, options: IntOverlayOptions) -> IntShapes {
let clockwise = options.output_direction == ContourDirection::Clockwise;
let mut fills = self.filter(string_rule);
let mut shapes = Vec::new();
let mut holes = Vec::new();
let mut contour_buffer = Vec::new();
let mut bin_store = BinStore::new();
let mut link_index = 0;
while link_index < fills.len() {
let fill = fills[link_index];
if fill == 0 {
link_index += 1;
continue;
}
let direction = fill & SUBJ_TOP == SUBJ_TOP;
let paths = self.get_paths(link_index, direction, &mut fills).split_loops(
options.min_output_area,
&mut contour_buffer,
&mut bin_store,
);
for mut path in paths.into_iter() {
let order = path.is_clockwise_ordered();
let is_hole = order == direction;
if is_hole {
if clockwise == order {
// clockwise == direction
path.reverse();
}
holes.push(path);
} else {
if clockwise != order {
path.reverse();
}
shapes.push(vec![path]);
}
}
}
shapes.join_unsorted_holes(holes, clockwise);
shapes
}
#[inline]
fn get_paths(&self, start_index: usize, clockwise: bool, fills: &mut [u8]) -> IntPath {
let start_link = unsafe {
// SAFETY: start_index originates from iterating the fills array, which mirrors links.
self.links.get_unchecked(start_index)
};
let mut link_id = start_index;
let mut node_id = start_link.b.id;
let last_node_id = start_link.a.id;
let mut path = IntPath::new();
path.push(start_link.a.point);
fills[start_index] = start_link.visit_fill(fills[start_index], start_link.a.id, clockwise);
// Find a closed tour
while node_id != last_node_id {
link_id = self.find_nearest_link_to(link_id, node_id, clockwise, fills);
let link = unsafe {
// SAFETY: link_id comes from find_nearest_link_to, which only yields valid link indices.
self.links.get_unchecked(link_id)
};
fills[link_id] = link.visit_fill(fills[link_id], node_id, clockwise);
node_id = if link.a.id == node_id {
path.push(link.a.point);
link.b.id
} else {
path.push(link.b.point);
link.a.id
};
}
path
}
fn find_nearest_link_to(
&self,
target_index: usize,
node_id: usize,
clockwise: bool,
fills: &[u8],
) -> usize {
let indices = unsafe {
// SAFETY: node_id comes from an endpoint of an existing link, so it indexes nodes.
self.nodes.get_unchecked(node_id)
};
let mut is_first = true;
let mut first_index = usize::MAX;
let mut second_index = usize::MAX;
let mut pos = 0;
for (i, &link_index) in indices.iter().enumerate() {
if link_index == target_index {
continue;
}
let (link, fill) = unsafe {
// SAFETY: link_index comes from the adjacency list; fills shares the same length as links.
(
self.links.get_unchecked(link_index),
*fills.get_unchecked(link_index),
)
};
if link.is_move_possible(fill, node_id, clockwise) {
if is_first {
first_index = link_index;
is_first = false;
} else {
second_index = link_index;
pos = i;
break;
}
}
}
if first_index == usize::MAX {
let (link, fill) = unsafe {
// SAFETY: target_index is the caller's current link id, so it is within bounds for both arrays.
(
self.links.get_unchecked(target_index),
*fills.get_unchecked(target_index),
)
};
if link.is_move_possible(fill, node_id, clockwise) {
return target_index;
} else {
panic!("no move found")
}
}
if second_index == usize::MAX {
return first_index;
}
let target = unsafe {
// SAFETY: target_index is either target_index or first_index; both were validated above.
self.links.get_unchecked(target_index)
};
let (c, a) = if target.a.id == node_id {
(target.a.point, target.b.point)
} else {
(target.b.point, target.a.point)
};
// more the one vectors
let b = unsafe {
// SAFETY: first_index came from indices, so it indexes links.
self.links.get_unchecked(first_index)
}
.other(node_id)
.point;
let mut vector_solver = NearestVector::new(c, a, b, first_index, clockwise);
// add second vector
let point = unsafe {
// SAFETY: second_index is also sourced from indices, matching links' bounds.
self.links.get_unchecked(second_index)
}
.other(node_id)
.point;
vector_solver.add(point, second_index);
// check the rest vectors
for &link_index in indices.iter().skip(pos + 1) {
let (link, fill) = unsafe {
// SAFETY: link_index traverses the same adjacency slice; indices and arrays are aligned.
(
self.links.get_unchecked(link_index),
*fills.get_unchecked(link_index),
)
};
if link.is_move_possible(fill, node_id, clockwise) {
let p = link.other(node_id).point;
vector_solver.add(p, link_index);
}
}
vector_solver.best_id
}
}
#[cfg(test)]
mod tests {
use crate::core::fill_rule::FillRule;
use crate::string::overlay::StringOverlay;
use crate::string::rule::StringRule;
use alloc::vec;
use i_float::int::point::IntPoint;
#[test]
fn test_0() {
let paths = vec![vec![
IntPoint::new(-10, 10),
IntPoint::new(-10, -10),
IntPoint::new(10, -10),
IntPoint::new(10, 10),
]];
let window = vec![
IntPoint::new(-5, -5),
IntPoint::new(-5, 5),
IntPoint::new(5, 5),
IntPoint::new(5, -5),
];
let mut overlay = StringOverlay::with_shape(&paths);
overlay.add_string_contour(&window);
let graph = overlay.build_graph_view(FillRule::NonZero).unwrap();
let r = graph.extract_shapes_custom(StringRule::Slice, Default::default());
assert_eq!(r.len(), 2);
}
}