diff --git a/node-graph/nodes/repeat/src/repeat_nodes.rs b/node-graph/nodes/repeat/src/repeat_nodes.rs index 66801603f8..eb43dac53a 100644 --- a/node-graph/nodes/repeat/src/repeat_nodes.rs +++ b/node-graph/nodes/repeat/src/repeat_nodes.rs @@ -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 + Default + Send + Clone + 'static>( - ctx: impl Ctx + DeriveCtx, - #[implementations( - Context -> List, - Context -> List, - Context -> List>, - Context -> List, - Context -> List, - )] - content: impl Node, Output = List>, +/// 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( + ctx: impl Ctx + DeriveCtx + ExtractIndex, + content: impl Node, Output = (T, Attr)>, #[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 + Default + Send + Clone + 'static>( #[default(5)] #[hard(1..)] count: u32, -) -> Result, Interrupt> { +) -> Result)>, 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 { + 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::, _>::new(ValueNode(single_default_vector())); - let layout = Node::::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::, _>::new(ValueNode(single_default_vector())); - let layout = Node::::layout(&lift).clone(); - let repeated = super::repeat_array(&ctx, ElementLazyInput::, _>::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);