bevy/crates/bevy_math/Cargo.toml

61 lines
2 KiB
TOML
Raw Normal View History

[package]
name = "bevy_math"
version = "0.14.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.27", features = ["bytemuck"] }
thiserror = "1.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.14.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.14.0-dev", features = ["approx"] }
glam = { version = "0.27", features = ["approx"] }
[features]
default = ["rand", "bevy_reflect"]
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"]
[lints]
workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["-Zunstable-options", "--cfg", "docsrs"]
all-features = true