mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Push the repeat array copies onto a derived level
This commit is contained in:
@@ -5,6 +5,7 @@ use core_types::extent::{ExtentIn, LevelIn, ValueIn};
|
||||
use core_types::gpoll::{Extent, GPoll, GraphError, Interrupt};
|
||||
use core_types::list::List;
|
||||
use core_types::registry::types::{Angle, PixelSize};
|
||||
use core_types::attribute::{Attr, Transform as TransformAttr};
|
||||
use core_types::{ATTR_TRANSFORM, Color, Ctx, DeriveCtx, ExtractIndex, InjectVarArgs};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::{Graphic, Vector};
|
||||
@@ -45,17 +46,13 @@ fn repeat_extent(content: ExtentIn<'_>, count: ValueIn<'_, u32>, _reverse: Value
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Repeat"))]
|
||||
pub fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
||||
ctx: impl Ctx + DeriveCtx,
|
||||
#[implementations(
|
||||
Context -> List<Graphic>,
|
||||
Context -> List<Vector>,
|
||||
Context -> List<Raster<CPU>>,
|
||||
Context -> List<Color>,
|
||||
Context -> List<GradientStops>,
|
||||
)]
|
||||
content: impl Node<Context<'_>, Output = List<T>>,
|
||||
/// Each copy evaluates the content within the copy's index pushed in, the
|
||||
/// copy's step transform composed between the lane transform's translation
|
||||
/// and matrix parts.
|
||||
#[node_macro::node(category("Repeat"), extent(repeat_array_extent))]
|
||||
pub fn repeat_array<T>(
|
||||
ctx: impl Ctx + DeriveCtx + ExtractIndex,
|
||||
content: impl Node<Context<'_>, Output = (T, Attr<TransformAttr>)>,
|
||||
#[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: PixelSize,
|
||||
@@ -63,37 +60,34 @@ pub fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
||||
#[default(5)]
|
||||
#[hard(1..)]
|
||||
count: u32,
|
||||
) -> Result<List<T>, Interrupt> {
|
||||
) -> Result<IList<(T, Attr<TransformAttr>)>, Interrupt> {
|
||||
let angle = angle.to_radians();
|
||||
// A single copy has no steps between copies, so the denominator is kept at 1 to avoid `0. / 0.` producing a NaN transform
|
||||
let total = (count - 1).max(1) as f64;
|
||||
let spilled = ctx.index_head();
|
||||
|
||||
let mut result_list = List::new();
|
||||
|
||||
for index in 0..count {
|
||||
let angle = index as f64 * angle / total;
|
||||
let translation = index as f64 * direction / total;
|
||||
let transform = DAffine2::from_angle(angle) * DAffine2::from_translation(translation);
|
||||
|
||||
let mark = core_types::record::stack::sp();
|
||||
let generated_content = content.eval(&ctx.promoted(&spilled, index as u64))?;
|
||||
|
||||
for row_index in 0..generated_content.len() {
|
||||
let Some(mut row) = generated_content.clone_item(row_index) else { continue };
|
||||
|
||||
let local_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let local_translation = DAffine2::from_translation(local_transform.translation);
|
||||
let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
|
||||
*row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix;
|
||||
|
||||
result_list.push(row);
|
||||
}
|
||||
// SAFETY: rows are cloned into result_list and generated_content is owned, so no record borrow into this iteration's frames remains.
|
||||
unsafe { core_types::record::stack::rewind(mark) };
|
||||
let inner = content.inner_extent(ctx)?;
|
||||
let (copy, rest) = ctx.split_innermost(inner);
|
||||
if copy >= count as u64 {
|
||||
return Err(GraphError::past_end().into());
|
||||
}
|
||||
let step_angle = copy as f64 * angle / total;
|
||||
let translation = copy as f64 * direction / total;
|
||||
let transform = DAffine2::from_angle(step_angle) * DAffine2::from_translation(translation);
|
||||
|
||||
Ok(result_list)
|
||||
let mut frame = IndexLink { index: 0, outer: None };
|
||||
let (element, local_transform) = content.eval(&ctx.push_level(&mut frame, copy, rest))?;
|
||||
let local_translation = DAffine2::from_translation(local_transform.translation);
|
||||
let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
|
||||
Ok((element, Attr(local_translation * transform * local_matrix)))
|
||||
}
|
||||
|
||||
/// The pushed level's extent is the copy count; inner levels forward to the
|
||||
/// content, whose extent is taken uniform across copies (queried at copy 0).
|
||||
fn repeat_array_extent(content: ExtentIn<'_>, _direction: ValueIn<'_, DVec2>, _angle: ValueIn<'_, f64>, count: ValueIn<'_, u32>, level: LevelIn) -> GPoll<Extent> {
|
||||
match level.pushed() {
|
||||
true => count.get().map(|count| Extent::Exactly(count as usize)),
|
||||
false => content.at(level),
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Repeat"))]
|
||||
@@ -259,36 +253,6 @@ mod test {
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeat_array_spaces_copies_along_the_direction() {
|
||||
test_ctx!(ctx, cell);
|
||||
let direction = DVec2::new(1.5, 0.);
|
||||
let count = 3;
|
||||
|
||||
let lift = RecordLift::<List<Vector>, _>::new(ValueNode(single_default_vector()));
|
||||
let layout = Node::<ContextImpl>::layout(&lift).clone();
|
||||
let repeated = super::repeat_array(&ctx, ElementLazyInput::new(&lift, &cell, 0, &layout), direction, 0., count).unwrap();
|
||||
|
||||
assert_eq!(repeated.len(), count as usize);
|
||||
for (index, translation) in row_translations(&repeated, ATTR_TRANSFORM).into_iter().enumerate() {
|
||||
let expected = index as f64 * direction / (count - 1) as f64;
|
||||
assert!(translation.abs_diff_eq(expected, 1e-10), "copy {index}: {translation:?} != {expected:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeat_array_single_copy_stays_finite() {
|
||||
test_ctx!(ctx, cell);
|
||||
|
||||
let lift = RecordLift::<List<Vector>, _>::new(ValueNode(single_default_vector()));
|
||||
let layout = Node::<ContextImpl>::layout(&lift).clone();
|
||||
let repeated = super::repeat_array(&ctx, ElementLazyInput::<List<Vector>, _>::new(&lift, &cell, 0, &layout), DVec2::new(12., 10.), 45., 1).unwrap();
|
||||
|
||||
assert_eq!(repeated.len(), 1);
|
||||
let transform: DAffine2 = repeated.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
assert!(transform.abs_diff_eq(DAffine2::IDENTITY, 1e-10), "single copy must not divide by zero: {transform:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeat_radial_rotates_copies_around_the_center() {
|
||||
test_ctx!(ctx, cell);
|
||||
|
||||
Reference in New Issue
Block a user