Basically it's https://github.com/bevyengine/bevy/pull/13792 with the
bumped versions of `encase` and `hexasphere`.
---------
Co-authored-by: Robert Swain <robert.swain@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
Bevy's direction types have `new` and `new_unchecked` constructors, but
no unchecked variant for the `Dir2::from_xy` and `Dir3::from_xyz`
methods.
For me, this has several times lead to constructing directions like
this, in cases where the components of the direction are already known
to be normalized:
```rust
let normal = Dir2::new_unchecked(Vec2::new(-ray.direction.x.signum(), 0.0));
```
```rust
segment.direction =
Dir2::new_unchecked(Vec2::new(-segment.direction.x, segment.direction.y));
```
For consistency and ergonomics, it would be nice to have unchecked
variants of `Dir2::from_xy` and `Dir3::from_xyz`:
```rust
let normal = Dir2::from_xy_unchecked(-ray.direction.x.signum(), 0.0);
```
```rust
segment.direction = Dir2::from_xy_unchecked(-segment.direction.x, segment.direction.y);
```
## Solution
Add `Dir2::from_xy_unchecked` and `Dir3::from_xyz_unchecked`.
# Objective
Previously, this area of bevy_math used raw translation and rotations to
encode isometries, which did not exist earlier. The goal of this PR is
to make the codebase of bevy_math more harmonious by using actual
isometries (`Isometry2d`/`Isometry3d`) in these places instead — this
will hopefully make the interfaces more digestible for end-users, in
addition to facilitating conversions.
For instance, together with the addition of #14478, this means that a
bounding box for a collider with an isometric `Transform` can be
computed as
```rust
collider.aabb_3d(collider_transform.to_isometry())
```
instead of using manual destructuring.
## Solution
- The traits `Bounded2d` and `Bounded3d` now use `Isometry2d` and
`Isometry3d` (respectively) instead of `translation` and `rotation`
parameters; e.g.:
```rust
/// A trait with methods that return 3D bounding volumes for a shape.
pub trait Bounded3d {
/// Get an axis-aligned bounding box for the shape translated and
rotated by the given isometry.
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d;
/// Get a bounding sphere for the shape translated and rotated by the
given isometry.
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere;
}
```
- Similarly, the `from_point_cloud` constructors for axis-aligned
bounding boxes and bounding circles/spheres now take isometries instead
of separate `translation` and `rotation`; e.g.:
```rust
/// Computes the smallest [`Aabb3d`] containing the given set of points,
/// transformed by the rotation and translation of the given isometry.
///
/// # Panics
///
/// Panics if the given set of points is empty.
#[inline(always)]
pub fn from_point_cloud(
isometry: Isometry3d,
points: impl Iterator<Item = impl Into<Vec3A>>,
) -> Aabb3d { //... }
```
This has a couple additional results:
1. The end-user no longer interacts directly with `Into<Vec3A>` or
`Into<Rot2>` parameters; these conversions all happen earlier now,
inside the isometry types.
2. Similarly, almost all intermediate `Vec3 -> Vec3A` conversions have
been eliminated from the `Bounded3d` implementations for primitives.
This probably has some performance benefit, but I have not measured it
as of now.
## Testing
Existing unit tests help ensure that nothing has been broken in the
refactor.
---
## Migration Guide
The `Bounded2d` and `Bounded3d` traits now take `Isometry2d` and
`Isometry3d` parameters (respectively) instead of separate translation
and rotation arguments. Existing calls to `aabb_2d`, `bounding_circle`,
`aabb_3d`, and `bounding_sphere` will have to be changed to use
isometries instead. A straightforward conversion is to refactor just by
calling `Isometry2d/3d::new`, as follows:
```rust
// Old:
let aabb = my_shape.aabb_2d(my_translation, my_rotation);
// New:
let aabb = my_shape.aabb_2d(Isometry2d::new(my_translation, my_rotation));
```
However, if the old translation and rotation are 3d
translation/rotations originating from a `Transform` or
`GlobalTransform`, then `to_isometry` may be used instead. For example:
```rust
// Old:
let bounding_sphere = my_shape.bounding_sphere(shape_transform.translation, shape_transform.rotation);
// New:
let bounding_sphere = my_shape.bounding_sphere(shape_transform.to_isometry());
```
This discussion also applies to the `from_point_cloud` construction
method of `Aabb2d`/`BoundingCircle`/`Aabb3d`/`BoundingSphere`, which has
similarly been altered to use isometries.
# Objective
Previously, our cubic spline constructors would produce
`CubicCurve`/`RationalCurve` output with no data when they themselves
didn't hold enough control points to produce a well-formed curve.
Attempting to sample the resulting empty "curves" (e.g. by calling
`CubicCurve::position`) would crash the program (😓).
The objectives of this PR are:
1. Ensure that the curve output of `bevy_math`'s spline constructions
are never invalid as data.
2. Provide a type-level guarantee that `CubicCurve` and `RationalCurve`
actually function as curves.
## Solution
This has a few pieces. Firstly, the curve generator traits
`CubicGenerator`, `CyclicCubicGenerator`, and `RationalGenerator` are
now fallible — they have associated error types, and the
curve-generation functions are allowed to fail:
```rust
/// Implement this on cubic splines that can generate a cubic curve from their spline parameters.
pub trait CubicGenerator<P: VectorSpace> {
/// An error type indicating why construction might fail.
type Error;
/// Build a [`CubicCurve`] by computing the interpolation coefficients for each curve segment.
fn to_curve(&self) -> Result<CubicCurve<P>, Self::Error>;
}
```
All existing spline constructions use this together with errors that
indicate when they didn't have the right control data and provide curves
which have at least one segment whenever they return an `Ok` variant.
Next, `CubicCurve` and `RationalCurve` have been blessed with a
guarantee that their internal array of segments (`segments`) is never
empty. In particular, this field is no longer public, so that invalid
curves cannot be built using struct instantiation syntax. To compensate
for this shortfall for users (in particular library authors who might
want to implement their own generators), there is a new method
`from_segments` on these for constructing a curve from a list of
segments, failing if the list is empty:
```rust
/// Create a new curve from a collection of segments. If the collection of segments is empty,
/// a curve cannot be built and `None` will be returned instead.
pub fn from_segments(segments: impl Into<Vec<CubicSegment<P>>>) -> Option<Self> { //... }
```
All existing methods on `CyclicCurve` and `CubicCurve` maintain the
invariant, so the direct construction of invalid values by users is
impossible.
## Testing
Run unit tests from `bevy_math::cubic_splines`. Additionally, run the
`cubic_splines` example and try to get it to crash using small numbers
of control points: it uses the fallible constructors directly, so if
invalid data is ever constructed, it is basically guaranteed to crash.
---
## Migration Guide
The `to_curve` method on Bevy's cubic splines is now fallible (returning
a `Result`), meaning that any existing calls will need to be updated by
handling the possibility of an error variant.
Similarly, any custom implementation of `CubicGenerator` or
`RationalGenerator` will need to be amended to include an `Error` type
and be made fallible itself.
Finally, the fields of `CubicCurve` and `RationalCurve` are now private,
so any direct constructions of these structs from segments will need to
be replaced with the new `CubicCurve::from_segments` and
`RationalCurve::from_segments` methods.
---
## Design
The main thing to justify here is the choice for the curve internals to
remain the same. After all, if they were able to cause crashes in the
first place, it's worth wondering why safeguards weren't put in place on
the types themselves to prevent that.
My view on this is that the problem was really that the internals of
these methods implicitly relied on the assumption that the value they
were operating on was *actually a curve*, when this wasn't actually
guaranteed. Now, it's possible to make a bunch of small changes inside
the curve struct methods to account for that, but I think that's worse
than just guaranteeing that the data is valid upstream — sampling is
about as hot a code path as we're going to get in this area, and hitting
an additional branch every time it happens just to check that the struct
contains valid data is probably a waste of resources.
Another way of phrasing this is that even if we're only interested in
solving the crashes, the curve's validity needs to be checked at some
point, and it's almost certainly better to do this once at the point of
construction than every time the curve is sampled.
In cases where the control data is supplied dynamically, users would
already have to deal with empty curve outputs basically not working.
Anecdotally, I ran into this while writing the `cubic_splines` example,
and I think the diff illustrates the improvement pretty nicely — the
code no longer has to anticipate whether the output will be good or not;
it just has to handle the `Result`.
The cost of all this, of course, is that we have to guarantee that the
new invariant is actually maintained whenever we extend the API.
However, for the most part, I don't expect users to want to do much
surgery on the internals of their curves anyway.
# Objective
- Fix issue #2611
## Solution
- Add `--generate-link-to-definition` to all the `rustdoc-args` arrays
in the `Cargo.toml`s (for docs.rs)
- Add `--generate-link-to-definition` to the `RUSTDOCFLAGS` environment
variable in the docs workflow (for dev-docs.bevyengine.org)
- Document all the workspace crates in the docs workflow (needed because
otherwise only the source code of the `bevy` package will be included,
making the argument useless)
- I think this also fixes#3662, since it fixes the bug on
dev-docs.bevyengine.org, while on docs.rs it has been fixed for a while
on their side.
---
## Changelog
- The source code viewer on docs.rs now includes links to the
definitions.
# Objective
Fill a gap in the functionality of our curve constructions by allowing
users to easily build cyclic curves from control data.
## Solution
Here I opted for something lightweight and discoverable. There is a new
`CyclicCubicGenerator` trait with a method `to_curve_cyclic` which uses
splines' control data to create curves that are cyclic. For now, its
signature is exactly like that of `CubicGenerator` — `to_curve_cyclic`
just yields a `CubicCurve`:
```rust
/// Implement this on cubic splines that can generate a cyclic cubic curve from their spline parameters.
///
/// This makes sense only when the control data can be interpreted cyclically.
pub trait CyclicCubicGenerator<P: VectorSpace> {
/// Build a cyclic [`CubicCurve`] by computing the interpolation coefficients for each curve segment.
fn to_curve_cyclic(&self) -> CubicCurve<P>;
}
```
This trait has been implemented for `CubicHermite`,
`CubicCardinalSpline`, `CubicBSpline`, and `LinearSpline`:
<img width="753" alt="Screenshot 2024-07-01 at 8 58 27 PM"
src="https://github.com/bevyengine/bevy/assets/2975848/69ae0802-3b78-4fb9-b73a-6f842cf3b33c">
<img width="628" alt="Screenshot 2024-07-01 at 9 00 14 PM"
src="https://github.com/bevyengine/bevy/assets/2975848/2992175a-a96c-40fc-b1a1-5206c3572cde">
<img width="606" alt="Screenshot 2024-07-01 at 8 59 36 PM"
src="https://github.com/bevyengine/bevy/assets/2975848/9e99eb3a-dbe6-42da-886c-3d3e00410d03">
<img width="603" alt="Screenshot 2024-07-01 at 8 59 01 PM"
src="https://github.com/bevyengine/bevy/assets/2975848/d037bc0c-396a-43af-ab5c-fad9a29417ef">
(Each type pictured respectively with the control points rendered as
green spheres; tangents not pictured in the case of the Hermite spline.)
These curves are all parametrized so that the output of `to_curve` and
the output of `to_curve_cyclic` are similar. For instance, in
`CubicCardinalSpline`, the first output segment is a curve segment
joining the first and second control points in each, although it is
constructed differently. In the other cases, the segments from
`to_curve` are a subset of those in `to_curve_cyclic`, with the new
segments appearing at the end.
## Testing
I rendered cyclic splines from control data and made sure they looked
reasonable. Existing tests are intact for splines where previous code
was modified. (Note that the coefficient computation for cyclic spline
segments is almost verbatim identical to that of their non-cyclic
counterparts.)
The Bezier benchmarks also look fine.
---
## Changelog
- Added `CyclicCubicGenerator` trait to `bevy_math::cubic_splines` for
creating cyclic curves from control data.
- Implemented `CyclicCubicGenerator` for `CubicHermite`,
`CubicCardinalSpline`, `CubicBSpline`, and `LinearSpline`.
- `bevy_math` now depends on `itertools`.
---
## Discussion
### Design decisions
The biggest thing here is just the approach taken in the first place:
namely, the cyclic constructions use new methods on the same old
structs. This choice was made to reduce friction and increase
discoverability but also because creating new ones just seemed
unnecessary: the underlying data would have been the same, so creating
something like "`CyclicCubicBSpline`" whose internally-held control data
is regarded as cyclic in nature doesn't really accomplish much — the end
result for the user is basically the same either way.
Similarly, I don't presently see a pressing need for `to_curve_cyclic`
to output something other than a `CubicCurve`, although changing this in
the future may be useful. See below.
A notable omission here is that `CyclicCubicGenerator` is not
implemented for `CubicBezier`. This is not a gap waiting to be filled —
`CubicBezier` just doesn't have enough data to join its start with its
end without just making up the requisite control points wholesale. In
all the cases where `CyclicCubicGenerator` has been implemented here,
the fashion in which the ends are connected is quite natural and follows
the semantics of the associated spline construction.
### Future direction
There are two main things here:
1. We should investigate whether we should do something similar for
NURBS. I just don't know that much about NURBS at the moment, so I
regarded this as out of scope for the PR.
2. We may eventually want to change the output type of
`CyclicCubicGenerator::to_curve_cyclic` to a type which reifies the
cyclic nature of the curve output. This wasn't done in this PR because
I'm unsure how much value a type-level guarantee of cyclicity actually
has, but if some useful features make sense only in the case of cyclic
curves, this might be worth pursuing.
Reference to #14299.
# Objective
- Ensuring consistent practice of instantiating 3D primitive shapes in
Bevy.
## Solution
- Add `new` method, containing `radius` and `height` arguments, to Cone
3D primitive shape.
## Testing
- Instantiated cone using same values (radius is `2.` and height is
`5.`), using the current method and the added `new` method.
- Basic setup of Bevy Default Plugins and `3DCameraBundle`.
---
## Showcase
<details>
<summary>Click to view showcase</summary>
```rust
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let new_cone = meshes.add(Cone::new(2., 5.));
commands.spawn(PbrBundle {
mesh: new_cone,
..default()
});
let old_cone = meshes.add(Cone {
radius: 2.,
height: 5.,
});
commands.spawn(PbrBundle {
mesh: old_cone,
material: materials.add(Color::WHITE),
transform: Transform::from_xyz(10., 0., 0.),
..default()
});
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(20., 20., 20.).looking_at(Vec3::ZERO, Dir3::Y),
..default()
});
}
```
</details>
![image](https://github.com/user-attachments/assets/267f8124-8734-4c20-8840-fcf35375a778)
- Pink Cone is created using the `new` method.
- Black Cone is created using the existing method.
## Migration Guide
- Addition of `new` method to the 3D primitive Cone struct.
# Objective
`Annulus` is missing `Bounded2d` even though the implementation is
trivial.
## Solution
Implement `Bounded2d` for `Annulus`.
## Testing
There is a basic test to verify that the produced bounding volumes are
correct.
# Objective
Fixes#14308.
#14269 added the `Isometry2d` and `Isometry3d` types, but they don't
have usage examples or much documentation on what the types actually
represent or what they may be useful for.
In addition, their module is public and the types are not re-exported at
the crate root, unlike all the other core math types like Glam's types,
direction types, and `Rot2`.
## Solution
Improve the documentation of `Isometry2d` and `Isometry3d`, explaining
what they represent and can be useful for, along with doc examples on
common high-level usage. I also made the way the types are exported
consistent with other core math types.
This does add some duplication, but I personally think having good docs
for this is valuable, and people are also less likely to look at the
module-level docs than type-level docs.
# Objective
The isometry types added in #14269 support transforming other isometries
and points, as well as computing the inverse of an isometry using
`inverse`.
However, transformations like `iso1.inverse() * iso2` and `iso.inverse()
* point` can be optimized for single-shot cases using custom methods
that avoid an extra rotation operation.
## Solution
Add `inverse_mul` and `inverse_transform_point` for `Isometry2d` and
`Isometry3d`. Note that these methods are only faster when the isometry
can't be reused for multiple transformations.
## Testing
All of the methods have a test, similarly to the existing transformation
operations.
# Objective
Creating isometry types with just a translation is a bit more verbose
than it needs to be for cases where you don't have an existing vector to
pass in.
```rust
let iso = Isometry3d::from_translation(Vec3::new(2.0, 1.0, -1.0));
```
This could be made more ergonomic with a method similar to
`Dir2::from_xy`, `Dir3::from_xyz`, and `Transform::from_xyz`:
```rust
let iso = Isometry3d::from_xyz(2.0, 1.0, -1.0);
```
## Solution
Add `Isometry2d::from_xy` and `Isometry3d::from_xyz`.
# Objective
Introduce isometry types for describing relative and absolute position
in mathematical contexts.
## Solution
For the time being, this is a very minimal implementation. This
implements the following faculties for two- and three-dimensional
isometry types:
- Identity transformations
- Creation from translations and/or rotations
- Inverses
- Multiplication (composition) of isometries with each other
- Application of isometries to points (as vectors)
- Conversion of isometries to affine transformations
There is obviously a lot more that could be added, so I erred on the
side of adding things that I knew would be useful, with the idea of
expanding this in the near future as needed.
(I also fixed some random doc problems in `bevy_math`.)
---
## Design
One point of interest here is the matter of if/when to use aligned
types. In the implementation of 3d isometries, I used `Vec3A` rather
than `Vec3` because it has no impact on size/alignment, but I'm still
not sure about that decision (although it is easily changed).
For 2d isometries — which are encoded by four floats — the idea of
shoving them into a single 128-bit buffer (`__m128` or whatever) sounds
kind of enticing, but it's more involved and would involve writing
unsafe code, so I didn't do that for now.
## Future work
- Expand the API to include shortcuts like `inverse_mul` and
`inverse_transform` for efficiency reasons.
- Include more convenience constructors and methods (e.g. `from_xy`,
`from_xyz`).
- Refactor `bevy_math::bounding` to use the isometry types.
- Add conversions to/from isometries for `Transform`/`GlobalTransform`
in `bevy_transform`.
# Objective
- Bevy currently has lot of invalid intra-doc links, let's fix them!
- Also make CI test them, to avoid future regressions.
- Helps with #1983 (but doesn't fix it, as there could still be explicit
links to docs.rs that are broken)
## Solution
- Make `cargo r -p ci -- doc-check` check fail on warnings (could also
be changed to just some specific lints)
- Manually fix all the warnings (note that in some cases it was unclear
to me what the fix should have been, I'll try to highlight them in a
self-review)
# Objective
With an unlucky denormalised quaternion (or just a regular very
denormalised quaternion), it's possible to obtain NaN values for AABB's
in shapes which rely on an AABB for a disk.
## Solution
Add an additional `.max(Vec3::ZERO)` clamp to get rid of negative values
arising due to numerical errors.
Fixup some unnecessary calculations and improve variable names in
relevant code, aiming for consistency.
## Discussion
These two (nontrivial) lines of code are repeated at least 5 times,
maybe they could be their own method.
Bump version after release
This PR has been auto-generated
Co-authored-by: Bevy Auto Releaser <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: François Mockers <mockersf@gmail.com>
# 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>
This is an attempt to address issue #13725, which was about the
geometric primitives in the bevy_math crate lacking some detail in the
docs.
# Objective
Fixes#13725
## Solution
Added details to the docstrings. Mostly this consisted of specifying
that the primitives are centered on the origin, or describing how
they're defined (e.g., a circle is the set of all points some distance
from the origin).
## Testing
No testing, since the only changes were to docs.
# Objective
- Primitives should not use poorly defined types like `usize`,
especially since they are serializable
## Solution
- Use `u32` instead of `usize`
- The generic array types do not need to be changed because this size is
not actually stored or serialized anywhere
---
## Migration Guide
- `RegularPolygon` now uses `u32` instead of `usize` for the number of
sides
i based the design on @mgi388 in the discussion about the issue.
i added the illustration in such a way that it shows up when you hover
your mouse over the type, i hope this is what was meant by the issue
no unit tests were added bc obviously
Fixes#13664
# Objective
Partially address #13408
Rework of #13613
Unify the very nice forms of interpolation specifically present in
`bevy_math` under a shared trait upon which further behavior can be
based.
The ideas in this PR were prompted by [Lerp smoothing is broken by Freya
Holmer](https://www.youtube.com/watch?v=LSNQuFEDOyQ).
## Solution
There is a new trait `StableInterpolate` in `bevy_math::common_traits`
which enshrines a quite-specific notion of interpolation with a lot of
guarantees:
```rust
/// A type with a natural interpolation that provides strong subdivision guarantees.
///
/// Although the only required method is `interpolate_stable`, many things are expected of it:
///
/// 1. The notion of interpolation should follow naturally from the semantics of the type, so
/// that inferring the interpolation mode from the type alone is sensible.
///
/// 2. The interpolation recovers something equivalent to the starting value at `t = 0.0`
/// and likewise with the ending value at `t = 1.0`.
///
/// 3. Importantly, the interpolation must be *subdivision-stable*: for any interpolation curve
/// between two (unnamed) values and any parameter-value pairs `(t0, p)` and `(t1, q)`, the
/// interpolation curve between `p` and `q` must be the *linear* reparametrization of the original
/// interpolation curve restricted to the interval `[t0, t1]`.
///
/// The last of these conditions is very strong and indicates something like constant speed. It
/// is called "subdivision stability" because it guarantees that breaking up the interpolation
/// into segments and joining them back together has no effect.
///
/// Here is a diagram depicting it:
/// ```text
/// top curve = u.interpolate_stable(v, t)
///
/// t0 => p t1 => q
/// |-------------|---------|-------------|
/// 0 => u / \ 1 => v
/// / \
/// / \
/// / linear \
/// / reparametrization \
/// / t = t0 * (1 - s) + t1 * s \
/// / \
/// |-------------------------------------|
/// 0 => p 1 => q
///
/// bottom curve = p.interpolate_stable(q, s)
/// ```
///
/// Note that some common forms of interpolation do not satisfy this criterion. For example,
/// [`Quat::lerp`] and [`Rot2::nlerp`] are not subdivision-stable.
///
/// Furthermore, this is not to be used as a general trait for abstract interpolation.
/// Consumers rely on the strong guarantees in order for behavior based on this trait to be
/// well-behaved.
///
/// [`Quat::lerp`]: crate::Quat::lerp
/// [`Rot2::nlerp`]: crate::Rot2::nlerp
pub trait StableInterpolate: Clone {
/// Interpolate between this value and the `other` given value using the parameter `t`.
/// Note that the parameter `t` is not necessarily clamped to lie between `0` and `1`.
/// When `t = 0.0`, `self` is recovered, while `other` is recovered at `t = 1.0`,
/// with intermediate values lying between the two.
fn interpolate_stable(&self, other: &Self, t: f32) -> Self;
}
```
This trait has a blanket implementation over `NormedVectorSpace`, where
`lerp` is used, along with implementations for `Rot2`, `Quat`, and the
direction types using variants of `slerp`. Other areas may choose to
implement this trait in order to hook into its functionality, but the
stringent requirements must actually be met.
This trait bears no direct relationship with `bevy_animation`'s
`Animatable` trait, although they may choose to use `interpolate_stable`
in their trait implementations if they wish, as both traits involve
type-inferred interpolations of the same kind. `StableInterpolate` is
not a supertrait of `Animatable` for a couple reasons:
1. Notions of interpolation in animation are generally going to be much
more general than those allowed under these constraints.
2. Laying out these generalized interpolation notions is the domain of
`bevy_animation` rather than of `bevy_math`. (Consider also that
inferring interpolation from types is not universally desirable.)
Similarly, this is not implemented on `bevy_color`'s color types,
although their current mixing behavior does meet the conditions of the
trait.
As an aside, the subdivision-stability condition is of interest
specifically for the [Curve
RFC](https://github.com/bevyengine/rfcs/pull/80), where it also ensures
a kind of stability for subsampling.
Importantly, this trait ensures that the "smooth following" behavior
defined in this PR behaves predictably:
```rust
/// Smoothly nudge this value towards the `target` at a given decay rate. The `decay_rate`
/// parameter controls how fast the distance between `self` and `target` decays relative to
/// the units of `delta`; the intended usage is for `decay_rate` to generally remain fixed,
/// while `delta` is something like `delta_time` from an updating system. This produces a
/// smooth following of the target that is independent of framerate.
///
/// More specifically, when this is called repeatedly, the result is that the distance between
/// `self` and a fixed `target` attenuates exponentially, with the rate of this exponential
/// decay given by `decay_rate`.
///
/// For example, at `decay_rate = 0.0`, this has no effect.
/// At `decay_rate = f32::INFINITY`, `self` immediately snaps to `target`.
/// In general, higher rates mean that `self` moves more quickly towards `target`.
///
/// # Example
/// ```
/// # use bevy_math::{Vec3, StableInterpolate};
/// # let delta_time: f32 = 1.0 / 60.0;
/// let mut object_position: Vec3 = Vec3::ZERO;
/// let target_position: Vec3 = Vec3::new(2.0, 3.0, 5.0);
/// // Decay rate of ln(10) => after 1 second, remaining distance is 1/10th
/// let decay_rate = f32::ln(10.0);
/// // Calling this repeatedly will move `object_position` towards `target_position`:
/// object_position.smooth_nudge(&target_position, decay_rate, delta_time);
/// ```
fn smooth_nudge(&mut self, target: &Self, decay_rate: f32, delta: f32) {
self.interpolate_stable_assign(target, 1.0 - f32::exp(-decay_rate * delta));
}
```
As the documentation indicates, the intention is for this to be called
in game update systems, and `delta` would be something like
`Time::delta_seconds` in Bevy, allowing positions, orientations, and so
on to smoothly follow a target. A new example, `smooth_follow`,
demonstrates a basic implementation of this, with a sphere smoothly
following a sharply moving target:
https://github.com/bevyengine/bevy/assets/2975848/7124b28b-6361-47e3-acf7-d1578ebd0347
## Testing
Tested by running the example with various parameters.
# Objective
- `Rotation2d` is a very long name for a commonly used type.
## Solution
- Rename it to `Rot2` to match `glam`'s naming convention (e.g. `Vec2`)
I ran a poll, and `Rot2` was the favorite of the candidate names.
This is not actually a breaking change, since `Rotation2d` has not been
shipped yet.
---------
Co-authored-by: Alice Cecile <alice.i.cecil@gmail.com>
# Objective
- Due to coherency, it was previously not possible to implement
`Bounded3d` for `Extrusion<MyCustomPrimitive>`. This PR fixes that.
## Solution
- Added a new trait `BoundedExtrusion: Primitive2d + Bounded2d` which
provides functions for bounding boxes and spheres of extrusions of 2D
primitives.
- Changed all implementations of `Bounded3d for Extrusion<T>` to
`BoundedExtrusion for T`
- Implemented `Bounded3d for Extrusion<T: BoundedExtrusion>`
- Removed the `extrusion_bounding_box` and `extrusion_bounding_sphere`
functions and used them as default implementations in `BoundedExtrusion`
## Testing
- This PR does not change any implementations
---------
Co-authored-by: Lynn Büttgenbach <62256001+solis-lumine-vorago@users.noreply.github.com>
Co-authored-by: Matty <weatherleymatthew@gmail.com>
# Objective
Fill the gap in this functionality by implementing it for `Rotation2d`.
We have this already for `Quat` in addition to the direction types.
## Solution
`bevy_math::sampling` now contains an implementation of
`Distribution<Rotation2d>` for `Standard`, along with the associated
convenience implementation `Rotation2d: FromRng`, which allows syntax
like this for creating a random rotation:
```rust
// With `FromRng`:
let rotation = Rotation2d::from_rng(rng);
// With `rand::random`:
let another_rotation: Rotation2d = random();
// With `Rng::gen`:
let yet_another_rotation: Rotation2d = rng.gen();
```
I also cleaned up the documentation a little bit, seeding the `Rng`s
instead of building them from entropy, along with adding a handful of
inline directives.
# Objective
- Implement `Bounded3d` for some `Extrusion<T>`
- Provide methods to calculate `Aabb3d`s and `BoundingSphere`s for any
extrusion with a `Bounded2d` base shape
## Solution
- Implemented `Bounded3d` for all 2D `bevy_math` primitives with the
exception of `Plane2d`. As far as I can see, `Plane2d` is pretty much a
line? and I think it is very unintuitive to extrude a plane and get a
plane as a result.
- Add `extrusion_bounding_box` and `extrusion_bounding_sphere`. These
are not always used internally since there are faster methods for
specific extrusions. Both of them produce the optimal result within
precision limits though.
## Testing
- Bounds for extrusions are tested within the same module. All unique
implementations are tested.
- The correctness was validated visually aswell.
---------
Co-authored-by: Raphael Büttgenbach <62256001+solis-lumine-vorago@users.noreply.github.com>
Co-authored-by: IQuick 143 <IQuick143cz@gmail.com>
# Objective
Filling a hole in the API: Previously, there was no particularly
ergonomic way to go from, e.g., a pair of directions to the rotation
that links them.
## Solution
We introduce a small suite of API methods to `Dir2` to address this:
```rust
/// Get the rotation that rotates this direction to `other`.
pub fn rotation_to(self, other: Self) -> Rotation2d { //... }
/// Get the rotation that rotates `other` to this direction.
pub fn rotation_from(self, other: Self) -> Rotation2d { //... }
/// Get the rotation that rotates the X-axis to this direction.
pub fn rotation_from_x(self) -> Rotation2d { //... }
/// Get the rotation that rotates this direction to the X-axis.
pub fn rotation_to_x(self) -> Rotation2d { //... }
/// Get the rotation that rotates this direction to the Y-axis.
pub fn rotation_from_y(self) -> Rotation2d { //... }
/// Get the rotation that rotates the Y-axis to this direction.
pub fn rotation_to_y(self) -> Rotation2d { //... }
```
I also removed some language from the `Rotation2d` docs that is
misleading: the radian and angle conversion functions are already clear
about which angles they spit out, and `Rotation2d` itself doesn't have
any bounds on angles or anything.
# Objective
Implements #13647
## Solution
Created two enums, CompassQuadrant and CompassOctant inside compass.rs
with impls To and From Dir2. Used dir.to_angle().to_degrees() and
matched against the resulting value. I could have skipped to_degrees()
and matched against the radian value, but I thought this was more
readable. I'm probably wrong lol.
## Testing
Tested various dirs to compass variations.
---
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
When working on `leafwing-input-manager` and in my games, I've found
these compass directions to be both clear and useful when attempting to
describe angles in 2 dimensions.
This was directly used when mapping gamepad inputs into 4-way movement
as a virtual dpad, and I expect other uses are common in games.
## Solution
- Add constants corresponding to the 4 cardinal and 4 semi-cardinal
directions.
## Testing
- I've validated the quadrants of each of the directions through
self-review.
---------
Co-authored-by: Alice Cecile <alice.i.cecil@gmail.com>
# Objective
Fixes#13535.
## Solution
I implemented `Reflect` for close to all math types now, except for some
types that it would cause issues (like some boxed types).
## Testing
- Everything seems to still build, will await CI though.
---
## Changelog
- Made close to all math types implement `Reflect`.
# Objective
Fixes#13456
## Solution
Moved `bevy_math`'s `Reflect` impls from `bevy_reflect` to `bevy_math`.
### Quick note
I accidentally used the same commit message while resolving a merge
conflict (first time I had to resolve a conflict). Sorry about that.
# Objective
- Create a new 2D primitive, Rhombus, also knows as "Diamond Shape"
- Simplify the creation and handling of isometric projections
- Extend Bevy's arsenal of 2D primitives
## Testing
- New unit tests created in bevy_math/ primitives and bev_math/ bounding
- Tested translations, rotations, wireframe, bounding sphere, aabb and
creation parameters
---------
Co-authored-by: Luís Figueiredo <luispcfigueiredo@tecnico.ulisboa.pt>
# Objective
The `ConicalFrustum` primitive should support meshing.
## Solution
Implement meshing for the `ConicalFrustum` primitive. The implementation
is nearly identical to `Cylinder` meshing, but supports two radii.
The default conical frustum is equivalent to a cone with a height of 1
and a radius of 0.5, truncated at half-height.
![kuva](https://github.com/bevyengine/bevy/assets/57632562/b4cab136-ff55-4056-b818-1218e4f38845)
# Objective
Adopted #11748
## Solution
I've rebased on main to fix the merge conflicts. ~~Not quite ready to
merge yet~~
* Clippy is happy and the tests are passing, but...
* ~~The new shapes in `examples/2d/2d_shapes.rs` don't look right at
all~~ Never mind, looks like radians and degrees just got mixed up at
some point?
* I have updated one doc comment based on a review in the original PR.
---------
Co-authored-by: Alexis "spectria" Horizon <spectria.limina@gmail.com>
Co-authored-by: Alexis "spectria" Horizon <118812919+spectria-limina@users.noreply.github.com>
Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Ben Harper <ben@tukom.org>
# Objective
Adopted #12659.
Resolved the merge conflicts on #12659;
* I merged the `triangle_tests` added by this PR and by #13020.
* I moved the [commented out
code](https://github.com/bevyengine/bevy/pull/12659#discussion_r1536640427)
from the original PR into a separate test with `#[should_panic]`.
---------
Co-authored-by: Vitor Falcao <vitorfhc@protonmail.com>
Co-authored-by: Ben Harper <ben@tukom.org>
I am unsure if this needs changing, so let me know if I need to change
anything else.
# Objective
Fixes#13461.
## Solution
I applied the changes as suggested in the issue, and updated the doc
comments accordingly
## Testing
I don't think this needs too much testing, but there are no `cargo test`
failures.
# 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.
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.
# Objective
- Fixes#13092.
## Solution
- Renamed the `inset()` method in `Rect`, `IRect` and `URect` to
`inflate()`.
- Added `EMPTY` constants to all `Rect` variants, represented by corners
with the maximum numerical values for each kind.
---
## Migration Guide
- Replace `Rect::inset()`, `IRect::inset()` and `URect::inset()` calls
with `inflate()`.
# Objective
Add interior and boundary sampling for the `Tetrahedron` primitive. This
is part of ongoing work to bring the primitives to parity with each
other in terms of their capabilities.
## Solution
`Tetrahedron` implements the `ShapeSample` trait. To support this, there
is a new public method `Tetrahedron::faces` which gets the faces of a
tetrahedron as `Triangle3d`s. There are more sophisticated ideas for
getting the faces we might want to consider in the future (e.g.
adjusting according to the orientation), but this method gives the most
mathematically straightforward answer, giving the faces the orientation
induced by the tetrahedron itself.
# Objective
Fixes#13189
## Solution
To add the reflect impls I needed to make all the struct fields pub. I
don't think there's any harm for these types, but just a note for
review.
---------
Co-authored-by: Ben Harper <ben@tukom.org>