Improve Segment2d/Segment3d API and docs (#18206)

# Objective

#17404 reworked the `Segment2d` and `Segment3d` types to be defined by
two endpoints rather than a direction and half-length. However, the API
is still very minimal and limited, and documentation is inconsistent and
outdated.

## Solution

Add the following helper methods for `Segment2d` and `Segment3d`:

- `from_scaled_direction`
- `from_ray_and_length`
- `length_squared`
- `direction`
- `try_direction`
- `scaled_direction`
- `transformed`
- `reversed`

`Segment2d` has a few 2D-specific methods:

- `left_normal`
- `try_left_normal`
- `scaled_left_normal`
- `right_normal`
- `try_right_normal`
- `scaled_right_normal`

There are now also `From` implementations for converting `[Vec2; 2]` and
`(Vec2, Vec2)` to a `Segment2d`, and similarly for 3D.

I have also updated documentation to be more accurate and consistent,
and simplified a few methods.

---

## Prior Art

Parry's
[`Segment`](https://docs.rs/parry2d/latest/parry2d/shape/struct.Segment.html)
type has a lot of similar methods, though my implementation is a bit
more comprehensive. A lot of these methods can be useful for various
geometry algorithms.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Greeble <166992735+greeble-dev@users.noreply.github.com>
This commit is contained in:
Joona Aalto 2025-03-09 22:21:31 +02:00 committed by GitHub
parent 11e0ef5391
commit 9f6d628c48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 316 additions and 67 deletions

View File

@ -634,9 +634,7 @@ where
return; return;
} }
let segment = Segment2d::new(self.point1, self.point2) let segment = Segment2d::new(self.point1, self.point2).transformed(self.isometry);
.rotated(self.isometry.rotation)
.translated(self.isometry.translation);
if self.draw_arrow { if self.draw_arrow {
self.gizmos self.gizmos

View File

@ -228,10 +228,7 @@ where
return; return;
} }
let isometry: Isometry3d = isometry.into(); let transformed = primitive.transformed(isometry);
let transformed = primitive
.rotated(isometry.rotation)
.translated(isometry.translation.into());
self.line(transformed.point1(), transformed.point2(), color); self.line(transformed.point1(), transformed.point2(), color);
} }
} }

View File

@ -881,9 +881,9 @@ mod tests {
#[test] #[test]
fn segment() { 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 translation = Vec2::new(2.0, 1.0);
let isometry = Isometry2d::from_translation(translation); 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); let aabb = segment.aabb_2d(isometry);
assert_eq!(aabb.min, Vec2::new(1.0, 0.5)); assert_eq!(aabb.min, Vec2::new(1.0, 0.5));

View File

@ -349,7 +349,7 @@ mod tests {
#[test] #[test]
fn segment() { fn segment() {
let extrusion = Extrusion::new( 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, 4.0,
); );
let translation = Vec3::new(3., 4., 5.); let translation = Vec3::new(3., 4., 5.);

View File

@ -457,9 +457,8 @@ mod tests {
#[test] #[test]
fn segment() { 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 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); let aabb = segment.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.5, 0.0)); assert_eq!(aabb.min, Vec3A::new(1.0, 0.5, 0.0));

View File

@ -5,7 +5,7 @@ use thiserror::Error;
use super::{Measured2d, Primitive2d, WindingOrder}; use super::{Measured2d, Primitive2d, WindingOrder};
use crate::{ use crate::{
ops::{self, FloatPow}, ops::{self, FloatPow},
Dir2, Rot2, Vec2, Dir2, InvalidDirectionError, Isometry2d, Ray2d, Rot2, Vec2,
}; };
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
@ -1211,7 +1211,7 @@ pub struct Line2d {
} }
impl Primitive2d for 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)] #[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
@ -1227,7 +1227,7 @@ pub struct Segment2d {
impl Primitive2d for Segment2d {} impl Primitive2d for Segment2d {}
impl Segment2d { impl Segment2d {
/// Create a new `Segment2d` from its endpoints /// Create a new `Segment2d` from its endpoints.
#[inline(always)] #[inline(always)]
pub const fn new(point1: Vec2, point2: Vec2) -> Self { pub const fn new(point1: Vec2, point2: Vec2) -> Self {
Self { Self {
@ -1235,62 +1235,194 @@ impl Segment2d {
} }
} }
/// Create a new `Segment2d` from its endpoints and compute its geometric center /// Create a new `Segment2d` from its endpoints and compute its geometric center.
///
/// # Panics
///
/// Panics if `point1 == point2`
#[inline(always)] #[inline(always)]
#[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")] #[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")]
pub fn from_points(point1: Vec2, point2: Vec2) -> (Self, Vec2) { pub fn from_points(point1: Vec2, point2: Vec2) -> (Self, Vec2) {
(Self::new(point1, point2), (point1 + point2) / 2.) (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)] #[inline(always)]
pub fn from_direction_and_length(direction: Dir2, length: f32) -> Segment2d { pub fn from_direction_and_length(direction: Dir2, length: f32) -> Self {
let half_length = length / 2.; let endpoint = 0.5 * length * direction;
Self::new(direction * -half_length, direction * half_length) 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)] #[inline(always)]
pub fn point1(&self) -> Vec2 { pub fn point1(&self) -> Vec2 {
self.vertices[0] 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)] #[inline(always)]
pub fn point2(&self) -> Vec2 { pub fn point2(&self) -> Vec2 {
self.vertices[1] self.vertices[1]
} }
/// Get the segment's center /// Compute the midpoint between the two endpoints of the line segment.
#[inline(always)] #[inline(always)]
#[doc(alias = "midpoint")] #[doc(alias = "midpoint")]
pub fn center(&self) -> Vec2 { 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)] #[inline(always)]
pub fn length(&self) -> f32 { pub fn length(&self) -> f32 {
self.point1().distance(self.point2()) 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, InvalidDirectionError> {
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, InvalidDirectionError> {
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, InvalidDirectionError> {
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<Isometry2d>) -> 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)] #[inline(always)]
pub fn translated(&self, translation: Vec2) -> Segment2d { pub fn translated(&self, translation: Vec2) -> Segment2d {
Self::new(self.point1() + translation, self.point2() + translation) 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)] #[inline(always)]
pub fn rotated(&self, rotation: Rot2) -> Segment2d { pub fn rotated(&self, rotation: Rot2) -> Segment2d {
Segment2d::new(rotation * self.point1(), rotation * self.point2()) 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)] #[inline(always)]
pub fn rotated_around(&self, rotation: Rot2, point: Vec2) -> Segment2d { 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 // 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) 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)] #[inline(always)]
pub fn rotated_around_center(&self, rotation: Rot2) -> Segment2d { pub fn rotated_around_center(&self, rotation: Rot2) -> Segment2d {
self.rotated_around(rotation, self.center()) 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)] #[inline(always)]
pub fn centered(&self) -> Segment2d { pub fn centered(&self) -> Segment2d {
let center = self.center(); let center = self.center();
self.translated(-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)] #[inline(always)]
pub fn resized(&self, length: f32) -> Segment2d { pub fn resized(&self, length: f32) -> Segment2d {
let offset_from_origin = self.center(); let offset_from_origin = self.center();
let centered = self.centered(); let centered = self.translated(-offset_from_origin);
let ratio = length / self.length(); let ratio = length / self.length();
let segment = Segment2d::new(centered.point1() * ratio, centered.point2() * ratio); let segment = Segment2d::new(centered.point1() * ratio, centered.point2() * ratio);
segment.translated(offset_from_origin) 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. /// A series of connected line segments in 2D space.

View File

@ -3,7 +3,7 @@ use core::f32::consts::{FRAC_PI_3, PI};
use super::{Circle, Measured2d, Measured3d, Primitive2d, Primitive3d}; use super::{Circle, Measured2d, Measured3d, Primitive2d, Primitive3d};
use crate::{ use crate::{
ops::{self, FloatPow}, ops::{self, FloatPow},
Dir3, InvalidDirectionError, Isometry3d, Mat3, Vec2, Vec3, Dir3, InvalidDirectionError, Isometry3d, Mat3, Ray3d, Vec2, Vec3,
}; };
#[cfg(feature = "bevy_reflect")] #[cfg(feature = "bevy_reflect")]
@ -349,8 +349,7 @@ pub struct Line3d {
} }
impl Primitive3d for Line3d {} impl Primitive3d for Line3d {}
/// A segment of a line going through the origin along a direction in 3D space. /// A line segment defined by two endpoints in 3D space.
#[doc(alias = "LineSegment3d")]
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
@ -358,6 +357,7 @@ impl Primitive3d for Line3d {}
all(feature = "serialize", feature = "bevy_reflect"), all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize) reflect(Serialize, Deserialize)
)] )]
#[doc(alias = "LineSegment3d")]
pub struct Segment3d { pub struct Segment3d {
/// The endpoints of the line segment. /// The endpoints of the line segment.
pub vertices: [Vec3; 2], pub vertices: [Vec3; 2],
@ -365,7 +365,7 @@ pub struct Segment3d {
impl Primitive3d for Segment3d {} impl Primitive3d for Segment3d {}
impl Segment3d { impl Segment3d {
/// Create a new `Segment3d` from its endpoints /// Create a new `Segment3d` from its endpoints.
#[inline(always)] #[inline(always)]
pub const fn new(point1: Vec3, point2: Vec3) -> Self { pub const fn new(point1: Vec3, point2: Vec3) -> Self {
Self { Self {
@ -373,65 +373,130 @@ impl Segment3d {
} }
} }
/// Create a new `Segment3d` from a direction and full length of the segment /// Create a new `Segment3d` from its endpoints and compute its geometric center.
#[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)] #[inline(always)]
#[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")] #[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")]
pub fn from_points(point1: Vec3, point2: Vec3) -> (Self, Vec3) { pub fn from_points(point1: Vec3, point2: Vec3) -> (Self, Vec3) {
(Self::new(point1, point2), (point1 + point2) / 2.) (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)] #[inline(always)]
pub fn point1(&self) -> Vec3 { pub fn point1(&self) -> Vec3 {
self.vertices[0] 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)] #[inline(always)]
pub fn point2(&self) -> Vec3 { pub fn point2(&self) -> Vec3 {
self.vertices[1] self.vertices[1]
} }
/// Get the center of the segment /// Compute the midpoint between the two endpoints of the line segment.
#[inline(always)] #[inline(always)]
#[doc(alias = "midpoint")] #[doc(alias = "midpoint")]
pub fn center(&self) -> Vec3 { 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)] #[inline(always)]
pub fn length(&self) -> f32 { pub fn length(&self) -> f32 {
self.point1().distance(self.point2()) 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, InvalidDirectionError> {
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<Isometry3d>) -> 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)] #[inline(always)]
pub fn translated(&self, translation: Vec3) -> Segment3d { pub fn translated(&self, translation: Vec3) -> Segment3d {
Self::new(self.point1() + translation, self.point2() + translation) 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)] #[inline(always)]
pub fn rotated(&self, rotation: Quat) -> Segment3d { pub fn rotated(&self, rotation: Quat) -> Segment3d {
Segment3d::new( Segment3d::new(rotation * self.point1(), rotation * self.point2())
rotation.mul_vec3(self.point1()),
rotation.mul_vec3(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)] #[inline(always)]
pub fn rotated_around(&self, rotation: Quat, point: Vec3) -> Segment3d { 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 // 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) 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)] #[inline(always)]
pub fn rotated_around_center(&self, rotation: Quat) -> Segment3d { pub fn rotated_around_center(&self, rotation: Quat) -> Segment3d {
self.rotated_around(rotation, self.center()) 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)] #[inline(always)]
pub fn centered(&self) -> Segment3d { pub fn centered(&self) -> Segment3d {
let center = self.center(); let center = self.center();
self.translated(-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)] #[inline(always)]
pub fn resized(&self, length: f32) -> Segment3d { pub fn resized(&self, length: f32) -> Segment3d {
let offset_from_origin = self.center(); let offset_from_origin = self.center();
let centered = self.centered(); let centered = self.translated(-offset_from_origin);
let ratio = length / self.length(); let ratio = length / self.length();
let segment = Segment3d::new(centered.point1() * ratio, centered.point2() * ratio); let segment = Segment3d::new(centered.point1() * ratio, centered.point2() * ratio);
segment.translated(offset_from_origin) 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. /// A series of connected line segments in 3D space.