General path manipulation can be performed on the Polygon type. We can construct a Polygon from any type implementing Iterator<Item=P2>, which Ngon does.
let square = Polygon::from(Ngon::square(world.center(), 200.));
canvas.paint(Filled(square));
Polygon provides an iterator over its vertices we can use to manipulate them, and build a new polygon. In this code we use that to distort the polygon by sampling Fbm noise with time as the z position:
let fbm = Fbm::new().set_seed(world.seed as u32);
let shift_by_timed_noise = |v| {
let sample_point = P3::new(v.x, v.y, ctx.time.as_secs_f32().sin() / 5.);
let t = fbm.noise(sample_point) * 60.;
v.translate(V2::new(t, t))
};
let square = Polygon::from(Ngon::square(world.center(), 200.));
let square = Polygon::from(square.vertices().map(shift_by_timed_noise));
Further, we can iteratively subdivide and apply this same distortion to make more interesting shapes:
let fbm = Fbm::new().set_seed(world.seed as u32);
let shift_by_timed_noise = |(v, strength): (P2, f32)| {
let sample_point =
P3::new(v.x / 100., v.y / 100., ctx.time.as_secs_f32().sin() / 2.);
let t = fbm.noise(sample_point) * strength;
v.translate(V2::new(t, t))
};
let square = Polygon::from(Ngon::square(world.center(), 200.));
let initial_strength = 15.;
let splotches =
std::iter::successors(Some((square, initial_strength)), |(shape, strength)| {
let next_strength = strength / 1.5;
let next_shape = Polygon::from(
shape
.clone()
.subdivide()
.vertices()
.map(|v| (v, *strength))
.map(shift_by_timed_noise),
);
Some((next_shape, next_strength))
})
.map(|(s, _)| s);
for splotch in splotches.skip(8).take(1) {
canvas.paint(Filled(splotch));
}