Reworked Segment types into their cartesian forms (#17404)
# Objective Segment2d and Segment3d are currently hard to work with because unlike many other primary shapes, they are bound to the origin. The objective of this PR is to allow these segments to exist anywhere in cartesian space, making them much more useful in a variety of contexts. ## Solution Reworking the existing segment type's internal fields and methods to allow them to exist anywhere in cartesian space. I have done both reworks for 2d and 3d segments but I was unsure if I should just have it all here or not so feel free to tell me how I should proceed, for now I have only pushed Segment2d changes. As I am not a very seasoned contributor, this first implementation is very likely sloppy and will need some additional work from my end, I am open to all criticisms and willing to work to get this to bevy's standards. ## Testing I am not very familiar with the standards of testing. Of course my changes had to pass the thorough existing tests for primitive shapes. I also checked the gizmo 2d shapes intersection example and everything looked fine. I did add a few utility methods to the types that have no tests yet. I am willing to implement some if it is deemed necessary ## Migration Guide The segment type constructors changed so if someone previously created a Segment2d with a direction and length they would now need to use the `from_direction` constructor --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
This commit is contained in:
parent
21f1e3045c
commit
7c8da1c05d
@ -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<Config, Clear>,
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<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();
|
||||
|
||||
Aabb2d::new(isometry.translation, half_size)
|
||||
Aabb2d::from_point_cloud(isometry, &[self.point1(), self.point2()])
|
||||
}
|
||||
|
||||
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> 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));
|
||||
|
@ -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);
|
||||
|
@ -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<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();
|
||||
|
||||
Aabb3d::new(isometry.translation, half_size)
|
||||
Aabb3d::from_point_cloud(isometry, [self.point1(), self.point2()].iter().copied())
|
||||
}
|
||||
|
||||
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> 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));
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -359,22 +359,25 @@ 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 its endpoints
|
||||
#[inline(always)]
|
||||
pub const fn new(point1: Vec3, point2: Vec3) -> Self {
|
||||
Self {
|
||||
vertices: [point1, point2],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `Segment3d` from a direction and full length of the segment
|
||||
#[inline(always)]
|
||||
pub fn new(direction: Dir3, length: f32) -> Self {
|
||||
Self {
|
||||
direction,
|
||||
half_length: length / 2.0,
|
||||
}
|
||||
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
|
||||
@ -383,27 +386,81 @@ impl Segment3d {
|
||||
///
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user