Implement From translation and rotation for isometries (#15733)

# Objective

Several of our APIs (namely gizmos and bounding) use isometries on
current Bevy main. This is nicer than separate properties in a lot of
cases, but users have still expressed usability concerns.

One problem is that in a lot of cases, you only care about e.g.
translation, so you end up with this:

```rust
gizmos.cross_2d(
    Isometry2d::from_translation(Vec2::new(-160.0, 120.0)),
    12.0,
    FUCHSIA,
);
```

The isometry adds quite a lot of length and verbosity, and isn't really
that relevant since only the translation is important here.

It would be nice if you could use the translation directly, and only
supply an isometry if both translation and rotation are needed. This
would make the following possible:

```rust
gizmos.cross_2d(Vec2::new(-160.0, 120.0), 12.0, FUCHSIA);
```

removing a lot of verbosity.

## Solution

Implement `From<Vec2>` and `From<Rot2>` for `Isometry2d`, and
`From<Vec3>`, `From<Vec3A>`, and `From<Quat>` for `Isometry3d`. These
are lossless conversions that fit the semantics of `From`.

This makes the proposed API possible! The methods must now simply take
an `impl Into<IsometryNd>`, and this works:

```rust
gizmos.cross_2d(Vec2::new(-160.0, 120.0), 12.0, FUCHSIA);
```
This commit is contained in:
Joona Aalto 2024-10-08 19:09:28 +03:00 committed by GitHub
parent 8d53c0af91
commit 21b78b5990
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 430 additions and 344 deletions

View file

@ -50,14 +50,14 @@ where
#[inline]
pub fn arc_2d(
&mut self,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
arc_angle: f32,
radius: f32,
color: impl Into<Color>,
) -> Arc2dBuilder<'_, 'w, 's, Config, Clear> {
Arc2dBuilder {
gizmos: self,
isometry,
isometry: isometry.into(),
arc_angle,
radius,
color: color.into(),
@ -176,13 +176,13 @@ where
&mut self,
angle: f32,
radius: f32,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Arc3dBuilder<'_, 'w, 's, Config, Clear> {
Arc3dBuilder {
gizmos: self,
start_vertex: Vec3::X,
isometry,
isometry: isometry.into(),
angle,
radius,
color: color.into(),

View file

@ -51,13 +51,13 @@ where
#[inline]
pub fn ellipse(
&mut self,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
half_size: Vec2,
color: impl Into<Color>,
) -> EllipseBuilder<'_, 'w, 's, Config, Clear> {
EllipseBuilder {
gizmos: self,
isometry,
isometry: isometry.into(),
half_size,
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
@ -92,13 +92,13 @@ where
#[inline]
pub fn ellipse_2d(
&mut self,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
half_size: Vec2,
color: impl Into<Color>,
) -> Ellipse2dBuilder<'_, 'w, 's, Config, Clear> {
Ellipse2dBuilder {
gizmos: self,
isometry,
isometry: isometry.into(),
half_size,
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
@ -131,13 +131,13 @@ where
#[inline]
pub fn circle(
&mut self,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
radius: f32,
color: impl Into<Color>,
) -> EllipseBuilder<'_, 'w, 's, Config, Clear> {
EllipseBuilder {
gizmos: self,
isometry,
isometry: isometry.into(),
half_size: Vec2::splat(radius),
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
@ -172,13 +172,13 @@ where
#[inline]
pub fn circle_2d(
&mut self,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
radius: f32,
color: impl Into<Color>,
) -> Ellipse2dBuilder<'_, 'w, 's, Config, Clear> {
Ellipse2dBuilder {
gizmos: self,
isometry,
isometry: isometry.into(),
half_size: Vec2::splat(radius),
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
@ -214,14 +214,14 @@ where
#[inline]
pub fn sphere(
&mut self,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
radius: f32,
color: impl Into<Color>,
) -> SphereBuilder<'_, 'w, 's, Config, Clear> {
SphereBuilder {
gizmos: self,
radius,
isometry,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
}

View file

@ -30,7 +30,13 @@ where
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn cross(&mut self, isometry: Isometry3d, half_size: f32, color: impl Into<Color>) {
pub fn cross(
&mut self,
isometry: impl Into<Isometry3d>,
half_size: f32,
color: impl Into<Color>,
) {
let isometry = isometry.into();
let color: Color = color.into();
[Vec3::X, Vec3::Y, Vec3::Z]
.map(|axis| axis * half_size)
@ -59,7 +65,13 @@ where
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn cross_2d(&mut self, isometry: Isometry2d, half_size: f32, color: impl Into<Color>) {
pub fn cross_2d(
&mut self,
isometry: impl Into<Isometry2d>,
half_size: f32,
color: impl Into<Color>,
) {
let isometry = isometry.into();
let color: Color = color.into();
[Vec2::X, Vec2::Y]
.map(|axis| axis * half_size)

View file

@ -482,10 +482,11 @@ where
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn rect(&mut self, isometry: Isometry3d, size: Vec2, color: impl Into<Color>) {
pub fn rect(&mut self, isometry: impl Into<Isometry3d>, size: Vec2, color: impl Into<Color>) {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [tl, tr, br, bl] = rect_inner(size).map(|vec2| isometry * vec2.extend(0.));
self.linestrip([tl, tr, br, bl, tl], color);
}
@ -709,10 +710,16 @@ where
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn rect_2d(&mut self, isometry: Isometry2d, size: Vec2, color: impl Into<Color>) {
pub fn rect_2d(
&mut self,
isometry: impl Into<Isometry2d>,
size: Vec2,
color: impl Into<Color>,
) {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [tl, tr, br, bl] = rect_inner(size).map(|vec2| isometry * vec2);
self.linestrip_2d([tl, tr, br, bl, tl], color);
}

View file

@ -218,14 +218,14 @@ where
/// ```
pub fn grid(
&mut self,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
cell_count: UVec2,
spacing: Vec2,
color: impl Into<Color>,
) -> GridBuilder2d<'_, 'w, 's, Config, Clear> {
GridBuilder2d {
gizmos: self,
isometry,
isometry: isometry.into(),
spacing,
cell_count,
skew: Vec2::ZERO,
@ -272,14 +272,14 @@ where
/// ```
pub fn grid_3d(
&mut self,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
cell_count: UVec3,
spacing: Vec3,
color: impl Into<Color>,
) -> GridBuilder3d<'_, 'w, 's, Config, Clear> {
GridBuilder3d {
gizmos: self,
isometry,
isometry: isometry.into(),
spacing,
cell_count,
skew: Vec3::ZERO,
@ -326,11 +326,12 @@ where
/// ```
pub fn grid_2d(
&mut self,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
cell_count: UVec2,
spacing: Vec2,
color: impl Into<Color>,
) -> GridBuilder2d<'_, 'w, 's, Config, Clear> {
let isometry = isometry.into();
GridBuilder2d {
gizmos: self,
isometry: Isometry3d::new(

View file

@ -41,20 +41,10 @@ fn point_light_gizmo(
) {
let position = transform.translation();
gizmos
.primitive_3d(
&Sphere {
radius: point_light.radius,
},
Isometry3d::from_translation(position),
color,
)
.primitive_3d(&Sphere::new(point_light.radius), position, color)
.resolution(16);
gizmos
.sphere(
Isometry3d::from_translation(position),
point_light.range,
color,
)
.sphere(position, point_light.range, color)
.resolution(32);
}
@ -68,13 +58,7 @@ fn spot_light_gizmo(
) {
let (_, rotation, translation) = transform.to_scale_rotation_translation();
gizmos
.primitive_3d(
&Sphere {
radius: spot_light.radius,
},
Isometry3d::from_translation(translation),
color,
)
.primitive_3d(&Sphere::new(spot_light.radius), translation, color)
.resolution(16);
// Offset the tip of the cone to the light position.

View file

@ -33,7 +33,7 @@ pub trait GizmoPrimitive2d<P: Primitive2d> {
fn primitive_2d(
&mut self,
primitive: &P,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_>;
}
@ -53,12 +53,13 @@ where
fn primitive_2d(
&mut self,
primitive: &Dir2,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let start = Vec2::ZERO;
let end = *primitive * MIN_LINE_LEN;
self.arrow_2d(isometry * start, isometry * end, color);
@ -80,13 +81,14 @@ where
fn primitive_2d(
&mut self,
primitive: &Arc2d,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let start_iso = isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.half_angle));
self.arc_2d(
@ -113,7 +115,7 @@ where
fn primitive_2d(
&mut self,
primitive: &Circle,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
self.circle_2d(isometry, primitive.radius, color)
@ -135,13 +137,14 @@ where
fn primitive_2d(
&mut self,
primitive: &CircularSector,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let color = color.into();
let start_iso =
@ -177,13 +180,14 @@ where
fn primitive_2d(
&mut self,
primitive: &CircularSegment,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let color = color.into();
let start_iso =
@ -218,7 +222,7 @@ where
fn primitive_2d<'a>(
&mut self,
primitive: &Ellipse,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
self.ellipse_2d(isometry, primitive.half_size, color)
@ -280,12 +284,12 @@ where
fn primitive_2d(
&mut self,
primitive: &Annulus,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Annulus2dBuilder {
gizmos: self,
isometry,
isometry: isometry.into(),
inner_radius: primitive.inner_circle.radius,
outer_radius: primitive.outer_circle.radius,
color: color.into(),
@ -340,12 +344,13 @@ where
fn primitive_2d(
&mut self,
primitive: &Rhombus,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
};
let isometry = isometry.into();
let [a, b, c, d] =
[(1.0, 0.0), (0.0, 1.0), (-1.0, 0.0), (0.0, -1.0)].map(|(sign_x, sign_y)| {
Vec2::new(
@ -373,9 +378,10 @@ where
fn primitive_2d(
&mut self,
primitive: &Capsule2d,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
let isometry = isometry.into();
let polymorphic_color: Color = color.into();
if !self.enabled {
@ -465,13 +471,13 @@ where
fn primitive_2d(
&mut self,
primitive: &Line2d,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Line2dBuilder {
gizmos: self,
direction: primitive.direction,
isometry,
isometry: isometry.into(),
color: color.into(),
draw_arrow: false,
}
@ -523,9 +529,10 @@ where
fn primitive_2d(
&mut self,
primitive: &Plane2d,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
let isometry = isometry.into();
let polymorphic_color: Color = color.into();
if !self.enabled {
@ -604,7 +611,7 @@ where
fn primitive_2d(
&mut self,
primitive: &Segment2d,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Segment2dBuilder {
@ -612,7 +619,7 @@ where
direction: primitive.direction,
half_length: primitive.half_length,
isometry,
isometry: isometry.into(),
color: color.into(),
draw_arrow: Default::default(),
@ -658,13 +665,15 @@ where
fn primitive_2d(
&mut self,
primitive: &Polyline2d<N>,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip_2d(
primitive
.vertices
@ -691,13 +700,15 @@ where
fn primitive_2d(
&mut self,
primitive: &BoxedPolyline2d,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip_2d(
primitive
.vertices
@ -724,12 +735,15 @@ where
fn primitive_2d(
&mut self,
primitive: &Triangle2d,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c] = primitive.vertices;
let positions = [a, b, c, a].map(|vec2| isometry * vec2);
self.linestrip_2d(positions, color);
@ -751,13 +765,15 @@ where
fn primitive_2d(
&mut self,
primitive: &Rectangle,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c, d] =
[(1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0)].map(|(sign_x, sign_y)| {
Vec2::new(
@ -786,13 +802,15 @@ where
fn primitive_2d(
&mut self,
primitive: &Polygon<N>,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
// Check if the polygon needs a closing point
let closing_point = {
let first = primitive.vertices.first();
@ -829,13 +847,15 @@ where
fn primitive_2d(
&mut self,
primitive: &BoxedPolygon,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let closing_point = {
let first = primitive.vertices.first();
(primitive.vertices.last() != first)
@ -870,13 +890,15 @@ where
fn primitive_2d(
&mut self,
primitive: &RegularPolygon,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let points = (0..=primitive.sides)
.map(|n| single_circle_coordinate(primitive.circumcircle.radius, primitive.sides, n))
.map(|vec2| isometry * vec2);

View file

@ -31,7 +31,7 @@ pub trait GizmoPrimitive3d<P: Primitive3d> {
fn primitive_3d(
&mut self,
primitive: &P,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_>;
}
@ -51,9 +51,10 @@ where
fn primitive_3d(
&mut self,
primitive: &Dir3,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
let isometry = isometry.into();
let start = Vec3::ZERO;
let end = primitive.as_vec3();
self.arrow(isometry * start, isometry * end, color);
@ -75,7 +76,7 @@ where
fn primitive_3d(
&mut self,
primitive: &Sphere,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
self.sphere(isometry, primitive.radius, color)
@ -136,13 +137,13 @@ where
fn primitive_3d(
&mut self,
primitive: &Plane3d,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Plane3dBuilder {
gizmos: self,
normal: primitive.normal,
isometry,
isometry: isometry.into(),
color: color.into(),
cell_count: UVec2::splat(3),
spacing: Vec2::splat(1.0),
@ -188,13 +189,14 @@ where
fn primitive_3d(
&mut self,
primitive: &Line3d,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let color = color.into();
let direction = primitive.direction.as_vec3();
self.arrow(isometry * Vec3::ZERO, isometry * direction, color);
@ -222,13 +224,14 @@ where
fn primitive_3d(
&mut self,
primitive: &Segment3d,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let direction = primitive.direction.as_vec3();
self.line(isometry * direction, isometry * (-direction), color);
}
@ -250,13 +253,14 @@ where
fn primitive_3d(
&mut self,
primitive: &Polyline3d<N>,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip(primitive.vertices.map(|vec3| isometry * vec3), color);
}
}
@ -276,13 +280,14 @@ where
fn primitive_3d(
&mut self,
primitive: &BoxedPolyline3d,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip(
primitive
.vertices
@ -309,13 +314,14 @@ where
fn primitive_3d(
&mut self,
primitive: &Triangle3d,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c] = primitive.vertices;
self.linestrip([a, b, c, a].map(|vec3| isometry * vec3), color);
}
@ -336,13 +342,15 @@ where
fn primitive_3d(
&mut self,
primitive: &Cuboid,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
// transform the points from the reference unit cube to the cuboid coords
let vertices @ [a, b, c, d, e, f, g, h] = [
[1.0, 1.0, 1.0],
@ -429,14 +437,14 @@ where
fn primitive_3d(
&mut self,
primitive: &Cylinder,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Cylinder3dBuilder {
gizmos: self,
radius: primitive.radius,
half_height: primitive.half_height,
isometry,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_RESOLUTION,
}
@ -515,14 +523,14 @@ where
fn primitive_3d(
&mut self,
primitive: &Capsule3d,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Capsule3dBuilder {
gizmos: self,
radius: primitive.radius,
half_length: primitive.half_length,
isometry,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_RESOLUTION,
}
@ -655,14 +663,14 @@ where
fn primitive_3d(
&mut self,
primitive: &Cone,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Cone3dBuilder {
gizmos: self,
radius: primitive.radius,
height: primitive.height,
isometry,
isometry: isometry.into(),
color: color.into(),
base_resolution: DEFAULT_RESOLUTION,
height_resolution: DEFAULT_RESOLUTION,
@ -757,7 +765,7 @@ where
fn primitive_3d(
&mut self,
primitive: &ConicalFrustum,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
ConicalFrustum3dBuilder {
@ -765,7 +773,7 @@ where
radius_top: primitive.radius_top,
radius_bottom: primitive.radius_bottom,
height: primitive.height,
isometry,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_RESOLUTION,
}
@ -861,14 +869,14 @@ where
fn primitive_3d(
&mut self,
primitive: &Torus,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Torus3dBuilder {
gizmos: self,
minor_radius: primitive.minor_radius,
major_radius: primitive.major_radius,
isometry,
isometry: isometry.into(),
color: color.into(),
minor_resolution: DEFAULT_RESOLUTION,
major_resolution: DEFAULT_RESOLUTION,
@ -937,13 +945,15 @@ impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d<Tetrahedron> for Gizmos<'w, '
fn primitive_3d(
&mut self,
primitive: &Tetrahedron,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c, d] = primitive.vertices.map(|vec3| isometry * vec3);
let lines = [(a, b), (a, c), (a, d), (b, c), (b, d), (c, d)];

View file

@ -240,7 +240,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// ```
pub fn rounded_rect(
&mut self,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
size: Vec2,
color: impl Into<Color>,
) -> RoundedRectBuilder<'_, 'w, 's, T> {
@ -248,7 +248,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
RoundedRectBuilder {
gizmos: self,
config: RoundedBoxConfig {
isometry,
isometry: isometry.into(),
color: color.into(),
corner_radius,
arc_resolution: DEFAULT_ARC_RESOLUTION,
@ -294,10 +294,11 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// ```
pub fn rounded_rect_2d(
&mut self,
isometry: Isometry2d,
isometry: impl Into<Isometry2d>,
size: Vec2,
color: impl Into<Color>,
) -> RoundedRectBuilder<'_, 'w, 's, T> {
let isometry = isometry.into();
let corner_radius = size.min_element() * DEFAULT_CORNER_RADIUS;
RoundedRectBuilder {
gizmos: self,
@ -351,7 +352,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
/// ```
pub fn rounded_cuboid(
&mut self,
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
size: Vec3,
color: impl Into<Color>,
) -> RoundedCuboidBuilder<'_, 'w, 's, T> {
@ -359,7 +360,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
RoundedCuboidBuilder {
gizmos: self,
config: RoundedBoxConfig {
isometry,
isometry: isometry.into(),
color: color.into(),
corner_radius,
arc_resolution: DEFAULT_ARC_RESOLUTION,

View file

@ -24,9 +24,9 @@ fn point_cloud_2d_center(points: &[Vec2]) -> Vec2 {
/// A trait with methods that return 2D bounding volumes for a shape.
pub trait Bounded2d {
/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d;
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d;
/// Get a bounding circle for the shape translated and rotated by the given isometry.
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle;
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle;
}
/// A 2D axis-aligned bounding box, or bounding rectangle
@ -58,7 +58,9 @@ impl Aabb2d {
///
/// Panics if the given set of points is empty.
#[inline(always)]
pub fn from_point_cloud(isometry: Isometry2d, points: &[Vec2]) -> Aabb2d {
pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> Aabb2d {
let isometry = isometry.into();
// Transform all points by rotation
let mut iter = points.iter().map(|point| isometry.rotation * *point);
@ -472,7 +474,9 @@ impl BoundingCircle {
///
/// The bounding circle is not guaranteed to be the smallest possible.
#[inline(always)]
pub fn from_point_cloud(isometry: Isometry2d, points: &[Vec2]) -> BoundingCircle {
pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> BoundingCircle {
let isometry = isometry.into();
let center = point_cloud_2d_center(points);
let mut radius_squared = 0.0;

View file

@ -16,11 +16,13 @@ use smallvec::SmallVec;
use super::{Aabb2d, Bounded2d, BoundingCircle};
impl Bounded2d for Circle {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
Aabb2d::new(isometry.translation, Vec2::splat(self.radius))
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
BoundingCircle::new(isometry.translation, self.radius)
}
}
@ -58,19 +60,23 @@ fn arc_bounding_points(arc: Arc2d, rotation: impl Into<Rot2>) -> SmallVec<[Vec2;
}
impl Bounded2d for Arc2d {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
// If our arc covers more than a circle, just return the bounding box of the circle.
if self.half_angle >= PI {
return Circle::new(self.radius).aabb_2d(isometry);
}
let isometry = isometry.into();
Aabb2d::from_point_cloud(
Isometry2d::from_translation(isometry.translation),
&arc_bounding_points(*self, isometry.rotation),
)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
// There are two possibilities for the bounding circle.
if self.is_major() {
// If the arc is major, then the widest distance between two points is a diameter of the arc's circle;
@ -86,7 +92,9 @@ impl Bounded2d for Arc2d {
}
impl Bounded2d for CircularSector {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
// If our sector covers more than a circle, just return the bounding box of the circle.
if self.half_angle() >= PI {
return Circle::new(self.radius()).aabb_2d(isometry);
@ -99,8 +107,10 @@ impl Bounded2d for CircularSector {
Aabb2d::from_point_cloud(Isometry2d::from_translation(isometry.translation), &bounds)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
if self.arc.is_major() {
let isometry = isometry.into();
// If the arc is major, that is, greater than a semicircle,
// then bounding circle is just the circle defining the sector.
BoundingCircle::new(isometry.translation, self.arc.radius)
@ -121,17 +131,19 @@ impl Bounded2d for CircularSector {
}
impl Bounded2d for CircularSegment {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
self.arc.aabb_2d(isometry)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
self.arc.bounding_circle(isometry)
}
}
impl Bounded2d for Ellipse {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
// V = (hh * cos(beta), hh * sin(beta))
// #####*#####
// ### | ###
@ -160,23 +172,28 @@ impl Bounded2d for Ellipse {
Aabb2d::new(isometry.translation, half_size)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
BoundingCircle::new(isometry.translation, self.semi_major())
}
}
impl Bounded2d for Annulus {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
Aabb2d::new(isometry.translation, Vec2::splat(self.outer_circle.radius))
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
BoundingCircle::new(isometry.translation, self.outer_circle.radius)
}
}
impl Bounded2d for Rhombus {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
let [rotated_x_half_diagonal, rotated_y_half_diagonal] = [
isometry.rotation * Vec2::new(self.half_diagonals.x, 0.0),
isometry.rotation * Vec2::new(0.0, self.half_diagonals.y),
@ -191,13 +208,16 @@ impl Bounded2d for Rhombus {
}
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
BoundingCircle::new(isometry.translation, self.circumradius())
}
}
impl Bounded2d for Plane2d {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
let normal = isometry.rotation * *self.normal;
let facing_x = normal == Vec2::X || normal == Vec2::NEG_X;
let facing_y = normal == Vec2::Y || normal == Vec2::NEG_Y;
@ -211,13 +231,16 @@ impl Bounded2d for Plane2d {
Aabb2d::new(isometry.translation, half_size)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
BoundingCircle::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded2d for Line2d {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
let direction = isometry.rotation * *self.direction;
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
@ -230,13 +253,16 @@ impl Bounded2d for Line2d {
Aabb2d::new(isometry.translation, half_size)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
BoundingCircle::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded2d for Segment2d {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
// Rotate the segment by `rotation`
let direction = isometry.rotation * *self.direction;
let half_size = (self.half_length * direction).abs();
@ -244,33 +270,35 @@ impl Bounded2d for Segment2d {
Aabb2d::new(isometry.translation, half_size)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
BoundingCircle::new(isometry.translation, self.half_length)
}
}
impl<const N: usize> Bounded2d for Polyline2d<N> {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
Aabb2d::from_point_cloud(isometry, &self.vertices)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
BoundingCircle::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded2d for BoxedPolyline2d {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
Aabb2d::from_point_cloud(isometry, &self.vertices)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
BoundingCircle::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded2d for Triangle2d {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
let [a, b, c] = self.vertices.map(|vtx| isometry.rotation * vtx);
let min = Vec2::new(a.x.min(b.x).min(c.x), a.y.min(b.y).min(c.y));
@ -282,7 +310,8 @@ impl Bounded2d for Triangle2d {
}
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
let [a, b, c] = self.vertices;
// The points of the segment opposite to the obtuse or right angle if one exists
@ -313,7 +342,9 @@ impl Bounded2d for Triangle2d {
}
impl Bounded2d for Rectangle {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
// Compute the AABB of the rotated rectangle by transforming the half-extents
// by an absolute rotation matrix.
let (sin, cos) = isometry.rotation.sin_cos();
@ -323,34 +354,37 @@ impl Bounded2d for Rectangle {
Aabb2d::new(isometry.translation, half_size)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
let radius = self.half_size.length();
BoundingCircle::new(isometry.translation, radius)
}
}
impl<const N: usize> Bounded2d for Polygon<N> {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
Aabb2d::from_point_cloud(isometry, &self.vertices)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
BoundingCircle::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded2d for BoxedPolygon {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
Aabb2d::from_point_cloud(isometry, &self.vertices)
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
BoundingCircle::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded2d for RegularPolygon {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
let mut min = Vec2::ZERO;
let mut max = Vec2::ZERO;
@ -365,13 +399,16 @@ impl Bounded2d for RegularPolygon {
}
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
BoundingCircle::new(isometry.translation, self.circumcircle.radius)
}
}
impl Bounded2d for Capsule2d {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
// Get the line segment between the hemicircles of the rotated capsule
let segment = Segment2d {
// Multiplying a normalized vector (Vec2::Y) with a rotation returns a normalized vector.
@ -390,7 +427,8 @@ impl Bounded2d for Capsule2d {
}
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
BoundingCircle::new(isometry.translation, self.radius + self.half_length)
}
}

View file

@ -17,9 +17,11 @@ use crate::{bounding::Bounded2d, primitives::Circle};
use super::{Aabb3d, Bounded3d, BoundingSphere};
impl BoundedExtrusion for Circle {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Z;
let top = (segment_dir * half_depth).abs();
@ -34,7 +36,8 @@ impl BoundedExtrusion for Circle {
}
impl BoundedExtrusion for Ellipse {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let Vec2 { x: a, y: b } = self.half_size;
let normal = isometry.rotation * Vec3A::Z;
let conjugate_rot = isometry.rotation.conjugate();
@ -64,7 +67,8 @@ impl BoundedExtrusion for Ellipse {
}
impl BoundedExtrusion for Line2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let dir = isometry.rotation * Vec3A::from(self.direction.extend(0.));
let half_depth = (isometry.rotation * Vec3A::new(0., 0., half_depth)).abs();
@ -80,7 +84,8 @@ impl BoundedExtrusion for Line2d {
}
impl BoundedExtrusion for Segment2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let half_size = isometry.rotation * Vec3A::from(self.point1().extend(0.));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
@ -89,7 +94,8 @@ impl BoundedExtrusion for Segment2d {
}
impl<const N: usize> BoundedExtrusion for Polyline2d<N> {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb =
Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
@ -99,7 +105,8 @@ impl<const N: usize> BoundedExtrusion for Polyline2d<N> {
}
impl BoundedExtrusion for BoxedPolyline2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
@ -108,7 +115,8 @@ impl BoundedExtrusion for BoxedPolyline2d {
}
impl BoundedExtrusion for Triangle2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
@ -117,7 +125,7 @@ impl BoundedExtrusion for Triangle2d {
}
impl BoundedExtrusion for Rectangle {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
Cuboid {
half_size: self.half_size.extend(half_depth),
}
@ -126,7 +134,8 @@ impl BoundedExtrusion for Rectangle {
}
impl<const N: usize> BoundedExtrusion for Polygon<N> {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb =
Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
@ -136,7 +145,8 @@ impl<const N: usize> BoundedExtrusion for Polygon<N> {
}
impl BoundedExtrusion for BoxedPolygon {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
@ -145,7 +155,8 @@ impl BoundedExtrusion for BoxedPolygon {
}
impl BoundedExtrusion for RegularPolygon {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(
isometry,
self.vertices(0.).into_iter().map(|v| v.extend(0.)),
@ -157,14 +168,13 @@ impl BoundedExtrusion for RegularPolygon {
}
impl BoundedExtrusion for Capsule2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Cylinder {
half_height: half_depth,
radius: self.radius,
}
.aabb_3d(Isometry3d::from_rotation(
isometry.rotation * Quat::from_rotation_x(FRAC_PI_2),
));
.aabb_3d(isometry.rotation * Quat::from_rotation_x(FRAC_PI_2));
let up = isometry.rotation * Vec3A::new(0., self.half_length, 0.);
let half_size = aabb.max + up.abs();
@ -173,11 +183,11 @@ impl BoundedExtrusion for Capsule2d {
}
impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
self.base_shape.extrusion_aabb_3d(self.half_depth, isometry)
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
self.base_shape
.extrusion_bounding_sphere(self.half_depth, isometry)
}
@ -191,7 +201,8 @@ impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {
/// `impl BoundedExtrusion for MyShape {}`
pub trait BoundedExtrusion: Primitive2d + Bounded2d {
/// Get an axis-aligned bounding box for an extrusion with this shape as a base and the given `half_depth`, transformed by the given `translation` and `rotation`.
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: Isometry3d) -> Aabb3d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let cap_normal = isometry.rotation * Vec3A::Z;
let conjugate_rot = isometry.rotation.conjugate();
@ -213,7 +224,7 @@ pub trait BoundedExtrusion: Primitive2d + Bounded2d {
// Calculate the `Aabb2d` of the base shape. The shape is rotated so that the line of intersection is parallel to the Y axis in the `Aabb2d` calculations.
// This guarantees that the X value of the `Aabb2d` is closest to the `ax` plane
let aabb2d = self.aabb_2d(Isometry2d::from_rotation(Rot2::radians(angle)));
let aabb2d = self.aabb_2d(Rot2::radians(angle));
(aabb2d.half_size().x * scale, aabb2d.center().x * scale)
});
@ -225,7 +236,13 @@ pub trait BoundedExtrusion: Primitive2d + Bounded2d {
}
/// Get a bounding sphere for an extrusion of the `base_shape` with the given `half_depth` with the given translation and rotation
fn extrusion_bounding_sphere(&self, half_depth: f32, isometry: Isometry3d) -> BoundingSphere {
fn extrusion_bounding_sphere(
&self,
half_depth: f32,
isometry: impl Into<Isometry3d>,
) -> BoundingSphere {
let isometry = isometry.into();
// We calculate the bounding circle of the base shape.
// Since each of the extrusions bases will have the same distance from its center,
// and they are just shifted along the Z-axis, the minimum bounding sphere will be the bounding sphere
@ -261,13 +278,12 @@ mod tests {
fn circle() {
let cylinder = Extrusion::new(Circle::new(0.5), 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = cylinder.aabb_3d(isometry);
let aabb = cylinder.aabb_3d(translation);
assert_eq!(aabb.center(), Vec3A::from(translation));
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 0.5, 1.0));
let bounding_sphere = cylinder.bounding_sphere(isometry);
let bounding_sphere = cylinder.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
}

View file

@ -27,9 +27,9 @@ fn point_cloud_3d_center(points: impl Iterator<Item = impl Into<Vec3A>>) -> Vec3
/// 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;
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d;
/// Get a bounding sphere for the shape translated and rotated by the given isometry.
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere;
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere;
}
/// A 3D axis-aligned bounding box
@ -62,9 +62,11 @@ impl Aabb3d {
/// Panics if the given set of points is empty.
#[inline(always)]
pub fn from_point_cloud(
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
points: impl Iterator<Item = impl Into<Vec3A>>,
) -> Aabb3d {
let isometry = isometry.into();
// Transform all points by rotation
let mut iter = points.map(|point| isometry.rotation * point.into());
@ -476,9 +478,11 @@ impl BoundingSphere {
/// The bounding sphere is not guaranteed to be the smallest possible.
#[inline(always)]
pub fn from_point_cloud(
isometry: Isometry3d,
isometry: impl Into<Isometry3d>,
points: &[impl Copy + Into<Vec3A>],
) -> BoundingSphere {
let isometry = isometry.into();
let center = point_cloud_3d_center(points.iter().map(|v| Into::<Vec3A>::into(*v)));
let mut radius_squared: f32 = 0.0;

View file

@ -15,17 +15,21 @@ use crate::{
use super::{Aabb3d, Bounded3d, BoundingSphere};
impl Bounded3d for Sphere {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
Aabb3d::new(isometry.translation, Vec3::splat(self.radius))
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.radius)
}
}
impl Bounded3d for InfinitePlane3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let normal = isometry.rotation * *self.normal;
let facing_x = normal == Vec3::X || normal == Vec3::NEG_X;
let facing_y = normal == Vec3::Y || normal == Vec3::NEG_Y;
@ -41,13 +45,15 @@ impl Bounded3d for InfinitePlane3d {
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded3d for Line3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let direction = isometry.rotation * *self.direction;
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
@ -61,13 +67,16 @@ impl Bounded3d for Line3d {
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded3d for Segment3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
// Rotate the segment by `rotation`
let direction = isometry.rotation * *self.direction;
let half_size = (self.half_length * direction).abs();
@ -75,33 +84,36 @@ impl Bounded3d for Segment3d {
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.half_length)
}
}
impl<const N: usize> Bounded3d for Polyline3d<N> {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
BoundingSphere::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded3d for BoxedPolyline3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
BoundingSphere::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded3d for Cuboid {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
// Compute the AABB of the rotated cuboid by transforming the half-size
// by an absolute rotation matrix.
let rot_mat = Mat3::from_quat(isometry.rotation);
@ -115,15 +127,18 @@ impl Bounded3d for Cuboid {
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.half_size.length())
}
}
impl Bounded3d for Cylinder {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * self.half_height;
let bottom = -top;
@ -137,14 +152,17 @@ impl Bounded3d for Cylinder {
}
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
let radius = ops::hypot(self.radius, self.half_height);
BoundingSphere::new(isometry.translation, radius)
}
}
impl Bounded3d for Capsule3d {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
// Get the line segment between the hemispheres of the rotated capsule
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * self.half_length;
@ -160,15 +178,18 @@ impl Bounded3d for Capsule3d {
}
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.radius + self.half_length)
}
}
impl Bounded3d for Cone {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * 0.5 * self.height;
let bottom = -top;
@ -182,7 +203,9 @@ impl Bounded3d for Cone {
}
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
// Get the triangular cross-section of the cone.
let half_height = 0.5 * self.height;
let triangle = Triangle2d::new(
@ -203,9 +226,11 @@ impl Bounded3d for Cone {
}
impl Bounded3d for ConicalFrustum {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * 0.5 * self.height;
let bottom = -top;
@ -223,7 +248,8 @@ impl Bounded3d for ConicalFrustum {
}
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
let half_height = 0.5 * self.height;
// To compute the bounding sphere, we'll get the center and radius of the circumcircle
@ -286,7 +312,9 @@ impl Bounded3d for ConicalFrustum {
}
impl Bounded3d for Torus {
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
// Compute the AABB of a flat disc with the major radius of the torus.
// Reference: http://iquilezles.org/articles/diskbbox/
let normal = isometry.rotation * Vec3A::Y;
@ -299,14 +327,16 @@ impl Bounded3d for Torus {
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.outer_radius())
}
}
impl Bounded3d for Triangle3d {
/// Get the bounding box of the triangle.
fn aabb_3d(&self, isometry: Isometry3d) -> Aabb3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let [a, b, c] = self.vertices;
let a = isometry.rotation * a;
@ -327,7 +357,9 @@ impl Bounded3d for Triangle3d {
/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
/// that contains the largest side of the triangle.
fn bounding_sphere(&self, isometry: Isometry3d) -> BoundingSphere {
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
if self.is_degenerate() || self.is_obtuse() {
let (p1, p2) = self.largest_side();
let (p1, p2) = (Vec3A::from(p1), Vec3A::from(p2));
@ -362,13 +394,12 @@ mod tests {
fn sphere() {
let sphere = Sphere { radius: 1.0 };
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = sphere.aabb_3d(isometry);
let aabb = sphere.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = sphere.bounding_sphere(isometry);
let bounding_sphere = sphere.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.0);
}
@ -376,25 +407,24 @@ mod tests {
#[test]
fn plane() {
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(isometry);
let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(translation);
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, -f32::MAX / 2.0));
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, f32::MAX / 2.0));
let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(isometry);
let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(translation);
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, -f32::MAX / 2.0));
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, f32::MAX / 2.0));
let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(isometry);
let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(translation);
assert_eq!(aabb3.min, Vec3A::new(-f32::MAX / 2.0, -f32::MAX / 2.0, 0.0));
assert_eq!(aabb3.max, Vec3A::new(f32::MAX / 2.0, f32::MAX / 2.0, 0.0));
let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(isometry);
let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(translation);
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
let bounding_sphere = InfinitePlane3d::new(Vec3::Y).bounding_sphere(isometry);
let bounding_sphere = InfinitePlane3d::new(Vec3::Y).bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
}
@ -402,28 +432,27 @@ mod tests {
#[test]
fn line() {
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb1 = Line3d { direction: Dir3::Y }.aabb_3d(isometry);
let aabb1 = Line3d { direction: Dir3::Y }.aabb_3d(translation);
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, 0.0));
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, 0.0));
let aabb2 = Line3d { direction: Dir3::X }.aabb_3d(isometry);
let aabb2 = Line3d { direction: Dir3::X }.aabb_3d(translation);
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, 0.0));
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, 0.0));
let aabb3 = Line3d { direction: Dir3::Z }.aabb_3d(isometry);
let aabb3 = Line3d { direction: Dir3::Z }.aabb_3d(translation);
assert_eq!(aabb3.min, Vec3A::new(2.0, 1.0, -f32::MAX / 2.0));
assert_eq!(aabb3.max, Vec3A::new(2.0, 1.0, f32::MAX / 2.0));
let aabb4 = Line3d {
direction: Dir3::from_xyz(1.0, 1.0, 1.0).unwrap(),
}
.aabb_3d(isometry);
.aabb_3d(translation);
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
let bounding_sphere = Line3d { direction: Dir3::Y }.bounding_sphere(isometry);
let bounding_sphere = Line3d { direction: Dir3::Y }.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
}
@ -431,16 +460,15 @@ mod tests {
#[test]
fn segment() {
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let segment =
Segment3d::from_points(Vec3::new(-1.0, -0.5, 0.0), Vec3::new(1.0, 0.5, 0.0)).0;
let aabb = segment.aabb_3d(isometry);
let aabb = segment.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.5, 0.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 1.5, 0.0));
let bounding_sphere = segment.bounding_sphere(isometry);
let bounding_sphere = segment.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
}
@ -454,13 +482,12 @@ mod tests {
Vec3::new(1.0, -1.0, -1.0),
]);
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = polyline.aabb_3d(isometry);
let aabb = polyline.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = polyline.bounding_sphere(isometry);
let bounding_sphere = polyline.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(
bounding_sphere.radius(),
@ -481,7 +508,7 @@ mod tests {
assert_eq!(aabb.min, Vec3A::from(translation) - expected_half_size);
assert_eq!(aabb.max, Vec3A::from(translation) + expected_half_size);
let bounding_sphere = cuboid.bounding_sphere(Isometry3d::from_translation(translation));
let bounding_sphere = cuboid.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(
bounding_sphere.radius(),
@ -493,9 +520,8 @@ mod tests {
fn cylinder() {
let cylinder = Cylinder::new(0.5, 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = cylinder.aabb_3d(isometry);
let aabb = cylinder.aabb_3d(translation);
assert_eq!(
aabb.min,
Vec3A::from(translation) - Vec3A::new(0.5, 1.0, 0.5)
@ -505,7 +531,7 @@ mod tests {
Vec3A::from(translation) + Vec3A::new(0.5, 1.0, 0.5)
);
let bounding_sphere = cylinder.bounding_sphere(isometry);
let bounding_sphere = cylinder.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
}
@ -514,9 +540,8 @@ mod tests {
fn capsule() {
let capsule = Capsule3d::new(0.5, 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = capsule.aabb_3d(isometry);
let aabb = capsule.aabb_3d(translation);
assert_eq!(
aabb.min,
Vec3A::from(translation) - Vec3A::new(0.5, 1.5, 0.5)
@ -526,7 +551,7 @@ mod tests {
Vec3A::from(translation) + Vec3A::new(0.5, 1.5, 0.5)
);
let bounding_sphere = capsule.bounding_sphere(isometry);
let bounding_sphere = capsule.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.5);
}
@ -538,13 +563,12 @@ mod tests {
height: 2.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = cone.aabb_3d(isometry);
let aabb = cone.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = cone.bounding_sphere(isometry);
let bounding_sphere = cone.bounding_sphere(translation);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.25
@ -560,13 +584,12 @@ mod tests {
height: 2.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = conical_frustum.aabb_3d(isometry);
let aabb = conical_frustum.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = conical_frustum.bounding_sphere(isometry);
let bounding_sphere = conical_frustum.bounding_sphere(translation);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.1875
@ -582,15 +605,14 @@ mod tests {
height: 1.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = conical_frustum.aabb_3d(isometry);
let aabb = conical_frustum.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(-3.0, 0.5, -5.0));
assert_eq!(aabb.max, Vec3A::new(7.0, 1.5, 5.0));
// For wide conical frusta like this, the circumcenter can be outside the frustum,
// so the center and radius should be clamped to the longest side.
let bounding_sphere = conical_frustum.bounding_sphere(isometry);
let bounding_sphere = conical_frustum.bounding_sphere(translation);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.5
@ -605,13 +627,12 @@ mod tests {
major_radius: 1.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let isometry = Isometry3d::from_translation(translation);
let aabb = torus.aabb_3d(isometry);
let aabb = torus.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(0.5, 0.5, -1.5));
assert_eq!(aabb.max, Vec3A::new(3.5, 1.5, 1.5));
let bounding_sphere = torus.bounding_sphere(isometry);
let bounding_sphere = torus.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.5);
}

View file

@ -192,6 +192,20 @@ impl From<Isometry2d> for Affine2 {
}
}
impl From<Vec2> for Isometry2d {
#[inline]
fn from(translation: Vec2) -> Self {
Isometry2d::from_translation(translation)
}
}
impl From<Rot2> for Isometry2d {
#[inline]
fn from(rotation: Rot2) -> Self {
Isometry2d::from_rotation(rotation)
}
}
impl Mul for Isometry2d {
type Output = Self;
@ -466,6 +480,27 @@ impl From<Isometry3d> for Affine3A {
}
}
impl From<Vec3> for Isometry3d {
#[inline]
fn from(translation: Vec3) -> Self {
Isometry3d::from_translation(translation)
}
}
impl From<Vec3A> for Isometry3d {
#[inline]
fn from(translation: Vec3A) -> Self {
Isometry3d::from_translation(translation)
}
}
impl From<Quat> for Isometry3d {
#[inline]
fn from(rotation: Quat) -> Self {
Isometry3d::from_rotation(rotation)
}
}
impl Mul for Isometry3d {
type Output = Self;

View file

@ -220,7 +220,8 @@ impl InfinitePlane3d {
/// `point`. The result is a signed value; it's positive if the point lies in the half-space
/// that the plane's normal vector points towards.
#[inline]
pub fn signed_distance(&self, isometry: Isometry3d, point: Vec3) -> f32 {
pub fn signed_distance(&self, isometry: impl Into<Isometry3d>, point: Vec3) -> f32 {
let isometry = isometry.into();
self.normal.dot(isometry.inverse() * point)
}
@ -228,7 +229,7 @@ impl InfinitePlane3d {
///
/// This projects the point orthogonally along the shortest path onto the plane.
#[inline]
pub fn project_point(&self, isometry: Isometry3d, point: Vec3) -> Vec3 {
pub fn project_point(&self, isometry: impl Into<Isometry3d>, point: Vec3) -> Vec3 {
point - self.normal * self.signed_distance(isometry, point)
}
@ -1374,36 +1375,36 @@ mod tests {
let point_in_plane = Vec3::X + Vec3::Z;
assert_eq!(
plane.signed_distance(Isometry3d::from_translation(origin), point_in_plane),
plane.signed_distance(origin, point_in_plane),
0.0,
"incorrect distance"
);
assert_eq!(
plane.project_point(Isometry3d::from_translation(origin), point_in_plane),
plane.project_point(origin, point_in_plane),
point_in_plane,
"incorrect point"
);
let point_outside = Vec3::Y;
assert_eq!(
plane.signed_distance(Isometry3d::from_translation(origin), point_outside),
plane.signed_distance(origin, point_outside),
-1.0,
"incorrect distance"
);
assert_eq!(
plane.project_point(Isometry3d::from_translation(origin), point_outside),
plane.project_point(origin, point_outside),
Vec3::ZERO,
"incorrect point"
);
let point_outside = Vec3::NEG_Y;
assert_eq!(
plane.signed_distance(Isometry3d::from_translation(origin), point_outside),
plane.signed_distance(origin, point_outside),
1.0,
"incorrect distance"
);
assert_eq!(
plane.project_point(Isometry3d::from_translation(origin), point_outside),
plane.project_point(origin, point_outside),
Vec3::ZERO,
"incorrect point"
);

View file

@ -1,6 +1,6 @@
//! This example demonstrates how to use the `Camera::viewport_to_world_2d` method.
use bevy::{color::palettes::basic::WHITE, math::Isometry2d, prelude::*};
use bevy::{color::palettes::basic::WHITE, prelude::*};
fn main() {
App::new()
@ -30,7 +30,7 @@ fn draw_cursor(
return;
};
gizmos.circle_2d(Isometry2d::from_translation(point), 10., WHITE);
gizmos.circle_2d(point, 10., WHITE);
}
fn setup(mut commands: Commands) {

View file

@ -186,14 +186,10 @@ fn render_volumes(mut gizmos: Gizmos, query: Query<(&CurrentVolume, &Intersects)
let color = if **intersects { AQUA } else { ORANGE_RED };
match volume {
CurrentVolume::Aabb(a) => {
gizmos.rect_2d(
Isometry2d::from_translation(a.center()),
a.half_size() * 2.,
color,
);
gizmos.rect_2d(a.center(), a.half_size() * 2., color);
}
CurrentVolume::Circle(c) => {
gizmos.circle_2d(Isometry2d::from_translation(c.center()), c.radius(), color);
gizmos.circle_2d(c.center(), c.radius(), color);
}
}
}
@ -288,7 +284,7 @@ fn setup(mut commands: Commands) {
fn draw_filled_circle(gizmos: &mut Gizmos, position: Vec2, color: Srgba) {
for r in [1., 2., 3.] {
gizmos.circle_2d(Isometry2d::from_translation(position), r, color);
gizmos.circle_2d(position, r, color);
}
}
@ -361,9 +357,7 @@ fn aabb_cast_system(
**intersects = toi.is_some();
if let Some(toi) = toi {
gizmos.rect_2d(
Isometry2d::from_translation(
aabb_cast.ray.ray.origin + *aabb_cast.ray.ray.direction * toi,
),
aabb_cast.ray.ray.origin + *aabb_cast.ray.ray.direction * toi,
aabb_cast.aabb.half_size() * 2.,
LIME,
);
@ -391,9 +385,7 @@ fn bounding_circle_cast_system(
**intersects = toi.is_some();
if let Some(toi) = toi {
gizmos.circle_2d(
Isometry2d::from_translation(
circle_cast.ray.ray.origin + *circle_cast.ray.ray.direction * toi,
),
circle_cast.ray.ray.origin + *circle_cast.ray.ray.direction * toi,
circle_cast.circle.radius(),
LIME,
);
@ -414,11 +406,7 @@ fn aabb_intersection_system(
) {
let center = get_intersection_position(&time);
let aabb = Aabb2d::new(center, Vec2::splat(50.));
gizmos.rect_2d(
Isometry2d::from_translation(center),
aabb.half_size() * 2.,
YELLOW,
);
gizmos.rect_2d(center, aabb.half_size() * 2., YELLOW);
for (volume, mut intersects) in volumes.iter_mut() {
let hit = match volume {
@ -437,11 +425,7 @@ fn circle_intersection_system(
) {
let center = get_intersection_position(&time);
let circle = BoundingCircle::new(center, 50.);
gizmos.circle_2d(
Isometry2d::from_translation(center),
circle.radius(),
YELLOW,
);
gizmos.circle_2d(center, circle.radius(), YELLOW);
for (volume, mut intersects) in volumes.iter_mut() {
let hit = match volume {

View file

@ -114,17 +114,9 @@ fn draw_bounds<Shape: Bounded2d + Send + Sync + 'static>(
let isometry = Isometry2d::new(translation, Rot2::radians(rotation));
let aabb = shape.0.aabb_2d(isometry);
gizmos.rect_2d(
Isometry2d::from_translation(aabb.center()),
aabb.half_size() * 2.0,
RED,
);
gizmos.rect_2d(aabb.center(), aabb.half_size() * 2.0, RED);
let bounding_circle = shape.0.bounding_circle(isometry);
gizmos.circle_2d(
Isometry2d::from_translation(bounding_circle.center),
bounding_circle.radius(),
BLUE,
);
gizmos.circle_2d(bounding_circle.center, bounding_circle.radius(), BLUE);
}
}

View file

@ -1,7 +1,6 @@
//! Demonstrates how to observe life-cycle triggers as well as define custom ones.
use bevy::{
math::Isometry2d,
prelude::*,
utils::{HashMap, HashSet},
};
@ -166,7 +165,7 @@ fn explode_mine(trigger: Trigger<Explode>, query: Query<&Mine>, mut commands: Co
fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) {
for mine in &mines {
gizmos.circle_2d(
Isometry2d::from_translation(mine.pos),
mine.pos,
mine.size,
Color::hsl((mine.size - 4.0) / 16.0 * 360.0, 1.0, 0.8),
);

View file

@ -67,11 +67,7 @@ fn draw_example_collection(
gizmos.rect_2d(Isometry2d::IDENTITY, Vec2::splat(650.), BLACK);
gizmos.cross_2d(
Isometry2d::from_translation(Vec2::new(-160., 120.)),
12.,
FUCHSIA,
);
gizmos.cross_2d(Vec2::new(-160., 120.), 12., FUCHSIA);
let domain = Interval::EVERYWHERE;
let curve = function_curve(domain, |t| Vec2::new(t, ops::sin(t / 25.0) * 100.0));
@ -89,11 +85,11 @@ fn draw_example_collection(
// Circles have 32 line-segments by default.
// You may want to increase this for larger circles.
my_gizmos
.circle_2d(Isometry2d::from_translation(Vec2::ZERO), 300., NAVY)
.circle_2d(Isometry2d::IDENTITY, 300., NAVY)
.resolution(64);
my_gizmos.ellipse_2d(
Isometry2d::new(Vec2::ZERO, Rot2::radians(time.elapsed_seconds() % TAU)),
Rot2::radians(time.elapsed_seconds() % TAU),
Vec2::new(100., 200.),
YELLOW_GREEN,
);
@ -101,7 +97,7 @@ fn draw_example_collection(
// Arcs default resolution is linearly interpolated between
// 1 and 32, using the arc length as scalar.
my_gizmos.arc_2d(
Isometry2d::from_rotation(Rot2::radians(sin_t_scaled / 10.)),
Rot2::radians(sin_t_scaled / 10.),
FRAC_PI_2,
310.,
ORANGE_RED,

View file

@ -78,19 +78,19 @@ fn draw_example_collection(
time: Res<Time>,
) {
gizmos.grid(
Isometry3d::from_rotation(Quat::from_rotation_x(PI / 2.)),
Quat::from_rotation_x(PI / 2.),
UVec2::splat(20),
Vec2::new(2., 2.),
// Light gray
LinearRgba::gray(0.65),
);
gizmos.grid(
Isometry3d::new(Vec3::ONE * 10.0, Quat::from_rotation_x(PI / 3. * 2.)),
Isometry3d::new(Vec3::splat(10.0), Quat::from_rotation_x(PI / 3. * 2.)),
UVec2::splat(20),
Vec2::new(2., 2.),
PURPLE,
);
gizmos.sphere(Isometry3d::from_translation(Vec3::ONE * 10.0), 1.0, PURPLE);
gizmos.sphere(Vec3::splat(10.0), 1.0, PURPLE);
gizmos
.primitive_3d(
@ -99,7 +99,7 @@ fn draw_example_collection(
half_size: Vec2::splat(1.0),
},
Isometry3d::new(
Vec3::ONE * 4.0 + Vec2::from(ops::sin_cos(time.elapsed_seconds())).extend(0.0),
Vec3::splat(4.0) + Vec2::from(ops::sin_cos(time.elapsed_seconds())).extend(0.0),
Quat::from_rotation_x(PI / 2. + time.elapsed_seconds()),
),
GREEN,
@ -120,11 +120,7 @@ fn draw_example_collection(
LIME,
);
gizmos.cross(
Isometry3d::from_translation(Vec3::new(-1., 1., 1.)),
0.5,
FUCHSIA,
);
gizmos.cross(Vec3::new(-1., 1., 1.), 0.5, FUCHSIA);
let domain = Interval::EVERYWHERE;
let curve = function_curve(domain, |t| {
@ -137,18 +133,10 @@ fn draw_example_collection(
.map(|t| (t, TEAL.mix(&HOT_PINK, t / 5.0)));
gizmos.curve_gradient_3d(curve, times_and_colors);
my_gizmos.sphere(
Isometry3d::from_translation(Vec3::new(1., 0.5, 0.)),
0.5,
RED,
);
my_gizmos.sphere(Vec3::new(1., 0.5, 0.), 0.5, RED);
my_gizmos
.rounded_cuboid(
Isometry3d::from_translation(Vec3::new(-2.0, 0.75, -0.75)),
Vec3::splat(0.9),
TURQUOISE,
)
.rounded_cuboid(Vec3::new(-2.0, 0.75, -0.75), Vec3::splat(0.9), TURQUOISE)
.edge_radius(0.1)
.arc_resolution(4);
@ -173,24 +161,17 @@ fn draw_example_collection(
.resolution(10);
// Circles have 32 line-segments by default.
my_gizmos.circle(
Isometry3d::from_rotation(Quat::from_rotation_arc(Vec3::Z, Vec3::Y)),
3.,
BLACK,
);
my_gizmos.circle(Quat::from_rotation_arc(Vec3::Z, Vec3::Y), 3., BLACK);
// You may want to increase this for larger circles or spheres.
my_gizmos
.circle(
Isometry3d::from_rotation(Quat::from_rotation_arc(Vec3::Z, Vec3::Y)),
3.1,
NAVY,
)
.circle(Quat::from_rotation_arc(Vec3::Z, Vec3::Y), 3.1, NAVY)
.resolution(64);
my_gizmos
.sphere(Isometry3d::IDENTITY, 3.2, BLACK)
.resolution(64);
gizmos.arrow(Vec3::ZERO, Vec3::ONE * 1.5, YELLOW);
gizmos.arrow(Vec3::ZERO, Vec3::splat(1.5), YELLOW);
// You can create more complex arrows using the arrow builder.
gizmos

View file

@ -6,7 +6,7 @@ use bevy::{
ecs::system::Commands,
gizmos::gizmos::Gizmos,
input::{mouse::MouseButtonInput, ButtonState},
math::{cubic_splines::*, vec2, Isometry2d},
math::{cubic_splines::*, vec2},
prelude::*,
};
@ -197,11 +197,7 @@ fn draw_control_points(
mut gizmos: Gizmos,
) {
for &(point, tangent) in &control_points.points_and_tangents {
gizmos.circle_2d(
Isometry2d::from_translation(point),
10.0,
Color::srgb(0.0, 1.0, 0.0),
);
gizmos.circle_2d(point, 10.0, Color::srgb(0.0, 1.0, 0.0));
if matches!(*spline_mode, SplineMode::Hermite) {
gizmos.arrow_2d(point, point + tangent, Color::srgb(1.0, 0.0, 0.0));
@ -403,16 +399,8 @@ fn draw_edit_move(
return;
};
gizmos.circle_2d(
Isometry2d::from_translation(start),
10.0,
Color::srgb(0.0, 1.0, 0.7),
);
gizmos.circle_2d(
Isometry2d::from_translation(start),
7.0,
Color::srgb(0.0, 1.0, 0.7),
);
gizmos.circle_2d(start, 10.0, Color::srgb(0.0, 1.0, 0.7));
gizmos.circle_2d(start, 7.0, Color::srgb(0.0, 1.0, 0.7));
gizmos.arrow_2d(start, end, Color::srgb(1.0, 0.0, 0.7));
}

View file

@ -197,23 +197,13 @@ fn bounding_shapes_2d(
BoundingShape::BoundingBox => {
// Get the AABB of the primitive with the rotation and translation of the mesh.
let aabb = HEART.aabb_2d(isometry);
gizmos.rect_2d(
Isometry2d::from_translation(aabb.center()),
aabb.half_size() * 2.,
WHITE,
);
gizmos.rect_2d(aabb.center(), aabb.half_size() * 2., WHITE);
}
BoundingShape::BoundingSphere => {
// Get the bounding sphere of the primitive with the rotation and translation of the mesh.
let bounding_circle = HEART.bounding_circle(isometry);
gizmos
.circle_2d(
Isometry2d::from_translation(bounding_circle.center()),
bounding_circle.radius(),
WHITE,
)
.circle_2d(bounding_circle.center(), bounding_circle.radius(), WHITE)
.resolution(64);
}
}
@ -244,7 +234,7 @@ fn bounding_shapes_3d(
gizmos.primitive_3d(
&Cuboid::from_size(Vec3::from(aabb.half_size()) * 2.),
Isometry3d::from_translation(aabb.center()),
aabb.center(),
WHITE,
);
}
@ -252,11 +242,7 @@ fn bounding_shapes_3d(
// Get the bounding sphere of the extrusion with the rotation and translation of the mesh.
let bounding_sphere = EXTRUSION.bounding_sphere(transform.to_isometry());
gizmos.sphere(
Isometry3d::from_translation(bounding_sphere.center()),
bounding_sphere.radius(),
WHITE,
);
gizmos.sphere(bounding_sphere.center(), bounding_sphere.radius(), WHITE);
}
}
}
@ -336,7 +322,9 @@ impl Measured2d for Heart {
// The `Bounded2d` or `Bounded3d` traits are used to compute the Axis Aligned Bounding Boxes or bounding circles / spheres for primitives.
impl Bounded2d for Heart {
fn aabb_2d(&self, isometry: Isometry2d) -> Aabb2d {
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d {
let isometry = isometry.into();
// The center of the circle at the center of the right wing of the heart
let circle_center = isometry.rotation * Vec2::new(self.radius, 0.0);
// The maximum X and Y positions of the two circles of the wings of the heart.
@ -353,7 +341,9 @@ impl Bounded2d for Heart {
}
}
fn bounding_circle(&self, isometry: Isometry2d) -> BoundingCircle {
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle {
let isometry = isometry.into();
// 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.
let offset = self.radius / ops::powf(2f32, 1.5);
// The center of the bounding circle