This commit is contained in:
Timon
2026-01-23 22:40:05 +00:00
committed by Keavon Chambers
parent a979ccd5bd
commit 51adb6ea1f
2 changed files with 250 additions and 6 deletions

View File

@@ -5,7 +5,7 @@ use core_types::blending::BlendMode;
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, List, ListDyn};
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
use core_types::transform::{Footprint, Transform};
use core_types::transform::{ApplyTransform, Footprint, Transform};
use core_types::uuid::NodeId;
use core_types::{
ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, CloneVarArgs, Color, Context, Ctx, ExtractAll,
@@ -38,6 +38,9 @@ trait VectorListIterMut {
fn for_each_vector_mut(&mut self, f: impl FnMut(&mut Vector, DAffine2));
fn vector_count(&self) -> usize;
/// Iterates over a mutable reference to each vector item's transform, used by the transform-assignment nodes.
fn vector_transforms_iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut DAffine2>;
}
impl VectorListIterMut for List<Graphic> {
@@ -54,6 +57,13 @@ impl VectorListIterMut for List<Graphic> {
fn vector_count(&self) -> usize {
self.iter_element_values().filter_map(|element| element.as_vector()).map(|list| list.len()).sum()
}
fn vector_transforms_iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut DAffine2> {
// Grab only the direct children
self.iter_element_values_mut()
.filter_map(|graphic| graphic.as_vector_mut())
.flat_map(|vector_list| vector_list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM))
}
}
impl VectorListIterMut for List<Vector> {
@@ -67,6 +77,10 @@ impl VectorListIterMut for List<Vector> {
fn vector_count(&self) -> usize {
self.len()
}
fn vector_transforms_iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut DAffine2> {
self.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM)
}
}
/// Uniquely sets the fill and/or stroke style of every vector element to individual colors sampled along a chosen gradient.
@@ -134,6 +148,239 @@ where
content
}
#[node_macro::node(category("Instancing"), path(core_types::vector))]
async fn assign_rotations<T: VectorListIterMut + 'n + Send>(
_: impl Ctx,
#[implementations(List<Graphic>, List<Vector>)] mut content: T,
#[range((0., 360.))]
#[default(360.)]
angle: Angle,
/// Whether to reverse the order.
reverse: bool,
/// Whether to randomize the rotation selection for each element.
#[default(true)]
randomize: bool,
/// The seed used for randomization.
seed: SeedValue,
#[range((-50., 50.))] bias: f64,
/// The number of elements to span across before repeating. A 0 value will span the entire range once.
repeat_every: u32,
) -> T {
let count = content.vector_count() as u32;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed as u64);
let apply = |(index, transform): (usize, &mut DAffine2)| {
let factor = match randomize {
true => {
if bias.abs() < 1e-6 {
rng.random::<f64>()
} else {
(1. - rng.random::<f64>() * (1. - 2_f64.powf(bias))).log2() / bias
}
}
false => match repeat_every {
0 => index as f64 / (count - 1).max(1) as f64,
1 => 0.,
_ => index as f64 % repeat_every as f64 / (repeat_every - 1) as f64,
},
};
let rotation_transform = DAffine2::from_angle(factor * angle.to_radians());
transform.left_apply_transform(&rotation_transform);
};
if reverse {
content.vector_transforms_iter_mut().rev().enumerate().for_each(apply);
} else {
content.vector_transforms_iter_mut().enumerate().for_each(apply);
}
content
}
#[node_macro::node(category("Instancing"), path(core_types::vector))]
async fn assign_translations<T: VectorListIterMut + 'n + Send>(
_: impl Ctx,
#[implementations(List<Graphic>, List<Vector>)] mut content: T,
x_min: f64,
x_max: f64,
y_min: f64,
y_max: f64,
/// Whether to reverse the order.
reverse: bool,
/// Whether to randomize the translation selection for each element.
#[default(true)]
randomize: bool,
/// The seed used for randomization.
seed: SeedValue,
#[range((-50., 50.))] x_bias: f64,
#[range((-50., 50.))] y_bias: f64,
/// The number of elements to span across before repeating. A 0 value will span the entire range once.
repeat_every: u32,
) -> T {
let count = content.vector_count() as u32;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed as u64);
let apply = |(index, transform): (usize, &mut DAffine2)| {
let (factor_x, factor_y) = match randomize {
true => {
let factor_x = if x_bias.abs() < 1e-6 {
rng.random::<f64>()
} else {
(1. - rng.random::<f64>() * (1. - 2_f64.powf(x_bias))).log2() / x_bias
};
let factor_y = if y_bias.abs() < 1e-6 {
rng.random::<f64>()
} else {
(1. - rng.random::<f64>() * (1. - 2_f64.powf(y_bias))).log2() / y_bias
};
(factor_x, factor_y)
}
false => match repeat_every {
0 => {
let factor = index as f64 / (count - 1).max(1) as f64;
(factor, factor)
}
1 => (0., 0.),
_ => {
let factor = index as f64 % repeat_every as f64 / (repeat_every - 1) as f64;
(factor, factor)
}
},
};
let translation = DVec2::new(x_min + (x_max - x_min) * factor_x, y_min + (y_max - y_min) * factor_y);
let translation_transform = DAffine2::from_translation(translation);
transform.left_apply_transform(&translation_transform);
};
if reverse {
content.vector_transforms_iter_mut().rev().enumerate().for_each(apply);
} else {
content.vector_transforms_iter_mut().enumerate().for_each(apply);
}
content
}
// TODO: decide if we want to keep both circular and non-circular versions
#[node_macro::node(category("Instancing"), path(core_types::vector))]
async fn assign_translations_circular<T: VectorListIterMut + 'n + Send>(
_: impl Ctx,
#[implementations(List<Graphic>, List<Vector>)] mut content: T,
radius_min: f64,
radius_max: f64,
#[default(-180.)] angle_min: Angle,
#[default(180.)] angle_max: Angle,
/// Whether to reverse the order.
reverse: bool,
/// Whether to randomize the translation selection for each element.
#[default(true)]
randomize: bool,
/// The seed used for randomization.
seed: SeedValue,
#[range((-50., 50.))] radius_bias: f64,
#[range((-50., 50.))] angle_bias: f64,
/// The number of elements to span across before repeating. A 0 value will span the entire range once.
repeat_every: u32,
) -> T {
let count = content.vector_count() as u32;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed as u64);
let apply = |(index, transform): (usize, &mut DAffine2)| {
let (factor_angle, factor_radius) = match randomize {
true => {
let factor_angle = if angle_bias.abs() < 1e-6 {
rng.random::<f64>()
} else {
(1. - rng.random::<f64>() * (1. - 2_f64.powf(angle_bias))).log2() / angle_bias
};
let factor_radius = if radius_bias.abs() < 1e-6 {
rng.random::<f64>()
} else {
(1. - rng.random::<f64>() * (1. - 2_f64.powf(radius_bias))).log2() / radius_bias
};
(factor_angle, factor_radius)
}
false => match repeat_every {
0 => {
let factor = index as f64 / (count - 1).max(1) as f64;
(factor, factor)
}
1 => (0., 0.),
_ => {
let factor = index as f64 % repeat_every as f64 / (repeat_every - 1) as f64;
(factor, factor)
}
},
};
let angle = factor_angle * (angle_max.to_radians() - angle_min.to_radians()) + angle_min.to_radians();
let radius = radius_min + (radius_max - radius_min) * factor_radius.sqrt();
let translation = DVec2::new(angle.cos() * radius, angle.sin() * radius);
let translation_transform = DAffine2::from_translation(translation);
transform.left_apply_transform(&translation_transform);
};
if reverse {
content.vector_transforms_iter_mut().rev().enumerate().for_each(apply);
} else {
content.vector_transforms_iter_mut().enumerate().for_each(apply);
}
content
}
#[node_macro::node(category("Instancing"), path(core_types::vector))]
async fn assign_scales<T: VectorListIterMut + 'n + Send>(
_: impl Ctx,
#[implementations(List<Graphic>, List<Vector>)] mut content: T,
#[default(0.5)] min_scale: f64,
#[default(2.)] max_scale: f64,
/// Whether to reverse the order.
reverse: bool,
/// Whether to randomize the scale selection for each element.
#[default(true)]
randomize: bool,
/// The seed used for randomization.
seed: SeedValue,
#[range((-50., 50.))] bias: f64,
/// The number of elements to span across before repeating. A 0 value will span the entire range once.
repeat_every: u32,
) -> T {
let count = content.vector_count() as u32;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed as u64);
let apply = |(index, transform): (usize, &mut DAffine2)| {
let factor = match randomize {
true => {
if bias.abs() < 1e-6 {
rng.random::<f64>()
} else {
(1. - rng.random::<f64>() * (1. - 2_f64.powf(bias))).log2() / bias
}
}
false => match repeat_every {
0 => index as f64 / (count - 1).max(1) as f64,
1 => 0.,
_ => index as f64 % repeat_every as f64 / (repeat_every - 1) as f64,
},
};
let scale_value = min_scale + (max_scale - min_scale) * factor;
let scale_transform = DAffine2::from_scale(DVec2::splat(scale_value));
transform.apply_transform(&scale_transform);
};
if reverse {
content.vector_transforms_iter_mut().rev().enumerate().for_each(apply);
} else {
content.vector_transforms_iter_mut().enumerate().for_each(apply);
}
content
}
/// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry.
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
async fn fill<F: Into<Fill> + 'n + Send, V: VectorListIterMut + 'n + Send>(