2021-08-14 19:21:36 +00:00
|
|
|
[package]
|
|
|
|
name = "bevy_math"
|
2024-07-08 12:54:08 +00:00
|
|
|
version = "0.15.0-dev"
|
2021-10-27 00:12:14 +00:00
|
|
|
edition = "2021"
|
2021-08-14 19:21:36 +00:00
|
|
|
description = "Provides math functionality for Bevy Engine"
|
|
|
|
homepage = "https://bevyengine.org"
|
|
|
|
repository = "https://github.com/bevyengine/bevy"
|
|
|
|
license = "MIT OR Apache-2.0"
|
|
|
|
keywords = ["bevy"]
|
2024-11-17 09:38:13 +00:00
|
|
|
rust-version = "1.81.0"
|
2021-08-14 19:21:36 +00:00
|
|
|
|
|
|
|
[dependencies]
|
2024-09-23 19:44:02 +00:00
|
|
|
glam = { version = "0.29", features = ["bytemuck"] }
|
2024-10-09 14:23:23 +00:00
|
|
|
derive_more = { version = "1", default-features = false, features = [
|
|
|
|
"error",
|
|
|
|
"from",
|
|
|
|
"display",
|
|
|
|
"into",
|
|
|
|
] }
|
Cyclic splines (#14106)
# 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.
2024-07-17 13:02:31 +00:00
|
|
|
itertools = "0.13.0"
|
2022-10-31 16:12:15 +00:00
|
|
|
serde = { version = "1", features = ["derive"], optional = true }
|
Add `Rotation2d` (#11658)
# Objective
Rotating vectors is a very common task. It is required for a variety of
things both within Bevy itself and in many third party plugins, for
example all over physics and collision detection, and for things like
Bevy's bounding volumes and several gizmo implementations.
For 3D, we can do this using a `Quat`, but for 2D, we do not have a
clear and efficient option. `Mat2` can be used for rotating vectors if
created using `Mat2::from_angle`, but this is not obvious to many users,
it doesn't have many rotation helpers, and the type does not give any
guarantees that it represents a valid rotation.
We should have a proper type for 2D rotations. In addition to allowing
for potential optimization, it would allow us to have a consistent and
explicitly documented representation used throughout the engine, i.e.
counterclockwise and in radians.
## Representation
The mathematical formula for rotating a 2D vector is the following:
```
new_x = x * cos - y * sin
new_y = x * sin + y * cos
```
Here, `sin` and `cos` are the sine and cosine of the rotation angle.
Computing these every time when a vector needs to be rotated can be
expensive, so the rotation shouldn't be just an `f32` angle. Instead, it
is often more efficient to represent the rotation using the sine and
cosine of the angle instead of storing the angle itself. This can be
freely passed around and reused without unnecessary computations.
The two options are either a 2x2 rotation matrix or a unit complex
number where the cosine is the real part and the sine is the imaginary
part. These are equivalent for the most part, but the unit complex
representation is a bit more memory efficient (two `f32`s instead of
four), so I chose that. This is like Nalgebra's
[`UnitComplex`](https://docs.rs/nalgebra/latest/nalgebra/geometry/type.UnitComplex.html)
type, which can be used for the
[`Rotation2`](https://docs.rs/nalgebra/latest/nalgebra/geometry/type.Rotation2.html)
type.
## Implementation
Add a `Rotation2d` type represented as a unit complex number:
```rust
/// A counterclockwise 2D rotation in radians.
///
/// The rotation angle is wrapped to be within the `]-pi, pi]` range.
pub struct Rotation2d {
/// The cosine of the rotation angle in radians.
///
/// This is the real part of the unit complex number representing the rotation.
pub cos: f32,
/// The sine of the rotation angle in radians.
///
/// This is the imaginary part of the unit complex number representing the rotation.
pub sin: f32,
}
```
Using it is similar to using `Quat`, but in 2D:
```rust
let rotation = Rotation2d::radians(PI / 2.0);
// Rotate vector (also works on Direction2d!)
assert_eq!(rotation * Vec2::X, Vec2::Y);
// Get angle as degrees
assert_eq!(rotation.as_degrees(), 90.0);
// Getting sin and cos is free
let (sin, cos) = rotation.sin_cos();
// "Subtract" rotations
let rotation2 = Rotation2d::FRAC_PI_4; // there are constants!
let diff = rotation * rotation2.inverse();
assert_eq!(diff.as_radians(), PI / 4.0);
// This is equivalent to the above
assert_eq!(rotation2.angle_between(rotation), PI / 4.0);
// Lerp
let rotation1 = Rotation2d::IDENTITY;
let rotation2 = Rotation2d::FRAC_PI_2;
let result = rotation1.lerp(rotation2, 0.5);
assert_eq!(result.as_radians(), std::f32::consts::FRAC_PI_4);
// Slerp
let rotation1 = Rotation2d::FRAC_PI_4);
let rotation2 = Rotation2d::degrees(-180.0); // we can use degrees too!
let result = rotation1.slerp(rotation2, 1.0 / 3.0);
assert_eq!(result.as_radians(), std::f32::consts::FRAC_PI_2);
```
There's also a `From<f32>` implementation for `Rotation2d`, which means
that methods can still accept radians as floats if the argument uses
`impl Into<Rotation2d>`. This means that adding `Rotation2d` shouldn't
even be a breaking change.
---
## Changelog
- Added `Rotation2d`
- Bounding volume methods now take an `impl Into<Rotation2d>`
- Gizmo methods with rotation now take an `impl Into<Rotation2d>`
## Future use cases
- Collision detection (a type like this is quite essential considering
how common vector rotations are)
- `Transform` helpers (e.g. return a 2D rotation about the Z axis from a
`Transform`)
- The rotation used for `Transform2d` (#8268)
- More gizmos, maybe meshes... everything in 2D that uses rotation
---------
Co-authored-by: Tristan Guichaoua <33934311+tguichaoua@users.noreply.github.com>
Co-authored-by: Robert Walter <robwalter96@gmail.com>
Co-authored-by: IQuick 143 <IQuick143cz@gmail.com>
2024-03-11 19:11:57 +00:00
|
|
|
libm = { version = "0.2", optional = true }
|
2024-02-01 19:22:28 +00:00
|
|
|
approx = { version = "0.5", optional = true }
|
2024-03-17 14:48:16 +00:00
|
|
|
rand = { version = "0.8", features = [
|
|
|
|
"alloc",
|
|
|
|
], default-features = false, optional = true }
|
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>
2024-07-08 00:57:08 +00:00
|
|
|
rand_distr = { version = "0.4.3", optional = true }
|
2024-05-23 16:12:46 +00:00
|
|
|
smallvec = { version = "1.11" }
|
2024-07-08 12:54:08 +00:00
|
|
|
bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [
|
2024-05-27 18:18:10 +00:00
|
|
|
"glam",
|
|
|
|
], optional = true }
|
2024-05-27 14:15:22 +00:00
|
|
|
|
2024-01-29 16:04:51 +00:00
|
|
|
[dev-dependencies]
|
|
|
|
approx = "0.5"
|
2024-03-17 14:48:16 +00:00
|
|
|
# Supply rngs for examples and tests
|
|
|
|
rand = "0.8"
|
|
|
|
rand_chacha = "0.3"
|
2024-03-27 20:48:20 +00:00
|
|
|
# Enable the approx feature when testing.
|
2024-07-08 12:54:08 +00:00
|
|
|
bevy_math = { path = ".", version = "0.15.0-dev", features = ["approx"] }
|
2024-09-23 19:44:02 +00:00
|
|
|
glam = { version = "0.29", features = ["approx"] }
|
2024-01-29 16:04:51 +00:00
|
|
|
|
2022-09-03 03:02:04 +00:00
|
|
|
[features]
|
2024-10-09 16:38:23 +00:00
|
|
|
default = ["rand", "bevy_reflect", "curve"]
|
2023-03-28 20:18:50 +00:00
|
|
|
serialize = ["dep:serde", "glam/serde"]
|
2024-01-02 18:10:44 +00:00
|
|
|
# Enable approx for glam types to approximate floating point equality comparisons and assertions
|
2024-02-01 19:22:28 +00:00
|
|
|
approx = ["dep:approx", "glam/approx"]
|
2022-09-03 03:02:04 +00:00
|
|
|
# Enable interoperation of glam types with mint-compatible libraries
|
|
|
|
mint = ["glam/mint"]
|
2024-01-06 22:01:57 +00:00
|
|
|
# Enable libm mathematical functions for glam types to ensure consistent outputs
|
|
|
|
# across platforms at the cost of losing hardware-level optimization using intrinsics
|
Add `Rotation2d` (#11658)
# Objective
Rotating vectors is a very common task. It is required for a variety of
things both within Bevy itself and in many third party plugins, for
example all over physics and collision detection, and for things like
Bevy's bounding volumes and several gizmo implementations.
For 3D, we can do this using a `Quat`, but for 2D, we do not have a
clear and efficient option. `Mat2` can be used for rotating vectors if
created using `Mat2::from_angle`, but this is not obvious to many users,
it doesn't have many rotation helpers, and the type does not give any
guarantees that it represents a valid rotation.
We should have a proper type for 2D rotations. In addition to allowing
for potential optimization, it would allow us to have a consistent and
explicitly documented representation used throughout the engine, i.e.
counterclockwise and in radians.
## Representation
The mathematical formula for rotating a 2D vector is the following:
```
new_x = x * cos - y * sin
new_y = x * sin + y * cos
```
Here, `sin` and `cos` are the sine and cosine of the rotation angle.
Computing these every time when a vector needs to be rotated can be
expensive, so the rotation shouldn't be just an `f32` angle. Instead, it
is often more efficient to represent the rotation using the sine and
cosine of the angle instead of storing the angle itself. This can be
freely passed around and reused without unnecessary computations.
The two options are either a 2x2 rotation matrix or a unit complex
number where the cosine is the real part and the sine is the imaginary
part. These are equivalent for the most part, but the unit complex
representation is a bit more memory efficient (two `f32`s instead of
four), so I chose that. This is like Nalgebra's
[`UnitComplex`](https://docs.rs/nalgebra/latest/nalgebra/geometry/type.UnitComplex.html)
type, which can be used for the
[`Rotation2`](https://docs.rs/nalgebra/latest/nalgebra/geometry/type.Rotation2.html)
type.
## Implementation
Add a `Rotation2d` type represented as a unit complex number:
```rust
/// A counterclockwise 2D rotation in radians.
///
/// The rotation angle is wrapped to be within the `]-pi, pi]` range.
pub struct Rotation2d {
/// The cosine of the rotation angle in radians.
///
/// This is the real part of the unit complex number representing the rotation.
pub cos: f32,
/// The sine of the rotation angle in radians.
///
/// This is the imaginary part of the unit complex number representing the rotation.
pub sin: f32,
}
```
Using it is similar to using `Quat`, but in 2D:
```rust
let rotation = Rotation2d::radians(PI / 2.0);
// Rotate vector (also works on Direction2d!)
assert_eq!(rotation * Vec2::X, Vec2::Y);
// Get angle as degrees
assert_eq!(rotation.as_degrees(), 90.0);
// Getting sin and cos is free
let (sin, cos) = rotation.sin_cos();
// "Subtract" rotations
let rotation2 = Rotation2d::FRAC_PI_4; // there are constants!
let diff = rotation * rotation2.inverse();
assert_eq!(diff.as_radians(), PI / 4.0);
// This is equivalent to the above
assert_eq!(rotation2.angle_between(rotation), PI / 4.0);
// Lerp
let rotation1 = Rotation2d::IDENTITY;
let rotation2 = Rotation2d::FRAC_PI_2;
let result = rotation1.lerp(rotation2, 0.5);
assert_eq!(result.as_radians(), std::f32::consts::FRAC_PI_4);
// Slerp
let rotation1 = Rotation2d::FRAC_PI_4);
let rotation2 = Rotation2d::degrees(-180.0); // we can use degrees too!
let result = rotation1.slerp(rotation2, 1.0 / 3.0);
assert_eq!(result.as_radians(), std::f32::consts::FRAC_PI_2);
```
There's also a `From<f32>` implementation for `Rotation2d`, which means
that methods can still accept radians as floats if the argument uses
`impl Into<Rotation2d>`. This means that adding `Rotation2d` shouldn't
even be a breaking change.
---
## Changelog
- Added `Rotation2d`
- Bounding volume methods now take an `impl Into<Rotation2d>`
- Gizmo methods with rotation now take an `impl Into<Rotation2d>`
## Future use cases
- Collision detection (a type like this is quite essential considering
how common vector rotations are)
- `Transform` helpers (e.g. return a 2D rotation about the Z axis from a
`Transform`)
- The rotation used for `Transform2d` (#8268)
- More gizmos, maybe meshes... everything in 2D that uses rotation
---------
Co-authored-by: Tristan Guichaoua <33934311+tguichaoua@users.noreply.github.com>
Co-authored-by: Robert Walter <robwalter96@gmail.com>
Co-authored-by: IQuick 143 <IQuick143cz@gmail.com>
2024-03-11 19:11:57 +00:00
|
|
|
libm = ["dep:libm", "glam/libm"]
|
2023-03-28 20:18:50 +00:00
|
|
|
# Enable assertions to check the validity of parameters passed to glam
|
|
|
|
glam_assert = ["glam/glam-assert"]
|
2023-10-22 23:01:28 +00:00
|
|
|
# Enable assertions in debug builds to check the validity of parameters passed to glam
|
|
|
|
debug_glam_assert = ["glam/debug-glam-assert"]
|
2024-03-17 14:48:16 +00:00
|
|
|
# Enable the rand dependency for shape_sampling
|
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>
2024-07-08 00:57:08 +00:00
|
|
|
rand = ["dep:rand", "dep:rand_distr", "glam/rand"]
|
2024-10-09 16:38:23 +00:00
|
|
|
# Include code related to the Curve trait
|
|
|
|
curve = []
|
2023-11-18 20:58:48 +00:00
|
|
|
|
|
|
|
[lints]
|
|
|
|
workspace = true
|
2024-03-08 20:03:09 +00:00
|
|
|
|
|
|
|
[package.metadata.docs.rs]
|
2024-07-29 23:10:16 +00:00
|
|
|
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
|
2024-03-08 20:03:09 +00:00
|
|
|
all-features = true
|