Refactor the node macro and simply most of the node implementations (#1942)

* Add support structure for new node macro to gcore

* Fix compile issues and code generation

* Implement new node_fn macro

* Implement property translation

* Fix NodeIO type generation

* Start translating math nodes

* Move node implementation to outer scope to allow usage of local imports

* Add expose attribute to allow controlling the parameter exposure

* Add rust analyzer support for #[implementations] attribute

* Migrate logic nodes

* Handle where clause properly

* Implement argument ident pattern preservation

* Implement adjustment layer mapping

* Fix node registry types

* Fix module paths

* Improve demo artwork comptibility

* Improve macro error reporting

* Fix handling of impl node implementations

* Fix nodeio type computation

* Fix opacity node and graph type resolution

* Fix loading of demo artworks

* Fix eslint

* Fix typo in macro test

* Remove node definitions for Adjustment Nodes

* Fix type alias property generation and make adjustments footprint aware

* Convert vector nodes

* Implement path overrides

* Fix stroke node

* Fix painted dreams

* Implement experimental type level specialization

* Fix poisson disk sampling -> all demo artworks should work again

* Port text node + make node macro more robust by implementing lifetime substitution

* Fix vector node tests

* Fix red dress demo + ci

* Fix clippy warnings

* Code review

* Fix primary input issues

* Improve math nodes and audit others

* Set no_properties when no automatic properties are derived

* Port vector generator nodes (could not derive all definitions yet)

* Various QA changes and add min/max/mode_range to number parameters

* Add min and max for f64 and u32

* Convert gpu nodes and clean up unused nodes

* Partially port transform node

* Allow implementations on call arg

* Port path modify node

* Start porting graphic element nodes

* Transform nodes in graphic_element.rs

* Port brush node

* Port nodes in wasm_executior

* Rename node macro

* Fix formatting

* Fix Mandelbrot node

* Formatting

* Fix Load Image and Load Resource nodes, add scope input to node macro

* Remove unnecessary underscores

* Begin attemping to make nodes resolution-aware

* Infer a generic manual compositon type on generic call arg

* Various fixes and work towards merging

* Final changes for merge!

* Fix tests, probably

* More free line removals!

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2024-09-20 12:50:30 +02:00
committed by GitHub
parent ca0d102296
commit e352c7fa71
92 changed files with 4255 additions and 7275 deletions

View File

@@ -2,7 +2,7 @@ use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::BlendMode;
use crate::Color;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use glam::DVec2;
use std::hash::{Hash, Hasher};

View File

@@ -1,52 +1,9 @@
use super::HandleId;
use crate::vector::{PointId, VectorData};
use crate::Node;
use bezier_rs::Subpath;
use glam::DVec2;
#[derive(Debug, Clone, Copy)]
pub struct CircleGenerator<Radius> {
radius: Radius,
}
#[node_macro::node_fn(CircleGenerator)]
fn circle_generator(_input: (), radius: f64) -> VectorData {
let radius: f64 = radius;
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))
}
#[derive(Debug, Clone, Copy)]
pub struct EllipseGenerator<RadiusX, RadiusY> {
radius_x: RadiusX,
radius_y: RadiusY,
}
#[node_macro::node_fn(EllipseGenerator)]
fn ellipse_generator(_input: (), radius_x: f64, radius_y: f64) -> VectorData {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius;
let corner2 = radius;
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
ellipse
.colinear_manipulators
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
}
ellipse
}
#[derive(Debug, Clone, Copy)]
pub struct RectangleGenerator<SizeX, SizeY, IsIndividual, CornerRadius, Clamped> {
size_x: SizeX,
size_y: SizeY,
is_individual: IsIndividual,
corner_radius: CornerRadius,
clamped: Clamped,
}
trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData;
}
@@ -77,59 +34,79 @@ impl CornerRadius for [f64; 4] {
}
}
#[node_macro::node_fn(RectangleGenerator)]
fn square_generator<T: CornerRadius>(_input: (), size_x: f64, size_y: f64, is_individual: bool, corner_radius: T, clamped: bool) -> VectorData {
corner_radius.generate(DVec2::new(size_x, size_y), clamped)
#[node_macro::node(category("Vector: Shape"))]
fn circle(_: (), _primary: (), #[default(50.)] radius: f64) -> VectorData {
let radius: f64 = radius;
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))
}
#[derive(Debug, Clone, Copy)]
pub struct RegularPolygonGenerator<Points, Radius> {
points: Points,
radius: Radius,
#[node_macro::node(category("Vector: Shape"))]
fn ellipse(_: (), _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorData {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius;
let corner2 = radius;
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
ellipse
.colinear_manipulators
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
}
ellipse
}
#[node_macro::node_fn(RegularPolygonGenerator)]
fn regular_polygon_generator(_input: (), points: u32, radius: f64) -> VectorData {
let points = points.into();
#[node_macro::node(category("Vector: Shape"))]
fn rectangle<T: CornerRadius>(
_: (),
_primary: (),
#[default(100)] width: f64,
#[default(100)] height: f64,
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
#[implementations(f64, [f64; 4])] corner_radius: T,
#[default(true)] clamped: bool,
) -> VectorData {
corner_radius.generate(DVec2::new(width, height), clamped)
}
#[node_macro::node(category("Vector: Shape"))]
fn regular_polygon(
_: (),
_primary: (),
#[default(6)]
#[min(3.)]
sides: u32,
#[default(50)] radius: f64,
) -> VectorData {
let points = sides.into();
let radius: f64 = radius * 2.;
super::VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))
}
#[derive(Debug, Clone, Copy)]
pub struct StarGenerator<Points, Radius, InnerRadius> {
points: Points,
radius: Radius,
inner_radius: InnerRadius,
}
#[node_macro::node_fn(StarGenerator)]
fn star_generator(_input: (), points: u32, radius: f64, inner_radius: f64) -> VectorData {
let points = points.into();
#[node_macro::node(category("Vector: Shape"))]
fn star(
_: (),
_primary: (),
#[default(5)]
#[min(2.)]
sides: u32,
#[default(50)] radius: f64,
#[default(25)] inner_radius: f64,
) -> VectorData {
let points = sides.into();
let diameter: f64 = radius * 2.;
let inner_diameter = inner_radius * 2.;
super::VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))
}
#[derive(Debug, Clone, Copy)]
pub struct LineGenerator<Pos1, Pos2> {
pos_1: Pos1,
pos_2: Pos2,
#[node_macro::node(category("Vector: Shape"))]
fn line(_: (), _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorData {
super::VectorData::from_subpath(Subpath::new_line(start, end))
}
#[node_macro::node_fn(LineGenerator)]
fn line_generator(_input: (), pos_1: DVec2, pos_2: DVec2) -> VectorData {
super::VectorData::from_subpath(Subpath::new_line(pos_1, pos_2))
}
#[derive(Debug, Clone, Copy)]
pub struct SplineGenerator<Positions> {
positions: Positions,
}
#[node_macro::node_fn(SplineGenerator)]
fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(positions));
#[node_macro::node(category("Vector: Shape"))]
fn spline(_: (), _primary: (), points: Vec<DVec2>) -> VectorData {
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(points));
for pair in spline.segment_domain.ids().windows(2) {
spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]);
}
@@ -137,13 +114,9 @@ fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
}
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
#[derive(Debug, Clone)]
pub struct PathGenerator<ColinearManipulators> {
colinear_manipulators: ColinearManipulators,
}
#[node_macro::node_fn(PathGenerator)]
fn generate_path(path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
#[node_macro::node(category(""))]
fn path(_: (), path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
let mut vector_data = super::VectorData::from_subpaths(path_data, false);
vector_data.colinear_manipulators = colinear_manipulators
.iter()
@@ -151,27 +124,3 @@ fn generate_path(path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<Po
.collect();
vector_data
}
// #[derive(Debug, Clone, Copy)]
// pub struct BlitSubpath<P> {
// path_data: P,
// }
// #[node_macro::node_fn(BlitSubpath)]
// fn blit_subpath(base_image: Image, path_data: VectorData) -> Image {
// // TODO: Get forma to compile
// use forma::prelude::*;
// let composition = Composition::new();
// let mut renderer = cpu::Renderer::new();
// let mut path_builder = PathBuilder::new();
// for path_segment in path_data.bezier_iter() {
// let points = path_segment.internal.get_points().collect::<Vec<_>>();
// match points.len() {
// 2 => path_builder.line_to(points[1].into()),
// 3 => path_builder.quad_to(points[1].into(), points[2].into()),
// 4 => path_builder.cubic_to(points[1].into(), points[2].into(), points[3].into()),
// }
// }
// base_image
// }

View File

@@ -1,4 +1,4 @@
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
/// Represents different ways of calculating the centroid.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]

View File

@@ -4,7 +4,7 @@ use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT};
use crate::renderer::format_transform_matrix;
use crate::Color;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use std::fmt::{self, Display, Write};

View File

@@ -7,7 +7,7 @@ use super::style::{PathStyle, Stroke};
use crate::{AlphaBlending, Color};
use bezier_rs::ManipulatorGroup;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use core::borrow::Borrow;
use glam::{DAffine2, DVec2};

View File

@@ -1,6 +1,6 @@
use super::HandleId;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use std::collections::HashMap;

View File

@@ -1,9 +1,8 @@
use super::*;
use crate::uuid::generate_uuid;
use crate::Node;
use bezier_rs::BezierHandles;
use dyn_any::{DynAny, StaticType};
use dyn_any::DynAny;
use core::hash::BuildHasher;
use std::collections::{HashMap, HashSet};
@@ -422,14 +421,15 @@ impl core::hash::Hash for VectorModification {
}
}
use crate::transform::Footprint;
/// A node that applies a procedural modification to some [`VectorData`].
#[derive(Debug, Clone, Copy)]
pub struct PathModify<VectorModificationNode> {
modification: VectorModificationNode,
}
#[node_macro::node_fn(PathModify)]
fn path_modify(mut vector_data: VectorData, modification: VectorModification) -> VectorData {
#[node_macro::node(category(""))]
async fn path_modify<F: 'n + Send + Sync + Clone>(
#[implementations((), Footprint)] input: F,
#[implementations(((), VectorData), (Footprint, VectorData))] vector_data: impl Node<F, Output = VectorData>,
modification: VectorModification,
) -> VectorData {
let mut vector_data = vector_data.eval(input).await;
modification.apply(&mut vector_data);
vector_data
}

View File

@@ -1,153 +1,101 @@
use super::misc::CentroidType;
use super::style::{Fill, GradientStops, Stroke};
use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentId, StrokeId, VectorData};
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, Transform, TransformMut};
use crate::{Color, GraphicGroup, Node};
use crate::{Color, GraphicGroup};
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
use glam::{DAffine2, DVec2};
use rand::{Rng, SeedableRng};
#[derive(Debug, Clone, Copy)]
pub struct AssignColorsNode<Fill, Stroke, Gradient, Reverse, Randomize, Seed, RepeatEvery> {
fill: Fill,
stroke: Stroke,
gradient: Gradient,
reverse: Reverse,
randomize: Randomize,
seed: Seed,
repeat_every: RepeatEvery,
trait VectorIterMut {
fn vector_iter_mut(&mut self) -> impl ExactSizeIterator<Item = &mut VectorData>;
}
#[node_macro::node_fn(AssignColorsNode)]
fn assign_colors_node(group: GraphicGroup, fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, seed: u32, repeat_every: u32) -> GraphicGroup {
let mut group = group;
let vector_data_list: Vec<_> = group.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).collect();
let list = (vector_data_list.len(), vector_data_list.into_iter());
assign_colors(
list,
AlignColorsOptions {
fill,
stroke,
gradient,
reverse,
randomize,
seed,
repeat_every,
},
);
group
impl VectorIterMut for GraphicGroup {
fn vector_iter_mut(&mut self) -> impl ExactSizeIterator<Item = &mut VectorData> {
self.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).collect::<Vec<_>>().into_iter()
}
}
#[node_macro::node_impl(AssignColorsNode)]
fn assign_colors_node(vector_data: VectorData, fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, seed: u32, repeat_every: u32) -> GraphicGroup {
let mut vector_data_list: Vec<_> = vector_data
.region_bezier_paths()
.map(|(_, subpath)| {
let mut vector = VectorData::from_subpath(subpath);
vector.style = vector_data.style.clone();
crate::GraphicElement::VectorData(Box::new(vector))
})
.collect();
let list = (vector_data_list.len(), vector_data_list.iter_mut().map(|element| element.as_vector_data_mut().unwrap()));
assign_colors(
list,
AlignColorsOptions {
fill,
stroke,
gradient,
reverse,
randomize,
seed,
repeat_every,
},
);
let mut group = GraphicGroup::new(vector_data_list);
group.transform = vector_data.transform;
group.alpha_blending = vector_data.alpha_blending;
group
impl VectorIterMut for VectorData {
fn vector_iter_mut(&mut self) -> impl ExactSizeIterator<Item = &mut VectorData> {
std::iter::once(self)
}
}
struct AlignColorsOptions {
fill: bool,
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn assign_colors<T: VectorIterMut>(
footprint: Footprint,
#[implementations((Footprint, GraphicGroup), (Footprint, VectorData))] vector_group: impl Node<Footprint, Output = T>,
#[default(true)] fill: bool,
stroke: bool,
gradient: GradientStops,
reverse: bool,
randomize: bool,
seed: u32,
seed: SeedValue,
repeat_every: u32,
}
) -> T {
let mut input = vector_group.eval(footprint).await;
let vector_data = input.vector_iter_mut();
let length = vector_data.len();
let gradient = if reverse { gradient.reversed() } else { gradient };
fn assign_colors<'a>((length, vector_data): (usize, impl Iterator<Item = &'a mut VectorData>), options: AlignColorsOptions) {
let gradient = if options.reverse { options.gradient.reversed() } else { options.gradient };
let mut rng = rand::rngs::StdRng::seed_from_u64(options.seed as u64);
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
for (i, vector_data) in vector_data.enumerate() {
let factor = match options.randomize {
let factor = match randomize {
true => rng.gen::<f64>(),
false => match options.repeat_every {
false => match repeat_every {
0 => i as f64 / (length - 1) as f64,
1 => 0.,
_ => i as f64 % options.repeat_every as f64 / (options.repeat_every - 1) as f64,
_ => i as f64 % repeat_every as f64 / (repeat_every - 1) as f64,
},
};
let color = gradient.evalute(factor);
if options.fill {
if fill {
vector_data.style.set_fill(Fill::Solid(color));
}
if options.stroke {
if stroke {
if let Some(stroke) = vector_data.style.stroke().and_then(|stroke| stroke.with_color(&Some(color))) {
vector_data.style.set_stroke(stroke);
}
}
}
input
}
#[derive(Debug, Clone, Copy)]
pub struct SetFillNode<Fill> {
fill: Fill,
}
#[node_macro::node_fn(SetFillNode)]
fn set_vector_data_fill<T: Into<Fill>>(mut vector_data: VectorData, fill: T) -> VectorData {
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn fill<T: Into<Fill> + 'n + Send>(
footprint: Footprint,
vector_data: impl Node<Footprint, Output = VectorData>,
#[implementations(Fill, Color, Option<Color>, crate::vector::style::Gradient)] fill: T, // TODO: Set the default to black
_backup_color: Option<Color>,
_backup_gradient: Gradient,
) -> VectorData {
let mut vector_data = vector_data.eval(footprint).await;
vector_data.style.set_fill(fill.into());
vector_data
}
#[derive(Debug, Clone, Copy)]
pub struct SetStrokeNode<Color, Weight, DashLengths, DashOffset, LineCap, LineJoin, MiterLimit> {
color: Color,
weight: Weight,
dash_lengths: DashLengths,
dash_offset: DashOffset,
line_cap: LineCap,
line_join: LineJoin,
miter_limit: MiterLimit,
}
#[node_macro::node_fn(SetStrokeNode)]
fn set_vector_data_stroke(
mut vector_data: VectorData,
color: Option<Color>,
weight: f64,
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn stroke(
footprint: Footprint,
vector_data: impl Node<Footprint, Output = VectorData>,
color: Option<Color>, // TODO: Set the default to black
#[default(5.)] weight: f64,
dash_lengths: Vec<f64>,
dash_offset: f64,
line_cap: super::style::LineCap,
line_join: super::style::LineJoin,
miter_limit: f64,
line_cap: crate::vector::style::LineCap,
line_join: crate::vector::style::LineJoin,
#[default(4.)] miter_limit: f64,
) -> VectorData {
let mut vector_data = vector_data.eval(footprint).await;
vector_data.style.set_stroke(Stroke {
color,
weight,
@@ -161,28 +109,22 @@ fn set_vector_data_stroke(
vector_data
}
#[derive(Debug, Clone, Copy)]
pub struct RepeatNode<Direction, Angle, Instances> {
direction: Direction,
angle: Angle,
instances: Instances,
}
#[node_macro::node_fn(RepeatNode)]
fn repeat_vector_data(vector_data: VectorData, direction: DVec2, angle: f64, instances: u32) -> VectorData {
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn repeat(footprint: Footprint, instance: impl Node<Footprint, Output = VectorData>, #[default(100., 100.)] direction: DVec2, angle: Angle, #[default(4)] instances: IntegerCount) -> VectorData {
let instance = instance.eval(footprint).await;
let angle = angle.to_radians();
let instances = instances.max(1);
let total = (instances - 1) as f64;
if instances == 1 {
return vector_data;
return instance;
}
// Repeat the vector data
let mut result = VectorData::empty();
let Some(bounding_box) = vector_data.bounding_box_with_transform(vector_data.transform) else {
return vector_data;
let Some(bounding_box) = instance.bounding_box_with_transform(instance.transform) else {
return instance;
};
let center = (bounding_box[0] + bounding_box[1]) / 2.;
@@ -192,31 +134,31 @@ fn repeat_vector_data(vector_data: VectorData, direction: DVec2, angle: f64, ins
let transform = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
result.concat(&vector_data, transform);
result.concat(&instance, transform);
}
result
}
#[derive(Debug, Clone, Copy)]
pub struct CircularRepeatNode<AngleOffset, Radius, Instances> {
angle_offset: AngleOffset,
radius: Radius,
instances: Instances,
}
#[node_macro::node_fn(CircularRepeatNode)]
fn circular_repeat_vector_data(vector_data: VectorData, angle_offset: f64, radius: f64, instances: u32) -> VectorData {
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn circular_repeat(
footprint: Footprint,
instance: impl Node<Footprint, Output = VectorData>,
angle_offset: Angle,
#[default(5)] radius: Length,
#[default(5)] instances: IntegerCount,
) -> VectorData {
let instance = instance.eval(footprint).await;
let instances = instances.max(1);
if instances == 1 {
return vector_data;
return instance;
}
let mut result = VectorData::empty();
let Some(bounding_box) = vector_data.bounding_box_with_transform(vector_data.transform) else {
return vector_data;
let Some(bounding_box) = instance.bounding_box_with_transform(instance.transform) else {
return instance;
};
let center = (bounding_box[0] + bounding_box[1]) / 2.;
@@ -226,27 +168,27 @@ fn circular_repeat_vector_data(vector_data: VectorData, angle_offset: f64, radiu
let angle = (std::f64::consts::TAU / instances as f64) * i as f64 + angle_offset.to_radians();
let rotation = DAffine2::from_angle(angle);
let transform = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
result.concat(&vector_data, transform);
result.concat(&instance, transform);
}
result
}
#[derive(Debug, Clone, Copy)]
pub struct BoundingBoxNode;
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn bounding_box<F: 'n + Copy + Send>(
#[implementations((), Footprint)] footprint: F,
#[implementations(((), VectorData), (Footprint, VectorData))] vector_data: impl Node<F, Output = VectorData>,
) -> VectorData {
let vector_data = vector_data.eval(footprint).await;
#[node_macro::node_fn(BoundingBoxNode)]
fn generate_bounding_box(vector_data: VectorData) -> VectorData {
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap();
VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1]))
}
#[derive(Debug, Clone, Copy)]
pub struct SolidifyStrokeNode;
#[node_macro::node_fn(SolidifyStrokeNode)]
fn solidify_stroke(vector_data: VectorData) -> VectorData {
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn solidify_stroke(footprint: Footprint, vector_data: impl Node<Footprint, Output = VectorData>) -> VectorData {
// Grab what we need from original data.
let vector_data = vector_data.eval(footprint).await;
let VectorData { transform, style, .. } = &vector_data;
let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty();
@@ -306,33 +248,22 @@ impl ConcatElement for GraphicGroup {
}
}
#[derive(Debug, Clone, Copy)]
pub struct CopyToPoints<Points, Instance, RandomScaleMin, RandomScaleMax, RandomScaleBias, RandomScaleSeed, RandomRotation, RandomRotationSeed> {
points: Points,
instance: Instance,
random_scale_min: RandomScaleMin,
random_scale_max: RandomScaleMax,
random_scale_bias: RandomScaleBias,
random_scale_seed: RandomScaleSeed,
random_rotation: RandomRotation,
random_rotation_seed: RandomRotationSeed,
}
#[allow(clippy::too_many_arguments)]
#[node_macro::node_fn(CopyToPoints)]
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + TransformMut + Send>(
footprint: Footprint,
points: impl Node<Footprint, Output = VectorData>,
#[expose]
#[implementations((Footprint, VectorData), (Footprint, GraphicGroup))]
instance: impl Node<Footprint, Output = I>,
random_scale_min: f64,
random_scale_max: f64,
#[default(1)] random_scale_min: f64,
#[default(1)] random_scale_max: f64,
random_scale_bias: f64,
random_scale_seed: u32,
random_rotation: f64,
random_rotation_seed: u32,
random_scale_seed: SeedValue,
random_rotation: Angle,
random_rotation_seed: SeedValue,
) -> I {
let points = self.points.eval(footprint).await;
let instance = self.instance.eval(footprint).await;
let points = points.eval(footprint).await;
let instance = instance.eval(footprint).await;
let random_scale_difference = random_scale_max - random_scale_min;
let points_list = points.point_domain.positions();
@@ -340,8 +271,8 @@ async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + Tr
let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY).unwrap_or_default();
let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]);
let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed as u64);
let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed as u64);
let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into());
let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into());
let do_scale = random_scale_difference.abs() > 1e-6;
let do_rotation = random_rotation.abs() > 1e-6;
@@ -379,28 +310,18 @@ async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + Tr
result
}
#[derive(Debug, Clone, Copy)]
pub struct SamplePoints<VectorData, Spacing, StartOffset, StopOffset, AdaptiveSpacing, LengthsOfSegmentsOfSubpaths> {
vector_data: VectorData,
spacing: Spacing,
start_offset: StartOffset,
stop_offset: StopOffset,
adaptive_spacing: AdaptiveSpacing,
lengths_of_segments_of_subpaths: LengthsOfSegmentsOfSubpaths,
}
#[node_macro::node_fn(SamplePoints)]
#[node_macro::node(category(""))]
async fn sample_points(
footprint: Footprint,
mut vector_data: impl Node<Footprint, Output = VectorData>,
vector_data: impl Node<Footprint, Output = VectorData>,
spacing: f64,
start_offset: f64,
stop_offset: f64,
adaptive_spacing: bool,
lengths_of_segments_of_subpaths: impl Node<Footprint, Output = Vec<f64>>,
) -> VectorData {
let vector_data = self.vector_data.eval(footprint).await;
let lengths_of_segments_of_subpaths = self.lengths_of_segments_of_subpaths.eval(footprint).await;
let vector_data = vector_data.eval(footprint).await;
let lengths_of_segments_of_subpaths = lengths_of_segments_of_subpaths.eval(footprint).await;
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
@@ -463,16 +384,24 @@ async fn sample_points(
result
}
#[derive(Debug, Clone, Copy)]
pub struct PoissonDiskPoints<SeparationDiskDiameter, Seed> {
separation_disk_diameter: SeparationDiskDiameter,
seed: Seed,
}
#[node_macro::node(category(""), path(graphene_core::vector))]
async fn poisson_disk_points<F: 'n + Copy + Send>(
#[implementations((), Footprint)] footprint: F,
#[implementations(((), VectorData), (Footprint, VectorData))] vector_data: impl Node<F, Output = VectorData>,
#[default(10.)]
#[min(0.01)]
separation_disk_diameter: f64,
seed: SeedValue,
) -> VectorData {
let vector_data = vector_data.eval(footprint).await;
#[node_macro::node_fn(PoissonDiskPoints)]
fn poisson_disk_points(vector_data: VectorData, separation_disk_diameter: f64, seed: u32) -> VectorData {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed as u64);
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let mut result = VectorData::empty();
if separation_disk_diameter <= 0.01 {
return result;
}
for mut subpath in vector_data.stroke_bezier_paths() {
if subpath.manipulator_groups().len() < 3 {
continue;
@@ -488,22 +417,18 @@ fn poisson_disk_points(vector_data: VectorData, separation_disk_diameter: f64, s
result
}
#[derive(Debug, Clone, Copy)]
pub struct LengthsOfSegmentsOfSubpaths;
#[node_macro::node(name("Lengths of Segments of Subpaths"), category(""))]
async fn lengths_of_segments_of_subpaths(footprint: Footprint, vector_data: impl Node<Footprint, Output = VectorData>) -> Vec<f64> {
let vector_data = vector_data.eval(footprint).await;
#[node_macro::node_fn(LengthsOfSegmentsOfSubpaths)]
fn lengths_of_segments_of_subpaths(vector_data: VectorData) -> Vec<f64> {
vector_data
.segment_bezier_iter()
.map(|(_id, bezier, _, _)| bezier.apply_transformation(|point| vector_data.transform.transform_point2(point)).length(None))
.collect()
}
#[derive(Debug, Clone, Copy)]
pub struct SplinesFromPointsNode;
#[node_macro::node_fn(SplinesFromPointsNode)]
fn splines_from_points(mut vector_data: VectorData) -> VectorData {
#[node_macro::node(name("Splines from Points"), category(""), path(graphene_core::vector))]
fn splines_from_points(_: (), mut vector_data: VectorData) -> VectorData {
let points = &vector_data.point_domain;
vector_data.segment_domain.clear();
@@ -527,17 +452,18 @@ fn splines_from_points(mut vector_data: VectorData) -> VectorData {
vector_data
}
pub struct MorphNode<Source, Target, StartIndex, Time> {
source: Source,
target: Target,
start_index: StartIndex,
time: Time,
}
#[node_macro::node_fn(MorphNode)]
async fn morph(footprint: Footprint, source: impl Node<Footprint, Output = VectorData>, target: impl Node<Footprint, Output = VectorData>, start_index: u32, time: f64) -> VectorData {
let source = self.source.eval(footprint).await;
let target = self.target.eval(footprint).await;
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn morph(
footprint: Footprint,
source: impl Node<Footprint, Output = VectorData>,
#[expose] target: impl Node<Footprint, Output = VectorData>,
#[range((0., 1.))]
#[default(0.5)]
time: Fraction,
#[min(0.)] start_index: IntegerCount,
) -> VectorData {
let source = source.eval(footprint).await;
let target = target.eval(footprint).await;
let mut result = VectorData::empty();
// Lerp styles
@@ -617,14 +543,9 @@ async fn morph(footprint: Footprint, source: impl Node<Footprint, Output = Vecto
result
}
#[derive(Debug, Clone, Copy)]
pub struct AreaNode<VectorData> {
vector_data: VectorData,
}
#[node_macro::node_fn(AreaNode)]
async fn area_node(empty: (), vector_data: impl Node<Footprint, Output = VectorData>) -> f64 {
let vector_data = self.vector_data.eval(Footprint::default()).await;
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorData>) -> f64 {
let vector_data = vector_data.eval(Footprint::default()).await;
let mut area = 0.;
let scale = vector_data.transform.decompose_scale();
@@ -634,15 +555,9 @@ async fn area_node(empty: (), vector_data: impl Node<Footprint, Output = VectorD
area * scale[0] * scale[1]
}
#[derive(Debug, Clone, Copy)]
pub struct CentroidNode<VectorData, CentroidType> {
vector_data: VectorData,
centroid_type: CentroidType,
}
#[node_macro::node_fn(CentroidNode)]
async fn centroid_node(empty: (), vector_data: impl Node<Footprint, Output = VectorData>, centroid_type: CentroidType) -> DVec2 {
let vector_data = self.vector_data.eval(Footprint::default()).await;
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorData>, centroid_type: CentroidType) -> DVec2 {
let vector_data = vector_data.eval(Footprint::default()).await;
if centroid_type == CentroidType::Area {
let mut area = 0.;
@@ -689,65 +604,50 @@ async fn centroid_node(empty: (), vector_data: impl Node<Footprint, Output = Vec
#[cfg(test)]
mod test {
use super::*;
use crate::transform::CullNode;
use crate::value::ClonedNode;
use crate::Node;
use bezier_rs::Bezier;
use std::pin::Pin;
#[derive(Clone)]
pub struct FutureWrapperNode<Node: Clone>(Node);
pub struct FutureWrapperNode<T: Clone>(T);
impl<'i, T: 'i, N: Node<'i, T> + Clone> Node<'i, T> for FutureWrapperNode<N>
where
N: Node<'i, T, Output: Send>,
{
type Output = Pin<Box<dyn core::future::Future<Output = N::Output> + 'i + Send>>;
fn eval(&'i self, input: T) -> Self::Output {
let result = self.0.eval(input);
Box::pin(async move { result })
impl<'i, T: 'i + Clone + Send> Node<'i, Footprint> for FutureWrapperNode<T> {
type Output = Pin<Box<dyn core::future::Future<Output = T> + 'i + Send>>;
fn eval(&'i self, _input: Footprint) -> Self::Output {
let value = self.0.clone();
Box::pin(async move { value })
}
}
#[test]
fn repeat() {
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorData> {
FutureWrapperNode(VectorData::from_subpath(data))
}
#[tokio::test]
async fn repeat() {
let direction = DVec2::X * 1.5;
let instances = 3;
let repeated = RepeatNode {
direction: ClonedNode::new(direction),
angle: ClonedNode::new(0.),
instances: ClonedNode::new(instances),
}
.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)));
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
assert_eq!(repeated.region_bezier_paths().count(), 3);
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
}
}
#[test]
fn repeat_transform_position() {
#[tokio::test]
async fn repeat_transform_position() {
let direction = DVec2::new(12., 10.);
let instances = 8;
let repeated = RepeatNode {
direction: ClonedNode::new(direction),
angle: ClonedNode::new(0.),
instances: ClonedNode::new(instances),
}
.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)));
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
assert_eq!(repeated.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
}
}
#[test]
fn circle_repeat() {
let repeated = CircularRepeatNode {
angle_offset: ClonedNode::new(45.),
radius: ClonedNode::new(4.),
instances: ClonedNode::new(8),
}
.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)));
#[tokio::test]
async fn circle_repeat() {
let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
assert_eq!(repeated.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
let expected_angle = (index as f64 + 1.) * 45.;
@@ -756,9 +656,12 @@ mod test {
assert!((actual_angle - expected_angle).abs() % 360. < 1e-5);
}
}
#[test]
fn bounding_box() {
let bounding_box = BoundingBoxNode.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)));
#[tokio::test]
async fn bounding_box() {
let bounding_box = BoundingBoxNode {
vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)),
};
let bounding_box = bounding_box.eval(Footprint::default()).await;
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);
@@ -766,7 +669,11 @@ mod test {
// test a VectorData with non-zero rotation
let mut square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE));
square.transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4);
let bounding_box = BoundingBoxNode.eval(square);
let bounding_box = BoundingBoxNode {
vector_data: FutureWrapperNode(square),
}
.eval(Footprint::default())
.await;
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
let sqrt2 = core::f64::consts::SQRT_2;
@@ -775,20 +682,10 @@ mod test {
}
#[tokio::test]
async fn copy_to_points() {
let points = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.));
let expected_points = points.point_domain.positions().to_vec();
let bounding_box = CopyToPoints {
points: CullNode::new(FutureWrapperNode(ClonedNode(points))),
instance: CullNode::new(FutureWrapperNode(ClonedNode(VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))))),
random_scale_min: FutureWrapperNode(ClonedNode(1.)),
random_scale_max: FutureWrapperNode(ClonedNode(1.)),
random_scale_bias: FutureWrapperNode(ClonedNode(0.)),
random_scale_seed: FutureWrapperNode(ClonedNode(0)),
random_rotation: FutureWrapperNode(ClonedNode(0.)),
random_rotation_seed: FutureWrapperNode(ClonedNode(0)),
}
.eval(Footprint::default())
.await;
let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.);
let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE);
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
let bounding_box = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await;
assert_eq!(bounding_box.region_bezier_paths().count(), expected_points.len());
for (index, (_, subpath)) in bounding_box.region_bezier_paths().enumerate() {
let offset = expected_points[index];
@@ -800,17 +697,8 @@ mod test {
}
#[tokio::test]
async fn sample_points() {
let path = VectorData::from_subpath(Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)));
let sample_points = SamplePoints {
vector_data: CullNode::new(FutureWrapperNode(ClonedNode(path))),
spacing: FutureWrapperNode(ClonedNode(30.)),
start_offset: FutureWrapperNode(ClonedNode(0.)),
stop_offset: FutureWrapperNode(ClonedNode(0.)),
adaptive_spacing: FutureWrapperNode(ClonedNode(false)),
lengths_of_segments_of_subpaths: CullNode::new(FutureWrapperNode(ClonedNode(vec![100.]))),
}
.eval(Footprint::default())
.await;
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 30., 0., 0., false, &FutureWrapperNode(vec![100.])).await;
assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
@@ -818,29 +706,22 @@ mod test {
}
#[tokio::test]
async fn adaptive_spacing() {
let path = VectorData::from_subpath(Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)));
let sample_points = SamplePoints {
vector_data: CullNode::new(FutureWrapperNode(ClonedNode(path))),
spacing: FutureWrapperNode(ClonedNode(18.)),
start_offset: FutureWrapperNode(ClonedNode(45.)),
stop_offset: FutureWrapperNode(ClonedNode(10.)),
adaptive_spacing: FutureWrapperNode(ClonedNode(true)),
lengths_of_segments_of_subpaths: CullNode::new(FutureWrapperNode(ClonedNode(vec![100.]))),
}
.eval(Footprint::default())
.await;
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 18., 45., 10., true, &FutureWrapperNode(vec![100.])).await;
assert_eq!(sample_points.point_domain.positions().len(), 4);
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
}
}
#[test]
fn poisson() {
let sample_points = PoissonDiskPoints {
separation_disk_diameter: ClonedNode(10. * std::f64::consts::SQRT_2),
seed: ClonedNode(0),
}
.eval(VectorData::from_subpath(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)));
#[tokio::test]
async fn poisson() {
let sample_points = super::poisson_disk_points(
Footprint::default(),
&vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
10. * std::f64::consts::SQRT_2,
0,
)
.await;
assert!(
(20..=40).contains(&sample_points.point_domain.positions().len()),
"actual len {}",
@@ -850,31 +731,24 @@ mod test {
assert!(point.length() < 50. + 1., "Expected point in circle {point}")
}
}
#[test]
fn lengths() {
let subpath = VectorData::from_subpath(Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)));
let lengths = LengthsOfSegmentsOfSubpaths.eval(subpath);
#[tokio::test]
async fn lengths() {
let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let lengths = lengths_of_segments_of_subpaths(Footprint::default(), &vector_node(subpath)).await;
assert_eq!(lengths, vec![100.]);
}
#[test]
fn spline() {
let subpath = VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.));
let spline = SplinesFromPointsNode.eval(subpath);
let spline = splines_from_points((), subpath);
assert_eq!(spline.stroke_bezier_paths().count(), 1);
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
}
#[tokio::test]
async fn morph() {
let source = VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.));
let target = VectorData::from_subpath(Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO));
let sample_points = MorphNode {
source: CullNode::new(FutureWrapperNode(ClonedNode(source))),
target: CullNode::new(FutureWrapperNode(ClonedNode(target))),
time: FutureWrapperNode(ClonedNode(0.5)),
start_index: FutureWrapperNode(ClonedNode(0)),
}
.eval(Footprint::default())
.await;
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
let sample_points = super::morph(Footprint::default(), &vector_node(source), &vector_node(target), 0.5, 0).await;
assert_eq!(
&sample_points.point_domain.positions()[..4],
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]