@@ -310,13 +310,13 @@ impl From<Reflect> for Tree {
310310 }
311311}
312312
313- /// Reflection about the X axis
313+ /// Reflection on the X axis
314314#[ derive( Clone , Facet ) ]
315315pub struct ReflectX {
316316 /// Shape to reflect
317317 pub shape : Tree ,
318318
319- /// Plane about which to reflect the shape
319+ /// X offset
320320 #[ facet( default = 0.0 ) ]
321321 pub offset : f64 ,
322322}
@@ -334,9 +334,9 @@ impl From<ReflectX> for Tree {
334334 }
335335}
336336
337- /// Reflection about the Y axis
337+ /// Reflection about the `X = Y` line
338338#[ derive( Clone , Facet ) ]
339- pub struct ReflectY {
339+ pub struct ReflectXY {
340340 /// Shape to reflect
341341 pub shape : Tree ,
342342
@@ -345,6 +345,30 @@ pub struct ReflectY {
345345 pub offset : f64 ,
346346}
347347
348+ impl From < ReflectXY > for Tree {
349+ fn from ( v : ReflectXY ) -> Self {
350+ Reflect {
351+ shape : v. shape ,
352+ plane : Plane {
353+ axis : Axis :: try_from ( Vec3 :: new ( -1.0 , 1.0 , 0.0 ) ) . unwrap ( ) ,
354+ offset : v. offset ,
355+ } ,
356+ }
357+ . into ( )
358+ }
359+ }
360+
361+ /// Reflection on the Y axis
362+ #[ derive( Clone , Facet ) ]
363+ pub struct ReflectY {
364+ /// Shape to reflect
365+ pub shape : Tree ,
366+
367+ /// Y offset
368+ #[ facet( default = 0.0 ) ]
369+ pub offset : f64 ,
370+ }
371+
348372impl From < ReflectY > for Tree {
349373 fn from ( v : ReflectY ) -> Self {
350374 Reflect {
@@ -358,13 +382,13 @@ impl From<ReflectY> for Tree {
358382 }
359383}
360384
361- /// Reflection about the Z axis
385+ /// Reflection on the Z axis
362386#[ derive( Clone , Facet ) ]
363387pub struct ReflectZ {
364388 /// Shape to reflect
365389 pub shape : Tree ,
366390
367- /// Plane about which to reflect the shape
391+ /// Z offset
368392 #[ facet( default = 0.0 ) ]
369393 pub offset : f64 ,
370394}
@@ -574,6 +598,32 @@ impl From<LoftZ> for Tree {
574598 }
575599}
576600
601+ /// Repeat a shape in the X axis
602+ ///
603+ /// This uses the modulo operator, which may introduce discontinuities; shapes
604+ /// should be designed to have the same value at `x = ±radius`.
605+ #[ derive( Clone , Facet ) ]
606+ pub struct RepeatX {
607+ /// Shape to repeat
608+ pub shape : Tree ,
609+ /// Radius of the region to repeat
610+ #[ facet( default = 1.0 ) ]
611+ pub radius : f64 ,
612+ /// X position about which to repeat
613+ #[ facet( default = 0.0 ) ]
614+ pub offset : f64 ,
615+ }
616+
617+ impl From < RepeatX > for Tree {
618+ fn from ( value : RepeatX ) -> Self {
619+ let ( x, y, z) = Tree :: axes ( ) ;
620+ let r = value. radius - value. offset ;
621+ value
622+ . shape
623+ . remap_xyz ( ( ( x + r) . modulo ( value. radius * 2.0 ) ) - r, y, z)
624+ }
625+ }
626+
577627////////////////////////////////////////////////////////////////////////////////
578628
579629/// Trait for a type which can visit each of the shapes in our library
@@ -766,4 +816,59 @@ mod test {
766816 let mut v = ValidateVisitor ;
767817 v. visit :: < BadShapeEnum > ( ) ;
768818 }
819+
820+ #[ test]
821+ fn repeat_x ( ) {
822+ let c = Circle {
823+ center : Vec2 :: new ( 0.0 , 0.0 ) ,
824+ radius : 0.5 ,
825+ } ;
826+ let r = RepeatX {
827+ shape : c. clone ( ) . into ( ) ,
828+ radius : 1.0 ,
829+ offset : 0.0 ,
830+ } ;
831+ let mut ctx = Context :: new ( ) ;
832+ let c = ctx. import ( & c. into ( ) ) ;
833+ let r = ctx. import ( & r. into ( ) ) ;
834+
835+ // Check the three repetitions closest to the center
836+ for i in 0 ..=1000 {
837+ let x = ( i as f64 / 1000.0 ) - ( 1000.0 - i as f64 ) / 1000.0 ;
838+ let vc = ctx. eval_xyz ( c, x, 0.0 , 0.0 ) . unwrap ( ) ;
839+ let err = ( vc - ctx. eval_xyz ( r, x, 0.0 , 0.0 ) . unwrap ( ) ) . abs ( ) ;
840+ assert ! ( err < 1e-6 , "bad err {err} at {x}" ) ;
841+ let err = ( vc - ctx. eval_xyz ( r, x + 2.0 , 0.0 , 0.0 ) . unwrap ( ) ) . abs ( ) ;
842+ assert ! ( err < 1e-6 , "bad err {err} at {x} (+1)" ) ;
843+ let err = ( vc - ctx. eval_xyz ( r, x - 2.0 , 0.0 , 0.0 ) . unwrap ( ) ) . abs ( ) ;
844+ assert ! ( err < 1e-6 , "bad err {err} at {x} (-1)" ) ;
845+ }
846+
847+ // Test with a circle centered at x = 1, with a smaller repeat radius
848+ let c = Circle {
849+ center : Vec2 :: new ( 1.0 , 0.0 ) ,
850+ radius : 0.5 ,
851+ } ;
852+ let r = RepeatX {
853+ shape : c. clone ( ) . into ( ) ,
854+ radius : 0.75 ,
855+ offset : 1.0 ,
856+ } ;
857+ let mut ctx = Context :: new ( ) ;
858+ let c = ctx. import ( & c. into ( ) ) ;
859+ let r = ctx. import ( & r. into ( ) ) ;
860+
861+ // Check the three repetitions closest to the center
862+ for i in 0 ..=1000 {
863+ let x = 0.75 * ( ( i as f64 / 1000.0 ) - ( 1000.0 - i as f64 ) / 1000.0 )
864+ + 1.0 ;
865+ let vc = ctx. eval_xyz ( c, x, 0.0 , 0.0 ) . unwrap ( ) ;
866+ let err = ( vc - ctx. eval_xyz ( r, x, 0.0 , 0.0 ) . unwrap ( ) ) . abs ( ) ;
867+ assert ! ( err < 1e-6 , "bad err {err} at {x}" ) ;
868+ let err = ( vc - ctx. eval_xyz ( r, x + 1.5 , 0.0 , 0.0 ) . unwrap ( ) ) . abs ( ) ;
869+ assert ! ( err < 1e-6 , "bad err {err} at {x} (+1)" ) ;
870+ let err = ( vc - ctx. eval_xyz ( r, x - 1.5 , 0.0 , 0.0 ) . unwrap ( ) ) . abs ( ) ;
871+ assert ! ( err < 1e-6 , "bad err {err} at {x} (-1)" ) ;
872+ }
873+ }
769874}
0 commit comments