diff --git a/crates/bevy_gizmos/src/primitives/dim2.rs b/crates/bevy_gizmos/src/primitives/dim2.rs index d38ba3ab4c..9535c28fbd 100644 --- a/crates/bevy_gizmos/src/primitives/dim2.rs +++ b/crates/bevy_gizmos/src/primitives/dim2.rs @@ -634,9 +634,7 @@ where return; } - let segment = Segment2d::new(self.point1, self.point2) - .rotated(self.isometry.rotation) - .translated(self.isometry.translation); + let segment = Segment2d::new(self.point1, self.point2).transformed(self.isometry); if self.draw_arrow { self.gizmos diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index 31a2274e84..898850ddea 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -228,10 +228,7 @@ where return; } - let isometry: Isometry3d = isometry.into(); - let transformed = primitive - .rotated(isometry.rotation) - .translated(isometry.translation.into()); + let transformed = primitive.transformed(isometry); 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 e1fe6afd77..202b125588 100644 --- a/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs @@ -881,9 +881,9 @@ mod tests { #[test] fn segment() { + let segment = Segment2d::new(Vec2::new(-1.0, -0.5), Vec2::new(1.0, 0.5)); let translation = Vec2::new(2.0, 1.0); let isometry = Isometry2d::from_translation(translation); - 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 47d5d66763..607d0f2746 100644 --- a/crates/bevy_math/src/bounding/bounded3d/extrusion.rs +++ b/crates/bevy_math/src/bounding/bounded3d/extrusion.rs @@ -349,7 +349,7 @@ mod tests { #[test] fn segment() { let extrusion = Extrusion::new( - Segment2d::from_direction_and_length(Dir2::new_unchecked(Vec2::NEG_Y), 3.), + Segment2d::new(Vec2::new(0.0, -1.5), Vec2::new(0.0, 1.5)), 4.0, ); let translation = Vec3::new(3., 4., 5.); diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 1f4e2a1666..ebfd0266e8 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -457,9 +457,8 @@ mod tests { #[test] fn segment() { - let translation = Vec3::new(2.0, 1.0, 0.0); - let segment = Segment3d::new(Vec3::new(-1.0, -0.5, 0.0), Vec3::new(1.0, 0.5, 0.0)); + let translation = Vec3::new(2.0, 1.0, 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 c336edac45..5ca224929a 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, Rot2, Vec2, + Dir2, InvalidDirectionError, Isometry2d, Ray2d, Rot2, Vec2, }; #[cfg(feature = "alloc")] @@ -1211,7 +1211,7 @@ pub struct Line2d { } impl Primitive2d for Line2d {} -/// A segment of a line going through the origin along a direction in 2D space. +/// A line segment defined by two endpoints in 2D space. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] @@ -1227,7 +1227,7 @@ pub struct Segment2d { impl Primitive2d for Segment2d {} impl Segment2d { - /// Create a new `Segment2d` from its endpoints + /// Create a new `Segment2d` from its endpoints. #[inline(always)] pub const fn new(point1: Vec2, point2: Vec2) -> Self { Self { @@ -1235,62 +1235,194 @@ impl Segment2d { } } - /// Create a new `Segment2d` from its endpoints and compute its geometric center - /// - /// # Panics - /// - /// Panics if `point1 == point2` + /// Create a new `Segment2d` from its endpoints and compute its geometric center. #[inline(always)] #[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")] pub fn from_points(point1: Vec2, point2: Vec2) -> (Self, Vec2) { (Self::new(point1, point2), (point1 + point2) / 2.) } - /// Create a new `Segment2d` at the origin from a `direction` and `length` + /// Create a new `Segment2d` centered at the origin with the given direction and length. + /// + /// The endpoints will be at `-direction * length / 2.0` and `direction * length / 2.0`. #[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) + pub fn from_direction_and_length(direction: Dir2, length: f32) -> Self { + let endpoint = 0.5 * length * direction; + Self { + vertices: [-endpoint, endpoint], + } } - /// Get the position of the first point on the line segment + /// Create a new `Segment2d` centered at the origin from a vector representing + /// the direction and length of the line segment. + /// + /// The endpoints will be at `-scaled_direction / 2.0` and `scaled_direction / 2.0`. + #[inline(always)] + pub fn from_scaled_direction(scaled_direction: Vec2) -> Self { + let endpoint = 0.5 * scaled_direction; + Self { + vertices: [-endpoint, endpoint], + } + } + + /// Create a new `Segment2d` starting from the origin of the given `ray`, + /// going in the direction of the ray for the given `length`. + /// + /// The endpoints will be at `ray.origin` and `ray.origin + length * ray.direction`. + #[inline(always)] + pub fn from_ray_and_length(ray: Ray2d, length: f32) -> Self { + Self { + vertices: [ray.origin, ray.get_point(length)], + } + } + + /// Get the position of the first endpoint of the line segment. #[inline(always)] pub fn point1(&self) -> Vec2 { self.vertices[0] } - /// Get the position of the second point on the line segment + /// Get the position of the second endpoint of the line segment. #[inline(always)] pub fn point2(&self) -> Vec2 { self.vertices[1] } - /// Get the segment's center + /// Compute the midpoint between the two endpoints of the line segment. #[inline(always)] #[doc(alias = "midpoint")] pub fn center(&self) -> Vec2 { - (self.point1() + self.point2()) / 2. + self.point1().midpoint(self.point2()) } - /// Get the segment's length + /// Compute the length of the line segment. #[inline(always)] pub fn length(&self) -> f32 { self.point1().distance(self.point2()) } - /// Get the segment translated by the given vector + /// Compute the squared length of the line segment. + #[inline(always)] + pub fn length_squared(&self) -> f32 { + self.point1().distance_squared(self.point2()) + } + + /// Compute the normalized direction pointing from the first endpoint to the second endpoint. + /// + /// For the non-panicking version, see [`Segment2d::try_direction`]. + /// + /// # Panics + /// + /// Panics if a valid direction could not be computed, for example when the endpoints are coincident, NaN, or infinite. + #[inline(always)] + pub fn direction(&self) -> Dir2 { + self.try_direction().unwrap_or_else(|err| { + panic!("Failed to compute the direction of a line segment: {err}") + }) + } + + /// Try to compute the normalized direction pointing from the first endpoint to the second endpoint. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid direction could not be computed, + /// for example when the endpoints are coincident, NaN, or infinite. + #[inline(always)] + pub fn try_direction(&self) -> Result { + Dir2::new(self.scaled_direction()) + } + + /// Compute the vector from the first endpoint to the second endpoint. + #[inline(always)] + pub fn scaled_direction(&self) -> Vec2 { + self.point2() - self.point1() + } + + /// Compute the normalized counterclockwise normal on the left-hand side of the line segment. + /// + /// For the non-panicking version, see [`Segment2d::try_left_normal`]. + /// + /// # Panics + /// + /// Panics if a valid normal could not be computed, for example when the endpoints are coincident, NaN, or infinite. + #[inline(always)] + pub fn left_normal(&self) -> Dir2 { + self.try_left_normal().unwrap_or_else(|err| { + panic!("Failed to compute the left-hand side normal of a line segment: {err}") + }) + } + + /// Try to compute the normalized counterclockwise normal on the left-hand side of the line segment. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid normal could not be computed, + /// for example when the endpoints are coincident, NaN, or infinite. + #[inline(always)] + pub fn try_left_normal(&self) -> Result { + Dir2::new(self.scaled_left_normal()) + } + + /// Compute the non-normalized counterclockwise normal on the left-hand side of the line segment. + /// + /// The length of the normal is the distance between the endpoints. + #[inline(always)] + pub fn scaled_left_normal(&self) -> Vec2 { + let scaled_direction = self.scaled_direction(); + Vec2::new(-scaled_direction.y, scaled_direction.x) + } + + /// Compute the normalized clockwise normal on the right-hand side of the line segment. + /// + /// For the non-panicking version, see [`Segment2d::try_right_normal`]. + /// + /// # Panics + /// + /// Panics if a valid normal could not be computed, for example when the endpoints are coincident, NaN, or infinite. + #[inline(always)] + pub fn right_normal(&self) -> Dir2 { + self.try_right_normal().unwrap_or_else(|err| { + panic!("Failed to compute the right-hand side normal of a line segment: {err}") + }) + } + + /// Try to compute the normalized clockwise normal on the right-hand side of the line segment. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid normal could not be computed, + /// for example when the endpoints are coincident, NaN, or infinite. + #[inline(always)] + pub fn try_right_normal(&self) -> Result { + Dir2::new(self.scaled_right_normal()) + } + + /// Compute the non-normalized clockwise normal on the right-hand side of the line segment. + /// + /// The length of the normal is the distance between the endpoints. + #[inline(always)] + pub fn scaled_right_normal(&self) -> Vec2 { + let scaled_direction = self.scaled_direction(); + Vec2::new(scaled_direction.y, -scaled_direction.x) + } + + /// Compute the segment transformed by the given [`Isometry2d`]. + #[inline(always)] + pub fn transformed(&self, isometry: impl Into) -> Self { + let isometry: Isometry2d = isometry.into(); + Self::new( + isometry.transform_point(self.point1()), + isometry.transform_point(self.point2()), + ) + } + + /// Compute 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 + /// Compute the segment rotated around the origin by the given rotation. #[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 + /// Compute the segment rotated around the given point by the given rotation. #[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 @@ -1299,28 +1431,57 @@ impl Segment2d { rotated.translated(point) } - /// Compute a new segment, based on the original segment rotated around its center + /// Compute the segment rotated around its own 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 + /// Compute the segment with its center at the origin, keeping the same direction and length. #[inline(always)] pub fn centered(&self) -> Segment2d { let center = self.center(); self.translated(-center) } - /// Get the segment with a new length + /// Compute the segment with a new length, keeping the same direction and center. #[inline(always)] pub fn resized(&self, length: f32) -> Segment2d { let offset_from_origin = self.center(); - let centered = self.centered(); + let centered = self.translated(-offset_from_origin); let ratio = length / self.length(); let segment = Segment2d::new(centered.point1() * ratio, centered.point2() * ratio); segment.translated(offset_from_origin) } + + /// Reverses the direction of the line segment by swapping the endpoints. + #[inline(always)] + pub fn reverse(&mut self) { + let [point1, point2] = &mut self.vertices; + core::mem::swap(point1, point2); + } + + /// Returns the line segment with its direction reversed by swapping the endpoints. + #[inline(always)] + #[must_use] + pub fn reversed(mut self) -> Self { + self.reverse(); + self + } +} + +impl From<[Vec2; 2]> for Segment2d { + #[inline(always)] + fn from(vertices: [Vec2; 2]) -> Self { + Self { vertices } + } +} + +impl From<(Vec2, Vec2)> for Segment2d { + #[inline(always)] + fn from((point1, point2): (Vec2, Vec2)) -> Self { + Self::new(point1, point2) + } } /// A series of connected line segments in 2D space. diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index a828fa8247..e39e3be729 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -3,7 +3,7 @@ use core::f32::consts::{FRAC_PI_3, PI}; use super::{Circle, Measured2d, Measured3d, Primitive2d, Primitive3d}; use crate::{ ops::{self, FloatPow}, - Dir3, InvalidDirectionError, Isometry3d, Mat3, Vec2, Vec3, + Dir3, InvalidDirectionError, Isometry3d, Mat3, Ray3d, Vec2, Vec3, }; #[cfg(feature = "bevy_reflect")] @@ -349,8 +349,7 @@ pub struct Line3d { } impl Primitive3d for Line3d {} -/// A segment of a line going through the origin along a direction in 3D space. -#[doc(alias = "LineSegment3d")] +/// A line segment defined by two endpoints in 3D space. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] @@ -358,6 +357,7 @@ impl Primitive3d for Line3d {} all(feature = "serialize", feature = "bevy_reflect"), reflect(Serialize, Deserialize) )] +#[doc(alias = "LineSegment3d")] pub struct Segment3d { /// The endpoints of the line segment. pub vertices: [Vec3; 2], @@ -365,7 +365,7 @@ pub struct Segment3d { impl Primitive3d for Segment3d {} impl Segment3d { - /// Create a new `Segment3d` from its endpoints + /// Create a new `Segment3d` from its endpoints. #[inline(always)] pub const fn new(point1: Vec3, point2: Vec3) -> Self { Self { @@ -373,65 +373,130 @@ impl Segment3d { } } - /// 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` + /// Create a new `Segment3d` from its endpoints and compute its geometric center. #[inline(always)] #[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")] pub fn from_points(point1: Vec3, point2: Vec3) -> (Self, Vec3) { (Self::new(point1, point2), (point1 + point2) / 2.) } - /// Get the position of the first point on the line segment + /// Create a new `Segment3d` centered at the origin with the given direction and length. + /// + /// The endpoints will be at `-direction * length / 2.0` and `direction * length / 2.0`. + #[inline(always)] + pub fn from_direction_and_length(direction: Dir3, length: f32) -> Self { + let endpoint = 0.5 * length * direction; + Self { + vertices: [-endpoint, endpoint], + } + } + + /// Create a new `Segment3d` centered at the origin from a vector representing + /// the direction and length of the line segment. + /// + /// The endpoints will be at `-scaled_direction / 2.0` and `scaled_direction / 2.0`. + #[inline(always)] + pub fn from_scaled_direction(scaled_direction: Vec3) -> Self { + let endpoint = 0.5 * scaled_direction; + Self { + vertices: [-endpoint, endpoint], + } + } + + /// Create a new `Segment3d` starting from the origin of the given `ray`, + /// going in the direction of the ray for the given `length`. + /// + /// The endpoints will be at `ray.origin` and `ray.origin + length * ray.direction`. + #[inline(always)] + pub fn from_ray_and_length(ray: Ray3d, length: f32) -> Self { + Self { + vertices: [ray.origin, ray.get_point(length)], + } + } + + /// Get the position of the first endpoint of the line segment. #[inline(always)] pub fn point1(&self) -> Vec3 { self.vertices[0] } - /// Get the position of the second point on the line segment + /// Get the position of the second endpoint of the line segment. #[inline(always)] pub fn point2(&self) -> Vec3 { self.vertices[1] } - /// Get the center of the segment + /// Compute the midpoint between the two endpoints of the line segment. #[inline(always)] #[doc(alias = "midpoint")] pub fn center(&self) -> Vec3 { - (self.point1() + self.point2()) / 2. + self.point1().midpoint(self.point2()) } - /// Get the length of the segment + /// Compute the length of the line segment. #[inline(always)] pub fn length(&self) -> f32 { self.point1().distance(self.point2()) } - /// Get the segment translated by a vector + /// Compute the squared length of the line segment. + #[inline(always)] + pub fn length_squared(&self) -> f32 { + self.point1().distance_squared(self.point2()) + } + + /// Compute the normalized direction pointing from the first endpoint to the second endpoint. + /// + /// For the non-panicking version, see [`Segment3d::try_direction`]. + /// + /// # Panics + /// + /// Panics if a valid direction could not be computed, for example when the endpoints are coincident, NaN, or infinite. + #[inline(always)] + pub fn direction(&self) -> Dir3 { + self.try_direction().unwrap_or_else(|err| { + panic!("Failed to compute the direction of a line segment: {err}") + }) + } + + /// Try to compute the normalized direction pointing from the first endpoint to the second endpoint. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if a valid direction could not be computed, + /// for example when the endpoints are coincident, NaN, or infinite. + #[inline(always)] + pub fn try_direction(&self) -> Result { + Dir3::new(self.scaled_direction()) + } + + /// Compute the vector from the first endpoint to the second endpoint. + #[inline(always)] + pub fn scaled_direction(&self) -> Vec3 { + self.point2() - self.point1() + } + + /// Compute the segment transformed by the given [`Isometry3d`]. + #[inline(always)] + pub fn transformed(&self, isometry: impl Into) -> Self { + let isometry: Isometry3d = isometry.into(); + Self::new( + isometry.transform_point(self.point1()).into(), + isometry.transform_point(self.point2()).into(), + ) + } + + /// Compute the segment translated by the given 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 + /// Compute the segment rotated around the origin by the given rotation. #[inline(always)] pub fn rotated(&self, rotation: Quat) -> Segment3d { - Segment3d::new( - rotation.mul_vec3(self.point1()), - rotation.mul_vec3(self.point2()), - ) + Segment3d::new(rotation * self.point1(), rotation * self.point2()) } - /// Compute a new segment, based on the original segment rotated around a given point + /// Compute the segment rotated around the given point by the given rotation. #[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 @@ -440,28 +505,57 @@ impl Segment3d { rotated.translated(point) } - /// Compute a new segment, based on the original segment rotated around its center + /// Compute the segment rotated around its own 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 + /// Compute the segment with its center at the origin, keeping the same direction and length. #[inline(always)] pub fn centered(&self) -> Segment3d { let center = self.center(); self.translated(-center) } - /// Get the segment with a new length + /// Compute the segment with a new length, keeping the same direction and center. #[inline(always)] pub fn resized(&self, length: f32) -> Segment3d { let offset_from_origin = self.center(); - let centered = self.centered(); + let centered = self.translated(-offset_from_origin); let ratio = length / self.length(); let segment = Segment3d::new(centered.point1() * ratio, centered.point2() * ratio); segment.translated(offset_from_origin) } + + /// Reverses the direction of the line segment by swapping the endpoints. + #[inline(always)] + pub fn reverse(&mut self) { + let [point1, point2] = &mut self.vertices; + core::mem::swap(point1, point2); + } + + /// Returns the line segment with its direction reversed by swapping the endpoints. + #[inline(always)] + #[must_use] + pub fn reversed(mut self) -> Self { + self.reverse(); + self + } +} + +impl From<[Vec3; 2]> for Segment3d { + #[inline(always)] + fn from(vertices: [Vec3; 2]) -> Self { + Self { vertices } + } +} + +impl From<(Vec3, Vec3)> for Segment3d { + #[inline(always)] + fn from((point1, point2): (Vec3, Vec3)) -> Self { + Self::new(point1, point2) + } } /// A series of connected line segments in 3D space.