mirror of
https://github.com/bevyengine/bevy
synced 2024-12-22 19:13:08 +00:00
3 commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
Matty
|
900f50d77d
|
Uniform mesh sampling (#14071)
# Objective Allow random sampling from the surfaces of triangle meshes. ## Solution This has two parts. Firstly, rendering meshes can now yield their collections of triangles through a method `Mesh::triangles`. This has signature ```rust pub fn triangles(&self) -> Result<Vec<Triangle3d>, MeshTrianglesError> { //... } ``` and fails in a variety of cases — the most obvious of these is that the mesh must have either the `TriangleList` or `TriangleStrip` topology, and the others correspond to malformed vertex or triangle-index data. With that in hand, we have the second piece, which is `UniformMeshSampler`, which is a `Vec3`-valued [distribution](https://docs.rs/rand/latest/rand/distributions/trait.Distribution.html) that samples uniformly from collections of triangles. It caches the triangles' distribution of areas so that after its initial setup, sampling is allocation-free. It is constructed via `UniformMeshSampler::try_new`, which looks like this: ```rust pub fn try_new<T: Into<Vec<Triangle3d>>>(triangles: T) -> Result<Self, ZeroAreaMeshError> { //... } ``` It fails if the collection of triangles has zero area. The sum of these parts means that you can sample random points from a mesh as follows: ```rust let triangles = my_mesh.triangles().unwrap(); let mut rng = StdRng::seed_from_u64(8765309); let distribution = UniformMeshSampler::try_new(triangles).unwrap(); // 10000 random points from the surface of my_mesh: let sample_points: Vec<Vec3> = distribution.sample_iter(&mut rng).take(10000).collect(); ``` ## Testing Tested by instantiating meshes and sampling as demonstrated above. --- ## Changelog - Added `Mesh::triangles` method to get a collection of triangles from a mesh. - Added `UniformMeshSampler` to `bevy_math::sampling`. This is a distribution which allows random sampling over collections of triangles (such as those provided through meshes). --- ## Discussion ### Design decisions The main thing here was making sure to have a good separation between the parts of this in `bevy_render` and in `bevy_math`. Getting the triangles from a mesh seems like a reasonable step after adding `Triangle3d` to `bevy_math`, so I decided to make all of the random sampling operate at that level, with the fallible conversion to triangles doing most of the work. Notably, the sampler could be called something else that reflects that its input is a collection of triangles, but if/when we add other kinds of meshes to `bevy_math` (e.g. half-edge meshes), the fact that `try_new` takes an `impl Into<Vec<Triangle3d>>` means that those meshes just need to satisfy that trait bound in order to work immediately with this sampling functionality. In that case, the result would just be something like this: ```rust let dist = UniformMeshSampler::try_new(mesh).unwrap(); ``` I think this highlights that most of the friction is really just from extracting data from `Mesh`. It's maybe worth mentioning also that "collection of triangles" (`Vec<Triangle3d>`) sits downstream of any other kind of triangle mesh, since the topology connecting the triangles has been effectively erased, which makes an `Into<Vec<Triangle3d>>` trait bound seem all the more natural to me. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
Matty
|
d2ef88f5e8
|
Add Distribution access methods for ShapeSample trait (#13315)
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`](https://docs.rs/rand/latest/rand/distributions/trait.Distribution.html) 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`): ```rust 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: ```rust // 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: ```rust 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. |
||
Matty
|
3a7923ea92
|
Random sampling of directions and quaternions (#12857)
# Objective Augment Bevy's random sampling capabilities by providing good tools for producing random directions and rotations. ## Solution The `rand` crate has a natural tool for providing `Distribution`s whose output is a type that doesn't require any additional data to sample values — namely, [`Standard`](https://docs.rs/rand/latest/rand/distributions/struct.Standard.html). Here, our existing `ShapeSample` implementations have been put to good use in providing these, resulting in patterns like the following: ```rust // Using thread-local rng let random_direction1: Dir3 = random(); // Using an explicit rng let random_direction2: Dir3 = rng.gen(); // Using an explicit rng coupled explicitly with Standard let random_directions: Vec<Dir3> = rng.sample_iter(Standard).take(5).collect(); ``` Furthermore, we have introduced a trait `FromRng` which provides sugar for `rng.gen()` that is more namespace-friendly (in this author's opinion): ```rust let random_direction = Dir3::from_rng(rng); ``` The types this has been implemented for are `Dir2`, `Dir3`, `Dir3A`, and `Quat`. Notably, `Quat` uses `glam`'s implementation rather than an in-house one, and as a result, `bevy_math`'s "rand" feature now enables that of `glam`. --- ## Changelog - Created `standard` submodule in `sampling` to hold implementations and other items related to the `Standard` distribution. - "rand" feature of `bevy_math` now enables that of `glam`. --- ## Discussion From a quick glance at `Quat`'s distribution implementation in `glam`, I am a bit suspicious, since it is simple and doesn't match any algorithm that I came across in my research. I will do a little more digging as a follow-up to this and see if it's actually uniform (maybe even using those tools I wrote — what a thrill). As an aside, I'd also like to say that I think [`Distribution`](https://docs.rs/rand/latest/rand/distributions/trait.Distribution.html) is really, really good. It integrates with distributions provided externally (e.g. in `rand` itself and its extensions) along with doing a good job of isolating the source of randomness, so that output can be reliably reproduced if need be. Finally, `Distribution::sample_iter` is quite good for ergonomically acquiring lots of random values. At one point I found myself writing traits to describe random sampling and essentially reinvented this one. I just think it's good, and I think it's worth centralizing around to a significant extent. |