bevy/crates/bevy_math/Cargo.toml

67 lines
2.2 KiB
TOML
Raw Normal View History

[package]
name = "bevy_math"
version = "0.15.0-dev"
edition = "2021"
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"]
rust-version = "1.68.2"
[dependencies]
glam = { version = "0.29", features = ["bytemuck"] }
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"
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 }
approx = { version = "0.5", optional = true }
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 }
smallvec = { version = "1.11" }
bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [
"glam",
], optional = true }
[dev-dependencies]
approx = "0.5"
# Supply rngs for examples and tests
rand = "0.8"
rand_chacha = "0.3"
# Enable the approx feature when testing.
bevy_math = { path = ".", version = "0.15.0-dev", features = ["approx"] }
glam = { version = "0.29", features = ["approx"] }
[features]
default = ["rand", "bevy_reflect", "curve"]
serialize = ["dep:serde", "glam/serde"]
# Enable approx for glam types to approximate floating point equality comparisons and assertions
approx = ["dep:approx", "glam/approx"]
# Enable interoperation of glam types with mint-compatible libraries
mint = ["glam/mint"]
# 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"]
# Enable assertions to check the validity of parameters passed to glam
glam_assert = ["glam/glam-assert"]
# Enable assertions in debug builds to check the validity of parameters passed to glam
debug_glam_assert = ["glam/debug-glam-assert"]
# 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"]
# Include code related to the Curve trait
curve = []
[lints]
workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
all-features = true