Skip to content

Commit ebb7e11

Browse files
authored
Add a workaround for flickering vector mesh rendering produced by the Boolean Operation node (#3884)
* Add a workaround for flickering vector mesh rendering produced by the Boolean Operation node * Code review feedback
1 parent 992dd06 commit ebb7e11

File tree

2 files changed

+44
-13
lines changed

2 files changed

+44
-13
lines changed

node-graph/libraries/rendering/src/renderer.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -854,8 +854,9 @@ impl Render for Table<Vector> {
854854
(id, mask_type, vector_row)
855855
});
856856

857-
if vector.is_branching() {
858-
for mut face_path in vector.construct_faces().filter(|face| !(face.area() < 0.0)) {
857+
let use_face_fill = vector.use_face_fill();
858+
if use_face_fill {
859+
for mut face_path in vector.construct_faces().filter(|face| face.area() >= 0.) {
859860
face_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
860861

861862
let face_d = face_path.to_svg();
@@ -917,7 +918,7 @@ impl Render for Table<Vector> {
917918
render_params.override_paint_order = can_draw_aligned_stroke && can_use_paint_order;
918919

919920
let mut style = row.element.style.clone();
920-
if needs_separate_alignment_fill || vector.is_branching() {
921+
if needs_separate_alignment_fill || use_face_fill {
921922
style.clear_fill();
922923
}
923924

@@ -929,6 +930,10 @@ impl Render for Table<Vector> {
929930
}
930931
attributes.push_val(fill_and_stroke);
931932

933+
if vector.is_branching() && !use_face_fill {
934+
attributes.push("fill-rule", "evenodd");
935+
}
936+
932937
let opacity = row.alpha_blending.opacity(render_params.for_mask);
933938
if opacity < 1. {
934939
attributes.push("opacity", opacity.to_string());
@@ -1024,10 +1029,10 @@ impl Render for Table<Vector> {
10241029
let wants_stroke_below = row.element.style.stroke().is_some_and(|s| s.paint_order == vector::style::PaintOrder::StrokeBelow);
10251030

10261031
// Closures to avoid duplicated fill/stroke drawing logic
1027-
let do_fill_path = |scene: &mut Scene, path: &kurbo::BezPath| match row.element.style.fill() {
1032+
let do_fill_path = |scene: &mut Scene, path: &kurbo::BezPath, fill_rule: peniko::Fill| match row.element.style.fill() {
10281033
Fill::Solid(color) => {
10291034
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
1030-
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, path);
1035+
scene.fill(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, path);
10311036
}
10321037
Fill::Gradient(gradient) => {
10331038
let mut stops = peniko::ColorStops::new();
@@ -1079,25 +1084,27 @@ impl Render for Table<Vector> {
10791084
Default::default()
10801085
};
10811086
let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array());
1082-
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), path);
1087+
scene.fill(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), path);
10831088
}
10841089
Fill::None => {}
10851090
};
10861091

1092+
// Branching vectors without regions (e.g. mesh grids) need face-by-face fill rendering.
1093+
let use_face_fill = row.element.use_face_fill();
10871094
let do_fill = |scene: &mut Scene| {
1088-
if row.element.is_branching() {
1089-
// For branching paths, fill each face separately
1090-
for mut face_path in row.element.construct_faces().filter(|face| !(face.area() < 0.0)) {
1095+
if use_face_fill {
1096+
for mut face_path in row.element.construct_faces().filter(|face| face.area() >= 0.) {
10911097
face_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
10921098
let mut kurbo_path = kurbo::BezPath::new();
10931099
for element in face_path {
10941100
kurbo_path.push(element);
10951101
}
1096-
do_fill_path(scene, &kurbo_path);
1102+
do_fill_path(scene, &kurbo_path, peniko::Fill::NonZero);
10971103
}
1104+
} else if row.element.is_branching() {
1105+
do_fill_path(scene, &path, peniko::Fill::EvenOdd);
10981106
} else {
1099-
// Simple fill of the entire path
1100-
do_fill_path(scene, &path);
1107+
do_fill_path(scene, &path, peniko::Fill::NonZero);
11011108
}
11021109
};
11031110

node-graph/libraries/vector-types/src/vector/vector_attributes.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,10 @@ impl FaceSideSet {
710710
self.set.insert(self.index(side));
711711
}
712712

713+
fn remove(&mut self, side: FaceSide) {
714+
self.set.set(self.index(side), false);
715+
}
716+
713717
fn contains(&self, side: FaceSide) -> bool {
714718
self.set.contains(self.index(side))
715719
}
@@ -1102,6 +1106,19 @@ impl<Upstream> Vector<Upstream> {
11021106
(0..self.point_domain.len()).any(|point_index| self.segment_domain.connected_count(point_index) > 2)
11031107
}
11041108

1109+
pub fn has_regions(&self) -> bool {
1110+
!self.region_domain.id.is_empty()
1111+
}
1112+
1113+
/// Determines if face-by-face fill rendering should be used.
1114+
/// Branching vectors without regions (e.g. mesh grids) need face-by-face fill rendering.
1115+
/// Branching vectors with regions (e.g. boolean operation results) use even-odd fill
1116+
/// on the main stroke path instead, since face decomposition can't determine which
1117+
/// bounded faces should vs. shouldn't be filled in boolean results.
1118+
pub fn use_face_fill(&self) -> bool {
1119+
self.is_branching() && !self.has_regions()
1120+
}
1121+
11051122
pub fn construct_faces(&self) -> FaceIterator<'_, Upstream> {
11061123
let mut adjacency: Vec<Vec<FaceSide>> = vec![Vec::new(); self.point_domain.len()];
11071124
for (segment_index, (&start, &end)) in self.segment_domain.start_point.iter().zip(&self.segment_domain.end_point).enumerate() {
@@ -1134,7 +1151,14 @@ impl<Upstream> Vector<Upstream> {
11341151
if seen.contains(side) {
11351152
continue;
11361153
}
1137-
if (self.construct_face(&adjacency, side, &mut faces, &mut seen)).is_none() {
1154+
if self.construct_face(&adjacency, side, &mut faces, &mut seen).is_none() {
1155+
// Undo `seen` markings for sides added during this failed face construction,
1156+
// so they remain available for future face constructions starting from different sides.
1157+
if let Some(&last_start) = faces.face_start.last() {
1158+
for &failed_side in &faces.sides[last_start..] {
1159+
seen.remove(failed_side);
1160+
}
1161+
}
11381162
faces.backtrack();
11391163
}
11401164
}

0 commit comments

Comments
 (0)