diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 66c8009e02..ac47f4a057 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -42,40 +42,66 @@ impl CornerRadius for [f64; 4] { /// Generates a circle shape with a chosen radius. #[node_macro::node(category("Vector: Shape"))] fn circle( - _: impl Ctx, - _primary: (), - #[unit(" px")] - #[default(50.)] - radius: f64, + _: impl Ctx, + _primary: (), + #[unit(" px")] + #[default(50.)] + radius: f64, ) -> Table { - let radius = radius.abs(); - Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))) + let radius = radius.abs(); + // 1. Create the vector + let mut circle = Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))); + + // Created the collinear_manipulators so that all handles are linked, making it easier to edit the circle as a circle instead of a 4 point shape. + let ids = circle.segment_domain.ids(); + let len = ids.len(); + for i in 0..len { + circle.colinear_manipulators.push([ + HandleId::end(ids[i]), + HandleId::primary(ids[(i + 1) % len]), + ]); + } + + Table::new_from_element(circle) } /// Generates an arc shape forming a portion of a circle which may be open, closed, or a pie slice. #[node_macro::node(category("Vector: Shape"))] fn arc( - _: impl Ctx, - _primary: (), - #[unit(" px")] - #[default(50.)] - radius: f64, - start_angle: Angle, - #[default(270.)] - #[range((0., 360.))] - sweep_angle: Angle, - arc_type: ArcType, + _: impl Ctx, + _primary: (), + #[unit(" px")] + #[default(50.)] + radius: f64, + start_angle: Angle, + #[default(270.)] + #[range((0., 360.))] + sweep_angle: Angle, + arc_type: ArcType, ) -> Table { - Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc( - radius, - start_angle / 360. * std::f64::consts::TAU, - sweep_angle / 360. * std::f64::consts::TAU, - match arc_type { - ArcType::Open => subpath::ArcType::Open, - ArcType::Closed => subpath::ArcType::Closed, - ArcType::PieSlice => subpath::ArcType::PieSlice, - }, - ))) + let mut arc_vector = Vector::from_subpath(subpath::Subpath::new_arc( + radius, + start_angle / 360. * std::f64::consts::TAU, + sweep_angle / 360. * std::f64::consts::TAU, + match arc_type { + ArcType::Open => subpath::ArcType::Open, + ArcType::Closed => subpath::ArcType::Closed, + ArcType::PieSlice => subpath::ArcType::PieSlice, + }, + )); + + // 2. Link handles only if both adjacent segments are cubic beziers + let len = arc_vector.segment_domain.ids().len(); + for i in 0..len.saturating_sub(1) { + if arc_vector.segment_domain.handles()[i].is_cubic() && arc_vector.segment_domain.handles()[i + 1].is_cubic() { + arc_vector.colinear_manipulators.push([ + HandleId::end(arc_vector.segment_domain.ids()[i]), + HandleId::primary(arc_vector.segment_domain.ids()[i + 1]) + ]); + } + } + + Table::new_from_element(arc_vector) } /// Generates a spiral shape that winds from an inner to an outer radius. @@ -90,14 +116,29 @@ fn spiral( #[default(25)] outer_radius: f64, #[default(90.)] angular_resolution: f64, ) -> Table { - Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_spiral( + + let mut spiral_vector = Vector::from_subpath(subpath::Subpath::new_spiral( inner_radius, outer_radius, turns, start_angle.to_radians(), angular_resolution.to_radians(), spiral_type, - ))) + )); + + + let len = spiral_vector.segment_domain.ids().len(); + for i in 0..len.saturating_sub(1) { + // Ensure both segments meeting at the anchor point are cubic beziers + if spiral_vector.segment_domain.handles()[i].is_cubic() && spiral_vector.segment_domain.handles()[i + 1].is_cubic() { + spiral_vector.colinear_manipulators.push([ + HandleId::end(spiral_vector.segment_domain.ids()[i]), + HandleId::primary(spiral_vector.segment_domain.ids()[i + 1]) + ]); + } + } + + Table::new_from_element(spiral_vector) } /// Generates an ellipse shape (an oval or stretched circle) with the chosen radii.