From 5dbd827728c3624b943c67b5b1a63aee07f8f698 Mon Sep 17 00:00:00 2001 From: Matty Date: Wed, 22 May 2024 09:13:04 -0400 Subject: [PATCH] Annulus sampling (#13471) # 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: Screenshot 2024-05-22 at 8 01 34 AM 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. --- .../bevy_math/src/sampling/shape_sampling.rs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/crates/bevy_math/src/sampling/shape_sampling.rs b/crates/bevy_math/src/sampling/shape_sampling.rs index 520c1e87b7..95dfe677fd 100644 --- a/crates/bevy_math/src/sampling/shape_sampling.rs +++ b/crates/bevy_math/src/sampling/shape_sampling.rs @@ -191,6 +191,35 @@ impl ShapeSample for Sphere { } } +impl ShapeSample for Annulus { + type Output = Vec2; + + fn sample_interior(&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(&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;