New nodes: Resample Points, Spline from Points (#1226)
* Create node (no implementation) * Resampling - WIP * use bezier::from_linear_dvec2 * Use from anchors instead of Bezier * Tidy up anchor collection & subpath creation * Add Spline from Points node (not implemented) * Add spline from points node implementation * Update resampling * Update minimum density 0.01 -> 1.0 * Add a way to create a custom vector network * Add spline from points node to spline tool * Fix crash when no points * Add anchor method to subpath * Exact start and end point * Fix compile errors from rebase * Fix spline tool * Rename 'Density' to 'Spacing' * Fix transforms * Only close subpaths with >1 anchor * Fix compile * Fix compile error * Fix from points with many subpaths * Fix new_cubic_spline crash with one point * Rename to resample as polyline * Fix div zero * Fix missing file * Fix resample * Rename to resample points --------- Co-authored-by: hypercube <0hypercube@gmail.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
3419e739af
commit
5acb2cff06
|
|
@ -2170,6 +2170,27 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
properties: node_properties::circular_repeat_properties,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Resample Points",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_core::vector::ResamplePoints<_>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||
DocumentInputType::value("Spacing", TaggedValue::F64(100.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::resample_points_properties,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Spline from Points",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_core::vector::SplineFromPointsNode"),
|
||||
inputs: vec![DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true)],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Image Segmentation",
|
||||
category: "Image Adjustments",
|
||||
|
|
|
|||
|
|
@ -1868,6 +1868,12 @@ pub fn circular_repeat_properties(document_node: &DocumentNode, node_id: NodeId,
|
|||
vec![LayoutGroup::Row { widgets: angle_offset }, LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: count }]
|
||||
}
|
||||
|
||||
pub fn resample_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let spacing = number_widget(document_node, node_id, 1, "Spacing", NumberInput::default().min(1.), true);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: spacing }]
|
||||
}
|
||||
|
||||
/// Fill Node Widgets LayoutGroup
|
||||
pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let fill_type_index = 1;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,13 @@ use super::*;
|
|||
impl Bezier {
|
||||
/// Convert a euclidean distance ratio along the `Bezier` curve to a parametric `t`-value.
|
||||
pub fn euclidean_to_parametric(&self, ratio: f64, error: f64) -> f64 {
|
||||
if ratio < error {
|
||||
return 0.;
|
||||
}
|
||||
if 1. - ratio < error {
|
||||
return 1.;
|
||||
}
|
||||
|
||||
let mut low = 0.;
|
||||
let mut mid = 0.;
|
||||
let mut high = 1.;
|
||||
|
|
|
|||
|
|
@ -112,6 +112,11 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||
&self.manipulator_groups
|
||||
}
|
||||
|
||||
/// Returns a vector of all the anchors (DVec2) for this `Subpath`.
|
||||
pub fn anchors(&self) -> Vec<DVec2> {
|
||||
self.manipulator_groups().iter().map(|group| group.anchor).collect()
|
||||
}
|
||||
|
||||
/// Returns if the Subpath is equivalent to a single point.
|
||||
pub fn is_point(&self) -> bool {
|
||||
if self.is_empty() {
|
||||
|
|
@ -254,7 +259,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||
/// Construct a cubic spline from a list of points.
|
||||
/// Based on <https://mathworld.wolfram.com/CubicSpline.html>.
|
||||
pub fn new_cubic_spline(points: Vec<DVec2>) -> Self {
|
||||
if points.is_empty() {
|
||||
if points.len() < 2 {
|
||||
return Self::new(Vec::new(), false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -252,4 +252,16 @@ mod tests {
|
|||
assert_eq!(closed_subpath.t_value_to_parametric(SubpathTValue::GlobalParametric(0.)), (0, 0.));
|
||||
assert_eq!(closed_subpath.t_value_to_parametric(SubpathTValue::GlobalParametric(1.)), (4, 1.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_start_end() {
|
||||
let start = DVec2::new(20., 30.);
|
||||
let end = DVec2::new(60., 45.);
|
||||
let handle = DVec2::new(75., 85.);
|
||||
|
||||
let subpath: Subpath<EmptyId> = Subpath::from_bezier(&Bezier::from_quadratic_dvec2(start, handle, end));
|
||||
|
||||
assert_eq!(subpath.evaluate(SubpathTValue::GlobalEuclidean(0.0)), start);
|
||||
assert_eq!(subpath.evaluate(SubpathTValue::GlobalEuclidean(1.0)), end);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ use super::style::{Fill, FillType, Gradient, GradientType, Stroke};
|
|||
use super::VectorData;
|
||||
use crate::{Color, Node};
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
|
||||
use bezier_rs::{Subpath, SubpathTValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use num_traits::Zero;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SetFillNode<FillType, SolidColor, GradientType, Start, End, Transform, Positions> {
|
||||
|
|
@ -147,3 +147,41 @@ fn generate_bounding_box(vector_data: VectorData) -> VectorData {
|
|||
vector_data.transform.transform_point2(bounding_box[1]),
|
||||
)])
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ResamplePoints<Spacing> {
|
||||
spacing: Spacing,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(ResamplePoints)]
|
||||
fn resample_points(mut vector_data: VectorData, spacing: f64) -> VectorData {
|
||||
for subpath in &mut vector_data.subpaths {
|
||||
if subpath.is_empty() || spacing.is_zero() || !spacing.is_finite() {
|
||||
continue;
|
||||
}
|
||||
|
||||
subpath.apply_transform(vector_data.transform);
|
||||
let length = subpath.length(None);
|
||||
let rounded_count = (length / spacing).round();
|
||||
|
||||
if rounded_count >= 1. {
|
||||
let new_anchors = (0..=rounded_count as usize).map(|c| subpath.evaluate(SubpathTValue::GlobalEuclidean(c as f64 / rounded_count)));
|
||||
*subpath = Subpath::from_anchors(new_anchors, subpath.closed() && rounded_count as usize > 1);
|
||||
}
|
||||
|
||||
subpath.apply_transform(vector_data.transform.inverse());
|
||||
}
|
||||
vector_data
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SplineFromPointsNode {}
|
||||
|
||||
#[node_macro::node_fn(SplineFromPointsNode)]
|
||||
fn spline_from_points(mut vector_data: VectorData) -> VectorData {
|
||||
for subpath in &mut vector_data.subpaths {
|
||||
*subpath = Subpath::new_cubic_spline(subpath.anchors());
|
||||
}
|
||||
|
||||
vector_data
|
||||
}
|
||||
|
|
|
|||
|
|
@ -634,6 +634,8 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
register_node!(graphene_core::vector::RepeatNode<_, _>, 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::ResamplePoints<_>, input: VectorData, params: [f64]),
|
||||
register_node!(graphene_core::vector::SplineFromPointsNode, input: VectorData, 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]),
|
||||
|
|
|
|||
Loading…
Reference in New Issue