From 226b96260cbc175435e93f4ba7900973ea7a38d4 Mon Sep 17 00:00:00 2001 From: Ezbaze <68749104+Ezbaze@users.noreply.github.com> Date: Sun, 27 Aug 2023 22:22:09 +0100 Subject: [PATCH] New nodes: shape/curve primitives (#1389) * Add new Primitive Shape/Curve Nodes * Elipse Node and Debug * N-input Spline node * Debuging * Debug * fmt * remov debug * Changes from code review * Debug: Empty Spline Input * Changes from code review * Fix spelling of ellipse * Rename polygon to regular polygon --------- Co-authored-by: Keavon Chambers --- .../document_node_types.rs | 85 ++++++++++++++++- .../node_properties.rs | 79 ++++++++++++++++ .../tool/tool_messages/polygon_tool.rs | 2 +- libraries/bezier-rs/src/subpath/core.rs | 8 +- .../gcore/src/vector/generator_nodes.rs | 91 +++++++++++++++++-- node-graph/graph-craft/src/document/value.rs | 5 + .../interpreted-executor/src/node_registry.rs | 8 +- 7 files changed, 263 insertions(+), 15 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 280c9d03..f3d360e9 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -1915,11 +1915,90 @@ fn static_nodes() -> Vec { }, (*IMAGINATE_NODE).clone(), DocumentNodeType { - name: "Unit Circle Generator", + name: "Circle", category: "Vector", - identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::UnitCircleGenerator"), - inputs: vec![DocumentInputType::none()], + identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::CircleGenerator<_>"), + inputs: vec![DocumentInputType::none(), DocumentInputType::value("Radius", TaggedValue::F32(50.), false)], outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::circle_properties, + ..Default::default() + }, + DocumentNodeType { + name: "Ellipse", + category: "Vector", + identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::EllipseGenerator<_, _>"), + inputs: vec![ + DocumentInputType::none(), + DocumentInputType::value("Radius X", TaggedValue::F32(50.), false), + DocumentInputType::value("Radius Y", TaggedValue::F32(25.), false), + ], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::ellipse_properties, + ..Default::default() + }, + DocumentNodeType { + name: "Rectangle", + category: "Vector", + identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::RectangleGenerator<_, _>"), + inputs: vec![ + DocumentInputType::none(), + DocumentInputType::value("Size X", TaggedValue::F32(100.), false), + DocumentInputType::value("Size Y", TaggedValue::F32(100.), false), + ], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::rectangle_properties, + ..Default::default() + }, + DocumentNodeType { + name: "Regular Polygon", + category: "Vector", + identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::RegularPolygonGenerator<_, _>"), + inputs: vec![ + DocumentInputType::none(), + DocumentInputType::value("Sides", TaggedValue::U32(6), false), + DocumentInputType::value("Radius", TaggedValue::F32(50.), false), + ], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::regular_polygon_properties, + ..Default::default() + }, + DocumentNodeType { + name: "Star", + category: "Vector", + identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::StarGenerator<_, _, _>"), + inputs: vec![ + DocumentInputType::none(), + DocumentInputType::value("Sides", TaggedValue::U32(5), false), + DocumentInputType::value("Radius", TaggedValue::F32(50.), false), + DocumentInputType::value("Inner Radius", TaggedValue::F32(25.), false), + ], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::star_properties, + ..Default::default() + }, + DocumentNodeType { + name: "Line", + category: "Vector", + identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::LineGenerator<_, _>"), + inputs: vec![ + DocumentInputType::none(), + DocumentInputType::value("Start", TaggedValue::DVec2(DVec2::new(0., -50.)), false), + DocumentInputType::value("End", TaggedValue::DVec2(DVec2::new(0., 50.)), false), + ], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::line_properties, + ..Default::default() + }, + DocumentNodeType { + name: "Spline", + category: "Vector", + identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::SplineGenerator<_>"), + inputs: vec![ + DocumentInputType::none(), + DocumentInputType::value("Points", TaggedValue::VecDVec2(vec![DVec2::new(0., -50.), DVec2::new(25., 0.), DVec2::new(0., 50.)]), false), + ], + outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)], + properties: node_properties::spline_properties, ..Default::default() }, DocumentNodeType { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index 0c94a747..8e433659 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -217,6 +217,36 @@ fn vec_f32_input(document_node: &DocumentNode, node_id: NodeId, index: usize, na } widgets } + +fn vec_dvec2_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_props: TextInput, blank_assist: bool) -> Vec { + let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Color, blank_assist); + + let from_string = |string: &str| { + string + .split(|c: char| !c.is_alphanumeric() && !matches!(c, '.' | '+' | '-')) + .filter(|x| !x.is_empty()) + .map(|x| x.parse::().ok()) + .collect::>>() + .map(|numbers| numbers.chunks_exact(2).map(|vals| DVec2::new(vals[0], vals[1])).collect()) + .map(TaggedValue::VecDVec2) + }; + + if let NodeInput::Value { + tagged_value: TaggedValue::VecDVec2(x), + exposed: false, + } = &document_node.inputs[index] + { + widgets.extend_from_slice(&[ + Separator::new(SeparatorType::Unrelated).widget_holder(), + text_props + .value(x.iter().map(|v| format!("({}, {})", v.x, v.y)).collect::>().join(", ")) + .on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index)) + .widget_holder(), + ]) + } + widgets +} + fn font_inputs(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> (Vec, Option>) { let mut first_widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let mut second_widgets = None; @@ -1139,6 +1169,55 @@ pub fn modulo_properties(document_node: &DocumentNode, node_id: NodeId, _context vec![operand("Modulo", 1)] } +pub fn circle_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + vec![LayoutGroup::Row { + widgets: number_widget(document_node, node_id, 1, "Radius", NumberInput::default(), true), + }] +} + +pub fn ellipse_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + let operand = |name: &str, index| { + let widgets = number_widget(document_node, node_id, index, name, NumberInput::default(), true); + + LayoutGroup::Row { widgets } + }; + vec![operand("Radius X", 1), operand("Radius Y", 2)] +} + +pub fn rectangle_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + let operand = |name: &str, index| { + let widgets = number_widget(document_node, node_id, index, name, NumberInput::default(), true); + + LayoutGroup::Row { widgets } + }; + vec![operand("Size X", 1), operand("Size Y", 2)] +} + +pub fn regular_polygon_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + let points = number_widget(document_node, node_id, 1, "Points", NumberInput::default().min(3.), true); + let radius = number_widget(document_node, node_id, 2, "Radius", NumberInput::default(), true); + + vec![LayoutGroup::Row { widgets: points }, LayoutGroup::Row { widgets: radius }] +} + +pub fn star_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + let points = number_widget(document_node, node_id, 1, "Points", NumberInput::default().min(2.), true); + let radius = number_widget(document_node, node_id, 2, "Radius", NumberInput::default(), true); + let inner_radius = number_widget(document_node, node_id, 3, "Inner Radius", NumberInput::default(), true); + + vec![LayoutGroup::Row { widgets: points }, LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: inner_radius }] +} + +pub fn line_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + let operand = |name: &str, index| vec2_widget(document_node, node_id, index, name, "X", "Y", "px", add_blank_assist); + vec![operand("Start", 1), operand("End", 2)] +} +pub fn spline_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + vec![LayoutGroup::Row { + widgets: vec_dvec2_input(document_node, node_id, 1, "Points", TextInput::default().centered(true), true), + }] +} + pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { let translation_assist = |widgets: &mut Vec| { let pivot_index = 5; diff --git a/editor/src/messages/tool/tool_messages/polygon_tool.rs b/editor/src/messages/tool/tool_messages/polygon_tool.rs index f7cbe911..b3afca51 100644 --- a/editor/src/messages/tool/tool_messages/polygon_tool.rs +++ b/editor/src/messages/tool/tool_messages/polygon_tool.rs @@ -265,7 +265,7 @@ impl Fsm for PolygonToolFsmState { let subpath = match tool_options.primitive_shape_type { PrimitiveShapeType::Polygon => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.), - PrimitiveShapeType::Star => bezier_rs::Subpath::new_regular_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5), + PrimitiveShapeType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5), }; graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses); diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index 288e1cf7..3abf507a 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -235,8 +235,8 @@ impl Subpath { Self::from_anchors(anchor_positions, true) } - /// Constructs a regular star polygon (n-star). See [new_regular_polygon], but with interspersed vertices at an `inner_radius`. - pub fn new_regular_star_polygon(center: DVec2, sides: u64, radius: f64, inner_radius: f64) -> Self { + /// Constructs a star polygon (n-star). See [new_regular_polygon], but with interspersed vertices at an `inner_radius`. + pub fn new_star_polygon(center: DVec2, sides: u64, radius: f64, inner_radius: f64) -> Self { let anchor_positions = (0..sides * 2).map(|i| { let angle = (i as f64) * 0.5 * std::f64::consts::TAU / (sides as f64); let center = center + DVec2::ONE * radius; @@ -254,6 +254,10 @@ impl Subpath { /// Construct a cubic spline from a list of points. /// Based on . pub fn new_cubic_spline(points: Vec) -> Self { + if points.is_empty() { + return Self::new(Vec::new(), false); + } + // Number of points = number of points to find handles for let len_points = points.len(); diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index bd2d6b26..a4e223a9 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -6,19 +6,94 @@ use bezier_rs::Subpath; use glam::DVec2; -pub struct UnitCircleGenerator; +#[derive(Debug, Clone, Copy)] +pub struct CircleGenerator { + radius: Radius, +} -#[node_macro::node_fn(UnitCircleGenerator)] -fn unit_circle(_input: ()) -> VectorData { - super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE)) +#[node_macro::node_fn(CircleGenerator)] +fn circle_generator(_input: (), radius: f32) -> VectorData { + let radius: f64 = radius.into(); + super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))) } #[derive(Debug, Clone, Copy)] -pub struct UnitSquareGenerator; +pub struct EllipseGenerator { + radius_x: RadiusX, + radius_y: RadiusY, +} -#[node_macro::node_fn(UnitSquareGenerator)] -fn unit_square(_input: ()) -> VectorData { - super::VectorData::from_subpaths(vec![Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE)]) +#[node_macro::node_fn(EllipseGenerator)] +fn ellipse_generator(_input: (), radius_x: f32, radius_y: f32) -> VectorData { + let radius = DVec2::new(radius_x as f64, radius_y as f64); + let corner1 = -radius; + let corner2 = radius; + super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2)) +} + +#[derive(Debug, Clone, Copy)] +pub struct RectangleGenerator { + size_x: SizeX, + size_y: SizeY, +} + +#[node_macro::node_fn(RectangleGenerator)] +fn square_generator(_input: (), size_x: f32, size_y: f32) -> VectorData { + let size = DVec2::new(size_x as f64, size_y as f64); + let corner1 = -size / 2.; + let corner2 = size / 2.; + + super::VectorData::from_subpaths(vec![Subpath::new_rect(corner1, corner2)]) +} + +#[derive(Debug, Clone, Copy)] +pub struct RegularPolygonGenerator { + points: Points, + radius: Radius, +} + +#[node_macro::node_fn(RegularPolygonGenerator)] +fn regular_polygon_generator(_input: (), points: u32, radius: f32) -> VectorData { + let points = points.into(); + let radius: f64 = (radius * 2.).into(); + super::VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)) +} + +#[derive(Debug, Clone, Copy)] +pub struct StarGenerator { + points: Points, + radius: Radius, + inner_radius: InnerRadius, +} + +#[node_macro::node_fn(StarGenerator)] +fn star_generator(_input: (), points: u32, radius: f32, inner_radius: f32) -> VectorData { + let points = points.into(); + let diameter: f64 = (radius * 2.).into(); + let inner_diameter = (inner_radius * 2.).into(); + + super::VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)) +} + +#[derive(Debug, Clone, Copy)] +pub struct LineGenerator { + pos_1: Pos1, + pos_2: Pos2, +} + +#[node_macro::node_fn(LineGenerator)] +fn line_generator(_input: (), pos_1: DVec2, pos_2: DVec2) -> VectorData { + super::VectorData::from_subpaths(vec![Subpath::new_line(pos_1, pos_2)]) +} + +#[derive(Debug, Clone, Copy)] +pub struct SplineGenerator { + positions: Positions, +} + +#[node_macro::node_fn(SplineGenerator)] +fn spline_generator(_input: (), positions: Vec) -> VectorData { + super::VectorData::from_subpaths(vec![Subpath::new_cubic_spline(positions)]) } // TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index b7951eed..0bf632b4 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -42,6 +42,7 @@ pub enum TaggedValue { Fill(graphene_core::vector::style::Fill), Stroke(graphene_core::vector::style::Stroke), VecF32(Vec), + VecDVec2(Vec), RedGreenBlue(graphene_core::raster::RedGreenBlue), NoiseType(graphene_core::raster::NoiseType), RelativeAbsolute(graphene_core::raster::RelativeAbsolute), @@ -100,6 +101,7 @@ impl Hash for TaggedValue { Self::Fill(fill) => fill.hash(state), Self::Stroke(stroke) => stroke.hash(state), Self::VecF32(vec_f32) => vec_f32.iter().for_each(|val| val.to_bits().hash(state)), + Self::VecDVec2(vec_dvec2) => vec_dvec2.iter().for_each(|val| val.to_array().iter().for_each(|x| x.to_bits().hash(state))), Self::RedGreenBlue(red_green_blue) => red_green_blue.hash(state), Self::NoiseType(noise_type) => noise_type.hash(state), Self::RelativeAbsolute(relative_absolute) => relative_absolute.hash(state), @@ -165,6 +167,7 @@ impl<'a> TaggedValue { TaggedValue::Fill(x) => Box::new(x), TaggedValue::Stroke(x) => Box::new(x), TaggedValue::VecF32(x) => Box::new(x), + TaggedValue::VecDVec2(x) => Box::new(x), TaggedValue::RedGreenBlue(x) => Box::new(x), TaggedValue::NoiseType(x) => Box::new(x), TaggedValue::RelativeAbsolute(x) => Box::new(x), @@ -233,6 +236,7 @@ impl<'a> TaggedValue { TaggedValue::Fill(_) => concrete!(graphene_core::vector::style::Fill), TaggedValue::Stroke(_) => concrete!(graphene_core::vector::style::Stroke), TaggedValue::VecF32(_) => concrete!(Vec), + TaggedValue::VecDVec2(_) => concrete!(Vec), TaggedValue::RedGreenBlue(_) => concrete!(graphene_core::raster::RedGreenBlue), TaggedValue::NoiseType(_) => concrete!(graphene_core::raster::NoiseType), TaggedValue::RelativeAbsolute(_) => concrete!(graphene_core::raster::RelativeAbsolute), @@ -288,6 +292,7 @@ impl<'a> TaggedValue { x if x == TypeId::of::() => Ok(TaggedValue::Fill(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::Stroke(*downcast(input).unwrap())), x if x == TypeId::of::>() => Ok(TaggedValue::VecF32(*downcast(input).unwrap())), + x if x == TypeId::of::>() => Ok(TaggedValue::VecDVec2(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::RedGreenBlue(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::NoiseType(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::RelativeAbsolute(*downcast(input).unwrap())), diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index cb0e0f95..1bdb38f1 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -630,7 +630,13 @@ fn node_registry() -> HashMap, input: VectorData, params: [DVec2, u32]), register_node!(graphene_core::vector::BoundingBoxNode, input: VectorData, params: []), register_node!(graphene_core::vector::CircularRepeatNode<_, _, _>, input: VectorData, params: [f32, f32, u32]), - register_node!(graphene_core::vector::generator_nodes::UnitCircleGenerator, input: (), params: []), + register_node!(graphene_core::vector::generator_nodes::CircleGenerator<_>, input: (), params: [f32]), + register_node!(graphene_core::vector::generator_nodes::EllipseGenerator<_, _>, input: (), params: [f32, f32]), + register_node!(graphene_core::vector::generator_nodes::RectangleGenerator<_, _>, input: (), params: [f32, f32]), + register_node!(graphene_core::vector::generator_nodes::RegularPolygonGenerator<_, _>, input: (), params: [u32, f32]), + register_node!(graphene_core::vector::generator_nodes::StarGenerator<_, _, _>, input: (), params: [u32, f32, f32]), + register_node!(graphene_core::vector::generator_nodes::LineGenerator<_, _>, input: (), params: [DVec2, DVec2]), + register_node!(graphene_core::vector::generator_nodes::SplineGenerator<_>, input: (), params: [Vec]), register_node!( graphene_core::vector::generator_nodes::PathGenerator<_>, input: Vec>,