bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Add `Distribution` access methods for `ShapeSample` trait

Open mweatherley opened this issue 9 months ago • 0 comments

Stolen from #12835.

Objective

Sometimes you want to sample a whole bunch of points from a shape instead of just one. You can write your own loop to do this, but it's really more idiomatic to use a rand Distribution with the sample_iter method. Distributions also support other useful things like mapping, and they are suitable as generic items for consumption by other APIs.

Solution

ShapeSample has been given two new automatic trait methods, interior_dist and boundary_dist. They both have similar signatures (recall that Output is the output type for ShapeSample):

fn interior_dist(self) -> impl Distribution<Self::Output>
where Self: Sized { //... }

These have default implementations which are powered by wrapper structs InteriorOf and BoundaryOf that actually implement Distribution — the implementations effectively just call ShapeSample::sample_interior and ShapeSample::sample_boundary on the contained type.

The upshot is that this allows iteration as follows:

// Get an iterator over boundary points of a rectangle:
let rectangle = Rectangle::new(1.0, 2.0);
let boundary_iter = rectangle.boundary_dist().sample_iter(rng);
// Collect a bunch of boundary points at once:
let boundary_pts: Vec<Vec2> = boundary_iter.take(1000).collect();

Alternatively, you can use InteriorOf/BoundaryOf explicitly to similar effect:

let boundary_pts: Vec<Vec2> = BoundaryOf(rectangle).sample_iter(rng).take(1000).collect();

Changelog

  • Added InteriorOf and BoundaryOf distribution wrapper structs in bevy_math::sampling::shape_sampling.
  • Added interior_dist and boundary_dist automatic trait methods to ShapeSample.
  • Made shape_sampling module public with explanatory documentation.

Discussion

Design choices

The main point of interest here is just the choice of impl Distribution instead of explicitly using InteriorOf/BoundaryOf return types for interior_dist and boundary_dist. The reason for this choice is that it allows future optimizations for repeated sampling — for example, instead of just wrapping the base type, interior_dist/boundary_dist could construct auxiliary data that is held over between sampling operations.

mweatherley avatar May 10 '24 15:05 mweatherley