Skip to content

Commit

Permalink
Annulus sampling (#13471)
Browse files Browse the repository at this point in the history
# Objective

Add random sampling for the `Annulus` primitive. This is part of ongoing
work to bring the various `bevy_math` primitives to feature parity.

## Solution

`Annulus` implements `ShapeSample`. Boundary sampling is implemented in
the obvious way, and interior sampling works exactly as in the
implementation for `Circle`, using the fact that the square of the
radius should be taken uniformly from between r^2 and R^2, where r and R
are the inner and outer radii respectively.

## Testing

I generated a bunch of random points and rendered them. Here's 1000
points on the interior of the default annulus:
<img width="1440" alt="Screenshot 2024-05-22 at 8 01 34 AM"
src="https://github.com/bevyengine/bevy/assets/2975848/19c31bb0-edba-477f-b247-2b12d854afae">

This looks kind of weird around the edges, but I verified that they're
all actually inside the annulus, so I assume it has to do with the fact
that the rendered circles have some radius.
  • Loading branch information
mweatherley authored May 22, 2024
1 parent d2ef88f commit 5dbd827
Showing 1 changed file with 29 additions and 0 deletions.
29 changes: 29 additions & 0 deletions crates/bevy_math/src/sampling/shape_sampling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,35 @@ impl ShapeSample for Sphere {
}
}

impl ShapeSample for Annulus {
type Output = Vec2;

fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let inner_radius = self.inner_circle.radius;
let outer_radius = self.outer_circle.radius;

// Like random sampling for a circle, radius is weighted by the square.
let r_squared = rng.gen_range((inner_radius * inner_radius)..(outer_radius * outer_radius));
let r = r_squared.sqrt();
let theta = rng.gen_range(0.0..TAU);

Vec2::new(r * theta.cos(), r * theta.sin())
}

fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let total_perimeter = self.inner_circle.perimeter() + self.outer_circle.perimeter();
let inner_prob = (self.inner_circle.perimeter() / total_perimeter) as f64;

// Sample from boundary circles, choosing which one by weighting by perimeter:
let inner = rng.gen_bool(inner_prob);
if inner {
self.inner_circle.sample_boundary(rng)
} else {
self.outer_circle.sample_boundary(rng)
}
}
}

impl ShapeSample for Rectangle {
type Output = Vec2;

Expand Down

0 comments on commit 5dbd827

Please sign in to comment.