Improve instancing nodes (make them output group data, add 'Instance Repeat', fix Flatten Vector Elements click targets, and more) (#2610)

* Improve instancing nodes (make them output group data, add 'Instance Repeat', fix Flatten Vector Elements click targets, and more)

* Fix test?

* Fix more tests?

* Fix moar test??

* Clean up instance method naming
This commit is contained in:
Keavon Chambers
2025-04-22 17:55:57 -07:00
committed by GitHub
parent a29802de36
commit ac9fb2b02d
33 changed files with 811 additions and 529 deletions

View File

@@ -1,39 +1,74 @@
use crate::instances::Instance;
use crate::vector::{VectorData, VectorDataTable};
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, OwnedContextImpl};
use glam::{DAffine2, DVec2};
use crate::instances::{InstanceRef, Instances};
use crate::raster::Color;
use crate::raster::image::ImageFrameTable;
use crate::transform::TransformMut;
use crate::vector::VectorDataTable;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, OwnedContextImpl};
use glam::DVec2;
#[node_macro::node(name("Instance on Points"), category("Vector: Shape"), path(graphene_core::vector))]
async fn instance_on_points(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
async fn instance_on_points<T: Into<GraphicElement> + Default + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
points: VectorDataTable,
#[implementations(Context -> VectorDataTable)] instance_node: impl Node<'n, Context<'static>, Output = VectorDataTable>,
) -> VectorDataTable {
let mut result = VectorDataTable::empty();
#[implementations(Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> ImageFrameTable<Color>)] instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
reverse: bool,
) -> GraphicGroupTable {
let mut result_table = GraphicGroupTable::empty();
for Instance { instance: points, transform, .. } in points.instances() {
for (index, &point) in points.point_domain.positions().iter().enumerate() {
for InstanceRef { instance: points, transform, .. } in points.instance_ref_iter() {
let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point);
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
let instanced = instance_node.eval(new_ctx.into_context()).await;
let generated_instance = instance.eval(new_ctx.into_context()).await;
for instanced in instanced.instances() {
let instanced = result.push_instance(instanced);
*instanced.transform *= DAffine2::from_translation(transformed_point);
for mut instanced in generated_instance.instance_iter() {
instanced.transform.translate(transformed_point);
result_table.push(instanced.to_graphic_element());
}
};
let range = points.point_domain.positions().iter().enumerate();
if reverse {
for (index, &point) in range.rev() {
iteration(index, point).await;
}
} else {
for (index, &point) in range {
iteration(index, point).await;
}
}
}
// TODO: Remove once we support empty tables, currently this is here to avoid crashing
if result.is_empty() {
return VectorDataTable::new(VectorData::empty());
}
result
result_table
}
#[node_macro::node(category("Attributes"), path(graphene_core::vector))]
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_repeat<T: Into<GraphicElement> + Default + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> ImageFrameTable<Color>)] instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
#[default(1)] count: u64,
reverse: bool,
) -> GraphicGroupTable {
let count = count.max(1) as usize;
let mut result_table = GraphicGroupTable::empty();
for index in 0..count {
let index = if reverse { count - index - 1 } else { index };
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
let generated_instance = instance.eval(new_ctx.into_context()).await;
for instanced in generated_instance.instance_iter() {
result_table.push(instanced.to_graphic_element());
}
}
result_table
}
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
match ctx.vararg(0).map(|dynamic| dynamic.downcast_ref::<DVec2>()) {
Ok(Some(position)) => return *position,
@@ -43,7 +78,7 @@ async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
Default::default()
}
#[node_macro::node(category("Attributes"), path(graphene_core::vector))]
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_index(ctx: impl Ctx + ExtractIndex) -> f64 {
match ctx.try_index() {
Some(index) => return index as f64,
@@ -87,10 +122,17 @@ mod test {
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
let points = VectorDataTable::new(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
let repeated = super::instance_on_points(owned, points, &rect).await;
let repeated = super::instance_on_points(owned, points, &rect, false).await;
assert_eq!(repeated.len(), positions.len());
for (position, instanced) in positions.into_iter().zip(repeated.instances()) {
let bounds = instanced.instance.bounding_box_with_transform(*instanced.transform).unwrap();
for (position, instanced) in positions.into_iter().zip(repeated.instance_ref_iter()) {
let bounds = instanced
.instance
.as_vector_data()
.unwrap()
.one_instance_ref()
.instance
.bounding_box_with_transform(*instanced.transform)
.unwrap();
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
assert_eq!((bounds[1] - bounds[0]).x, position.y);
}

View File

@@ -253,9 +253,9 @@ fn isometric_grid_test() {
// Works properly
let grid = grid((), (), GridType::Isometric, 10., (30., 30.).into(), 5, 5);
assert_eq!(grid.one_instance().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance().instance.segment_bezier_iter() {
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
assert!(
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
@@ -268,9 +268,9 @@ fn isometric_grid_test() {
#[test]
fn skew_isometric_grid_test() {
let grid = grid((), (), GridType::Isometric, 10., (40., 30.).into(), 5, 5);
assert_eq!(grid.one_instance().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance().instance.segment_bezier_iter() {
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
let vector = bezier.start - bezier.end;
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;

View File

@@ -433,29 +433,29 @@ impl VectorData {
}
}
pub fn concat(&mut self, other: &Self, transform: DAffine2, node_id: u64) {
let point_map = other
pub fn concat(&mut self, additional: &Self, transform_of_additional: DAffine2, collision_hash_seed: u64) {
let point_map = additional
.point_domain
.ids()
.iter()
.filter(|id| self.point_domain.ids().contains(id))
.map(|&old| (old, old.generate_from_hash(node_id)))
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
.collect::<HashMap<_, _>>();
let segment_map = other
let segment_map = additional
.segment_domain
.ids()
.iter()
.filter(|id| self.segment_domain.ids().contains(id))
.map(|&old| (old, old.generate_from_hash(node_id)))
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
.collect::<HashMap<_, _>>();
let region_map = other
let region_map = additional
.region_domain
.ids()
.iter()
.filter(|id| self.region_domain.ids().contains(id))
.map(|&old| (old, old.generate_from_hash(node_id)))
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
.collect::<HashMap<_, _>>();
let id_map = IdMap {
@@ -465,14 +465,14 @@ impl VectorData {
region_map,
};
self.point_domain.concat(&other.point_domain, transform, &id_map);
self.segment_domain.concat(&other.segment_domain, transform, &id_map);
self.region_domain.concat(&other.region_domain, transform, &id_map);
self.point_domain.concat(&additional.point_domain, transform_of_additional, &id_map);
self.segment_domain.concat(&additional.segment_domain, transform_of_additional, &id_map);
self.region_domain.concat(&additional.region_domain, transform_of_additional, &id_map);
// TODO: properly deal with fills such as gradients
self.style = other.style.clone();
self.style = additional.style.clone();
self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied());
self.colinear_manipulators.extend(additional.colinear_manipulators.iter().copied());
}
}

View File

@@ -181,6 +181,14 @@ impl PointDomain {
}
}
pub fn len(&self) -> usize {
self.id.len()
}
pub fn is_empty(&self) -> bool {
self.id.is_empty()
}
/// Iterate over point IDs and positions
pub fn iter(&self) -> impl Iterator<Item = (PointId, DVec2)> + '_ {
self.ids().iter().copied().zip(self.positions().iter().copied())

View File

@@ -424,7 +424,7 @@ impl core::hash::Hash for VectorModification {
/// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable {
let vector_data_transform = *vector_data.one_instance().transform;
let vector_data_transform = *vector_data.one_instance_ref().transform;
let vector_data = vector_data.one_instance_mut().instance;
modification.apply(vector_data);

View File

@@ -2,7 +2,8 @@ use super::algorithms::offset_subpath::offset_subpath;
use super::misc::CentroidType;
use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
use crate::instances::{InstanceMut, Instances};
use crate::instances::{Instance, InstanceMut, Instances};
use crate::raster::image::ImageFrameTable;
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, SeedValue};
use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, Transform, TransformMut};
@@ -11,8 +12,10 @@ use crate::vector::style::{LineCap, LineJoin};
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
use bezier_rs::{Join, ManipulatorGroup, Subpath, SubpathTValue, TValue};
use core::f64::consts::PI;
use core::hash::{Hash, Hasher};
use glam::{DAffine2, DVec2};
use rand::{Rng, SeedableRng};
use std::collections::hash_map::DefaultHasher;
/// Implemented for types that can be converted to an iterator of vector data.
/// Used for the fill and stroke node so they can be used on VectorData or GraphicGroup
@@ -23,15 +26,15 @@ trait VectorDataTableIterMut {
impl VectorDataTableIterMut for GraphicGroupTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = InstanceMut<VectorData>> {
// Grab only the direct children
self.instances_mut()
self.instance_mut_iter()
.filter_map(|element| element.instance.as_vector_data_mut())
.flat_map(move |vector_data| vector_data.instances_mut())
.flat_map(move |vector_data| vector_data.instance_mut_iter())
}
}
impl VectorDataTableIterMut for VectorDataTable {
fn vector_iter_mut(&mut self) -> impl Iterator<Item = InstanceMut<VectorData>> {
self.instances_mut()
self.instance_mut_iter()
}
}
@@ -198,7 +201,7 @@ where
async fn repeat<I: 'n + Send>(
_: impl Ctx,
// TODO: Implement other GraphicElementRendered types.
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
#[default(100., 100.)]
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
direction: DVec2,
@@ -221,13 +224,14 @@ where
for index in 0..instances {
let angle = index as f64 * angle / total;
let translation = index as f64 * direction / total;
let modification = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
let transform = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
let mut new_graphic_element = instance.to_graphic_element().clone();
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
let new_instance = result_table.push(new_graphic_element);
*new_instance.transform = modification;
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
}
result_table
@@ -237,7 +241,7 @@ where
async fn circular_repeat<I: 'n + Send>(
_: impl Ctx,
// TODO: Implement other GraphicElementRendered types.
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
angle_offset: Angle,
#[default(5)] radius: f64,
#[default(5)] instances: IntegerCount,
@@ -256,13 +260,14 @@ where
for index in 0..instances {
let rotation = DAffine2::from_angle((std::f64::consts::TAU / instances as f64) * index as f64 + angle_offset.to_radians());
let modification = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
let transform = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
let mut new_graphic_element = instance.to_graphic_element().clone();
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
let new_instance = result_table.push(new_graphic_element);
*new_instance.transform = modification;
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
}
result_table
@@ -274,7 +279,7 @@ async fn copy_to_points<I: 'n + Send>(
points: VectorDataTable,
#[expose]
/// Artwork to be copied and placed at each point.
#[implementations(VectorDataTable, GraphicGroupTable)]
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)]
instance: Instances<I>,
/// Minimum range of randomized sizes given to each instance.
#[default(1)]
@@ -301,7 +306,7 @@ where
Instances<I>: GraphicElementRendered,
{
let points_transform = points.transform();
let points_list = points.instances().flat_map(|element| element.instance.point_domain.positions());
let points_list = points.instance_ref_iter().flat_map(|element| element.instance.point_domain.positions());
let random_scale_difference = random_scale_max - random_scale_min;
@@ -316,7 +321,7 @@ where
let mut result_table = GraphicGroupTable::default();
for (index, &point) in points_list.into_iter().enumerate() {
for &point in points_list.into_iter() {
let center_transform = DAffine2::from_translation(instance_center);
let translation = points_transform.transform_point2(point);
@@ -342,11 +347,14 @@ where
random_scale_min
};
let mut new_graphic_element = instance.to_graphic_element().clone();
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform;
let new_instance = result_table.push(new_graphic_element);
*new_instance.transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform;
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
}
result_table
@@ -355,7 +363,7 @@ where
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn mirror<I: 'n + Send>(
_: impl Ctx,
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
#[default(0., 0.)] center: DVec2,
#[range((-90., 90.))] angle: Angle,
#[default(true)] keep_original: bool,
@@ -382,20 +390,25 @@ where
);
// Apply reflection around the center point
let modification = DAffine2::from_translation(mirror_center) * reflection * DAffine2::from_translation(-mirror_center);
let transform = DAffine2::from_translation(mirror_center) * reflection * DAffine2::from_translation(-mirror_center);
// Add original instance depending on the keep_original flag
if keep_original {
result_table.push(instance.to_graphic_element());
result_table.push(Instance {
instance: instance.to_graphic_element().clone(),
transform: DAffine2::IDENTITY,
alpha_blending: Default::default(),
source_node_id: None,
});
}
// Create and add mirrored instance
let mut mirrored_element = instance.to_graphic_element();
mirrored_element.new_ids_from_hash(None);
// Apply the transformation to the mirrored instance
let mirrored_instance = result_table.push(mirrored_element);
*mirrored_instance.transform = modification;
result_table.push(Instance {
instance: instance.to_graphic_element(),
transform,
alpha_blending: Default::default(),
source_node_id: None,
});
result_table
}
@@ -417,7 +430,7 @@ async fn round_corners(
) -> VectorDataTable {
let source_transform = source.transform();
let source_transform_inverse = source_transform.inverse();
let source = source.one_instance().instance;
let source = source.one_instance_ref().instance;
let upstream_graphics_group = source.upstream_graphic_group.clone();
// Flip the roundness to help with user intuition
@@ -517,7 +530,7 @@ async fn spatial_merge_by_distance(
distance: f64,
) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let point_count = vector_data.point_domain.positions().len();
// Find min x and y for grid cell normalization
@@ -638,10 +651,10 @@ async fn spatial_merge_by_distance(
#[node_macro::node(category("Debug"), path(graphene_core::vector))]
async fn box_warp(_: impl Ctx, vector_data: VectorDataTable, #[expose] rectangle: VectorDataTable) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance.clone();
let vector_data = vector_data.one_instance_ref().instance.clone();
let target_transform = rectangle.transform();
let target = rectangle.one_instance().instance;
let target = rectangle.one_instance_ref().instance;
// Get the bounding box of the source vector data
let source_bbox = vector_data.bounding_box_with_transform(vector_data_transform).unwrap_or([DVec2::ZERO, DVec2::ONE]);
@@ -727,7 +740,7 @@ async fn remove_handles(
max_handle_distance: f64,
) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let mut vector_data = vector_data.one_instance().instance.clone();
let mut vector_data = vector_data.one_instance_ref().instance.clone();
for (_, handles, start, end) in vector_data.segment_domain.handles_mut() {
// Only convert to linear if handles are within the threshold distance
@@ -773,7 +786,7 @@ async fn generate_handles(
curvature: f64,
) -> VectorDataTable {
let source_transform = source.transform();
let source = source.one_instance().instance;
let source = source.one_instance_ref().instance;
let mut result = VectorData::empty();
result.style = source.style.clone();
@@ -955,7 +968,7 @@ async fn generate_handles(
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let mut result = vector_data
.bounding_box()
@@ -972,7 +985,7 @@ async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTa
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn dimensions(_: impl Ctx, vector_data: VectorDataTable) -> DVec2 {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
vector_data
.bounding_box_with_transform(vector_data_transform)
.map(|[top_left, bottom_right]| bottom_right - top_left)
@@ -982,7 +995,7 @@ async fn dimensions(_: impl Ctx, vector_data: VectorDataTable) -> DVec2 {
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let subpaths = vector_data.stroke_bezier_paths();
let mut result = VectorData::empty();
@@ -1018,7 +1031,7 @@ async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, l
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let stroke = vector_data.style.stroke().clone().unwrap_or_default();
let bezpaths = vector_data.stroke_bezpath_iter();
@@ -1072,15 +1085,20 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
// 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.
fn flatten_group(graphic_group_table: &GraphicGroupTable, output: &mut InstanceMut<VectorData>) {
for current_element in graphic_group_table.instances() {
for (group_index, current_element) in graphic_group_table.instance_ref_iter().enumerate() {
match current_element.instance {
GraphicElement::VectorData(vector_data_table) => {
// Loop through every row of the VectorDataTable and concatenate each instance's subpath into the output VectorData instance.
for vector_data_instance in vector_data_table.instances() {
for (vector_index, vector_data_instance) in vector_data_table.instance_ref_iter().enumerate() {
let other = vector_data_instance.instance;
let transform = *current_element.transform * *vector_data_instance.transform;
let node_id = current_element.source_node_id.map(|node_id| node_id.0).unwrap_or_default();
output.instance.concat(other, transform, node_id);
let mut hasher = DefaultHasher::new();
(group_index, vector_index, node_id).hash(&mut hasher);
let collision_hash_seed = hasher.finish();
output.instance.concat(other, transform, collision_hash_seed);
// Use the last encountered style as the output style
output.instance.style = vector_data_instance.instance.style.clone();
@@ -1088,7 +1106,7 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
}
GraphicElement::GraphicGroup(graphic_group) => {
let mut graphic_group = graphic_group.clone();
for instance in graphic_group.instances_mut() {
for instance in graphic_group.instance_mut_iter() {
*instance.transform = *current_element.transform * *instance.transform;
}
@@ -1100,7 +1118,7 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
}
let mut output_table = VectorDataTable::default();
let Some(mut output) = output_table.instances_mut().next() else { return output_table };
let Some(mut output) = output_table.instance_mut_iter().next() else { return output_table };
flatten_group(&graphic_group_input, &mut output);
@@ -1113,7 +1131,7 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
let spacing = spacing.max(0.01);
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
// Create an iterator over the bezier segments with enumeration and peeking capability.
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
@@ -1274,7 +1292,7 @@ async fn position_on_path(
let euclidian = !parameterized_distance;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
let progress = progress.clamp(0., subpaths_count);
@@ -1306,7 +1324,7 @@ async fn tangent_on_path(
let euclidian = !parameterized_distance;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
let progress = progress.clamp(0., subpaths_count);
@@ -1340,7 +1358,7 @@ async fn poisson_disk_points(
seed: SeedValue,
) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let mut result = VectorData::empty();
@@ -1391,7 +1409,7 @@ async fn poisson_disk_points(
#[node_macro::node(category(""), path(graphene_core::vector))]
async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec<f64> {
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
vector_data
.segment_bezier_iter()
@@ -1447,7 +1465,7 @@ async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTabl
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let mut vector_data = vector_data.one_instance().instance.clone();
let mut vector_data = vector_data.one_instance_ref().instance.clone();
let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();
@@ -1500,14 +1518,14 @@ async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)]
async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDataTable, #[default(0.5)] time: Fraction, #[min(0.)] start_index: IntegerCount) -> VectorDataTable {
let time = time.clamp(0., 1.);
let source_alpha_blending = source.one_instance().alpha_blending;
let target_alpha_blending = target.one_instance().alpha_blending;
let source_alpha_blending = source.one_instance_ref().alpha_blending;
let target_alpha_blending = target.one_instance_ref().alpha_blending;
let source_transform = source.transform();
let target_transform = target.transform();
let source = source.one_instance().instance;
let target = target.one_instance().instance;
let source = source.one_instance_ref().instance;
let target = target.one_instance_ref().instance;
let mut result = VectorDataTable::default();
@@ -1699,7 +1717,7 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
let source_transform = source.transform();
let source = source.one_instance().instance;
let source = source.one_instance_ref().instance;
let mut result = VectorDataTable::new(bevel_algorithm(source.clone(), source_transform, distance));
*result.transform_mut() = source_transform;
@@ -1709,7 +1727,7 @@ fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length)
#[node_macro::node(name("Merge by Distance"), category("Vector"), path(graphene_core::vector))]
fn merge_by_distance(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
let source_transform = source.transform();
let mut source = source.one_instance().instance.clone();
let mut source = source.one_instance_ref().instance.clone();
source.merge_by_distance(distance);
@@ -1725,7 +1743,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<
let vector_data = vector_data.eval(new_ctx).await;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
let mut area = 0.;
let scale = vector_data_transform.decompose_scale();
@@ -1742,7 +1760,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N
let vector_data = vector_data.eval(new_ctx).await;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance().instance;
let vector_data = vector_data.one_instance_ref().instance;
if centroid_type == CentroidType::Area {
let mut area = 0.;
@@ -1814,7 +1832,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(), repeated).await;
let vector_data = vector_data.instances().next().unwrap().instance;
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
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);
@@ -1826,7 +1844,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(), repeated).await;
let vector_data = vector_data.instances().next().unwrap().instance;
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
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);
@@ -1836,7 +1854,7 @@ mod test {
async fn circular_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(), repeated).await;
let vector_data = vector_data.instances().next().unwrap().instance;
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
assert_eq!(vector_data.region_bezier_paths().count(), 8);
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
@@ -1851,7 +1869,7 @@ mod test {
#[tokio::test]
async fn bounding_box() {
let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await;
let bounding_box = bounding_box.instances().next().unwrap().instance;
let bounding_box = bounding_box.instance_ref_iter().next().unwrap().instance;
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.),]);
@@ -1865,7 +1883,7 @@ mod test {
}
.eval(Footprint::default())
.await;
let bounding_box = bounding_box.instances().next().unwrap().instance;
let bounding_box = bounding_box.instance_ref_iter().next().unwrap().instance;
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
let expected_bounding_box = [DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.)];
@@ -1882,7 +1900,7 @@ mod test {
let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await;
let flatten_vector_elements = super::flatten_vector_elements(Footprint::default(), copy_to_points).await;
let flattened_copy_to_points = flatten_vector_elements.instances().next().unwrap().instance;
let flattened_copy_to_points = flatten_vector_elements.instance_ref_iter().next().unwrap().instance;
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
@@ -1898,7 +1916,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, vec![100.]).await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
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}");
@@ -1908,7 +1926,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, vec![100.]).await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
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}");
@@ -1923,7 +1941,7 @@ mod test {
0,
)
.await;
let sample_points = sample_points.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
assert!(
(20..=40).contains(&sample_points.point_domain.positions().len()),
"actual len {}",
@@ -1942,7 +1960,7 @@ mod test {
#[tokio::test]
async fn spline() {
let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
let spline = spline.instances().next().unwrap().instance;
let spline = spline.instance_ref_iter().next().unwrap().instance;
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.)]);
}
@@ -1951,7 +1969,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.instances().next().unwrap().instance;
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
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.)]
@@ -1974,7 +1992,7 @@ 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.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 8);
assert_eq!(beveled.segment_domain.ids().len(), 8);
@@ -1997,7 +2015,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((), vector_node(source), 5.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3);
@@ -2021,7 +2039,7 @@ mod test {
*vector_data_table.one_instance_mut().transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 4);
assert_eq!(beveled.segment_domain.ids().len(), 3);
@@ -2039,7 +2057,7 @@ 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.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);
@@ -2060,7 +2078,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.);
let beveled = beveled.instances().next().unwrap().instance;
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
assert_eq!(beveled.point_domain.positions().len(), 6);
assert_eq!(beveled.segment_domain.ids().len(), 5);