Skip to content

Commit 9c456d3

Browse files
committed
Make it not crash
1 parent f988e99 commit 9c456d3

1 file changed

Lines changed: 54 additions & 46 deletions

File tree

  • node-graph/nodes/path-bool/src

node-graph/nodes/path-bool/src/lib.rs

Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -78,33 +78,28 @@ struct WindingNumber {
7878
}
7979

8080
impl linesweeper::topology::WindingNumber for WindingNumber {
81-
type Tag = usize;
81+
type Tag = (usize, usize);
8282

83-
fn single(tag: usize, positive: bool) -> Self {
84-
let mut elems = SmallVec::with_capacity(tag);
85-
let sign = if positive { 1 } else { -1 };
86-
for _ in 0..tag {
87-
elems.push(0);
88-
}
89-
elems.push(sign);
83+
fn single((tag, out_of): (usize, usize), positive: bool) -> Self {
84+
let mut elems = SmallVec::with_capacity(out_of);
85+
elems.resize(out_of, 0);
86+
elems[tag] = if positive { 1 } else { -1 };
9087
Self { elems }
9188
}
9289
}
9390

9491
impl std::ops::AddAssign for WindingNumber {
9592
fn add_assign(&mut self, rhs: Self) {
96-
if self.elems.len() < rhs.elems.len() {
97-
self.elems.resize(rhs.elems.len(), 0);
93+
if rhs.elems.is_empty() {
94+
return;
9895
}
99-
100-
for (me, them) in self.elems.iter_mut().zip(&rhs.elems) {
101-
*me += *them;
96+
if self.elems.is_empty() {
97+
self.elems = rhs.elems;
98+
} else {
99+
for (me, them) in self.elems.iter_mut().zip(&rhs.elems) {
100+
*me += *them;
101+
}
102102
}
103-
104-
// Removing trailing zeros normalizes the representation so that the derived
105-
// PartialEq works. (Alternatively, we could write our own PartialEq.)
106-
let trailing_zeros = self.elems.iter().rev().take_while(|w| **w == 0).count();
107-
self.elems.truncate(self.elems.len() - trailing_zeros);
108103
}
109104
}
110105

@@ -119,50 +114,63 @@ impl std::ops::Add for WindingNumber {
119114

120115
impl WindingNumber {
121116
fn is_inside(&self, op: BooleanOperation) -> bool {
117+
let is_in = |w: &i16| *w != 0;
118+
let is_out = |w: &i16| *w == 0;
122119
match op {
123-
BooleanOperation::Union => self.elems.iter().any(|w| *w != 0),
124-
BooleanOperation::SubtractFront => self.elems.first().is_some_and(|w| *w != 0) && self.elems.iter().skip(1).all(|w| *w == 0),
125-
BooleanOperation::SubtractBack => self.elems.last().is_some_and(|w| *w != 0) && self.elems.iter().rev().skip(1).all(|w| *w == 0),
126-
BooleanOperation::Intersect => self.elems.iter().all(|w| *w != 0),
127-
BooleanOperation::Difference => self.elems.iter().any(|w| *w != 0) && !self.elems.iter().all(|w| *w != 0),
120+
BooleanOperation::Union => self.elems.iter().any(is_in),
121+
BooleanOperation::SubtractFront => self.elems.first().is_some_and(is_in) && self.elems.iter().skip(1).all(is_out),
122+
BooleanOperation::SubtractBack => self.elems.last().is_some_and(is_in) && self.elems.iter().rev().skip(1).all(is_out),
123+
BooleanOperation::Intersect => !self.elems.is_empty() && self.elems.iter().all(is_in),
124+
BooleanOperation::Difference => self.elems.iter().any(is_in) && !self.elems.iter().all(is_in),
128125
}
129126
}
130127
}
131128

132-
fn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>>, boolean_operation: BooleanOperation) -> Table<Vector> {
129+
fn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>> + Clone, boolean_operation: BooleanOperation) -> Table<Vector> {
133130
const EPSILON: f64 = 1e-5;
134131
let mut table = Table::new();
135-
let mut alpha_blending = None;
136-
let mut source_node_id = None;
137132
let mut paths = Vec::new();
138-
for v in vector {
139-
if alpha_blending.is_none() {
140-
alpha_blending = Some(*v.alpha_blending);
141-
source_node_id = Some(*v.source_node_id);
142-
}
133+
let mut row = TableRow::<Vector>::default();
134+
135+
// How should we style the result? The previous implementation copied it
136+
// from either the first or the last row, depending on the boolean op.
137+
// We copy that behaviour, although I'm not sure what motivated it.
138+
let copy_from = if matches!(boolean_operation, BooleanOperation::SubtractFront) {
139+
vector.clone().next()
140+
} else {
141+
vector.clone().next_back()
142+
};
143+
if let Some(copy_from) = copy_from {
144+
row.alpha_blending = *copy_from.alpha_blending;
145+
row.source_node_id = *copy_from.source_node_id;
146+
row.element.style = copy_from.element.style.clone();
147+
row.element.upstream_data = copy_from.element.upstream_data.clone();
148+
}
143149

150+
for v in vector {
144151
paths.push(to_bez_path(v.element, *v.transform));
145152
}
146153

147-
log::warn!("boolean op {boolean_operation:?} on paths:");
154+
log::debug!("boolean op {boolean_operation:?} on paths:");
148155
for p in &paths {
149-
log::warn!("{}", p.to_svg());
156+
log::debug!("{}", p.to_svg());
150157
}
151158

152-
// unwrap: Topology::from_paths only errors on a non-closed path, and our paths are closed by construction.
153-
let top = Topology::<WindingNumber>::from_paths(paths.iter().enumerate().map(|(idx, path)| (path, idx)), EPSILON).unwrap();
159+
// unwrap: Topology::from_paths only errors on a non-closed path, and our paths are closed because `push_subpath`
160+
// always makes closed subpaths.
161+
let top = Topology::<WindingNumber>::from_paths(paths.iter().enumerate().map(|(idx, path)| (path, (idx, paths.len()))), EPSILON).unwrap();
154162
let contours = top.contours(|w| w.is_inside(boolean_operation));
155163

156-
log::warn!("boolean op output paths:");
164+
log::debug!("boolean op output paths:");
157165
for c in contours.contours() {
158-
log::warn!("{}", c.path.to_svg());
166+
log::debug!("{}", c.path.to_svg());
159167
}
160-
table.push(TableRow {
161-
element: from_bez_paths(contours.contours().map(|c| &c.path)),
162-
transform: DAffine2::IDENTITY,
163-
alpha_blending: alpha_blending.unwrap_or_default(),
164-
source_node_id: source_node_id.unwrap_or_default(),
165-
});
168+
169+
for subpath in from_bez_paths(contours.contours().map(|c| &c.path)) {
170+
row.element.append_subpath(subpath, false);
171+
}
172+
173+
table.push(row);
166174
table
167175
}
168176

@@ -275,7 +283,7 @@ fn push_subpath(path: &mut BezPath, subpath: &Subpath<PointId>, transform: DAffi
275283
let transform = Affine::new(transform.to_cols_array());
276284
let mut first = true;
277285

278-
for seg in subpath.iter() {
286+
for seg in subpath.iter_closed() {
279287
if first {
280288
first = false;
281289
path.move_to(transform * seg.start());
@@ -284,7 +292,7 @@ fn push_subpath(path: &mut BezPath, subpath: &Subpath<PointId>, transform: DAffi
284292
}
285293
}
286294

287-
fn from_bez_paths<'a>(paths: impl Iterator<Item = &'a BezPath>) -> Vector {
295+
fn from_bez_paths<'a>(paths: impl Iterator<Item = &'a BezPath>) -> Vec<Subpath<PointId>> {
288296
let mut all_subpaths = Vec::new();
289297

290298
for path in paths {
@@ -323,7 +331,7 @@ fn from_bez_paths<'a>(paths: impl Iterator<Item = &'a BezPath>) -> Vector {
323331
}
324332
}
325333

326-
Vector::from_subpaths(all_subpaths, false)
334+
all_subpaths
327335
}
328336

329337
type Path = Vec<path_bool::PathSegment>;

0 commit comments

Comments
 (0)