diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 3a2c6a778b..9bcab4679e 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1943,7 +1943,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte if let Fill::Gradient(gradient) = fill.clone() { let mut row = vec![TextLabel::new("").widget_instance()]; match gradient.gradient_type { - GradientType::Linear => add_blank_assist(&mut row), + GradientType::Linear | GradientType::Mesh => add_blank_assist(&mut row), GradientType::Radial => { let orientation = if (gradient.end.x - gradient.start.x).abs() > f64::EPSILON * 1e6 { gradient.end.x > gradient.start.x diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index f6b8f49be9..d77c02ed74 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -174,8 +174,14 @@ impl LayoutHolder for GradientTool { } .into() }), + RadioEntryData::new("Mesh").label("Mesh").tooltip_label(" Gradient").on_update(move |_| { + GradientToolMessage::UpdateOptions { + options: GradientOptionsUpdate::Type(GradientType::Mesh), + } + .into() + }), ]) - .selected_index(Some((self.options.gradient_type == GradientType::Radial) as u32)) + .selected_index(Some(self.options.gradient_type as u32)) .widget_instance(); let reverse_stops = IconButton::new("Reverse", 24) diff --git a/node-graph/libraries/no-std-types/src/color/color_types.rs b/node-graph/libraries/no-std-types/src/color/color_types.rs index abcb1e9e86..ab3fe1b34a 100644 --- a/node-graph/libraries/no-std-types/src/color/color_types.rs +++ b/node-graph/libraries/no-std-types/src/color/color_types.rs @@ -1101,6 +1101,20 @@ impl Color { } } +pub fn bilerp_color(c00: Color, c10: Color, c01: Color, c11: Color, u: f64, v: f64) -> Color { + let (u, v) = (u as f32, v as f32); + let w00 = (1. - u) * (1. - v); + let w10 = u * (1. - v); + let w01 = (1. - u) * v; + let w11 = u * v; + Color::from_rgbaf32_unchecked( + w00 * c00.r() + w10 * c10.r() + w01 * c01.r() + w11 * c11.r(), + w00 * c00.g() + w10 * c10.g() + w01 * c01.g() + w11 * c11.g(), + w00 * c00.b() + w10 * c10.b() + w01 * c01.b() + w11 * c11.b(), + w00 * c00.a() + w10 * c10.a() + w01 * c01.a() + w11 * c11.a(), + ) +} + #[cfg(test)] mod tests { use super::*; diff --git a/node-graph/libraries/rendering/src/render_ext.rs b/node-graph/libraries/rendering/src/render_ext.rs index df1690afd2..d48a608752 100644 --- a/node-graph/libraries/rendering/src/render_ext.rs +++ b/node-graph/libraries/rendering/src/render_ext.rs @@ -72,6 +72,7 @@ impl RenderExt for Gradient { gradient_id, start.x, start.y, radius, stop ); } + _ => (), } gradient_id diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index 6f1690ad37..37961b7205 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -2,7 +2,7 @@ use crate::render_ext::RenderExt; use crate::to_peniko::BlendModeExt; use core_types::blending::BlendMode; use core_types::bounds::{BoundingBox, RenderBoundingBox}; -use core_types::color::{Alpha, Color}; +use core_types::color::{Alpha, Color, bilerp_color}; use core_types::math::quad::Quad; use core_types::render_complexity::RenderComplexity; use core_types::table::{Table, TableRow}; @@ -17,14 +17,15 @@ use graphic_types::vector_types::subpath::Subpath; use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint}; use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign}; use graphic_types::{Artboard, Graphic}; -use kurbo::{Affine, Cap, Join, Shape}; +use kurbo::{Affine, Cap, Join, ParamCurve, Shape}; use num_traits::Zero; use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::sync::{Arc, LazyLock}; -use vector_types::gradient::GradientSpreadMethod; +use vector_types::gradient::{self, GradientSpreadMethod}; +use vector_types::vector::misc::point_to_dvec2; use vello::*; /// Cached 16x16 transparency checkerboard image data (two 8x8 cells of #ffffff and #cccccc). @@ -967,6 +968,131 @@ impl Render for Table { } } + let is_mesh_fill = matches!( + &row.element.style.fill, + Fill::Gradient(g) if g.gradient_type == GradientType::Mesh + ); + if is_mesh_fill { + // Split the mesh into 8 x 8 subgrids + let grid_num = 8; + let grid_stride_uv = 1. / grid_num as f64; + + let segments: Vec<_> = row.element.segment_iter().map(|(_, path_seg, _, _)| path_seg).collect(); + let points = row.element.point_domain.positions(); + + let top_seg = segments[0]; + let right_seg = segments[1]; + let bottom_seg = segments[2]; + let left_seg = segments[3]; + + let top_left_color = Color::RED; + let top_right_color = Color::BLUE; + let bottom_left_color = Color::GREEN; + let bottom_right_color = Color::YELLOW; + + let bilerp = |u: f64, v: f64| points[3] * (1. - u) * (1. - v) + points[2] * u * (1. - v) + points[0] * (1. - u) * v + points[1] * u * v; + + // Create the position and color info of the grid that is splitted from the original mesh + let mut grid_info: Vec> = vec![]; + + for i in 0..=grid_num { + let u = i as f64 * grid_stride_uv; + let mut grid_info_row: Vec<(DVec2, Color)> = vec![]; + + for j in 0..=grid_num { + let v = j as f64 * grid_stride_uv; + let c1_u = point_to_dvec2(bottom_seg.eval(1. - u)); + let c2_u = point_to_dvec2(top_seg.eval(u)); + let d1_v = point_to_dvec2(left_seg.eval(v)); + let d2_v = point_to_dvec2(right_seg.eval(1. - v)); + let lc = (1. - v) * c1_u + v * c2_u; + let ld = (1. - u) * d1_v + u * d2_v; + + let pos = lc + ld - bilerp(u, v); + let color = bilerp_color(bottom_left_color, bottom_right_color, top_left_color, top_right_color, u, v); + grid_info_row.push((pos, color)); + } + grid_info.push(grid_info_row); + } + + // Create poloygons using the vertex position data + let mut idx = generate_uuid(); + + for i in 0..grid_num { + for j in 0..grid_num { + let bl = grid_info[i][j]; + let tl = grid_info[i][j + 1]; + let br = grid_info[i + 1][j]; + let tr = grid_info[i + 1][j + 1]; + + // linear gradient for the bottom line + write!( + &mut render.svg_defs, + r##""##, + bl.0.x, + bl.0.y, + br.0.x, + br.0.y, + bl.1.to_rgb_hex_srgb_from_gamma(), + br.1.to_rgb_hex_srgb_from_gamma(), + ) + .unwrap(); + + // linear gradient for the top line + write!( + &mut render.svg_defs, + r##""##, + tl.0.x, + tl.0.y, + tr.0.x, + tr.0.y, + tl.1.to_rgb_hex_srgb_from_gamma(), + tr.1.to_rgb_hex_srgb_from_gamma(), + ) + .unwrap(); + + // top mask gradient + write!( + &mut render.svg_defs, + r##""##, + (bl.0.x + br.0.x) / 2., + (bl.0.y + br.0.y) / 2., + (tl.0.x + tr.0.x) / 2., + (tl.0.y + tr.0.y) / 2., + ) + .unwrap(); + + // mask + write!( + &mut render.svg_defs, + r##""##, + bl.0.x, bl.0.y, br.0.x, br.0.y, tr.0.x, tr.0.y, tl.0.x, tl.0.y, + ) + .unwrap(); + + render.leaf_tag("polygon", |attributes| { + attributes.push("points", format!("{},{} {},{} {},{} {},{}", bl.0.x, bl.0.y, br.0.x, br.0.y, tr.0.x, tr.0.y, tl.0.x, tl.0.y)); + attributes.push("fill", format!("url(#gb{idx})")); + }); + + render.leaf_tag("polygon", |attributes| { + attributes.push("points", format!("{},{} {},{} {},{} {},{}", bl.0.x, bl.0.y, br.0.x, br.0.y, tr.0.x, tr.0.y, tl.0.x, tl.0.y)); + attributes.push("fill", format!("url(#gt{idx})")); + attributes.push("mask", format!("url(#m{idx})")); + }); + + idx += 1; + } + } + + // Create a linear gradients, bottom-left -> bottom-right + + // Create a linear gradient mask from top to bottom + + // Create a linear gradient, top->left -> top->right with applying the mask + return; + } + render.leaf_tag("path", |attributes| { attributes.push("d", path.clone()); let matrix = format_transform_matrix(element_transform); @@ -1159,6 +1285,12 @@ impl Render for Table { } .into() } + // TODO!!!!!!!!!!!!!!!! + GradientType::Mesh => peniko::LinearGradientPosition { + start: to_point(start), + end: to_point(end), + } + .into(), }, extend: match gradient.spread_method { GradientSpreadMethod::Pad => peniko::Extend::Pad, @@ -1752,6 +1884,15 @@ impl Render for Table { r#"{stop_string}"# ); } + // TODO!!!!!!!!!!!!!!!!!!!!!! + GradientType::Mesh => { + let (x1, y1) = (start.x, start.y); + let (x2, y2) = (end.x, end.y); + let _ = write!( + &mut attributes.0.svg_defs, + r#"{stop_string}"# + ); + } } attributes.push("fill", format!("url('#{gradient_id}')")); diff --git a/node-graph/libraries/vector-types/src/gradient.rs b/node-graph/libraries/vector-types/src/gradient.rs index f5c241c2f0..a9642c628d 100644 --- a/node-graph/libraries/vector-types/src/gradient.rs +++ b/node-graph/libraries/vector-types/src/gradient.rs @@ -9,6 +9,7 @@ pub enum GradientType { #[default] Linear, Radial, + Mesh, } // TODO: Someday we could switch this to a Box[T] to avoid over-allocation diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index 40fb786b06..fbbe288a07 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -14,6 +14,7 @@ use kurbo::simplify::{SimplifyOptions, simplify_bezpath}; use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveArclen, PathEl, PathSeg, Shape}; use rand::{Rng, SeedableRng}; use std::collections::hash_map::DefaultHasher; +use std::vec; use vector_types::subpath::{BezierHandles, ManipulatorGroup}; use vector_types::vector::PointDomain; use vector_types::vector::algorithms::bezpath_algorithms::{self, TValue, eval_pathseg_euclidean, evaluate_bezpath, split_bezpath, tangent_on_bezpath}; @@ -26,6 +27,7 @@ use vector_types::vector::misc::{ }; use vector_types::vector::style::{Fill, Gradient, GradientStops, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt}; +use wasm_bindgen::UnwrapThrowExt; /// Implemented for types that can be converted to an iterator of vector rows. /// Used for the fill and stroke node so they can be used on `Table` or `Table`.