2024-06-10 21:15:21 +00:00
//! This example demonstrates how you can add your own custom primitives to bevy highlighting
//! traits you may want to implement for your primitives to achieve different functionalities.
use std ::f32 ::consts ::{ PI , SQRT_2 } ;
use bevy ::{
color ::palettes ::css ::{ RED , WHITE } ,
input ::common_conditions ::input_just_pressed ,
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
math ::{
bounding ::{
Aabb2d , Bounded2d , Bounded3d , BoundedExtrusion , BoundingCircle , BoundingVolume ,
} ,
Isometry2d ,
2024-06-10 21:15:21 +00:00
} ,
prelude ::* ,
render ::{
camera ::ScalingMode ,
mesh ::{ Extrudable , ExtrusionBuilder , PerimeterSegment } ,
render_asset ::RenderAssetUsages ,
} ,
} ;
const HEART : Heart = Heart ::new ( 0.5 ) ;
const EXTRUSION : Extrusion < Heart > = Extrusion {
base_shape : Heart ::new ( 0.5 ) ,
half_depth : 0.5 ,
} ;
// The transform of the camera in 2D
const TRANSFORM_2D : Transform = Transform {
translation : Vec3 ::ZERO ,
rotation : Quat ::IDENTITY ,
scale : Vec3 ::ONE ,
} ;
// The projection used for the camera in 2D
const PROJECTION_2D : Projection = Projection ::Orthographic ( OrthographicProjection {
near : - 1.0 ,
far : 10.0 ,
viewport_origin : Vec2 ::new ( 0.5 , 0.5 ) ,
scaling_mode : ScalingMode ::AutoMax {
max_width : 8.0 ,
max_height : 20.0 ,
} ,
area : Rect {
min : Vec2 ::NEG_ONE ,
max : Vec2 ::ONE ,
} ,
} ) ;
// The transform of the camera in 3D
const TRANSFORM_3D : Transform = Transform {
translation : Vec3 ::ZERO ,
// The camera is pointing at the 3D shape
rotation : Quat ::from_xyzw ( - 0.14521316 , - 0.0 , - 0.0 , 0.98940045 ) ,
scale : Vec3 ::ONE ,
} ;
// The projection used for the camera in 3D
const PROJECTION_3D : Projection = Projection ::Perspective ( PerspectiveProjection {
fov : PI / 4.0 ,
near : 0.1 ,
far : 1000.0 ,
aspect_ratio : 1.0 ,
} ) ;
/// State for tracking the currently displayed shape
#[ derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default, Reflect) ]
enum CameraActive {
#[ default ]
/// The 2D shape is displayed
Dim2 ,
/// The 3D shape is displayed
Dim3 ,
}
/// State for tracking the currently displayed shape
#[ derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default, Reflect) ]
enum BoundingShape {
#[ default ]
/// No bounding shapes
None ,
/// The bounding sphere or circle of the shape
BoundingSphere ,
/// The Axis Aligned Bounding Box (AABB) of the shape
BoundingBox ,
}
/// A marker component for our 2D shapes so we can query them separately from the camera
#[ derive(Component) ]
struct Shape2d ;
/// A marker component for our 3D shapes so we can query them separately from the camera
#[ derive(Component) ]
struct Shape3d ;
fn main ( ) {
App ::new ( )
. add_plugins ( DefaultPlugins )
. init_state ::< BoundingShape > ( )
. init_state ::< CameraActive > ( )
. add_systems ( Startup , setup )
. add_systems (
Update ,
(
( rotate_2d_shapes , bounding_shapes_2d ) . run_if ( in_state ( CameraActive ::Dim2 ) ) ,
( rotate_3d_shapes , bounding_shapes_3d ) . run_if ( in_state ( CameraActive ::Dim3 ) ) ,
update_bounding_shape . run_if ( input_just_pressed ( KeyCode ::KeyB ) ) ,
switch_cameras . run_if ( input_just_pressed ( KeyCode ::Space ) ) ,
) ,
)
. run ( ) ;
}
fn setup (
mut commands : Commands ,
mut meshes : ResMut < Assets < Mesh > > ,
mut materials : ResMut < Assets < StandardMaterial > > ,
) {
// Spawn the camera
commands . spawn ( Camera3dBundle {
transform : TRANSFORM_2D ,
projection : PROJECTION_2D ,
.. Default ::default ( )
} ) ;
// Spawn the 2D heart
commands . spawn ( (
PbrBundle {
// We can use the methods defined on the meshbuilder to customize the mesh.
mesh : meshes . add ( HEART . mesh ( ) . resolution ( 50 ) ) ,
material : materials . add ( StandardMaterial {
emissive : RED . into ( ) ,
base_color : RED . into ( ) ,
.. Default ::default ( )
} ) ,
transform : Transform ::from_xyz ( 0.0 , 0.0 , 0.0 ) ,
.. default ( )
} ,
Shape2d ,
) ) ;
// Spawn an extrusion of the heart.
commands . spawn ( (
PbrBundle {
transform : Transform ::from_xyz ( 0. , - 3. , - 10. )
. with_rotation ( Quat ::from_rotation_x ( - PI / 4. ) ) ,
// We can set a custom resolution for the round parts of the extrusion aswell.
mesh : meshes . add ( EXTRUSION . mesh ( ) . resolution ( 50 ) ) ,
material : materials . add ( StandardMaterial {
base_color : RED . into ( ) ,
.. Default ::default ( )
} ) ,
.. Default ::default ( )
} ,
Shape3d ,
) ) ;
// Point light for 3D
2024-10-01 03:20:43 +00:00
commands . spawn ( (
PointLight {
2024-06-10 21:15:21 +00:00
shadows_enabled : true ,
intensity : 10_000_000. ,
range : 100.0 ,
shadow_depth_bias : 0.2 ,
.. default ( )
} ,
2024-10-01 03:20:43 +00:00
Transform ::from_xyz ( 8.0 , 12.0 , 1.0 ) ,
) ) ;
2024-06-10 21:15:21 +00:00
// Example instructions
commands . spawn (
TextBundle ::from_section (
" Press 'B' to toggle between no bounding shapes, bounding boxes (AABBs) and bounding spheres / circles \n \
Press ' Space ' to switch between 3 D and 2 D " ,
TextStyle ::default ( ) ,
)
. with_style ( Style {
position_type : PositionType ::Absolute ,
top : Val ::Px ( 12.0 ) ,
left : Val ::Px ( 12.0 ) ,
.. default ( )
} ) ,
) ;
}
// Rotate the 2D shapes.
fn rotate_2d_shapes ( mut shapes : Query < & mut Transform , With < Shape2d > > , time : Res < Time > ) {
let elapsed_seconds = time . elapsed_seconds ( ) ;
for mut transform in shapes . iter_mut ( ) {
transform . rotation = Quat ::from_rotation_z ( elapsed_seconds ) ;
}
}
// Draw bounding boxes or circles for the 2D shapes.
fn bounding_shapes_2d (
shapes : Query < & Transform , With < Shape2d > > ,
mut gizmos : Gizmos ,
bounding_shape : Res < State < BoundingShape > > ,
) {
for transform in shapes . iter ( ) {
// Get the rotation angle from the 3D rotation.
let rotation = transform . rotation . to_scaled_axis ( ) . z ;
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
let rotation = Rot2 ::radians ( rotation ) ;
let isometry = Isometry2d ::new ( transform . translation . xy ( ) , rotation ) ;
2024-06-10 21:15:21 +00:00
match bounding_shape . get ( ) {
BoundingShape ::None = > ( ) ,
BoundingShape ::BoundingBox = > {
// Get the AABB of the primitive with the rotation and translation of the mesh.
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
let aabb = HEART . aabb_2d ( isometry ) ;
2024-06-10 21:15:21 +00:00
2024-08-28 01:37:19 +00:00
gizmos . rect_2d (
Isometry2d ::from_translation ( aabb . center ( ) ) ,
aabb . half_size ( ) * 2. ,
WHITE ,
) ;
2024-06-10 21:15:21 +00:00
}
BoundingShape ::BoundingSphere = > {
// Get the bounding sphere of the primitive with the rotation and translation of the mesh.
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
let bounding_circle = HEART . bounding_circle ( isometry ) ;
2024-06-10 21:15:21 +00:00
gizmos
2024-08-28 01:37:19 +00:00
. circle_2d (
Isometry2d ::from_translation ( bounding_circle . center ( ) ) ,
bounding_circle . radius ( ) ,
WHITE ,
)
2024-06-10 21:15:21 +00:00
. resolution ( 64 ) ;
}
}
}
}
// Rotate the 3D shapes.
fn rotate_3d_shapes ( mut shapes : Query < & mut Transform , With < Shape3d > > , time : Res < Time > ) {
let delta_seconds = time . delta_seconds ( ) ;
for mut transform in shapes . iter_mut ( ) {
transform . rotate_y ( delta_seconds ) ;
}
}
// Draw the AABBs or bounding spheres for the 3D shapes.
fn bounding_shapes_3d (
shapes : Query < & Transform , With < Shape3d > > ,
mut gizmos : Gizmos ,
bounding_shape : Res < State < BoundingShape > > ,
) {
for transform in shapes . iter ( ) {
match bounding_shape . get ( ) {
BoundingShape ::None = > ( ) ,
BoundingShape ::BoundingBox = > {
// Get the AABB of the extrusion with the rotation and translation of the mesh.
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
let aabb = EXTRUSION . aabb_3d ( transform . to_isometry ( ) ) ;
2024-06-10 21:15:21 +00:00
gizmos . primitive_3d (
& Cuboid ::from_size ( Vec3 ::from ( aabb . half_size ( ) ) * 2. ) ,
2024-08-28 01:37:19 +00:00
Isometry3d ::from_translation ( aabb . center ( ) ) ,
2024-06-10 21:15:21 +00:00
WHITE ,
) ;
}
BoundingShape ::BoundingSphere = > {
// Get the bounding sphere of the extrusion with the rotation and translation of the mesh.
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
let bounding_sphere = EXTRUSION . bounding_sphere ( transform . to_isometry ( ) ) ;
2024-06-10 21:15:21 +00:00
gizmos . sphere (
2024-08-28 01:37:19 +00:00
Isometry3d ::from_translation ( bounding_sphere . center ( ) ) ,
2024-06-10 21:15:21 +00:00
bounding_sphere . radius ( ) ,
WHITE ,
) ;
}
}
}
}
// Switch to the next bounding shape.
fn update_bounding_shape (
current : Res < State < BoundingShape > > ,
mut next : ResMut < NextState < BoundingShape > > ,
) {
next . set ( match current . get ( ) {
BoundingShape ::None = > BoundingShape ::BoundingBox ,
BoundingShape ::BoundingBox = > BoundingShape ::BoundingSphere ,
BoundingShape ::BoundingSphere = > BoundingShape ::None ,
} ) ;
}
// Switch between 2D and 3D cameras.
fn switch_cameras (
current : Res < State < CameraActive > > ,
mut next : ResMut < NextState < CameraActive > > ,
mut camera : Query < ( & mut Transform , & mut Projection ) > ,
) {
let next_state = match current . get ( ) {
CameraActive ::Dim2 = > CameraActive ::Dim3 ,
CameraActive ::Dim3 = > CameraActive ::Dim2 ,
} ;
next . set ( next_state ) ;
let ( mut transform , mut projection ) = camera . single_mut ( ) ;
match next_state {
CameraActive ::Dim2 = > {
* transform = TRANSFORM_2D ;
* projection = PROJECTION_2D ;
}
CameraActive ::Dim3 = > {
* transform = TRANSFORM_3D ;
* projection = PROJECTION_3D ;
}
} ;
}
/// A custom 2D heart primitive. The heart is made up of two circles centered at `Vec2::new(±radius, 0.)` each with the same `radius`.
2024-09-24 11:42:59 +00:00
///
2024-06-10 21:15:21 +00:00
/// The tip of the heart connects the two circles at a 45° angle from `Vec3::NEG_Y`.
#[ derive(Copy, Clone) ]
struct Heart {
/// The radius of each wing of the heart
radius : f32 ,
}
// The `Primitive2d` or `Primitive3d` trait is required by almost all other traits for primitives in bevy.
// Depending on your shape, you should implement either one of them.
impl Primitive2d for Heart { }
impl Heart {
const fn new ( radius : f32 ) -> Self {
Self { radius }
}
}
// The `Measured2d` and `Measured3d` traits are used to compute the perimeter, the area or the volume of a primitive.
// If you implement `Measured2d` for a 2D primitive, `Measured3d` is automatically implemented for `Extrusion<T>`.
impl Measured2d for Heart {
fn perimeter ( & self ) -> f32 {
2024-09-16 23:28:12 +00:00
self . radius * ( 2.5 * PI + ops ::powf ( 2 f32 , 1.5 ) + 2.0 )
2024-06-10 21:15:21 +00:00
}
fn area ( & self ) -> f32 {
let circle_area = PI * self . radius * self . radius ;
let triangle_area = self . radius * self . radius * ( 1.0 + 2 f32 . sqrt ( ) ) / 2.0 ;
let cutout = triangle_area - circle_area * 3.0 / 16.0 ;
2.0 * circle_area + 4.0 * cutout
}
}
// The `Bounded2d` or `Bounded3d` traits are used to compute the Axis Aligned Bounding Boxes or bounding circles / spheres for primitives.
impl Bounded2d for Heart {
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
fn aabb_2d ( & self , isometry : Isometry2d ) -> Aabb2d {
2024-06-10 21:15:21 +00:00
// The center of the circle at the center of the right wing of the heart
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
let circle_center = isometry . rotation * Vec2 ::new ( self . radius , 0.0 ) ;
2024-06-10 21:15:21 +00:00
// The maximum X and Y positions of the two circles of the wings of the heart.
let max_circle = circle_center . abs ( ) + Vec2 ::splat ( self . radius ) ;
// Since the two circles of the heart are mirrored around the origin, the minimum position is the negative of the maximum.
let min_circle = - max_circle ;
// The position of the tip at the bottom of the heart
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
let tip_position = isometry . rotation * Vec2 ::new ( 0.0 , - self . radius * ( 1. + SQRT_2 ) ) ;
2024-06-10 21:15:21 +00:00
Aabb2d {
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
min : isometry . translation + min_circle . min ( tip_position ) ,
max : isometry . translation + max_circle . max ( tip_position ) ,
2024-06-10 21:15:21 +00:00
}
}
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
fn bounding_circle ( & self , isometry : Isometry2d ) -> BoundingCircle {
2024-06-10 21:15:21 +00:00
// The bounding circle of the heart is not at its origin. This `offset` is the offset between the center of the bounding circle and its translation.
2024-09-16 23:28:12 +00:00
let offset = self . radius / ops ::powf ( 2 f32 , 1.5 ) ;
2024-06-10 21:15:21 +00:00
// The center of the bounding circle
Refactor Bounded2d/Bounded3d to use isometries (#14485)
# 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.
2024-07-29 23:37:02 +00:00
let center = isometry * Vec2 ::new ( 0.0 , - offset ) ;
2024-06-10 21:15:21 +00:00
// The radius of the bounding circle
let radius = self . radius * ( 1.0 + 2 f32 . sqrt ( ) ) - offset ;
BoundingCircle ::new ( center , radius )
}
}
// You can implement the `BoundedExtrusion` trait to implement `Bounded3d for Extrusion<Heart>`. There is a default implementation for both AABBs and bounding spheres,
// but you may be able to find faster solutions for your specific primitives.
impl BoundedExtrusion for Heart { }
// You can use the `Meshable` trait to create a `MeshBuilder` for the primitive.
impl Meshable for Heart {
// The meshbuilder can be used to create the actual mesh for that primitive.
type Output = HeartMeshBuilder ;
fn mesh ( & self ) -> Self ::Output {
Self ::Output {
heart : * self ,
resolution : 32 ,
}
}
}
// You can include any additional information needed for meshing the primitive in the meshbuilder.
struct HeartMeshBuilder {
heart : Heart ,
// The resolution determines the amount of vertices used for each wing of the heart
resolution : usize ,
}
// This trait is needed so that the configuration methods of the builder of the primitive are also available for the builder for the extrusion.
// If you do not want to support these configuration options for extrusions you can just implement them for your 2D mesh builder.
trait HeartBuilder {
/// Set the resolution for each of the wings of the heart.
fn resolution ( self , resolution : usize ) -> Self ;
}
impl HeartBuilder for HeartMeshBuilder {
fn resolution ( mut self , resolution : usize ) -> Self {
self . resolution = resolution ;
self
}
}
impl HeartBuilder for ExtrusionBuilder < Heart > {
fn resolution ( mut self , resolution : usize ) -> Self {
self . base_builder . resolution = resolution ;
self
}
}
impl MeshBuilder for HeartMeshBuilder {
// This is where you should build the actual mesh.
fn build ( & self ) -> Mesh {
let radius = self . heart . radius ;
// The curved parts of each wing (half) of the heart have an angle of `PI * 1.25` or 225°
let wing_angle = PI * 1.25 ;
// We create buffers for the vertices, their normals and UVs, as well as the indices used to connect the vertices.
let mut vertices = Vec ::with_capacity ( 2 * self . resolution ) ;
let mut uvs = Vec ::with_capacity ( 2 * self . resolution ) ;
let mut indices = Vec ::with_capacity ( 6 * self . resolution - 9 ) ;
// Since the heart is flat, we know all the normals are identical already.
let normals = vec! [ [ 0 f32 , 0 f32 , 1 f32 ] ; 2 * self . resolution ] ;
// The point in the middle of the two curved parts of the heart
vertices . push ( [ 0.0 ; 3 ] ) ;
uvs . push ( [ 0.5 , 0.5 ] ) ;
// The left wing of the heart, starting from the point in the middle.
for i in 1 .. self . resolution {
let angle = ( i as f32 / self . resolution as f32 ) * wing_angle ;
2024-09-16 23:28:12 +00:00
let ( sin , cos ) = ops ::sin_cos ( angle ) ;
2024-06-10 21:15:21 +00:00
vertices . push ( [ radius * ( cos - 1.0 ) , radius * sin , 0.0 ] ) ;
uvs . push ( [ 0.5 - ( cos - 1.0 ) / 4. , 0.5 - sin / 2. ] ) ;
}
// The bottom tip of the heart
vertices . push ( [ 0.0 , radius * ( - 1. - SQRT_2 ) , 0.0 ] ) ;
uvs . push ( [ 0.5 , 1. ] ) ;
// The right wing of the heart, starting from the bottom most point and going towards the middle point.
for i in 0 .. self . resolution - 1 {
let angle = ( i as f32 / self . resolution as f32 ) * wing_angle - PI / 4. ;
2024-09-16 23:28:12 +00:00
let ( sin , cos ) = ops ::sin_cos ( angle ) ;
2024-06-10 21:15:21 +00:00
vertices . push ( [ radius * ( cos + 1.0 ) , radius * sin , 0.0 ] ) ;
uvs . push ( [ 0.5 - ( cos + 1.0 ) / 4. , 0.5 - sin / 2. ] ) ;
}
// This is where we build all the triangles from the points created above.
// Each triangle has one corner on the middle point with the other two being adjacent points on the perimeter of the heart.
for i in 2 .. 2 * self . resolution as u32 {
indices . extend_from_slice ( & [ i - 1 , i , 0 ] ) ;
}
// Here, the actual `Mesh` is created. We set the indices, vertices, normals and UVs created above and specify the topology of the mesh.
Mesh ::new (
bevy ::render ::mesh ::PrimitiveTopology ::TriangleList ,
RenderAssetUsages ::default ( ) ,
)
. with_inserted_indices ( bevy ::render ::mesh ::Indices ::U32 ( indices ) )
. with_inserted_attribute ( Mesh ::ATTRIBUTE_POSITION , vertices )
. with_inserted_attribute ( Mesh ::ATTRIBUTE_NORMAL , normals )
. with_inserted_attribute ( Mesh ::ATTRIBUTE_UV_0 , uvs )
}
}
// The `Extrudable` trait can be used to easily implement meshing for extrusions.
impl Extrudable for HeartMeshBuilder {
2024-08-21 12:29:33 +00:00
fn perimeter ( & self ) -> Vec < PerimeterSegment > {
2024-06-10 21:15:21 +00:00
let resolution = self . resolution as u32 ;
vec! [
// The left wing of the heart
PerimeterSegment ::Smooth {
// The normals of the first and last vertices of smooth segments have to be specified manually.
first_normal : Vec2 ::X ,
last_normal : Vec2 ::new ( - 1.0 , - 1.0 ) . normalize ( ) ,
// These indices are used to index into the `ATTRIBUTE_POSITION` vec of your 2D mesh.
indices : ( 0 .. resolution ) . collect ( ) ,
} ,
// The bottom tip of the heart
PerimeterSegment ::Flat {
indices : vec ! [ resolution - 1 , resolution , resolution + 1 ] ,
} ,
// The right wing of the heart
PerimeterSegment ::Smooth {
first_normal : Vec2 ::new ( 1.0 , - 1.0 ) . normalize ( ) ,
last_normal : Vec2 ::NEG_X ,
indices : ( resolution + 1 .. 2 * resolution ) . chain ( [ 0 ] ) . collect ( ) ,
} ,
]
}
}