Skip to content

Commit e3bf603

Browse files
authored
Add ReflectXY and RepeatX shapes (#409)
1 parent 92f0445 commit e3bf603

1 file changed

Lines changed: 111 additions & 6 deletions

File tree

fidget-shapes/src/lib.rs

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
315315
pub 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+
348372
impl 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)]
363387
pub 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

Comments
 (0)