Skip to content

Commit e73e524

Browse files
liunicholas6Keavon
andauthored
Implement proper fill rendering for vector meshes (#3474)
* Implement branching mesh rendering for SVG * Patch mesh fill for Vello renderer * Patch tangent_at_start and tangent_at_end --------- Co-authored-by: Keavon Chambers <[email protected]>
1 parent c21ccf5 commit e73e524

File tree

5 files changed

+328
-11
lines changed

5 files changed

+328
-11
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
2222
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign};
2323
use graphic_types::{Artboard, Graphic};
2424
use kurbo::Affine;
25+
use kurbo::Shape;
2526
use num_traits::Zero;
2627
use std::collections::{HashMap, HashSet};
2728
use std::fmt::Write;
@@ -729,10 +730,10 @@ impl Render for Table<Vector> {
729730
let can_draw_aligned_stroke = path_is_closed && vector.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered());
730731
let can_use_paint_order = !(row.element.style.fill().is_none() || !row.element.style.fill().is_opaque() || mask_type == MaskType::Clip);
731732

732-
let needs_separate_fill = can_draw_aligned_stroke && !can_use_paint_order;
733+
let needs_separate_alignment_fill = can_draw_aligned_stroke && !can_use_paint_order;
733734
let wants_stroke_below = vector.style.stroke().map(|s| s.paint_order) == Some(PaintOrder::StrokeBelow);
734735

735-
if needs_separate_fill && !wants_stroke_below {
736+
if needs_separate_alignment_fill && !wants_stroke_below {
736737
render.leaf_tag("path", |attributes| {
737738
attributes.push("d", path.clone());
738739
let matrix = format_transform_matrix(element_transform);
@@ -753,7 +754,7 @@ impl Render for Table<Vector> {
753754
});
754755
}
755756

756-
let push_id = needs_separate_fill.then_some({
757+
let push_id = needs_separate_alignment_fill.then_some({
757758
let id = format!("alignment-{}", generate_uuid());
758759

759760
let mut element = row.element.clone();
@@ -770,6 +771,32 @@ impl Render for Table<Vector> {
770771
(id, mask_type, vector_row)
771772
});
772773

774+
if vector.is_branching() {
775+
for mut face_path in vector.construct_faces().filter(|face| !(face.area() < 0.0)) {
776+
face_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
777+
778+
let face_d = face_path.to_svg();
779+
render.leaf_tag("path", |attributes| {
780+
attributes.push("d", face_d.clone());
781+
let matrix = format_transform_matrix(element_transform);
782+
if !matrix.is_empty() {
783+
attributes.push("transform", matrix);
784+
}
785+
let mut style = row.element.style.clone();
786+
style.clear_stroke();
787+
let fill_only = style.render(
788+
&mut attributes.0.svg_defs,
789+
element_transform,
790+
applied_stroke_transform,
791+
bounds_matrix,
792+
transformed_bounds_matrix,
793+
render_params,
794+
);
795+
attributes.push_val(fill_only);
796+
});
797+
}
798+
}
799+
773800
render.leaf_tag("path", |attributes| {
774801
attributes.push("d", path.clone());
775802
let matrix = format_transform_matrix(element_transform);
@@ -807,7 +834,7 @@ impl Render for Table<Vector> {
807834
render_params.override_paint_order = can_draw_aligned_stroke && can_use_paint_order;
808835

809836
let mut style = row.element.style.clone();
810-
if needs_separate_fill {
837+
if needs_separate_alignment_fill || vector.is_branching() {
811838
style.clear_fill();
812839
}
813840

@@ -830,7 +857,7 @@ impl Render for Table<Vector> {
830857
});
831858

832859
// When splitting passes and stroke is below, draw the fill after the stroke.
833-
if needs_separate_fill && wants_stroke_below {
860+
if needs_separate_alignment_fill && wants_stroke_below {
834861
render.leaf_tag("path", |attributes| {
835862
attributes.push("d", path);
836863
let matrix = format_transform_matrix(element_transform);
@@ -916,10 +943,10 @@ impl Render for Table<Vector> {
916943
let wants_stroke_below = row.element.style.stroke().is_some_and(|s| s.paint_order == vector::style::PaintOrder::StrokeBelow);
917944

918945
// Closures to avoid duplicated fill/stroke drawing logic
919-
let do_fill = |scene: &mut Scene| match row.element.style.fill() {
946+
let do_fill_path = |scene: &mut Scene, path: &kurbo::BezPath| match row.element.style.fill() {
920947
Fill::Solid(color) => {
921948
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
922-
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
949+
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, path);
923950
}
924951
Fill::Gradient(gradient) => {
925952
let mut stops = peniko::ColorStops::new();
@@ -971,11 +998,28 @@ impl Render for Table<Vector> {
971998
Default::default()
972999
};
9731000
let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array());
974-
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path);
1001+
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), path);
9751002
}
9761003
Fill::None => {}
9771004
};
9781005

1006+
let do_fill = |scene: &mut Scene| {
1007+
if row.element.is_branching() {
1008+
// For branching paths, fill each face separately
1009+
for mut face_path in row.element.construct_faces().filter(|face| !(face.area() < 0.0)) {
1010+
face_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
1011+
let mut kurbo_path = kurbo::BezPath::new();
1012+
for element in face_path {
1013+
kurbo_path.push(element);
1014+
}
1015+
do_fill_path(scene, &kurbo_path);
1016+
}
1017+
} else {
1018+
// Simple fill of the entire path
1019+
do_fill_path(scene, &path);
1020+
}
1021+
};
1022+
9791023
let do_stroke = |scene: &mut Scene, width_scale: f64| {
9801024
if let Some(stroke) = row.element.style.stroke() {
9811025
let color = match stroke.color {
@@ -1090,7 +1134,7 @@ impl Render for Table<Vector> {
10901134
false => [Op::Fill, Op::Stroke], // Default
10911135
};
10921136

1093-
for operation in order {
1137+
for operation in &order {
10941138
match operation {
10951139
Op::Fill => do_fill(scene),
10961140
Op::Stroke => do_stroke(scene, 1.),

node-graph/libraries/vector-types/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ tinyvec = { workspace = true }
3131

3232
# Optional workspace dependencies
3333
serde = { workspace = true, optional = true }
34+
fixedbitset = "0.5.7"

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

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::subpath::{BezierHandles, ManipulatorGroup};
44
use crate::vector::{SegmentId, Vector};
55
use dyn_any::DynAny;
66
use glam::DVec2;
7-
use kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez};
7+
use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, QuadBez};
88
use std::ops::Sub;
99

1010
/// Represents different geometric interpretations of calculating the centroid (center of mass).
@@ -246,6 +246,93 @@ pub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> b
246246

247247
seg1_points.len() == seg2_points.len() && seg1_points.into_iter().zip(seg2_points).all(|(a, b)| cmp(a.x, b.x) && cmp(a.y, b.y))
248248
}
249+
pub trait Tangent {
250+
fn tangent_at(&self, t: f64) -> DVec2;
251+
252+
fn tangent_at_start(&self) -> DVec2 {
253+
self.tangent_at(0.0)
254+
}
255+
256+
fn tangent_at_end(&self) -> DVec2 {
257+
self.tangent_at(1.0)
258+
}
259+
}
260+
261+
trait ControlPoints {
262+
type Points: AsRef<[Point]>;
263+
fn control_points(&self) -> Self::Points;
264+
}
265+
266+
impl ControlPoints for kurbo::Line {
267+
type Points = [Point; 2];
268+
fn control_points(&self) -> Self::Points {
269+
[self.p0, self.p1]
270+
}
271+
}
272+
273+
impl ControlPoints for kurbo::QuadBez {
274+
type Points = [Point; 3];
275+
fn control_points(&self) -> Self::Points {
276+
[self.p0, self.p1, self.p2]
277+
}
278+
}
279+
280+
impl ControlPoints for kurbo::CubicBez {
281+
type Points = [Point; 4];
282+
fn control_points(&self) -> Self::Points {
283+
[self.p0, self.p1, self.p2, self.p3]
284+
}
285+
}
286+
287+
impl<T: ControlPoints + ParamCurveDeriv> Tangent for T {
288+
fn tangent_at(&self, t: f64) -> DVec2 {
289+
point_to_dvec2(self.deriv().eval(t))
290+
}
291+
292+
fn tangent_at_start(&self) -> DVec2 {
293+
let pts = self.control_points();
294+
let pts = pts.as_ref();
295+
let mut iter = pts.iter();
296+
iter.next()
297+
.and_then(|&start| iter.find(|&&p| p != start).map(|&p| DVec2 { x: p.x - start.x, y: p.y - start.y }))
298+
.unwrap_or_default()
299+
}
300+
301+
fn tangent_at_end(&self) -> DVec2 {
302+
let pts = self.control_points();
303+
let pts = pts.as_ref();
304+
let mut iter = pts.iter().rev();
305+
iter.next()
306+
.and_then(|&end| iter.find(|&&p| p != end).map(|&p| DVec2 { x: end.x - p.x, y: end.y - p.y }))
307+
.unwrap_or_default()
308+
}
309+
}
310+
311+
impl Tangent for kurbo::PathSeg {
312+
fn tangent_at(&self, t: f64) -> DVec2 {
313+
match self {
314+
PathSeg::Line(line) => line.tangent_at(t),
315+
PathSeg::Quad(quad) => quad.tangent_at(t),
316+
PathSeg::Cubic(cubic) => cubic.tangent_at(t),
317+
}
318+
}
319+
320+
fn tangent_at_start(&self) -> DVec2 {
321+
match self {
322+
PathSeg::Line(line) => line.tangent_at_start(),
323+
PathSeg::Quad(quad) => quad.tangent_at_start(),
324+
PathSeg::Cubic(cubic) => cubic.tangent_at_start(),
325+
}
326+
}
327+
328+
fn tangent_at_end(&self) -> DVec2 {
329+
match self {
330+
PathSeg::Line(line) => line.tangent_at_end(),
331+
PathSeg::Quad(quad) => quad.tangent_at_end(),
332+
PathSeg::Cubic(cubic) => cubic.tangent_at_end(),
333+
}
334+
}
335+
}
249336

250337
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
251338
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]

0 commit comments

Comments
 (0)