Instance tables refactor part 1: wrap graphical data in the new Instances<T> struct (#2230)

* Port VectorData to Instances<VectorData>

* Port ImageFrame<P> and TextureFrame to Instances<ImageFrame<P>> and Instances<TextureFrame>

* Avoid mutation with the TransformMut trait

* Port GraphicGroup to Instances<GraphicGroup>

* It compiles!

* Organize debugging

* Document upgrading

* Fix Brush node

* Restore TransformMut in lieu of TransformSet trait

* Fix tests

* Final code review
This commit is contained in:
Keavon Chambers
2025-01-28 23:51:12 -08:00
committed by GitHub
parent 408f9bffa1
commit eb0ff20d3c
43 changed files with 1855 additions and 1221 deletions

View File

@@ -1,21 +1,20 @@
use super::HandleId;
use crate::transform::Footprint;
use crate::vector::{PointId, VectorData};
use crate::vector::{HandleId, PointId, VectorData, VectorDataTable};
use bezier_rs::Subpath;
use glam::DVec2;
trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData;
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable;
}
impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData {
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4]))
VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
}
}
impl CornerRadius for [f64; 4] {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData {
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable {
let clamped_radius = if clamped {
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
@@ -31,28 +30,31 @@ impl CornerRadius for [f64; 4] {
} else {
self
};
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius))
VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
}
}
#[node_macro::node(category("Vector: Shape"))]
fn circle<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorData {
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))
fn circle<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
}
#[node_macro::node(category("Vector: Shape"))]
fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorData {
fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable {
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 mut ellipse = 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
VectorDataTable::new(ellipse)
}
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
@@ -64,7 +66,7 @@ fn rectangle<F: 'n + Send, T: CornerRadius>(
_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 {
) -> VectorDataTable {
corner_radius.generate(DVec2::new(width, height), clamped)
}
@@ -76,10 +78,10 @@ fn regular_polygon<F: 'n + Send>(
#[min(3.)]
sides: u32,
#[default(50)] radius: f64,
) -> VectorData {
) -> VectorDataTable {
let points = sides.into();
let radius: f64 = radius * 2.;
super::VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))
VectorDataTable::new(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
}
#[node_macro::node(category("Vector: Shape"))]
@@ -91,35 +93,39 @@ fn star<F: 'n + Send>(
sides: u32,
#[default(50)] radius: f64,
#[default(25)] inner_radius: f64,
) -> VectorData {
) -> VectorDataTable {
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))
VectorDataTable::new(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
}
#[node_macro::node(category("Vector: Shape"))]
fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorData {
super::VectorData::from_subpath(Subpath::new_line(start, end))
fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
}
#[node_macro::node(category("Vector: Shape"))]
fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec<DVec2>) -> VectorData {
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(points));
fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec<DVec2>) -> VectorDataTable {
let mut spline = 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])]);
}
spline
VectorDataTable::new(spline)
}
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
#[node_macro::node(category(""))]
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
let mut vector_data = super::VectorData::from_subpaths(path_data, false);
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
let mut vector_data = VectorData::from_subpaths(path_data, false);
vector_data.colinear_manipulators = colinear_manipulators
.iter()
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
.collect();
vector_data
VectorDataTable::new(vector_data)
}

View File

@@ -4,7 +4,8 @@ pub use attributes::*;
pub use modification::*;
use super::style::{PathStyle, Stroke};
use crate::{AlphaBlending, Color};
use crate::instances::Instances;
use crate::{AlphaBlending, Color, GraphicGroupTable};
use bezier_rs::ManipulatorGroup;
use dyn_any::DynAny;
@@ -12,6 +13,26 @@ use dyn_any::DynAny;
use core::borrow::Borrow;
use glam::{DAffine2, DVec2};
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<VectorDataTable, D::Error> {
use serde::Deserialize;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
enum EitherFormat {
VectorData(VectorData),
VectorDataTable(VectorDataTable),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data),
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
})
}
pub type VectorDataTable = Instances<VectorData>;
/// [VectorData] is passed between nodes.
/// It contains a list of subpaths (that may be open or closed), a transform, and some style information.
#[derive(Clone, Debug, PartialEq, DynAny)]
@@ -29,7 +50,7 @@ pub struct VectorData {
pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<crate::GraphicGroup>,
pub upstream_graphic_group: Option<GraphicGroupTable>,
}
impl core::hash::Hash for VectorData {

View File

@@ -1,4 +1,5 @@
use super::HandleId;
use crate::vector::vector_data::{HandleId, VectorData, VectorDataTable};
use crate::vector::ConcatElement;
use dyn_any::DynAny;
@@ -46,7 +47,7 @@ macro_rules! create_ids {
};
}
create_ids! { PointId, SegmentId, RegionId, StrokeId, FillId }
create_ids! { InstanceId, PointId, SegmentId, RegionId, StrokeId, FillId }
/// A no-op hasher that allows writing u64s (the id type).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@@ -503,7 +504,7 @@ impl RegionDomain {
}
}
impl super::VectorData {
impl VectorData {
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.
fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: bezier_rs::BezierHandles) -> bezier_rs::Bezier {
let start = self.point_domain.positions()[start];
@@ -698,7 +699,7 @@ impl StrokePathIterPointMetadata {
#[derive(Clone)]
pub struct StrokePathIter<'a> {
vector_data: &'a super::VectorData,
vector_data: &'a VectorData,
points: Vec<StrokePathIterPointMetadata>,
skip: usize,
done_one: bool,
@@ -774,7 +775,7 @@ impl bezier_rs::Identifier for PointId {
}
}
impl crate::vector::ConcatElement for super::VectorData {
impl ConcatElement for VectorData {
fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) {
let new_ids = other
.point_domain
@@ -813,6 +814,14 @@ impl crate::vector::ConcatElement for super::VectorData {
}
}
impl ConcatElement for VectorDataTable {
fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) {
for (instance, other_instance) in self.instances_mut().zip(other.instances()) {
instance.concat(other_instance, transform, node_id);
}
}
}
/// Represents the conversion of ids used when concatenating vector data with conflicting ids.
struct IdMap {
point_offset: usize,

View File

@@ -1,4 +1,5 @@
use super::*;
use crate::transform::Footprint;
use crate::uuid::generate_uuid;
use bezier_rs::BezierHandles;
@@ -421,7 +422,6 @@ impl core::hash::Hash for VectorModification {
}
}
use crate::transform::Footprint;
/// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))]
async fn path_modify<F: 'n + Send + Sync + Clone>(
@@ -431,15 +431,18 @@ async fn path_modify<F: 'n + Send + Sync + Clone>(
)]
input: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
modification: Box<VectorModification>,
) -> VectorData {
) -> VectorDataTable {
let mut vector_data = vector_data.eval(input).await;
modification.apply(&mut vector_data);
vector_data
let vector_data = vector_data.one_item_mut();
modification.apply(vector_data);
VectorDataTable::new(vector_data.clone())
}
#[test]

View File

@@ -1,12 +1,12 @@
use super::misc::CentroidType;
use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, Transform, TransformMut};
use crate::vector::style::LineJoin;
use crate::vector::PointDomain;
use crate::{Color, GraphicElement, GraphicGroup};
use crate::{Color, GraphicElement, GraphicGroup, GraphicGroupTable};
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
use glam::{DAffine2, DVec2};
@@ -18,21 +18,27 @@ trait VectorIterMut {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)>;
}
impl VectorIterMut for GraphicGroup {
impl VectorIterMut for GraphicGroupTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> {
let parent_transform = self.transform;
// Grab only the direct children (perhaps unintuitive?)
self.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector| {
let transform = parent_transform * vector.transform;
(vector, transform)
let instance = self.one_item_mut();
let parent_transform = instance.transform;
// Grab only the direct children
instance.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector_data| {
let vector_data = vector_data.one_item_mut();
let transform = parent_transform * vector_data.transform;
(vector_data, transform)
})
}
}
impl VectorIterMut for VectorData {
impl VectorIterMut for VectorDataTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> {
let transform = self.transform;
std::iter::once((self, transform))
self.instances_mut().map(|instance| {
let transform = instance.transform;
(instance, transform)
})
}
}
@@ -45,10 +51,10 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
() -> VectorData,
Footprint -> GraphicGroup,
Footprint -> VectorData,
() -> GraphicGroupTable,
() -> VectorDataTable,
Footprint -> GraphicGroupTable,
Footprint -> VectorDataTable,
)]
#[widget(ParsedWidgetOverride::Hidden)]
vector_group: impl Node<F, Output = T>,
@@ -60,13 +66,14 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32,
) -> T {
let mut input = vector_group.eval(footprint).await;
let length = input.vector_iter_mut().count();
let mut vector_group = vector_group.eval(footprint).await;
let length = vector_group.vector_iter_mut().count();
let gradient = if reverse { gradient.reversed() } else { gradient };
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
for (i, (vector_data, _)) in input.vector_iter_mut().enumerate() {
for (i, (vector_data, _)) in vector_group.vector_iter_mut().enumerate() {
let factor = match randomize {
true => rng.gen::<f64>(),
false => match repeat_every {
@@ -87,7 +94,8 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
}
}
}
input
vector_group
}
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
@@ -112,22 +120,22 @@ async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIter
)]
footprint: F,
#[implementations(
() -> VectorData,
() -> VectorData,
() -> VectorData,
() -> VectorData,
() -> GraphicGroup,
() -> GraphicGroup,
() -> GraphicGroup,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> VectorData,
Footprint -> VectorData,
Footprint -> VectorData,
Footprint -> GraphicGroup,
Footprint -> GraphicGroup,
Footprint -> GraphicGroup,
Footprint -> GraphicGroup,
() -> VectorDataTable,
() -> VectorDataTable,
() -> VectorDataTable,
() -> VectorDataTable,
() -> GraphicGroupTable,
() -> GraphicGroupTable,
() -> GraphicGroupTable,
() -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> VectorDataTable,
Footprint -> VectorDataTable,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
)]
vector_data: impl Node<F, Output = TargetTy>,
#[implementations(
@@ -176,14 +184,14 @@ async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy
)]
footprint: F,
#[implementations(
() -> VectorData,
() -> VectorData,
() -> GraphicGroup,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> VectorData,
Footprint -> GraphicGroup,
Footprint -> GraphicGroup,
() -> VectorDataTable,
() -> VectorDataTable,
() -> GraphicGroupTable,
() -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
)]
vector_data: impl Node<F, Output = TargetTy>,
#[implementations(
@@ -234,10 +242,10 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
footprint: F,
// TODO: Implement other GraphicElementRendered types.
#[implementations(
() -> VectorData,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> GraphicGroup,
() -> VectorDataTable,
() -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
)]
instance: impl Node<F, Output = I>,
#[default(100., 100.)]
@@ -245,7 +253,7 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
direction: DVec2,
angle: Angle,
#[default(4)] instances: IntegerCount,
) -> GraphicGroup {
) -> GraphicGroupTable {
let instance = instance.eval(footprint).await;
let first_vector_transform = instance.transform();
@@ -253,10 +261,10 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
let instances = instances.max(1);
let total = (instances - 1) as f64;
let mut result = GraphicGroup::EMPTY;
let mut result = GraphicGroup::default();
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else {
return result;
return GraphicGroupTable::new(result);
};
let center = (bounding_box[0] + bounding_box[1]) / 2.;
@@ -273,7 +281,7 @@ async fn repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + Transform
result.push((new_instance, None));
}
result
GraphicGroupTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -287,24 +295,24 @@ async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + T
footprint: F,
// TODO: Implement other GraphicElementRendered types.
#[implementations(
() -> VectorData,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> GraphicGroup,
() -> VectorDataTable,
() -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
)]
instance: impl Node<F, Output = I>,
angle_offset: Angle,
#[default(5)] radius: f64,
#[default(5)] instances: IntegerCount,
) -> GraphicGroup {
) -> GraphicGroupTable {
let instance = instance.eval(footprint).await;
let first_vector_transform = instance.transform();
let instances = instances.max(1);
let mut result = GraphicGroup::EMPTY;
let mut result = GraphicGroup::default();
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else {
return result;
return GraphicGroupTable::new(result);
};
let center = (bounding_box[0] + bounding_box[1]) / 2.;
@@ -322,7 +330,7 @@ async fn circular_repeat<F: 'n + Send + Copy, I: 'n + GraphicElementRendered + T
result.push((new_instance, None));
}
result
GraphicGroupTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -334,17 +342,17 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
)]
footprint: F,
#[implementations(
() -> VectorData,
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
points: impl Node<F, Output = VectorData>,
points: impl Node<F, Output = VectorDataTable>,
#[expose]
#[implementations(
() -> VectorData,
() -> GraphicGroup,
Footprint -> VectorData,
Footprint -> GraphicGroup,
() -> VectorDataTable,
() -> GraphicGroupTable,
Footprint -> VectorDataTable,
Footprint -> GraphicGroupTable,
)]
instance: impl Node<F, Output = I>,
#[default(1)] random_scale_min: f64,
@@ -353,9 +361,12 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
random_scale_seed: SeedValue,
random_rotation: Angle,
random_rotation_seed: SeedValue,
) -> GraphicGroup {
) -> GraphicGroupTable {
let points = points.eval(footprint).await;
let points = points.one_item();
let instance = instance.eval(footprint).await;
let instance_transform = instance.transform();
let random_scale_difference = random_scale_max - random_scale_min;
@@ -406,7 +417,7 @@ async fn copy_to_points<F: 'n + Send + Copy, I: GraphicElementRendered + ConcatE
result.push((new_instance, None));
}
result
GraphicGroupTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -417,18 +428,20 @@ async fn bounding_box<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
) -> VectorData {
vector_data: impl Node<F, Output = VectorDataTable>,
) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap();
let mut result = VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1]));
result.style = vector_data.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY);
result
VectorDataTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
@@ -439,23 +452,23 @@ async fn offset_path<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
distance: f64,
line_join: LineJoin,
#[default(4.)] miter_limit: f64,
) -> VectorData {
) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty();
result.style = vector_data.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY);
// Perform operation on all subpaths in this shape.
for mut subpath in subpaths {
for mut subpath in vector_data.stroke_bezier_paths() {
subpath.apply_transform(vector_data.transform);
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
@@ -472,7 +485,7 @@ async fn offset_path<F: 'n + Send>(
result.append_subpath(subpath_out, false);
}
result
VectorDataTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -483,14 +496,17 @@ async fn solidify_stroke<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
) -> VectorData {
vector_data: impl Node<F, Output = VectorDataTable>,
) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let transform = &vector_data.transform;
let style = &vector_data.style;
let VectorData { transform, style, .. } = &vector_data;
let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty();
@@ -531,7 +547,7 @@ async fn solidify_stroke<F: 'n + Send>(
result.style.set_stroke(Stroke::default());
}
result
VectorDataTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -542,12 +558,14 @@ async fn flatten_vector_elements<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
Footprint -> GraphicGroup,
() -> GraphicGroupTable,
Footprint -> GraphicGroupTable,
)]
graphic_group_input: impl Node<F, Output = GraphicGroup>,
) -> VectorData {
graphic_group_input: impl Node<F, Output = GraphicGroupTable>,
) -> VectorDataTable {
let graphic_group = graphic_group_input.eval(footprint).await;
let graphic_group = graphic_group.one_item();
// A node based solution to support passing through vector data could be a network node with a cache node connected to
// a flatten vector elements connected to an if else node, another connection from the cache directly
// To the if else node, and another connection from the cache to a matches type node connected to the if else node.
@@ -555,9 +573,12 @@ async fn flatten_vector_elements<F: 'n + Send>(
for (element, reference) in graphic_group.iter() {
match element {
GraphicElement::VectorData(vector_data) => {
result.concat(vector_data, current_transform, reference.map(|node_id| node_id.0).unwrap_or_default());
for instance in vector_data.instances() {
result.concat(instance, current_transform, reference.map(|node_id| node_id.0).unwrap_or_default());
}
}
GraphicElement::GraphicGroup(graphic_group) => {
let graphic_group = graphic_group.one_item();
concat_group(graphic_group, current_transform * graphic_group.transform, result);
}
_ => {}
@@ -566,25 +587,29 @@ async fn flatten_vector_elements<F: 'n + Send>(
}
let mut result = VectorData::empty();
concat_group(&graphic_group, DAffine2::IDENTITY, &mut result);
concat_group(graphic_group, DAffine2::IDENTITY, &mut result);
// TODO: This leads to incorrect stroke widths when flattening groups with different transforms.
result.style.set_stroke_transform(DAffine2::IDENTITY);
result
VectorDataTable::new(result)
}
pub trait ConcatElement {
fn concat(&mut self, other: &Self, transform: DAffine2, node_id: u64);
}
impl ConcatElement for GraphicGroup {
impl ConcatElement for GraphicGroupTable {
fn concat(&mut self, other: &Self, transform: DAffine2, _node_id: u64) {
let own = self.one_item_mut();
let other = other.one_item();
// TODO: Decide if we want to keep this behavior whereby the layers are flattened
for (mut element, footprint_mapping) in other.iter().cloned() {
*element.transform_mut() = transform * element.transform() * other.transform();
self.push((element, footprint_mapping));
own.push((element, footprint_mapping));
}
self.alpha_blending = other.alpha_blending;
own.alpha_blending = other.alpha_blending;
}
}
@@ -596,10 +621,10 @@ async fn sample_points<F: 'n + Send + Copy>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
spacing: f64,
start_offset: f64,
stop_offset: f64,
@@ -609,9 +634,10 @@ async fn sample_points<F: 'n + Send + Copy>(
Footprint -> Vec<f64>,
)]
subpath_segment_lengths: impl Node<F, Output = Vec<f64>>,
) -> VectorData {
) -> VectorDataTable {
// Evaluate vector data and subpath segment lengths asynchronously.
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await;
// Create an iterator over the bezier segments with enumeration and peeking capability.
@@ -753,7 +779,7 @@ async fn sample_points<F: 'n + Send + Copy>(
result.style.set_stroke_transform(vector_data.transform);
// Return the resulting vector data with newly generated points and segments.
result
VectorDataTable::new(result)
}
#[node_macro::node(category(""), path(graphene_core::vector))]
@@ -764,22 +790,23 @@ async fn poisson_disk_points<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
#[default(10.)]
#[min(0.01)]
separation_disk_diameter: f64,
seed: SeedValue,
) -> VectorData {
) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let mut result = VectorData::empty();
if separation_disk_diameter <= 0.01 {
return result;
return VectorDataTable::new(result);
}
for mut subpath in vector_data.stroke_bezier_paths() {
@@ -812,7 +839,7 @@ async fn poisson_disk_points<F: 'n + Send>(
result.style = vector_data.style.clone();
result.style.set_stroke_transform(DAffine2::IDENTITY);
result
VectorDataTable::new(result)
}
#[node_macro::node(category(""), path(graphene_core::vector))]
@@ -823,12 +850,13 @@ async fn subpath_segment_lengths<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
) -> Vec<f64> {
let vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item();
vector_data
.segment_bezier_iter()
@@ -844,17 +872,18 @@ async fn splines_from_points<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
) -> VectorData {
vector_data: impl Node<F, Output = VectorDataTable>,
) -> VectorDataTable {
// Evaluate the vector data within the given footprint.
let mut vector_data = vector_data.eval(footprint).await;
let vector_data = vector_data.one_item_mut();
// Exit early if there are no points to generate splines from.
if vector_data.point_domain.positions().is_empty() {
return vector_data;
return VectorDataTable::new(vector_data.clone());
}
let mut segment_domain = SegmentDomain::default();
@@ -887,7 +916,7 @@ async fn splines_from_points<F: 'n + Send>(
}
vector_data.segment_domain = segment_domain;
vector_data
VectorDataTable::new(vector_data.clone())
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -898,14 +927,15 @@ async fn jitter_points<F: 'n + Send>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
vector_data: impl Node<F, Output = VectorData>,
vector_data: impl Node<F, Output = VectorDataTable>,
#[default(5.)] amount: f64,
seed: SeedValue,
) -> VectorData {
let mut vector_data = vector_data.eval(footprint).await;
) -> VectorDataTable {
let vector_data = vector_data.eval(footprint).await;
let mut vector_data = vector_data.one_item().clone();
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
@@ -949,7 +979,7 @@ async fn jitter_points<F: 'n + Send>(
vector_data.transform = DAffine2::IDENTITY;
vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
vector_data
VectorDataTable::new(vector_data)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@@ -960,23 +990,26 @@ async fn morph<F: 'n + Send + Copy>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
source: impl Node<F, Output = VectorData>,
source: impl Node<F, Output = VectorDataTable>,
#[expose]
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
target: impl Node<F, Output = VectorData>,
target: impl Node<F, Output = VectorDataTable>,
#[range((0., 1.))]
#[default(0.5)]
time: Fraction,
#[min(0.)] start_index: IntegerCount,
) -> VectorData {
) -> VectorDataTable {
let source = source.eval(footprint).await;
let source = source.one_item();
let target = target.eval(footprint).await;
let target = target.one_item();
let mut result = VectorData::empty();
let time = time.clamp(0., 1.);
@@ -1055,7 +1088,7 @@ async fn morph<F: 'n + Send + Copy>(
}
}
result
VectorDataTable::new(result)
}
fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
@@ -1173,18 +1206,24 @@ async fn bevel<F: 'n + Send + Copy>(
)]
footprint: F,
#[implementations(
() -> VectorData,
Footprint -> VectorData,
() -> VectorDataTable,
Footprint -> VectorDataTable,
)]
source: impl Node<F, Output = VectorData>,
source: impl Node<F, Output = VectorDataTable>,
#[default(10.)] distance: Length,
) -> VectorData {
bevel_algorithm(source.eval(footprint).await, distance)
) -> VectorDataTable {
let source = source.eval(footprint).await;
let source = source.one_item();
let result = bevel_algorithm(source.clone(), distance);
VectorDataTable::new(result)
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorData>) -> f64 {
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>) -> f64 {
let vector_data = vector_data.eval(Footprint::default()).await;
let vector_data = vector_data.one_item();
let mut area = 0.;
let scale = vector_data.transform.decompose_scale();
@@ -1195,8 +1234,9 @@ async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorData>) ->
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorData>, centroid_type: CentroidType) -> DVec2 {
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
let vector_data = vector_data.eval(Footprint::default()).await;
let vector_data = vector_data.one_item();
if centroid_type == CentroidType::Area {
let mut area = 0.;
@@ -1260,8 +1300,8 @@ mod test {
}
}
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorData> {
FutureWrapperNode(VectorData::from_subpath(data))
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorDataTable> {
FutureWrapperNode(VectorDataTable::new(VectorData::from_subpath(data)))
}
#[tokio::test]
@@ -1270,6 +1310,7 @@ mod test {
let instances = 3;
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
let vector_data = vector_data.one_item();
assert_eq!(vector_data.region_bezier_paths().count(), 3);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@@ -1281,6 +1322,7 @@ mod test {
let instances = 8;
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
let vector_data = vector_data.one_item();
assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
@@ -1290,6 +1332,7 @@ mod 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;
let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await;
let vector_data = vector_data.one_item();
assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
let expected_angle = (index as f64 + 1.) * 45.;
@@ -1304,18 +1347,20 @@ mod test {
vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)),
};
let bounding_box = bounding_box.eval(Footprint::default()).await;
let bounding_box = bounding_box.one_item();
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.),]);
// test a VectorData with non-zero rotation
// 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 {
vector_data: FutureWrapperNode(square),
vector_data: FutureWrapperNode(VectorDataTable::new(square)),
}
.eval(Footprint::default())
.await;
let bounding_box = bounding_box.one_item();
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;
@@ -1329,6 +1374,7 @@ mod test {
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
let copy_to_points = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await;
let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(copy_to_points)).await;
let flattened_copy_to_points = flattened_copy_to_points.one_item();
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() {
let offset = expected_points[index];
@@ -1342,6 +1388,7 @@ mod test {
async fn sample_points() {
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;
let sample_points = sample_points.one_item();
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}");
@@ -1351,6 +1398,7 @@ mod test {
async fn adaptive_spacing() {
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;
let sample_points = sample_points.one_item();
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}");
@@ -1365,6 +1413,7 @@ mod test {
0,
)
.await;
let sample_points = sample_points.one_item();
assert!(
(20..=40).contains(&sample_points.point_domain.positions().len()),
"actual len {}",
@@ -1383,6 +1432,7 @@ mod test {
#[tokio::test]
async fn spline() {
let spline = splines_from_points(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
let spline = spline.one_item();
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.)]);
}
@@ -1391,6 +1441,7 @@ mod test {
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;
let sample_points = sample_points.one_item();
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.)]
@@ -1408,6 +1459,8 @@ mod test {
async fn bevel_rect() {
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 8);
assert_eq!(beveled.segment_domain.ids().len(), 8);
@@ -1429,6 +1482,7 @@ mod test {
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false);
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3);
@@ -1449,7 +1503,8 @@ mod test {
let mut vector_data = VectorData::from_subpath(source);
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
vector_data.transform = transform;
let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(vector_data), 5.).await;
let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(VectorDataTable::new(vector_data)), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3);
@@ -1468,6 +1523,8 @@ mod test {
async fn bevel_too_high() {
let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false);
let beveled = super::bevel(Footprint::default(), &vector_node(source), 999.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);
@@ -1487,6 +1544,7 @@ mod test {
let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO);
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false);
let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await;
let beveled = beveled.one_item();
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);