|
|
|
|
@@ -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.)]
|
|
|
|
|
|