@@ -5,6 +5,7 @@ use core_types::registry::types::{Angle, IntegerCount, Length, Multiplier, Perce
55use core_types:: table:: { Table , TableRow , TableRowMut } ;
66use core_types:: transform:: { Footprint , Transform } ;
77use core_types:: { CloneVarArgs , Color , Context , Ctx , ExtractAll , ExtractVarArgs , OwnedContextImpl } ;
8+ use dyn_any:: DynAny ;
89use glam:: { DAffine2 , DVec2 } ;
910use graphic_types:: Vector ;
1011use graphic_types:: raster_types:: { CPU , GPU , Raster } ;
@@ -225,8 +226,32 @@ where
225226 content
226227}
227228
228- #[ node_macro:: node( category( "Instancing" ) , path( core_types:: vector) ) ]
229- async fn repeat < I : ' n + Send + Clone > (
229+ #[ derive( Default , Debug , Clone , Copy , PartialEq , Eq , serde:: Serialize , serde:: Deserialize , Hash , DynAny , node_macro:: ChoiceType ) ]
230+ #[ widget( Radio ) ]
231+ pub enum RepeatSpacingMethod {
232+ #[ default]
233+ #[ serde( rename = "span" ) ]
234+ Span ,
235+ #[ serde( rename = "envelope" ) ]
236+ Envelope ,
237+ #[ serde( rename = "pitch" ) ]
238+ Pitch ,
239+ #[ serde( rename = "gap" ) ]
240+ Gap ,
241+ }
242+
243+ #[ derive( Default , Debug , Clone , Copy , PartialEq , Eq , serde:: Serialize , serde:: Deserialize , Hash , DynAny , node_macro:: ChoiceType ) ]
244+ #[ widget( Radio ) ]
245+ pub enum AngularSpacingMethod {
246+ #[ default]
247+ #[ serde( rename = "span" ) ]
248+ Span ,
249+ #[ serde( rename = "pitch" ) ]
250+ Pitch ,
251+ }
252+
253+ #[ node_macro:: node( category( "Instancing" ) , path( graphene_core:: vector) , properties( "repeat_properties" ) ) ]
254+ async fn repeat < I : ' n + Send + Clone + BoundingBox > (
230255 _: impl Ctx ,
231256 // TODO: Implement other graphical types.
232257 #[ implementations( Table <Graphic >, Table <Vector >, Table <Raster <CPU >>, Table <Color >, Table <GradientStops >) ] instance : Table < I > ,
@@ -235,16 +260,38 @@ async fn repeat<I: 'n + Send + Clone>(
235260 direction : PixelSize ,
236261 angle : Angle ,
237262 #[ default( 5 ) ] count : IntegerCount ,
263+ #[ default( RepeatSpacingMethod :: Span ) ] spacing_method : RepeatSpacingMethod ,
238264) -> Table < I > {
239265 let angle = angle. to_radians ( ) ;
240266 let count = count. max ( 1 ) ;
241267 let total = ( count - 1 ) as f64 ;
268+ let direction_normalized = direction. normalize ( ) ;
269+
270+ let width = if matches ! ( spacing_method, RepeatSpacingMethod :: Envelope | RepeatSpacingMethod :: Gap ) {
271+ match instance. bounding_box ( DAffine2 :: IDENTITY , false ) {
272+ RenderBoundingBox :: Rectangle ( [ min, max] ) => {
273+ let size = max - min;
274+ let dir_abs = direction_normalized. abs ( ) ;
275+ size. x * dir_abs. x + size. y * dir_abs. y
276+ }
277+ _ => 0.0 ,
278+ }
279+ } else {
280+ 0.0
281+ } ;
282+
283+ let ( pitch, offset) = match spacing_method {
284+ RepeatSpacingMethod :: Span => ( direction. length ( ) / total. max ( 1. ) , DVec2 :: ZERO ) ,
285+ RepeatSpacingMethod :: Envelope => ( ( direction. length ( ) - width) / total. max ( 1. ) , width / 2. * direction_normalized) ,
286+ RepeatSpacingMethod :: Pitch => ( direction. length ( ) , DVec2 :: ZERO ) ,
287+ RepeatSpacingMethod :: Gap => ( direction. length ( ) + width, DVec2 :: ZERO ) ,
288+ } ;
242289
243290 let mut result_table = Table :: new ( ) ;
244291
245292 for index in 0 ..count {
246- let angle = index as f64 * angle / total;
247- let translation = index as f64 * direction / total ;
293+ let angle = index as f64 * angle / total. max ( 1. ) ;
294+ let translation = offset + index as f64 * pitch * direction_normalized ;
248295 let transform = DAffine2 :: from_angle ( angle) * DAffine2 :: from_translation ( translation) ;
249296
250297 for row in instance. iter ( ) {
@@ -261,22 +308,34 @@ async fn repeat<I: 'n + Send + Clone>(
261308 result_table
262309}
263310
264- #[ node_macro:: node( category( "Instancing" ) , path( core_types :: vector) ) ]
311+ #[ node_macro:: node( category( "Instancing" ) , path( graphene_core :: vector) , properties ( "circular_repeat_properties" ) ) ]
265312async fn circular_repeat < I : ' n + Send + Clone > (
266313 _: impl Ctx ,
267314 #[ implementations( Table <Graphic >, Table <Vector >, Table <Raster <CPU >>, Table <Color >, Table <GradientStops >) ] instance : Table < I > ,
268- start_angle : Angle ,
315+ #[ default( 0. ) ] start_angle : Angle ,
316+ #[ default( 360. ) ] end_angle : Angle ,
269317 #[ unit( " px" ) ]
270318 #[ default( 5 ) ]
271319 radius : f64 ,
272320 #[ default( 5 ) ] count : IntegerCount ,
321+ #[ default( AngularSpacingMethod :: Span ) ] angular_spacing_method : AngularSpacingMethod ,
273322) -> Table < I > {
274323 let count = count. max ( 1 ) ;
324+ let start_rad = start_angle. to_radians ( ) ;
325+ let end_rad = end_angle. to_radians ( ) ;
326+ let total_angle = end_rad - start_rad;
327+ let total = ( count - 1 ) as f64 ;
328+
329+ let angular_pitch = match angular_spacing_method {
330+ AngularSpacingMethod :: Span => total_angle / total. max ( 1. ) ,
331+ AngularSpacingMethod :: Pitch => total_angle / count as f64 ,
332+ } ;
275333
276334 let mut result_table = Table :: new ( ) ;
277335
278336 for index in 0 ..count {
279- let angle = DAffine2 :: from_angle ( ( TAU / count as f64 ) * index as f64 + start_angle. to_radians ( ) ) ;
337+ let angle_rad = start_rad + index as f64 * angular_pitch;
338+ let angle = DAffine2 :: from_angle ( angle_rad) ;
280339 let translation = DAffine2 :: from_translation ( radius * DVec2 :: Y ) ;
281340 let transform = angle * translation;
282341
@@ -2417,6 +2476,7 @@ mod test {
24172476 direction,
24182477 0. ,
24192478 count,
2479+ super :: RepeatSpacingMethod :: Span ,
24202480 )
24212481 . await ;
24222482 let vector_table = super :: flatten_path ( Footprint :: default ( ) , repeated) . await ;
@@ -2436,6 +2496,7 @@ mod test {
24362496 direction,
24372497 0. ,
24382498 count,
2499+ super :: RepeatSpacingMethod :: Span ,
24392500 )
24402501 . await ;
24412502 let vector_table = super :: flatten_path ( Footprint :: default ( ) , repeated) . await ;
@@ -2447,7 +2508,16 @@ mod test {
24472508 }
24482509 #[ tokio:: test]
24492510 async fn circular_repeat ( ) {
2450- let repeated = super :: circular_repeat ( Footprint :: default ( ) , vector_node_from_bezpath ( Rect :: new ( -1. , -1. , 1. , 1. ) . to_path ( DEFAULT_ACCURACY ) ) , 45. , 4. , 8 ) . await ;
2511+ let repeated = super :: circular_repeat (
2512+ Footprint :: default ( ) ,
2513+ vector_node_from_bezpath ( Rect :: new ( -1. , -1. , 1. , 1. ) . to_path ( DEFAULT_ACCURACY ) ) ,
2514+ 45. ,
2515+ 360. ,
2516+ 4. ,
2517+ 8 ,
2518+ super :: AngularSpacingMethod :: Span ,
2519+ )
2520+ . await ;
24512521 let vector_table = super :: flatten_path ( Footprint :: default ( ) , repeated) . await ;
24522522 let vector = vector_table. iter ( ) . next ( ) . unwrap ( ) . element ;
24532523 assert_eq ! ( vector. region_manipulator_groups( ) . count( ) , 8 ) ;
@@ -2588,7 +2658,7 @@ mod test {
25882658 #[ tokio:: test]
25892659 async fn morph ( ) {
25902660 let rectangle = vector_node_from_bezpath ( Rect :: new ( 0. , 0. , 100. , 100. ) . to_path ( DEFAULT_ACCURACY ) ) ;
2591- let rectangles = super :: repeat ( Footprint :: default ( ) , rectangle, DVec2 :: new ( -100. , -100. ) , 0. , 2 ) . await ;
2661+ let rectangles = super :: repeat ( Footprint :: default ( ) , rectangle, DVec2 :: new ( -100. , -100. ) , 0. , 2 , super :: RepeatSpacingMethod :: Span ) . await ;
25922662 let morphed = super :: morph ( Footprint :: default ( ) , rectangles, 0.5 ) . await ;
25932663 let element = morphed. iter ( ) . next ( ) . unwrap ( ) . element ;
25942664 assert_eq ! (
0 commit comments