diff --git a/crates/bevy_gizmos/src/primitives/dim2.rs b/crates/bevy_gizmos/src/primitives/dim2.rs index beb9c6f315f24..d38ba3ab4ca95 100644 --- a/crates/bevy_gizmos/src/primitives/dim2.rs +++ b/crates/bevy_gizmos/src/primitives/dim2.rs @@ -540,10 +540,7 @@ where } // draw normal of the plane (orthogonal to the plane itself) let normal = primitive.normal; - let normal_segment = Segment2d { - direction: normal, - half_length: HALF_MIN_LINE_LEN, - }; + let normal_segment = Segment2d::from_direction_and_length(normal, HALF_MIN_LINE_LEN * 2.); self.primitive_2d( &normal_segment, // offset the normal so it starts on the plane line @@ -577,8 +574,8 @@ where { gizmos: &'a mut GizmoBuffer, - direction: Dir2, // Direction of the line segment - half_length: f32, // Half-length of the line segment + point1: Vec2, // First point of the segment + point2: Vec2, // Second point of the segment isometry: Isometry2d, // isometric transformation of the line segment color: Color, // color of the line segment @@ -616,8 +613,8 @@ where ) -> Self::Output<'_> { Segment2dBuilder { gizmos: self, - direction: primitive.direction, - half_length: primitive.half_length, + point1: primitive.point1(), + point2: primitive.point2(), isometry: isometry.into(), color: color.into(), @@ -637,14 +634,16 @@ where return; } - let direction = self.direction * self.half_length; - let start = self.isometry * (-direction); - let end = self.isometry * direction; + let segment = Segment2d::new(self.point1, self.point2) + .rotated(self.isometry.rotation) + .translated(self.isometry.translation); if self.draw_arrow { - self.gizmos.arrow_2d(start, end, self.color); + self.gizmos + .arrow_2d(segment.point1(), segment.point2(), self.color); } else { - self.gizmos.line_2d(start, end, self.color); + self.gizmos + .line_2d(segment.point1(), segment.point2(), self.color); } } } diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index b99ab9a31a793..31a2274e841b4 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -228,9 +228,11 @@ where return; } - let isometry = isometry.into(); - let direction = primitive.direction.as_vec3(); - self.line(isometry * direction, isometry * (-direction), color); + let isometry: Isometry3d = isometry.into(); + let transformed = primitive + .rotated(isometry.rotation) + .translated(isometry.translation.into()); + self.line(transformed.point1(), transformed.point2(), color); } } diff --git a/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs index e6c08136881b1..e1fe6afd774d5 100644 --- a/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs @@ -1,6 +1,7 @@ //! Contains [`Bounded2d`] implementations for [geometric primitives](crate::primitives). use crate::{ + bounding::BoundingVolume, ops, primitives::{ Annulus, Arc2d, Capsule2d, Circle, CircularSector, CircularSegment, Ellipse, Line2d, @@ -265,18 +266,15 @@ impl Bounded2d for Line2d { impl Bounded2d for Segment2d { fn aabb_2d(&self, isometry: impl Into) -> Aabb2d { - let isometry = isometry.into(); - - // Rotate the segment by `rotation` - let direction = isometry.rotation * *self.direction; - let half_size = (self.half_length * direction).abs(); - - Aabb2d::new(isometry.translation, half_size) + Aabb2d::from_point_cloud(isometry, &[self.point1(), self.point2()]) } fn bounding_circle(&self, isometry: impl Into) -> BoundingCircle { - let isometry = isometry.into(); - BoundingCircle::new(isometry.translation, self.half_length) + let isometry: Isometry2d = isometry.into(); + let local_center = self.center(); + let radius = local_center.distance(self.point1()); + let local_circle = BoundingCircle::new(local_center, radius); + local_circle.transformed_by(isometry.translation, isometry.rotation) } } @@ -336,8 +334,8 @@ impl Bounded2d for Triangle2d { if let Some((point1, point2)) = side_opposite_to_non_acute { // The triangle is obtuse or right, so the minimum bounding circle's diameter is equal to the longest side. // We can compute the minimum bounding circle from the line segment of the longest side. - let (segment, center) = Segment2d::from_points(point1, point2); - segment.bounding_circle(isometry * Isometry2d::from_translation(center)) + let segment = Segment2d::new(point1, point2); + segment.bounding_circle(isometry) } else { // The triangle is acute, so the smallest bounding circle is the circumcircle. let (Circle { radius }, circumcenter) = self.circumcircle(); @@ -417,11 +415,10 @@ impl Bounded2d for Capsule2d { let isometry = isometry.into(); // Get the line segment between the semicircles of the rotated capsule - let segment = Segment2d { - // Multiplying a normalized vector (Vec2::Y) with a rotation returns a normalized vector. - direction: isometry.rotation * Dir2::Y, - half_length: self.half_length, - }; + let segment = Segment2d::from_direction_and_length( + isometry.rotation * Dir2::Y, + self.half_length * 2., + ); let (a, b) = (segment.point1(), segment.point2()); // Expand the line segment by the capsule radius to get the capsule half-extents @@ -886,7 +883,7 @@ mod tests { fn segment() { let translation = Vec2::new(2.0, 1.0); let isometry = Isometry2d::from_translation(translation); - let segment = Segment2d::from_points(Vec2::new(-1.0, -0.5), Vec2::new(1.0, 0.5)).0; + let segment = Segment2d::new(Vec2::new(-1.0, -0.5), Vec2::new(1.0, 0.5)); let aabb = segment.aabb_2d(isometry); assert_eq!(aabb.min, Vec2::new(1.0, 0.5)); diff --git a/crates/bevy_math/src/bounding/bounded3d/extrusion.rs b/crates/bevy_math/src/bounding/bounded3d/extrusion.rs index 5403fa176ea53..47d5d6676356a 100644 --- a/crates/bevy_math/src/bounding/bounded3d/extrusion.rs +++ b/crates/bevy_math/src/bounding/bounded3d/extrusion.rs @@ -348,7 +348,10 @@ mod tests { #[test] fn segment() { - let extrusion = Extrusion::new(Segment2d::new(Dir2::new_unchecked(Vec2::NEG_Y), 3.), 4.0); + let extrusion = Extrusion::new( + Segment2d::from_direction_and_length(Dir2::new_unchecked(Vec2::NEG_Y), 3.), + 4.0, + ); let translation = Vec3::new(3., 4., 5.); let rotation = Quat::from_rotation_x(FRAC_PI_4); let isometry = Isometry3d::new(translation, rotation); diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 679d8577f0d41..1f4e2a1666d17 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -1,7 +1,7 @@ //! Contains [`Bounded3d`] implementations for [geometric primitives](crate::primitives). use crate::{ - bounding::{Bounded2d, BoundingCircle}, + bounding::{Bounded2d, BoundingCircle, BoundingVolume}, ops, primitives::{ Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d, @@ -76,18 +76,13 @@ impl Bounded3d for Line3d { impl Bounded3d for Segment3d { fn aabb_3d(&self, isometry: impl Into) -> Aabb3d { - let isometry = isometry.into(); - - // Rotate the segment by `rotation` - let direction = isometry.rotation * *self.direction; - let half_size = (self.half_length * direction).abs(); - - Aabb3d::new(isometry.translation, half_size) + Aabb3d::from_point_cloud(isometry, [self.point1(), self.point2()].iter().copied()) } fn bounding_sphere(&self, isometry: impl Into) -> BoundingSphere { let isometry = isometry.into(); - BoundingSphere::new(isometry.translation, self.half_length) + let local_sphere = BoundingSphere::new(self.center(), self.length() / 2.); + local_sphere.transformed_by(isometry.translation, isometry.rotation) } } @@ -464,8 +459,7 @@ mod tests { fn segment() { let translation = Vec3::new(2.0, 1.0, 0.0); - let segment = - Segment3d::from_points(Vec3::new(-1.0, -0.5, 0.0), Vec3::new(1.0, 0.5, 0.0)).0; + let segment = Segment3d::new(Vec3::new(-1.0, -0.5, 0.0), Vec3::new(1.0, 0.5, 0.0)); let aabb = segment.aabb_3d(translation); assert_eq!(aabb.min, Vec3A::new(1.0, 0.5, 0.0)); diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index cdd5b805cde3a..c336edac45a76 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -5,7 +5,7 @@ use thiserror::Error; use super::{Measured2d, Primitive2d, WindingOrder}; use crate::{ ops::{self, FloatPow}, - Dir2, Vec2, + Dir2, Rot2, Vec2, }; #[cfg(feature = "alloc")] @@ -1221,21 +1221,17 @@ impl Primitive2d for Line2d {} )] #[doc(alias = "LineSegment2d")] pub struct Segment2d { - /// The direction of the line segment - pub direction: Dir2, - /// Half the length of the line segment. The segment extends by this amount in both - /// the given direction and its opposite direction - pub half_length: f32, + /// The endpoints of the line segment. + pub vertices: [Vec2; 2], } impl Primitive2d for Segment2d {} impl Segment2d { - /// Create a new `Segment2d` from a direction and full length of the segment + /// Create a new `Segment2d` from its endpoints #[inline(always)] - pub fn new(direction: Dir2, length: f32) -> Self { + pub const fn new(point1: Vec2, point2: Vec2) -> Self { Self { - direction, - half_length: length / 2.0, + vertices: [point1, point2], } } @@ -1245,27 +1241,85 @@ impl Segment2d { /// /// Panics if `point1 == point2` #[inline(always)] + #[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")] pub fn from_points(point1: Vec2, point2: Vec2) -> (Self, Vec2) { - let diff = point2 - point1; - let length = diff.length(); + (Self::new(point1, point2), (point1 + point2) / 2.) + } - ( - // We are dividing by the length here, so the vector is normalized. - Self::new(Dir2::new_unchecked(diff / length), length), - (point1 + point2) / 2., - ) + /// Create a new `Segment2d` at the origin from a `direction` and `length` + #[inline(always)] + pub fn from_direction_and_length(direction: Dir2, length: f32) -> Segment2d { + let half_length = length / 2.; + Self::new(direction * -half_length, direction * half_length) } /// Get the position of the first point on the line segment #[inline(always)] pub fn point1(&self) -> Vec2 { - *self.direction * -self.half_length + self.vertices[0] } /// Get the position of the second point on the line segment #[inline(always)] pub fn point2(&self) -> Vec2 { - *self.direction * self.half_length + self.vertices[1] + } + + /// Get the segment's center + #[inline(always)] + #[doc(alias = "midpoint")] + pub fn center(&self) -> Vec2 { + (self.point1() + self.point2()) / 2. + } + + /// Get the segment's length + #[inline(always)] + pub fn length(&self) -> f32 { + self.point1().distance(self.point2()) + } + + /// Get the segment translated by the given vector + #[inline(always)] + pub fn translated(&self, translation: Vec2) -> Segment2d { + Self::new(self.point1() + translation, self.point2() + translation) + } + + /// Compute a new segment, based on the original segment rotated around the origin + #[inline(always)] + pub fn rotated(&self, rotation: Rot2) -> Segment2d { + Segment2d::new(rotation * self.point1(), rotation * self.point2()) + } + + /// Compute a new segment, based on the original segment rotated around a given point + #[inline(always)] + pub fn rotated_around(&self, rotation: Rot2, point: Vec2) -> Segment2d { + // We offset our segment so that our segment is rotated as if from the origin, then we can apply the offset back + let offset = self.translated(-point); + let rotated = offset.rotated(rotation); + rotated.translated(point) + } + + /// Compute a new segment, based on the original segment rotated around its center + #[inline(always)] + pub fn rotated_around_center(&self, rotation: Rot2) -> Segment2d { + self.rotated_around(rotation, self.center()) + } + + /// Get the segment with its center at the origin + #[inline(always)] + pub fn centered(&self) -> Segment2d { + let center = self.center(); + self.translated(-center) + } + + /// Get the segment with a new length + #[inline(always)] + pub fn resized(&self, length: f32) -> Segment2d { + let offset_from_origin = self.center(); + let centered = self.centered(); + let ratio = length / self.length(); + let segment = Segment2d::new(centered.point1() * ratio, centered.point2() * ratio); + segment.translated(offset_from_origin) } } diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 8d25df5b83d0a..a828fa8247587 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -359,51 +359,108 @@ impl Primitive3d for Line3d {} reflect(Serialize, Deserialize) )] pub struct Segment3d { - /// The direction of the line - pub direction: Dir3, - /// Half the length of the line segment. The segment extends by this amount in both - /// the given direction and its opposite direction - pub half_length: f32, + /// The endpoints of the line segment. + pub vertices: [Vec3; 2], } impl Primitive3d for Segment3d {} impl Segment3d { - /// Create a new `Segment3d` from a direction and full length of the segment + /// Create a new `Segment3d` from its endpoints #[inline(always)] - pub fn new(direction: Dir3, length: f32) -> Self { + pub const fn new(point1: Vec3, point2: Vec3) -> Self { Self { - direction, - half_length: length / 2.0, + vertices: [point1, point2], } } + /// Create a new `Segment3d` from a direction and full length of the segment + #[inline(always)] + pub fn from_direction_and_length(direction: Dir3, length: f32) -> Self { + let half_length = length / 2.; + Self::new(direction * -half_length, direction * half_length) + } + /// Create a new `Segment3d` from its endpoints and compute its geometric center /// /// # Panics /// /// Panics if `point1 == point2` #[inline(always)] + #[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")] pub fn from_points(point1: Vec3, point2: Vec3) -> (Self, Vec3) { - let diff = point2 - point1; - let length = diff.length(); - - ( - // We are dividing by the length here, so the vector is normalized. - Self::new(Dir3::new_unchecked(diff / length), length), - (point1 + point2) / 2., - ) + (Self::new(point1, point2), (point1 + point2) / 2.) } /// Get the position of the first point on the line segment #[inline(always)] pub fn point1(&self) -> Vec3 { - *self.direction * -self.half_length + self.vertices[0] } /// Get the position of the second point on the line segment #[inline(always)] pub fn point2(&self) -> Vec3 { - *self.direction * self.half_length + self.vertices[1] + } + + /// Get the center of the segment + #[inline(always)] + #[doc(alias = "midpoint")] + pub fn center(&self) -> Vec3 { + (self.point1() + self.point2()) / 2. + } + + /// Get the length of the segment + #[inline(always)] + pub fn length(&self) -> f32 { + self.point1().distance(self.point2()) + } + + /// Get the segment translated by a vector + #[inline(always)] + pub fn translated(&self, translation: Vec3) -> Segment3d { + Self::new(self.point1() + translation, self.point2() + translation) + } + + /// Compute a new segment, based on the original segment rotated around the origin + #[inline(always)] + pub fn rotated(&self, rotation: Quat) -> Segment3d { + Segment3d::new( + rotation.mul_vec3(self.point1()), + rotation.mul_vec3(self.point2()), + ) + } + + /// Compute a new segment, based on the original segment rotated around a given point + #[inline(always)] + pub fn rotated_around(&self, rotation: Quat, point: Vec3) -> Segment3d { + // We offset our segment so that our segment is rotated as if from the origin, then we can apply the offset back + let offset = self.translated(-point); + let rotated = offset.rotated(rotation); + rotated.translated(point) + } + + /// Compute a new segment, based on the original segment rotated around its center + #[inline(always)] + pub fn rotated_around_center(&self, rotation: Quat) -> Segment3d { + self.rotated_around(rotation, self.center()) + } + + /// Get the segment offset so that it's center is at the origin + #[inline(always)] + pub fn centered(&self) -> Segment3d { + let center = self.center(); + self.translated(-center) + } + + /// Get the segment with a new length + #[inline(always)] + pub fn resized(&self, length: f32) -> Segment3d { + let offset_from_origin = self.center(); + let centered = self.centered(); + let ratio = length / self.length(); + let segment = Segment3d::new(centered.point1() * ratio, centered.point2() * ratio); + segment.translated(offset_from_origin) } } diff --git a/examples/2d/bounding_2d.rs b/examples/2d/bounding_2d.rs index f4c24e739432f..a90718058c214 100644 --- a/examples/2d/bounding_2d.rs +++ b/examples/2d/bounding_2d.rs @@ -230,7 +230,10 @@ fn setup(mut commands: Commands) { commands.spawn(( Transform::from_xyz(-OFFSET_X, -OFFSET_Y, 0.), - Shape::Line(Segment2d::new(Dir2::from_xy(1., 0.3).unwrap(), 90.)), + Shape::Line(Segment2d::from_direction_and_length( + Dir2::from_xy(1., 0.3).unwrap(), + 90., + )), Spin, DesiredVolume::Circle, Intersects::default(), diff --git a/examples/math/render_primitives.rs b/examples/math/render_primitives.rs index 4d9b3325510bf..4332516ac27f5 100644 --- a/examples/math/render_primitives.rs +++ b/examples/math/render_primitives.rs @@ -187,12 +187,14 @@ const LINE2D: Line2d = Line2d { direction: Dir2::X }; const LINE3D: Line3d = Line3d { direction: Dir3::X }; const SEGMENT_2D: Segment2d = Segment2d { - direction: Dir2::X, - half_length: BIG_2D, + vertices: [Vec2::new(-BIG_2D / 2., 0.), Vec2::new(BIG_2D / 2., 0.)], }; + const SEGMENT_3D: Segment3d = Segment3d { - direction: Dir3::X, - half_length: BIG_3D, + vertices: [ + Vec3::new(-BIG_3D / 2., 0., 0.), + Vec3::new(BIG_3D / 2., 0., 0.), + ], }; const POLYLINE_2D: Polyline2d<4> = Polyline2d {