From e1a1b1101af69bae35d4c63656d57bd93d4b972a Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Sun, 29 Jun 2025 13:23:03 +0530 Subject: [PATCH 1/7] made spiral node --- .../transform_layer_message_handler.rs | 2 +- libraries/bezier-rs/src/subpath/core.rs | 51 ++++++++++++++++++- libraries/bezier-rs/src/utils.rs | 43 ++++++++++++++++ .../gcore/src/vector/generator_nodes.rs | 24 +++++++++ 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 287faf00d1..994e47d636 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -176,7 +176,7 @@ impl MessageHandler> for TransformLayer return; } - if !using_path_tool { + if !using_path_tool || !using_shape_tool { *selected.pivot = selected.mean_average_of_pivots(); self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot); self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(selected.mean_average_of_pivots()); diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index a18550db6d..b38039e9ef 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -1,6 +1,6 @@ use super::*; -use crate::consts::*; use crate::utils::format_point; +use crate::{BezierHandles, consts::*}; use glam::DVec2; use std::fmt::Write; @@ -271,6 +271,55 @@ impl Subpath { ) } + pub fn spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a + b * theta; + DVec2::new(r * theta.cos(), -r * theta.sin()) + } + + fn spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a + b * theta; + let dx = b * theta.cos() - r * theta.sin(); + let dy = b * theta.sin() + r * theta.cos(); + DVec2::new(dx, -dy).normalize() + } + + pub fn wrap_angle(angle: f64) -> f64 { + (angle + std::f64::consts::PI).rem_euclid(2.0 * std::f64::consts::PI) - std::f64::consts::PI + } + + fn spiral_arc_length(theta: f64, a: f64, b: f64) -> f64 { + let r = a + b * theta; + let sqrt_term = (r * r + b * b).sqrt(); + (r * sqrt_term + b * b * ((r + sqrt_term).ln())) / (2.0 * b) + } + + pub fn generate_equal_arc_bezier_spiral2(a: f64, b: f64, turns: u32, delta_theta: f64, angle_offset: f64) -> Self { + let mut manipulator_groups = Vec::new(); + let mut prev_in_handle = None; + let mut theta = 0.; + let theta_end = angle_offset + turns as f64 * std::f64::consts::TAU; + + while theta < theta_end { + let theta_next = f64::min(theta + delta_theta, theta_end); + let p0 = Self::spiral_point(theta, a, b); + let p3 = Self::spiral_point(theta_next, a, b); + let t0 = Self::spiral_tangent(theta, a, b); + let t1 = Self::spiral_tangent(theta_next, a, b); + + let arc_len = Self::spiral_arc_length(theta_next, a, b) - Self::spiral_arc_length(theta, a, b); + let d = arc_len / 3.0; + + let p1 = p0 + d * t0; + let p2 = p3 - d * t1; + + manipulator_groups.push(ManipulatorGroup::new(p0, prev_in_handle, Some(p1))); + prev_in_handle = Some(p2); + theta = theta_next; + } + + Self::new(manipulator_groups, false) + } + /// Constructs an ellipse with `corner1` and `corner2` as the two corners of the bounding box. pub fn new_ellipse(corner1: DVec2, corner2: DVec2) -> Self { let size = (corner1 - corner2).abs(); diff --git a/libraries/bezier-rs/src/utils.rs b/libraries/bezier-rs/src/utils.rs index 2e80b80e8e..ff1ca6db0a 100644 --- a/libraries/bezier-rs/src/utils.rs +++ b/libraries/bezier-rs/src/utils.rs @@ -302,6 +302,49 @@ pub fn format_point(svg: &mut String, prefix: &str, x: f64, y: f64) -> std::fmt: Ok(()) } +pub fn spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a + b * theta; + DVec2::new(r * theta.cos(), -r * theta.sin()) +} + +pub fn spiral_tangent(theta: f64, b: f64) -> DVec2 { + let dx = b * (theta.cos() - theta * theta.sin()); + let dy = b * (theta.sin() + theta * theta.cos()); + DVec2::new(dx, dy).normalize() +} + +pub fn wrap_angle(angle: f64) -> f64 { + (angle + std::f64::consts::PI).rem_euclid(2.0 * std::f64::consts::PI) - std::f64::consts::PI +} + +pub fn bezier_point(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> DVec2 { + let u = 1.0 - t; + p0 * u * u * u + p1 * 3.0 * u * u * t + p2 * 3.0 * u * t * t + p3 * t * t * t +} + +pub fn bezier_derivative(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> DVec2 { + let u = 1.0 - t; + -3.0 * u * u * p0 + 3.0 * (u * u - 2.0 * u * t) * p1 + 3.0 * (2.0 * u * t - t * t) * p2 + 3.0 * t * t * p3 +} + +pub fn esq_for_d(p0: DVec2, t0: DVec2, p3: DVec2, t1: DVec2, theta0: f64, theta1: f64, d: f64, a: f64, b: f64, samples: usize) -> f64 { + let p1 = p0 + d * t0; + let p2 = p3 - d * t1; + let mut total = 0.0; + for i in 1..samples { + let t = i as f64 / samples as f64; + let bez = bezier_point(p0, p1, p2, p3, t); + let bez_d = bezier_derivative(p0, p1, p2, p3, t); + let bez_angle = bez_d.y.atan2(bez_d.x); + + let theta = theta0 + (theta1 - theta0) * t; + let spiral_angle = theta; + let diff = wrap_angle(bez_angle - spiral_angle); + total += diff * diff * bez_d.length(); + } + total / samples as f64 +} + #[cfg(test)] mod tests { use super::*; diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index adab1ce531..ff8dff507d 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -1,3 +1,5 @@ +use std::f64::consts::{FRAC_PI_4, FRAC_PI_8, TAU}; + use super::misc::{ArcType, AsU64, GridType}; use super::{PointId, SegmentId, StrokeId}; use crate::Ctx; @@ -65,6 +67,28 @@ fn arc( ))) } +#[node_macro::node(category("Vector: Shape"))] +fn spiral( + _: impl Ctx, + _primary: (), + #[default(1.)] inner_radius: f64, + #[default(1.)] tightness: f64, + #[default(6)] + #[hard_min(1.)] + turns: u32, + #[default(0.)] + #[range((0., 360.))] + angle_offset: f64, +) -> VectorDataTable { + VectorDataTable::new(VectorData::from_subpath(Subpath::generate_equal_arc_bezier_spiral2( + inner_radius, + tightness, + turns, + FRAC_PI_4, + angle_offset.to_radians(), + ))) +} + #[node_macro::node(category("Vector: Shape"))] fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable { let radius = DVec2::new(radius_x, radius_y); From 2b819c11b9b8757bb52d4092d1934fa6b155022a Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Mon, 30 Jun 2025 15:26:58 +0530 Subject: [PATCH 2/7] number of turns in decimal and arc-angle implementation --- libraries/bezier-rs/src/subpath/core.rs | 38 ++++++++++++++++--- .../gcore/src/vector/generator_nodes.rs | 7 ++-- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index b38039e9ef..cd26d13cdf 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -293,14 +293,28 @@ impl Subpath { (r * sqrt_term + b * b * ((r + sqrt_term).ln())) / (2.0 * b) } - pub fn generate_equal_arc_bezier_spiral2(a: f64, b: f64, turns: u32, delta_theta: f64, angle_offset: f64) -> Self { + fn split_cubic_bezier(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> (DVec2, DVec2, DVec2, DVec2) { + let p01 = p0.lerp(p1, t); + let p12 = p1.lerp(p2, t); + let p23 = p2.lerp(p3, t); + + let p012 = p01.lerp(p12, t); + let p123 = p12.lerp(p23, t); + + let p0123 = p012.lerp(p123, t); // final split point + + (p0, p01, p012, p0123) // First half of the Bézier + } + + pub fn generate_equal_arc_bezier_spiral2(a: f64, b: f64, turns: f64, delta_theta: f64) -> Self { let mut manipulator_groups = Vec::new(); let mut prev_in_handle = None; - let mut theta = 0.; - let theta_end = angle_offset + turns as f64 * std::f64::consts::TAU; + let theta_end = turns * std::f64::consts::TAU; + let mut theta = 0.0; while theta < theta_end { - let theta_next = f64::min(theta + delta_theta, theta_end); + let theta_next = theta + delta_theta; + let p0 = Self::spiral_point(theta, a, b); let p3 = Self::spiral_point(theta_next, a, b); let t0 = Self::spiral_tangent(theta, a, b); @@ -312,8 +326,20 @@ impl Subpath { let p1 = p0 + d * t0; let p2 = p3 - d * t1; - manipulator_groups.push(ManipulatorGroup::new(p0, prev_in_handle, Some(p1))); - prev_in_handle = Some(p2); + let is_last_segment = theta_next >= theta_end; + if is_last_segment { + let t = (theta_end - theta) / (theta_next - theta); // t in [0, 1] + let (trim_p0, trim_p1, trim_p2, trim_p3) = Self::split_cubic_bezier(p0, p1, p2, p3, t); + + manipulator_groups.push(ManipulatorGroup::new(trim_p0, prev_in_handle, Some(trim_p1))); + prev_in_handle = Some(trim_p2); + manipulator_groups.push(ManipulatorGroup::new(trim_p3, prev_in_handle, None)); + break; + } else { + manipulator_groups.push(ManipulatorGroup::new(p0, prev_in_handle, Some(p1))); + prev_in_handle = Some(p2); + } + theta = theta_next; } diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index ff8dff507d..77eaf72341 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -75,16 +75,15 @@ fn spiral( #[default(1.)] tightness: f64, #[default(6)] #[hard_min(1.)] - turns: u32, - #[default(0.)] - #[range((0., 360.))] + turns: f64, + #[default(45.)] + #[range((1., 180.))] angle_offset: f64, ) -> VectorDataTable { VectorDataTable::new(VectorData::from_subpath(Subpath::generate_equal_arc_bezier_spiral2( inner_radius, tightness, turns, - FRAC_PI_4, angle_offset.to_radians(), ))) } From b8edd8aa4b928d7cbf1fe4d2df2e59f01593cc88 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Mon, 30 Jun 2025 17:27:25 +0530 Subject: [PATCH 3/7] logarithmic spiral --- libraries/bezier-rs/src/subpath/core.rs | 58 +++++++++++++++++++ .../gcore/src/vector/generator_nodes.rs | 22 ++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index cd26d13cdf..6d2656e0d7 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -346,6 +346,64 @@ impl Subpath { Self::new(manipulator_groups, false) } + pub fn log_spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a * (b * theta).exp(); // a * e^(bθ) + DVec2::new(r * theta.cos(), -r * theta.sin()) + } + + pub fn log_spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64) -> f64 { + let factor = (1. + b * b).sqrt(); + (a / b) * factor * ((b * theta_end).exp() - (b * theta_start).exp()) + } + + pub fn log_spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a * (b * theta).exp(); + let dx = r * (b * theta.cos() - theta.sin()); + let dy = r * (b * theta.sin() + theta.cos()); + + DVec2::new(dx, -dy).normalize() + } + + pub fn generate_logarithmic_spiral(a: f64, b: f64, turns: f64, delta_theta: f64) -> Self { + let mut manipulator_groups = Vec::new(); + let mut prev_in_handle = None; + let theta_end = turns * std::f64::consts::TAU; + + let mut theta = 0.0; + while theta < theta_end { + let theta_next = theta + delta_theta; + + let p0 = Self::log_spiral_point(theta, a, b); + let p3 = Self::log_spiral_point(theta_next, a, b); + let t0 = Self::log_spiral_tangent(theta, a, b); + let t1 = Self::log_spiral_tangent(theta_next, a, b); + + let arc_len = Self::log_spiral_arc_length(theta, theta_next, a, b); + let d = arc_len / 3.0; + + let p1 = p0 + d * t0; + let p2 = p3 - d * t1; + + let is_last_segment = theta_next >= theta_end; + if is_last_segment { + let t = (theta_end - theta) / (theta_next - theta); // t in [0, 1] + let (trim_p0, trim_p1, trim_p2, trim_p3) = Self::split_cubic_bezier(p0, p1, p2, p3, t); + + manipulator_groups.push(ManipulatorGroup::new(trim_p0, prev_in_handle, Some(trim_p1))); + prev_in_handle = Some(trim_p2); + manipulator_groups.push(ManipulatorGroup::new(trim_p3, prev_in_handle, None)); + break; + } else { + manipulator_groups.push(ManipulatorGroup::new(p0, prev_in_handle, Some(p1))); + prev_in_handle = Some(p2); + } + + theta = theta_next; + } + + Self::new(manipulator_groups, false) + } + /// Constructs an ellipse with `corner1` and `corner2` as the two corners of the bounding box. pub fn new_ellipse(corner1: DVec2, corner2: DVec2) -> Self { let size = (corner1 - corner2).abs(); diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index 77eaf72341..c37c57a881 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -68,7 +68,7 @@ fn arc( } #[node_macro::node(category("Vector: Shape"))] -fn spiral( +fn archimedean_spiral( _: impl Ctx, _primary: (), #[default(1.)] inner_radius: f64, @@ -88,6 +88,26 @@ fn spiral( ))) } +#[node_macro::node(category("Vector: Shape"))] +fn logarithmic_spiral( + _: impl Ctx, + _primary: (), + #[range((0.1, 1.))] + #[default(0.5)] + start_radius: f64, + #[range((0.1, 1.))] + #[default(0.2)] + growth: f64, + #[default(3)] + #[hard_min(0.5)] + turns: f64, + #[default(45.)] + #[range((1., 180.))] + angle_offset: f64, +) -> VectorDataTable { + VectorDataTable::new(VectorData::from_subpath(Subpath::generate_logarithmic_spiral(start_radius, growth, turns, angle_offset.to_radians()))) +} + #[node_macro::node(category("Vector: Shape"))] fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable { let radius = DVec2::new(radius_x, radius_y); From 917e79e53ddbab6b26bd6f92300a60d85f9eeaa3 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Tue, 1 Jul 2025 23:01:51 +0530 Subject: [PATCH 4/7] unified log and arc spiral into spiral node --- .../node_graph/document_node_definitions.rs | 1 + .../document/node_graph/node_properties.rs | 69 ++++++++++- libraries/bezier-rs/src/subpath/core.rs | 109 ++---------------- libraries/bezier-rs/src/subpath/structs.rs | 6 + libraries/bezier-rs/src/utils.rs | 104 ++++++++++++----- .../gcore/src/vector/generator_nodes.rs | 53 +++------ node-graph/gcore/src/vector/misc.rs | 8 ++ node-graph/graph-craft/src/document/value.rs | 1 + 8 files changed, 184 insertions(+), 167 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 63a001ba57..ae63fe737c 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -2150,6 +2150,7 @@ fn static_node_properties() -> NodeProperties { map.insert("math_properties".to_string(), Box::new(node_properties::math_properties)); map.insert("rectangle_properties".to_string(), Box::new(node_properties::rectangle_properties)); map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties)); + map.insert("spiral_properties".to_string(), Box::new(node_properties::spiral_properties)); map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties)); map.insert( "identity_properties".to_string(), 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 d074781c14..3a6b785974 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -23,9 +23,9 @@ use graphene_std::raster_types::{CPU, GPU, RasterDataTable}; use graphene_std::text::Font; use graphene_std::transform::{Footprint, ReferencePoint}; use graphene_std::vector::VectorDataTable; -use graphene_std::vector::misc::GridType; use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm}; use graphene_std::vector::misc::{CentroidType, PointSpacingType}; +use graphene_std::vector::misc::{GridType, SpiralType}; use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops}; use graphene_std::vector::style::{GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin}; use graphene_std::{GraphicGroupTable, NodeInputDecleration}; @@ -1227,6 +1227,73 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte widgets } +pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { + use graphene_std::vector::generator_nodes::spiral::*; + + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in exposure_properties: {err}"); + return Vec::new(); + } + }; + let spiral_type = enum_choice::() + .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpiralTypeInput::INDEX, true, context)) + .property_row(); + + let mut widgets = vec![spiral_type]; + + let Some(spiral_type_input) = document_node.inputs.get(SpiralTypeInput::INDEX) else { + log::warn!("A widget failed to be built because its node's input index is invalid."); + return vec![]; + }; + if let Some(&TaggedValue::SpiralType(spiral_type)) = spiral_type_input.as_non_exposed_value() { + match spiral_type { + SpiralType::Archimedean => { + let start_radius = LayoutGroup::Row { + widgets: number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default()), + }; + + let tightness = LayoutGroup::Row { + widgets: number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, TightnessInput::INDEX, true, context), NumberInput::default()), + }; + + widgets.extend([start_radius, tightness]); + } + SpiralType::Logarithmic => { + let start_radius = LayoutGroup::Row { + widgets: number_widget( + ParameterWidgetsInfo::from_index(document_node, node_id, StartRadiusInput::INDEX, true, context), + NumberInput::default().min(0.1), + ), + }; + + let growth = LayoutGroup::Row { + widgets: number_widget( + ParameterWidgetsInfo::from_index(document_node, node_id, GrowthInput::INDEX, true, context), + NumberInput::default().max(1.).min(0.1).increment_behavior(NumberInputIncrementBehavior::Add).increment_step(0.02), + ), + }; + + widgets.extend([start_radius, growth]); + } + } + } + + let turns = number_widget( + ParameterWidgetsInfo::from_index(document_node, node_id, TurnsInput::INDEX, true, context), + NumberInput::default().min(0.1), + ); + let angle_offset = number_widget( + ParameterWidgetsInfo::from_index(document_node, node_id, AngleOffsetInput::INDEX, true, context), + NumberInput::default().min(0.1).max(180.), + ); + + widgets.extend([LayoutGroup::Row { widgets: turns }, LayoutGroup::Row { widgets: angle_offset }]); + + widgets +} + pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SPACING: &str = "Use a point sampling density controlled by a distance between, or specific number of, points."; pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SEPARATION: &str = "Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled)."; pub(crate) const SAMPLE_POLYLINE_TOOLTIP_QUANTITY: &str = "Number of points to place along the path."; diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index 6d2656e0d7..bf8d2ecc9a 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -1,5 +1,5 @@ use super::*; -use crate::utils::format_point; +use crate::utils::{format_point, spiral_arc_length, spiral_point, spiral_tangent, split_cubic_bezier}; use crate::{BezierHandles, consts::*}; use glam::DVec2; use std::fmt::Write; @@ -271,100 +271,7 @@ impl Subpath { ) } - pub fn spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { - let r = a + b * theta; - DVec2::new(r * theta.cos(), -r * theta.sin()) - } - - fn spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 { - let r = a + b * theta; - let dx = b * theta.cos() - r * theta.sin(); - let dy = b * theta.sin() + r * theta.cos(); - DVec2::new(dx, -dy).normalize() - } - - pub fn wrap_angle(angle: f64) -> f64 { - (angle + std::f64::consts::PI).rem_euclid(2.0 * std::f64::consts::PI) - std::f64::consts::PI - } - - fn spiral_arc_length(theta: f64, a: f64, b: f64) -> f64 { - let r = a + b * theta; - let sqrt_term = (r * r + b * b).sqrt(); - (r * sqrt_term + b * b * ((r + sqrt_term).ln())) / (2.0 * b) - } - - fn split_cubic_bezier(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> (DVec2, DVec2, DVec2, DVec2) { - let p01 = p0.lerp(p1, t); - let p12 = p1.lerp(p2, t); - let p23 = p2.lerp(p3, t); - - let p012 = p01.lerp(p12, t); - let p123 = p12.lerp(p23, t); - - let p0123 = p012.lerp(p123, t); // final split point - - (p0, p01, p012, p0123) // First half of the Bézier - } - - pub fn generate_equal_arc_bezier_spiral2(a: f64, b: f64, turns: f64, delta_theta: f64) -> Self { - let mut manipulator_groups = Vec::new(); - let mut prev_in_handle = None; - let theta_end = turns * std::f64::consts::TAU; - - let mut theta = 0.0; - while theta < theta_end { - let theta_next = theta + delta_theta; - - let p0 = Self::spiral_point(theta, a, b); - let p3 = Self::spiral_point(theta_next, a, b); - let t0 = Self::spiral_tangent(theta, a, b); - let t1 = Self::spiral_tangent(theta_next, a, b); - - let arc_len = Self::spiral_arc_length(theta_next, a, b) - Self::spiral_arc_length(theta, a, b); - let d = arc_len / 3.0; - - let p1 = p0 + d * t0; - let p2 = p3 - d * t1; - - let is_last_segment = theta_next >= theta_end; - if is_last_segment { - let t = (theta_end - theta) / (theta_next - theta); // t in [0, 1] - let (trim_p0, trim_p1, trim_p2, trim_p3) = Self::split_cubic_bezier(p0, p1, p2, p3, t); - - manipulator_groups.push(ManipulatorGroup::new(trim_p0, prev_in_handle, Some(trim_p1))); - prev_in_handle = Some(trim_p2); - manipulator_groups.push(ManipulatorGroup::new(trim_p3, prev_in_handle, None)); - break; - } else { - manipulator_groups.push(ManipulatorGroup::new(p0, prev_in_handle, Some(p1))); - prev_in_handle = Some(p2); - } - - theta = theta_next; - } - - Self::new(manipulator_groups, false) - } - - pub fn log_spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { - let r = a * (b * theta).exp(); // a * e^(bθ) - DVec2::new(r * theta.cos(), -r * theta.sin()) - } - - pub fn log_spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64) -> f64 { - let factor = (1. + b * b).sqrt(); - (a / b) * factor * ((b * theta_end).exp() - (b * theta_start).exp()) - } - - pub fn log_spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 { - let r = a * (b * theta).exp(); - let dx = r * (b * theta.cos() - theta.sin()); - let dy = r * (b * theta.sin() + theta.cos()); - - DVec2::new(dx, -dy).normalize() - } - - pub fn generate_logarithmic_spiral(a: f64, b: f64, turns: f64, delta_theta: f64) -> Self { + pub fn new_spiral(a: f64, b: f64, turns: f64, delta_theta: f64, spiral_type: SpiralType) -> Self { let mut manipulator_groups = Vec::new(); let mut prev_in_handle = None; let theta_end = turns * std::f64::consts::TAU; @@ -373,12 +280,12 @@ impl Subpath { while theta < theta_end { let theta_next = theta + delta_theta; - let p0 = Self::log_spiral_point(theta, a, b); - let p3 = Self::log_spiral_point(theta_next, a, b); - let t0 = Self::log_spiral_tangent(theta, a, b); - let t1 = Self::log_spiral_tangent(theta_next, a, b); + let p0 = spiral_point(theta, a, b, spiral_type); + let p3 = spiral_point(theta_next, a, b, spiral_type); + let t0 = spiral_tangent(theta, a, b, spiral_type); + let t1 = spiral_tangent(theta_next, a, b, spiral_type); - let arc_len = Self::log_spiral_arc_length(theta, theta_next, a, b); + let arc_len = spiral_arc_length(theta, theta_next, a, b, spiral_type); let d = arc_len / 3.0; let p1 = p0 + d * t0; @@ -387,7 +294,7 @@ impl Subpath { let is_last_segment = theta_next >= theta_end; if is_last_segment { let t = (theta_end - theta) / (theta_next - theta); // t in [0, 1] - let (trim_p0, trim_p1, trim_p2, trim_p3) = Self::split_cubic_bezier(p0, p1, p2, p3, t); + let (trim_p0, trim_p1, trim_p2, trim_p3) = split_cubic_bezier(p0, p1, p2, p3, t); manipulator_groups.push(ManipulatorGroup::new(trim_p0, prev_in_handle, Some(trim_p1))); prev_in_handle = Some(trim_p2); diff --git a/libraries/bezier-rs/src/subpath/structs.rs b/libraries/bezier-rs/src/subpath/structs.rs index f0ef24dd7d..38a67a0836 100644 --- a/libraries/bezier-rs/src/subpath/structs.rs +++ b/libraries/bezier-rs/src/subpath/structs.rs @@ -144,3 +144,9 @@ pub enum ArcType { Closed, PieSlice, } + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub enum SpiralType { + Archimedean, + Logarithmic, +} diff --git a/libraries/bezier-rs/src/utils.rs b/libraries/bezier-rs/src/utils.rs index ff1ca6db0a..179d744376 100644 --- a/libraries/bezier-rs/src/utils.rs +++ b/libraries/bezier-rs/src/utils.rs @@ -1,5 +1,5 @@ use crate::consts::{MAX_ABSOLUTE_DIFFERENCE, STRICT_MAX_ABSOLUTE_DIFFERENCE}; -use crate::{ManipulatorGroup, Subpath}; +use crate::{ManipulatorGroup, SpiralType, Subpath}; use glam::{BVec2, DMat2, DVec2}; use std::fmt::Write; @@ -302,47 +302,91 @@ pub fn format_point(svg: &mut String, prefix: &str, x: f64, y: f64) -> std::fmt: Ok(()) } -pub fn spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { - let r = a + b * theta; +/// Returns a point on the given spiral type at angle `theta`. +pub fn spiral_point(theta: f64, a: f64, b: f64, spiral_type: SpiralType) -> DVec2 { + match spiral_type { + SpiralType::Archimedean => archimedean_spiral_point(theta, a, b), + SpiralType::Logarithmic => log_spiral_point(theta, a, b), + } +} + +/// Returns the tangent direction at angle `theta` for the given spiral type. +pub fn spiral_tangent(theta: f64, a: f64, b: f64, spiral_type: SpiralType) -> DVec2 { + match spiral_type { + SpiralType::Archimedean => archimedean_spiral_tangent(theta, a, b), + SpiralType::Logarithmic => log_spiral_tangent(theta, a, b), + } +} + +/// Computes arc length between two angles for the given spiral type. +pub fn spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64, spiral_type: SpiralType) -> f64 { + match spiral_type { + SpiralType::Archimedean => archimedean_spiral_arc_length(theta_start, theta_end, a, b), + SpiralType::Logarithmic => log_spiral_arc_length(theta_start, theta_end, a, b), + } +} + +/// Splits a cubic Bézier curve at parameter `t`, returning the first half. +pub fn split_cubic_bezier(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> (DVec2, DVec2, DVec2, DVec2) { + let p01 = p0.lerp(p1, t); + let p12 = p1.lerp(p2, t); + let p23 = p2.lerp(p3, t); + + let p012 = p01.lerp(p12, t); + let p123 = p12.lerp(p23, t); + + // final split point + let p0123 = p012.lerp(p123, t); + + // First half of the Bézier + (p0, p01, p012, p0123) +} + +/// Returns a point on a logarithmic spiral at angle `theta`. +pub fn log_spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a * (b * theta).exp(); // a * e^(bθ) DVec2::new(r * theta.cos(), -r * theta.sin()) } -pub fn spiral_tangent(theta: f64, b: f64) -> DVec2 { - let dx = b * (theta.cos() - theta * theta.sin()); - let dy = b * (theta.sin() + theta * theta.cos()); - DVec2::new(dx, dy).normalize() +/// Computes arc length along a logarithmic spiral between two angles. +pub fn log_spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64) -> f64 { + let factor = (1. + b * b).sqrt(); + (a / b) * factor * ((b * theta_end).exp() - (b * theta_start).exp()) } -pub fn wrap_angle(angle: f64) -> f64 { - (angle + std::f64::consts::PI).rem_euclid(2.0 * std::f64::consts::PI) - std::f64::consts::PI +/// Returns the tangent direction of a logarithmic spiral at angle `theta`. +pub fn log_spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a * (b * theta).exp(); + let dx = r * (b * theta.cos() - theta.sin()); + let dy = r * (b * theta.sin() + theta.cos()); + + DVec2::new(dx, -dy).normalize() } -pub fn bezier_point(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> DVec2 { - let u = 1.0 - t; - p0 * u * u * u + p1 * 3.0 * u * u * t + p2 * 3.0 * u * t * t + p3 * t * t * t +/// Returns a point on an Archimedean spiral at angle `theta`. +pub fn archimedean_spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a + b * theta; + DVec2::new(r * theta.cos(), -r * theta.sin()) } -pub fn bezier_derivative(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> DVec2 { - let u = 1.0 - t; - -3.0 * u * u * p0 + 3.0 * (u * u - 2.0 * u * t) * p1 + 3.0 * (2.0 * u * t - t * t) * p2 + 3.0 * t * t * p3 +/// Returns the tangent direction of an Archimedean spiral at angle `theta`. +pub fn archimedean_spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a + b * theta; + let dx = b * theta.cos() - r * theta.sin(); + let dy = b * theta.sin() + r * theta.cos(); + DVec2::new(dx, -dy).normalize() } -pub fn esq_for_d(p0: DVec2, t0: DVec2, p3: DVec2, t1: DVec2, theta0: f64, theta1: f64, d: f64, a: f64, b: f64, samples: usize) -> f64 { - let p1 = p0 + d * t0; - let p2 = p3 - d * t1; - let mut total = 0.0; - for i in 1..samples { - let t = i as f64 / samples as f64; - let bez = bezier_point(p0, p1, p2, p3, t); - let bez_d = bezier_derivative(p0, p1, p2, p3, t); - let bez_angle = bez_d.y.atan2(bez_d.x); +/// Computes arc length along an Archimedean spiral between two angles. +pub fn archimedean_spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64) -> f64 { + archimedean_spiral_arc_length_origin(theta_end, a, b) - archimedean_spiral_arc_length_origin(theta_start, a, b) +} - let theta = theta0 + (theta1 - theta0) * t; - let spiral_angle = theta; - let diff = wrap_angle(bez_angle - spiral_angle); - total += diff * diff * bez_d.length(); - } - total / samples as f64 +/// Computes arc length from origin to a point on Archimedean spiral at angle `theta`. +pub fn archimedean_spiral_arc_length_origin(theta: f64, a: f64, b: f64) -> f64 { + let r = a + b * theta; + let sqrt_term = (r * r + b * b).sqrt(); + (r * sqrt_term + b * b * ((r + sqrt_term).ln())) / (2.0 * b) } #[cfg(test)] diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index c37c57a881..139a58b4da 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -1,9 +1,8 @@ -use std::f64::consts::{FRAC_PI_4, FRAC_PI_8, TAU}; - use super::misc::{ArcType, AsU64, GridType}; use super::{PointId, SegmentId, StrokeId}; use crate::Ctx; use crate::registry::types::{Angle, PixelSize}; +use crate::vector::misc::SpiralType; use crate::vector::{HandleId, VectorData, VectorDataTable}; use bezier_rs::Subpath; use glam::DVec2; @@ -67,45 +66,29 @@ fn arc( ))) } -#[node_macro::node(category("Vector: Shape"))] -fn archimedean_spiral( +#[node_macro::node(category("Vector: Shape"), properties("spiral_properties"))] +fn spiral( _: impl Ctx, _primary: (), + spiral_type: SpiralType, + #[default(0.5)] start_radius: f64, #[default(1.)] inner_radius: f64, + #[default(0.2)] growth: f64, #[default(1.)] tightness: f64, - #[default(6)] - #[hard_min(1.)] - turns: f64, - #[default(45.)] - #[range((1., 180.))] - angle_offset: f64, + #[default(6)] turns: f64, + #[default(45.)] angle_offset: f64, ) -> VectorDataTable { - VectorDataTable::new(VectorData::from_subpath(Subpath::generate_equal_arc_bezier_spiral2( - inner_radius, - tightness, - turns, - angle_offset.to_radians(), - ))) -} + let (a, b) = match spiral_type { + SpiralType::Archimedean => (inner_radius, tightness), + SpiralType::Logarithmic => (start_radius, growth), + }; -#[node_macro::node(category("Vector: Shape"))] -fn logarithmic_spiral( - _: impl Ctx, - _primary: (), - #[range((0.1, 1.))] - #[default(0.5)] - start_radius: f64, - #[range((0.1, 1.))] - #[default(0.2)] - growth: f64, - #[default(3)] - #[hard_min(0.5)] - turns: f64, - #[default(45.)] - #[range((1., 180.))] - angle_offset: f64, -) -> VectorDataTable { - VectorDataTable::new(VectorData::from_subpath(Subpath::generate_logarithmic_spiral(start_radius, growth, turns, angle_offset.to_radians()))) + let spiral_type = match spiral_type { + SpiralType::Archimedean => bezier_rs::SpiralType::Archimedean, + SpiralType::Logarithmic => bezier_rs::SpiralType::Logarithmic, + }; + + VectorDataTable::new(VectorData::from_subpath(Subpath::new_spiral(a, b, turns, angle_offset.to_radians(), spiral_type))) } #[node_macro::node(category("Vector: Shape"))] diff --git a/node-graph/gcore/src/vector/misc.rs b/node-graph/gcore/src/vector/misc.rs index 4196c45d7d..41a66764bf 100644 --- a/node-graph/gcore/src/vector/misc.rs +++ b/node-graph/gcore/src/vector/misc.rs @@ -96,3 +96,11 @@ pub fn point_to_dvec2(point: Point) -> DVec2 { pub fn dvec2_to_point(value: DVec2) -> Point { Point { x: value.x, y: value.y } } + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)] +#[widget(Dropdown)] +pub enum SpiralType { + #[default] + Archimedean, + Logarithmic, +} diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 742b155557..2e5dc3b4ec 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -236,6 +236,7 @@ tagged_value! { ArcType(graphene_core::vector::misc::ArcType), MergeByDistanceAlgorithm(graphene_core::vector::misc::MergeByDistanceAlgorithm), PointSpacingType(graphene_core::vector::misc::PointSpacingType), + SpiralType(graphene_core::vector::misc::SpiralType), #[serde(alias = "LineCap")] StrokeCap(graphene_core::vector::style::StrokeCap), #[serde(alias = "LineJoin")] From 91a5c880906ecdf853f4fe8002cd30793bfca854 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 3 Jul 2025 16:52:26 +0530 Subject: [PATCH 5/7] add spiral shape in shape tool --- .../document/node_graph/node_properties.rs | 16 +- .../graph_modification_utils.rs | 4 + .../tool/common_functionality/shapes/mod.rs | 1 + .../shapes/polygon_shape.rs | 35 ++++ .../shapes/shape_utility.rs | 8 +- .../shapes/spiral_shape.rs | 121 ++++++++++++++ .../messages/tool/tool_messages/shape_tool.rs | 155 +++++++++++------- .../gcore/src/vector/generator_nodes.rs | 2 +- 8 files changed, 272 insertions(+), 70 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs 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 1ecaecb005..de7cb5507d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1251,11 +1251,17 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon match spiral_type { SpiralType::Archimedean => { let start_radius = LayoutGroup::Row { - widgets: number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default()), + widgets: number_widget( + ParameterWidgetsInfo::from_index(document_node, node_id, InnerRadiusInput::INDEX, true, context), + NumberInput::default().min(0.01), + ), }; let tightness = LayoutGroup::Row { - widgets: number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, TightnessInput::INDEX, true, context), NumberInput::default()), + widgets: number_widget( + ParameterWidgetsInfo::from_index(document_node, node_id, TightnessInput::INDEX, true, context), + NumberInput::default().unit(" px"), + ), }; widgets.extend([start_radius, tightness]); @@ -1264,14 +1270,14 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon let start_radius = LayoutGroup::Row { widgets: number_widget( ParameterWidgetsInfo::from_index(document_node, node_id, StartRadiusInput::INDEX, true, context), - NumberInput::default().min(0.1), + NumberInput::default().min(0.001), ), }; let growth = LayoutGroup::Row { widgets: number_widget( ParameterWidgetsInfo::from_index(document_node, node_id, GrowthInput::INDEX, true, context), - NumberInput::default().max(1.).min(0.1).increment_behavior(NumberInputIncrementBehavior::Add).increment_step(0.02), + NumberInput::default().max(0.5).min(0.1).increment_behavior(NumberInputIncrementBehavior::Add).increment_step(0.01), ), }; @@ -1286,7 +1292,7 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon ); let angle_offset = number_widget( ParameterWidgetsInfo::from_index(document_node, node_id, AngleOffsetInput::INDEX, true, context), - NumberInput::default().min(0.1).max(180.), + NumberInput::default().min(0.1).max(180.).unit(" °"), ); widgets.extend([LayoutGroup::Row { widgets: turns }, LayoutGroup::Row { widgets: angle_offset }]); diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 765bfc1eac..81b26b988a 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -346,6 +346,10 @@ pub fn get_star_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Star") } +pub fn get_spiral_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { + NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Spiral") +} + pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text") } diff --git a/editor/src/messages/tool/common_functionality/shapes/mod.rs b/editor/src/messages/tool/common_functionality/shapes/mod.rs index 44f40b5982..2e406db583 100644 --- a/editor/src/messages/tool/common_functionality/shapes/mod.rs +++ b/editor/src/messages/tool/common_functionality/shapes/mod.rs @@ -3,6 +3,7 @@ pub mod line_shape; pub mod polygon_shape; pub mod rectangle_shape; pub mod shape_utility; +pub mod spiral_shape; pub mod star_shape; pub use super::shapes::ellipse_shape::Ellipse; diff --git a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs index 82dcf10cfc..c37dcf2ed8 100644 --- a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs @@ -11,9 +11,11 @@ use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandle; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandleState; use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler; use crate::messages::tool::common_functionality::shapes::shape_utility::polygon_outline; +use crate::messages::tool::tool_messages::shape_tool::ShapeOptionsUpdate; use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; use graph_craft::document::NodeInput; @@ -148,4 +150,37 @@ impl Polygon { }); } } + + /// Updates the number of sides of a polygon or star node and syncs the Shape Tool UI widget accordingly. + /// Increases or decreases the side count based on user input, clamped to a minimum of 3. + pub fn update_sides(decrease: bool, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque) { + let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface)) else { + return; + }; + + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface) + .find_node_inputs("Regular Polygon") + .or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star")) + else { + return; + }; + + let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else { return }; + + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(n + 1))); + + let input: NodeInput; + if decrease { + input = NodeInput::value(TaggedValue::U32((n - 1).max(3)), false); + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices((n - 1).max(3)))); + } else { + input = NodeInput::value(TaggedValue::U32(n + 1), false); + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(n + 1))); + } + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 1), + input, + }); + } } diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index 955984150b..7bdcadf382 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -24,9 +24,10 @@ pub enum ShapeType { #[default] Polygon = 0, Star = 1, - Rectangle = 2, - Ellipse = 3, - Line = 4, + Spiral = 2, + Rectangle = 3, + Ellipse = 4, + Line = 5, } impl ShapeType { @@ -34,6 +35,7 @@ impl ShapeType { (match self { Self::Polygon => "Polygon", Self::Star => "Star", + Self::Spiral => "Spiral", Self::Rectangle => "Rectangle", Self::Ellipse => "Ellipse", Self::Line => "Line", diff --git a/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs b/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs new file mode 100644 index 0000000000..0370c07ed8 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs @@ -0,0 +1,121 @@ +use super::*; +use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; +use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate}; +use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; +use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapTypeConfiguration}; +use crate::messages::tool::tool_messages::shape_tool::ShapeOptionsUpdate; +use crate::messages::tool::tool_messages::tool_prelude::*; +use glam::DAffine2; +use graph_craft::document::NodeId; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use graphene_std::vector::misc::SpiralType; +use std::collections::VecDeque; +use std::f64::consts::TAU; + +#[derive(Default)] +pub struct Spiral; + +impl Spiral { + pub fn create_node(spiral_type: SpiralType, turns: f64) -> NodeTemplate { + let node_type = resolve_document_node_type("Spiral").expect("Spiral node can't be found"); + node_type.node_template_input_override([ + None, + Some(NodeInput::value(TaggedValue::SpiralType(spiral_type), false)), + Some(NodeInput::value(TaggedValue::F64(0.001), false)), + Some(NodeInput::value(TaggedValue::F64(0.1), false)), + None, + Some(NodeInput::value(TaggedValue::F64(0.1), false)), + Some(NodeInput::value(TaggedValue::F64(turns), false)), + ]) + } + + pub fn update_shape(document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, responses: &mut VecDeque) { + let viewport_drag_start = shape_tool_data.data.viewport_drag_start(document); + + let ignore = vec![layer]; + let snap_data = SnapData::ignore(document, ipp, &ignore); + let config = SnapTypeConfiguration::default(); + let document_mouse = document.metadata().document_to_viewport.inverse().transform_point2(ipp.mouse.position); + let snapped = shape_tool_data.data.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config); + let snapped_viewport_point = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); + shape_tool_data.data.snap_manager.update_indicator(snapped); + + let dragged_distance = (viewport_drag_start - snapped_viewport_point).length(); + + let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else { + return; + }; + + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral") else { + return; + }; + + let Some(&TaggedValue::F64(turns)) = node_inputs.get(6).unwrap().as_value() else { + return; + }; + + Self::update_radius(node_id, dragged_distance, turns, responses); + + responses.add(GraphOperationMessage::TransformSet { + layer, + transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., viewport_drag_start), + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } + + pub fn update_radius(node_id: NodeId, drag_length: f64, turns: f64, responses: &mut VecDeque) { + let archimedean_radius = drag_length / (turns * TAU); + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 5), + input: NodeInput::value(TaggedValue::F64(archimedean_radius), false), + }); + + // 0.2 is the default parameter + let factor = (0.2 * turns * TAU).exp(); + let logarithmic_radius = drag_length / factor; + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 2), + input: NodeInput::value(TaggedValue::F64(logarithmic_radius), false), + }); + } + + /// Updates the number of turns of a spiral node and recalculates its radius based on drag distance. + /// Also updates the Shape Tool's turns UI widget to reflect the change. + pub fn update_turns(drag_start: DVec2, decrease: bool, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else { + return; + }; + + let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral") else { + return; + }; + + let Some(&TaggedValue::F64(n)) = node_inputs.get(6).unwrap().as_value() else { return }; + + let input: NodeInput; + let turns: f64; + if decrease { + turns = (n - 1.).max(1.); + input = NodeInput::value(TaggedValue::F64(turns), false); + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Turns(turns))); + } else { + turns = n + 1.; + input = NodeInput::value(TaggedValue::F64(turns), false); + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Turns(turns))); + } + + let drag_length = drag_start.distance(ipp.mouse.position); + + Self::update_radius(node_id, drag_length, turns, responses); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 6), + input, + }); + } +} diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 5320000404..9cce9aa38f 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -3,26 +3,24 @@ use crate::consts::{DEFAULT_STROKE_WIDTH, SNAP_POINT_TOLERANCE}; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager; use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints}; use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon; use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays}; +use crate::messages::tool::common_functionality::shapes::spiral_shape::Spiral; use crate::messages::tool::common_functionality::shapes::star_shape::Star; use crate::messages::tool::common_functionality::shapes::{Ellipse, Line, Rectangle}; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration}; use crate::messages::tool::common_functionality::transformation_cage::{BoundingBoxManager, EdgeBool}; use crate::messages::tool::common_functionality::utility_functions::{closest_point, resize_bounds, rotate_bounds, skew_bounds, transforming_transform_cage}; -use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::document::NodeId; use graphene_std::Color; use graphene_std::renderer::Quad; -use graphene_std::vector::misc::ArcType; +use graphene_std::vector::misc::{ArcType, SpiralType}; use std::vec; #[derive(Default)] @@ -39,6 +37,8 @@ pub struct ShapeToolOptions { vertices: u32, shape_type: ShapeType, arc_type: ArcType, + spiral_type: SpiralType, + turns: f64, } impl Default for ShapeToolOptions { @@ -50,6 +50,8 @@ impl Default for ShapeToolOptions { vertices: 5, shape_type: ShapeType::Polygon, arc_type: ArcType::Open, + spiral_type: SpiralType::Archimedean, + turns: 5., } } } @@ -65,6 +67,8 @@ pub enum ShapeOptionsUpdate { Vertices(u32), ShapeType(ShapeType), ArcType(ArcType), + SpiralType(SpiralType), + Turns(f64), } #[impl_message(Message, ToolMessage, Shape)] @@ -101,6 +105,16 @@ fn create_sides_widget(vertices: u32) -> WidgetHolder { .widget_holder() } +fn create_turns_widget(turns: f64) -> WidgetHolder { + NumberInput::new(Some(turns)) + .label("Turns") + .min(0.5) + .max(1000.) + .mode(NumberInputMode::Increment) + .on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Turns(number_input.value.unwrap() as f64)).into()) + .widget_holder() +} + fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder { let entries = vec![vec![ MenuListEntry::new("Polygon") @@ -109,6 +123,9 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder { MenuListEntry::new("Star") .label("Star") .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()), + MenuListEntry::new("Spiral") + .label("Spiral") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Spiral)).into()), ]]; DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder() } @@ -123,6 +140,18 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .widget_holder() } +fn create_spiral_type_widget(spiral_type: SpiralType) -> WidgetHolder { + let entries = vec![vec![ + MenuListEntry::new("Archimedean") + .label("Archimedean") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::SpiralType(SpiralType::Archimedean)).into()), + MenuListEntry::new("Logarithmic") + .label("Logarithmic") + .on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::SpiralType(SpiralType::Logarithmic)).into()), + ]]; + DropdownInput::new(entries).selected_index(Some(spiral_type as u32)).widget_holder() +} + impl LayoutHolder for ShapeTool { fn layout(&self) -> Layout { let mut widgets = vec![]; @@ -137,6 +166,13 @@ impl LayoutHolder for ShapeTool { } } + if self.options.shape_type == ShapeType::Spiral { + widgets.push(create_spiral_type_widget(self.options.spiral_type)); + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + widgets.push(create_turns_widget(self.options.turns)); + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + } + if self.options.shape_type != ShapeType::Line { widgets.append(&mut self.options.fill.create_widgets( "Fill", @@ -203,6 +239,12 @@ impl<'a> MessageHandler> for ShapeTo ShapeOptionsUpdate::ArcType(arc_type) => { self.options.arc_type = arc_type; } + ShapeOptionsUpdate::SpiralType(spiral_type) => { + self.options.spiral_type = spiral_type; + } + ShapeOptionsUpdate::Turns(turns) => { + self.options.turns = turns; + } } self.fsm_state.update_hints(responses); @@ -327,6 +369,22 @@ impl ShapeToolData { } } } + + fn increase_no_sides_turns(&self, document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, shape_type: ShapeType, responses: &mut VecDeque, decrease: bool) { + if let Some(layer) = self.data.layer { + match shape_type { + ShapeType::Star | ShapeType::Polygon => { + Polygon::update_sides(decrease, layer, document, responses); + } + ShapeType::Spiral => { + Spiral::update_turns(self.data.viewport_drag_start(document), decrease, layer, document, ipp, responses); + } + _ => {} + } + } + + responses.add(NodeGraphMessage::RunDocumentGraph); + } } impl Fsm for ShapeToolFsmState { @@ -427,11 +485,24 @@ impl Fsm for ShapeToolFsmState { self } (ShapeToolFsmState::Ready(_), ShapeToolMessage::IncreaseSides) => { - responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(tool_options.vertices + 1))); + if matches!(tool_options.shape_type, ShapeType::Star | ShapeType::Polygon) { + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(tool_options.vertices + 1))); + } + + if matches!(tool_options.shape_type, ShapeType::Spiral) { + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Turns(tool_options.turns + 1.))); + } + self } (ShapeToolFsmState::Ready(_), ShapeToolMessage::DecreaseSides) => { - responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices((tool_options.vertices - 1).max(3)))); + if matches!(tool_options.shape_type, ShapeType::Star | ShapeType::Polygon) { + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices((tool_options.vertices - 1).max(3)))); + } + + if matches!(tool_options.shape_type, ShapeType::Spiral) { + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Turns((tool_options.turns - 1.).max(1.)))); + } self } ( @@ -468,61 +539,11 @@ impl Fsm for ShapeToolFsmState { self } (ShapeToolFsmState::Drawing(_), ShapeToolMessage::IncreaseSides) => { - if let Some(layer) = tool_data.data.layer { - let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface)) - else { - return self; - }; - - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface) - .find_node_inputs("Regular Polygon") - .or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star")) - else { - return self; - }; - - let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else { - return self; - }; - - responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(n + 1))); - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), - input: NodeInput::value(TaggedValue::U32(n + 1), false), - }); - responses.add(NodeGraphMessage::RunDocumentGraph); - } - + tool_data.increase_no_sides_turns(document, input, tool_options.shape_type, responses, false); self } (ShapeToolFsmState::Drawing(_), ShapeToolMessage::DecreaseSides) => { - if let Some(layer) = tool_data.data.layer { - let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface).or(graph_modification_utils::get_star_id(layer, &document.network_interface)) - else { - return self; - }; - - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface) - .find_node_inputs("Regular Polygon") - .or(NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star")) - else { - return self; - }; - - let Some(&TaggedValue::U32(n)) = node_inputs.get(1).unwrap().as_value() else { - return self; - }; - - responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices((n - 1).max(3)))); - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), - input: NodeInput::value(TaggedValue::U32((n - 1).max(3)), false), - }); - responses.add(NodeGraphMessage::RunDocumentGraph); - } - + tool_data.increase_no_sides_turns(document, input, tool_options.shape_type, responses, true); self } (ShapeToolFsmState::Ready(_), ShapeToolMessage::DragStart) => { @@ -578,7 +599,7 @@ impl Fsm for ShapeToolFsmState { }; match tool_data.current_shape { - ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle => tool_data.data.start(document, input), + ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Spiral => tool_data.data.start(document, input), ShapeType::Line => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); @@ -594,6 +615,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Rectangle => Rectangle::create_node(), ShapeType::Ellipse => Ellipse::create_node(), ShapeType::Line => Line::create_node(document, tool_data.data.drag_start), + ShapeType::Spiral => Spiral::create_node(tool_options.spiral_type, tool_options.turns), }; let nodes = vec![(NodeId(0), node)]; @@ -602,7 +624,7 @@ impl Fsm for ShapeToolFsmState { responses.add(Message::StartBuffer); match tool_data.current_shape { - ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star => { + ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Polygon | ShapeType::Star | ShapeType::Spiral => { responses.add(GraphOperationMessage::TransformSet { layer, transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), @@ -635,6 +657,7 @@ impl Fsm for ShapeToolFsmState { ShapeType::Line => Line::update_shape(document, input, layer, tool_data, modifier, responses), ShapeType::Polygon => Polygon::update_shape(document, input, layer, tool_data, modifier, responses), ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses), + ShapeType::Spiral => Spiral::update_shape(document, input, layer, tool_data, responses), } // Auto-panning @@ -813,6 +836,7 @@ impl Fsm for ShapeToolFsmState { responses.add(DocumentMessage::AbortTransaction); tool_data.data.cleanup(responses); tool_data.current_shape = shape; + responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(shape))); ShapeToolFsmState::Ready(shape) } @@ -837,6 +861,10 @@ impl Fsm for ShapeToolFsmState { ]), HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]), ], + ShapeType::Spiral => vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Spiral")]), + HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Turns")]), + ], ShapeType::Ellipse => vec![HintGroup(vec![ HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"), HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), @@ -867,6 +895,7 @@ impl Fsm for ShapeToolFsmState { HintInfo::keys([Key::Alt], "From Center"), HintInfo::keys([Key::Control], "Lock Angle"), ]), + _ => HintGroup(vec![]), }; common_hint_group.push(tool_hint_group); @@ -875,6 +904,10 @@ impl Fsm for ShapeToolFsmState { common_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")])); } + if matches!(shape, ShapeType::Spiral) { + common_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Turns")])); + } + HintData(common_hint_group) } ShapeToolFsmState::DraggingLineEndpoints => HintData(vec![ diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index 63a338186f..4774be4e10 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -80,7 +80,7 @@ fn spiral( _primary: (), spiral_type: SpiralType, #[default(0.5)] start_radius: f64, - #[default(1.)] inner_radius: f64, + #[default(0.)] inner_radius: f64, #[default(0.2)] growth: f64, #[default(1.)] tightness: f64, #[default(6)] turns: f64, From 1773c925ef51f39a822d95187b19f8e3e4e9ea2e Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 3 Jul 2025 17:30:01 +0530 Subject: [PATCH 6/7] fix min value and degree unit --- .../portfolio/document/node_graph/node_properties.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 de7cb5507d..018182c106 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1250,10 +1250,10 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon if let Some(&TaggedValue::SpiralType(spiral_type)) = spiral_type_input.as_non_exposed_value() { match spiral_type { SpiralType::Archimedean => { - let start_radius = LayoutGroup::Row { + let inner_radius = LayoutGroup::Row { widgets: number_widget( ParameterWidgetsInfo::from_index(document_node, node_id, InnerRadiusInput::INDEX, true, context), - NumberInput::default().min(0.01), + NumberInput::default().min(0.), ), }; @@ -1264,7 +1264,7 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon ), }; - widgets.extend([start_radius, tightness]); + widgets.extend([inner_radius, tightness]); } SpiralType::Logarithmic => { let start_radius = LayoutGroup::Row { @@ -1292,7 +1292,7 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon ); let angle_offset = number_widget( ParameterWidgetsInfo::from_index(document_node, node_id, AngleOffsetInput::INDEX, true, context), - NumberInput::default().min(0.1).max(180.).unit(" °"), + NumberInput::default().min(0.1).max(180.).unit("°"), ); widgets.extend([LayoutGroup::Row { widgets: turns }, LayoutGroup::Row { widgets: angle_offset }]); From 8870409b87f1bce733fc530bf28ab7c9dc740fba Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 Date: Thu, 10 Jul 2025 16:25:51 +0530 Subject: [PATCH 7/7] make it compile --- .../document/node_graph/node_properties.rs | 35 +++++++------------ 1 file changed, 12 insertions(+), 23 deletions(-) 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 82e2ee97dc..48b91e818d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1205,6 +1205,12 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::vector::generator_nodes::spiral::*; + let spiral_type = enum_choice::() + .for_socket(ParameterWidgetsInfo::new(node_id, SpiralTypeInput::INDEX, true, context)) + .property_row(); + + let mut widgets = vec![spiral_type]; + let document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, Err(err) => { @@ -1212,11 +1218,6 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon return Vec::new(); } }; - let spiral_type = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpiralTypeInput::INDEX, true, context)) - .property_row(); - - let mut widgets = vec![spiral_type]; let Some(spiral_type_input) = document_node.inputs.get(SpiralTypeInput::INDEX) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -1226,32 +1227,23 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon match spiral_type { SpiralType::Archimedean => { let inner_radius = LayoutGroup::Row { - widgets: number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, InnerRadiusInput::INDEX, true, context), - NumberInput::default().min(0.), - ), + widgets: number_widget(ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default().min(0.)), }; let tightness = LayoutGroup::Row { - widgets: number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, TightnessInput::INDEX, true, context), - NumberInput::default().unit(" px"), - ), + widgets: number_widget(ParameterWidgetsInfo::new(node_id, TightnessInput::INDEX, true, context), NumberInput::default().unit(" px")), }; widgets.extend([inner_radius, tightness]); } SpiralType::Logarithmic => { let start_radius = LayoutGroup::Row { - widgets: number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, StartRadiusInput::INDEX, true, context), - NumberInput::default().min(0.001), - ), + widgets: number_widget(ParameterWidgetsInfo::new(node_id, StartRadiusInput::INDEX, true, context), NumberInput::default().min(0.)), }; let growth = LayoutGroup::Row { widgets: number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, GrowthInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, GrowthInput::INDEX, true, context), NumberInput::default().max(0.5).min(0.1).increment_behavior(NumberInputIncrementBehavior::Add).increment_step(0.01), ), }; @@ -1261,12 +1253,9 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon } } - let turns = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, TurnsInput::INDEX, true, context), - NumberInput::default().min(0.1), - ); + let turns = number_widget(ParameterWidgetsInfo::new(node_id, TurnsInput::INDEX, true, context), NumberInput::default().min(0.1)); let angle_offset = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, AngleOffsetInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, AngleOffsetInput::INDEX, true, context), NumberInput::default().min(0.1).max(180.).unit("°"), );