-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Define a basic set of Primitives #10466
Changes from all commits
f681821
2795df6
4cc1bd4
3903a6e
00c2505
ef5c035
225ccb2
8538b51
5508362
4ef3913
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,185 @@ | ||||||
use super::Primitive2d; | ||||||
use crate::Vec2; | ||||||
|
||||||
/// A normalized vector pointing in a direction in 2D space | ||||||
#[derive(Clone, Copy, Debug)] | ||||||
pub struct Direction2d(Vec2); | ||||||
|
||||||
impl From<Vec2> for Direction2d { | ||||||
fn from(value: Vec2) -> Self { | ||||||
Self(value.normalize()) | ||||||
} | ||||||
} | ||||||
|
||||||
impl Direction2d { | ||||||
/// Create a direction from a [`Vec2`] that is already normalized | ||||||
pub fn from_normalized(value: Vec2) -> Self { | ||||||
debug_assert!(value.is_normalized()); | ||||||
Self(value) | ||||||
NiseVoid marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
} | ||||||
|
||||||
impl std::ops::Deref for Direction2d { | ||||||
type Target = Vec2; | ||||||
fn deref(&self) -> &Self::Target { | ||||||
&self.0 | ||||||
} | ||||||
} | ||||||
|
||||||
/// A circle primitive | ||||||
#[derive(Clone, Copy, Debug)] | ||||||
pub struct Circle { | ||||||
/// The radius of the circle | ||||||
pub radius: f32, | ||||||
} | ||||||
impl Primitive2d for Circle {} | ||||||
|
||||||
/// An unbounded plane in 2D space. It forms a separating surface through the origin, | ||||||
/// stretching infinitely far | ||||||
#[derive(Clone, Copy, Debug)] | ||||||
pub struct Plane2d { | ||||||
/// The normal of the plane. The plane will be placed perpendicular to this direction | ||||||
pub normal: Direction2d, | ||||||
} | ||||||
impl Primitive2d for Plane2d {} | ||||||
|
||||||
/// An infinite line along a direction in 2D space. | ||||||
/// | ||||||
/// For a finite line: [`Segment2d`] | ||||||
#[derive(Clone, Copy, Debug)] | ||||||
pub struct Line2d { | ||||||
/// The direction of the line. The line extends infinitely in both the given direction | ||||||
/// and its opposite direction | ||||||
pub direction: Direction2d, | ||||||
} | ||||||
impl Primitive2d for Line2d {} | ||||||
|
||||||
/// A segment of a line along a direction in 2D space. | ||||||
#[doc(alias = "LineSegment2d")] | ||||||
#[derive(Clone, Debug)] | ||||||
pub struct Segment2d { | ||||||
/// The direction of the line segment | ||||||
pub direction: Direction2d, | ||||||
/// 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, | ||||||
} | ||||||
impl Primitive2d for Segment2d {} | ||||||
|
||||||
impl Segment2d { | ||||||
/// Create a line segment from a direction and full length of the segment | ||||||
pub fn new(direction: Direction2d, length: f32) -> Self { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: ask what you need, and don't assume what the user have; that will avoid them having to pass
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Main reason I made it like that is because it's not all that common start with a half length, and even if you did the struct fields are public and don't have the footgun of not realizing its a half length (because you need to type out the field name). But maybe having it be called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed: because the fields are public, the constructor methods should be intuitive. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough |
||||||
Self { | ||||||
direction, | ||||||
half_length: length / 2., | ||||||
} | ||||||
} | ||||||
|
||||||
/// Get a line segment and translation from two points at each end of a line segment | ||||||
/// | ||||||
/// Panics if point1 == point2 | ||||||
pub fn from_points(point1: Vec2, point2: Vec2) -> (Self, Vec2) { | ||||||
let diff = point2 - point1; | ||||||
let length = diff.length(); | ||||||
( | ||||||
Self::new(Direction2d::from_normalized(diff / length), length), | ||||||
(point1 + point2) / 2., | ||||||
) | ||||||
} | ||||||
|
||||||
/// Get the position of the first point on the line segment | ||||||
pub fn point1(&self) -> Vec2 { | ||||||
*self.direction * -self.half_length | ||||||
NiseVoid marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
/// Get the position of the second point on the line segment | ||||||
pub fn point2(&self) -> Vec2 { | ||||||
*self.direction * self.half_length | ||||||
NiseVoid marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
} | ||||||
|
||||||
/// A series of connected line segments in 2D space. | ||||||
/// | ||||||
/// For a version without generics: [`BoxedPolyline2d`] | ||||||
#[derive(Clone, Debug)] | ||||||
pub struct Polyline2d<const N: usize> { | ||||||
/// The vertices of the polyline | ||||||
pub vertices: [Vec2; N], | ||||||
} | ||||||
impl<const N: usize> Primitive2d for Polyline2d<N> {} | ||||||
|
||||||
/// A series of connected line segments in 2D space, allocated on the heap | ||||||
/// in a `Box<[Vec2]>`. | ||||||
/// | ||||||
/// For a version without alloc: [`Polyline2d`] | ||||||
#[derive(Clone, Debug)] | ||||||
pub struct BoxedPolyline2d { | ||||||
alice-i-cecile marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// The vertices of the polyline | ||||||
pub vertices: Box<[Vec2]>, | ||||||
alice-i-cecile marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
impl Primitive2d for BoxedPolyline2d {} | ||||||
|
||||||
/// A triangle in 2D space | ||||||
#[derive(Clone, Debug)] | ||||||
pub struct Triangle2d { | ||||||
/// The vertices of the triangle | ||||||
pub vertices: [Vec2; 3], | ||||||
} | ||||||
impl Primitive2d for Triangle2d {} | ||||||
|
||||||
/// A rectangle primitive | ||||||
#[doc(alias = "Quad")] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A rectangle is a type of quadrilateral, but not the same thing; a quadrilateral is just a four-sided polygon, but it makes no guarantees on the positions of those vertices. But this is just an alias so it's probably fine? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's mostly there for anyone looking for a Quad so they get this as its the closest thing to what they might be looking for, since plenty of engines and programs don't offer you a rectangle but just a quad but then only allow you to set width/height anyway. |
||||||
#[derive(Clone, Copy, Debug)] | ||||||
pub struct Rectangle { | ||||||
/// The half width of the rectangle | ||||||
pub half_width: f32, | ||||||
/// The half height of the rectangle | ||||||
pub half_height: f32, | ||||||
} | ||||||
impl Primitive2d for Rectangle {} | ||||||
|
||||||
impl Rectangle { | ||||||
NiseVoid marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
/// Create a rectangle from a full width and height | ||||||
pub fn new(width: f32, height: f32) -> Self { | ||||||
Self::from_size(Vec2::new(width, height)) | ||||||
} | ||||||
|
||||||
/// Create a rectangle from a given full size | ||||||
pub fn from_size(size: Vec2) -> Self { | ||||||
NiseVoid marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
Self { | ||||||
half_width: size.x / 2., | ||||||
half_height: size.y / 2., | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
/// A polygon with N vertices. | ||||||
/// | ||||||
/// For a version without generics: [`BoxedPolygon`] | ||||||
#[derive(Clone, Debug)] | ||||||
pub struct Polygon<const N: usize> { | ||||||
/// The vertices of the polygon | ||||||
pub vertices: [Vec2; N], | ||||||
} | ||||||
impl<const N: usize> Primitive2d for Polygon<N> {} | ||||||
|
||||||
/// A polygon with a variable number of vertices, allocated on the heap | ||||||
/// in a `Box<[Vec2]>`. | ||||||
/// | ||||||
/// For a version without alloc: [`Polygon`] | ||||||
#[derive(Clone, Debug)] | ||||||
pub struct BoxedPolygon { | ||||||
/// The vertices of the polygon | ||||||
pub vertices: Box<[Vec2]>, | ||||||
} | ||||||
impl Primitive2d for BoxedPolygon {} | ||||||
|
||||||
/// A polygon where all vertices lie on a circle, equally far apart | ||||||
#[derive(Clone, Copy, Debug)] | ||||||
pub struct RegularPolygon { | ||||||
/// The circumcircle on which all vertices lie | ||||||
pub circumcircle: Circle, | ||||||
/// The number of sides | ||||||
pub sides: usize, | ||||||
} | ||||||
impl Primitive2d for RegularPolygon {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
use super::Primitive3d; | ||
use crate::Vec3; | ||
|
||
/// A normalized vector pointing in a direction in 3D space | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct Direction3d(Vec3); | ||
|
||
impl From<Vec3> for Direction3d { | ||
fn from(value: Vec3) -> Self { | ||
Self(value.normalize()) | ||
} | ||
} | ||
|
||
impl Direction3d { | ||
/// Create a direction from a [`Vec3`] that is already normalized | ||
pub fn from_normalized(value: Vec3) -> Self { | ||
debug_assert!(value.is_normalized()); | ||
Self(value) | ||
} | ||
} | ||
|
||
impl std::ops::Deref for Direction3d { | ||
type Target = Vec3; | ||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
/// A sphere primitive | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct Sphere { | ||
/// The radius of the sphere | ||
pub radius: f32, | ||
} | ||
impl Primitive3d for Sphere {} | ||
|
||
/// An unbounded plane in 3D space. It forms a separating surface through the origin, | ||
/// stretching infinitely far | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct Plane3d { | ||
/// The normal of the plane. The plane will be placed perpendicular to this direction | ||
pub normal: Direction3d, | ||
} | ||
impl Primitive3d for Plane3d {} | ||
|
||
/// An infinite line along a direction in 3D space. | ||
/// | ||
/// For a finite line: [`Segment3d`] | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct Line3d { | ||
/// The direction of the line | ||
pub direction: Direction3d, | ||
} | ||
impl Primitive3d for Line3d {} | ||
|
||
/// A segment of a line along a direction in 3D space. | ||
#[doc(alias = "LineSegment3d")] | ||
#[derive(Clone, Debug)] | ||
pub struct Segment3d { | ||
/// The direction of the line | ||
pub direction: Direction3d, | ||
/// 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, | ||
} | ||
impl Primitive3d for Segment3d {} | ||
|
||
impl Segment3d { | ||
/// Create a line segment from a direction and full length of the segment | ||
pub fn new(direction: Direction3d, length: f32) -> Self { | ||
Self { | ||
direction, | ||
half_length: length / 2., | ||
} | ||
} | ||
|
||
/// Get a line segment and translation from two points at each end of a line segment | ||
/// | ||
/// Panics if point1 == point2 | ||
pub fn from_points(point1: Vec3, point2: Vec3) -> (Self, Vec3) { | ||
let diff = point2 - point1; | ||
let length = diff.length(); | ||
( | ||
Self::new(Direction3d::from_normalized(diff / length), length), | ||
(point1 + point2) / 2., | ||
) | ||
} | ||
|
||
/// Get the position of the first point on the line segment | ||
pub fn point1(&self) -> Vec3 { | ||
*self.direction * -self.half_length | ||
} | ||
|
||
/// Get the position of the second point on the line segment | ||
pub fn point2(&self) -> Vec3 { | ||
*self.direction * self.half_length | ||
} | ||
} | ||
|
||
/// A series of connected line segments in 3D space. | ||
/// | ||
/// For a version without generics: [`BoxedPolyline3d`] | ||
#[derive(Clone, Debug)] | ||
pub struct Polyline3d<const N: usize> { | ||
/// The vertices of the polyline | ||
pub vertices: [Vec3; N], | ||
} | ||
impl<const N: usize> Primitive3d for Polyline3d<N> {} | ||
|
||
/// A series of connected line segments in 3D space, allocated on the heap | ||
/// in a `Box<[Vec3]>`. | ||
/// | ||
/// For a version without alloc: [`Polyline3d`] | ||
#[derive(Clone, Debug)] | ||
pub struct BoxedPolyline3d { | ||
/// The vertices of the polyline | ||
pub vertices: Box<[Vec3]>, | ||
} | ||
impl Primitive3d for BoxedPolyline3d {} | ||
|
||
/// A cuboid primitive, more commonly known as a box. | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct Cuboid { | ||
/// Half of the width, height and depth of the cuboid | ||
pub half_extents: Vec3, | ||
} | ||
impl Primitive3d for Cuboid {} | ||
|
||
impl Cuboid { | ||
NiseVoid marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Create a cuboid from a full x, y and z length | ||
pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Self { | ||
Self::from_size(Vec3::new(x_length, y_length, z_length)) | ||
} | ||
|
||
/// Create a cuboid from a given full size | ||
pub fn from_size(size: Vec3) -> Self { | ||
Self { | ||
half_extents: size / 2., | ||
} | ||
} | ||
} | ||
|
||
/// A cylinder primitive | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct Cylinder { | ||
/// The radius of the cylinder | ||
pub radius: f32, | ||
/// The half height of the cylinder | ||
pub half_height: f32, | ||
} | ||
impl Primitive3d for Cylinder {} | ||
|
||
impl Cylinder { | ||
/// Create a cylinder from a radius and full height | ||
pub fn new(radius: f32, height: f32) -> Self { | ||
Self { | ||
radius, | ||
half_height: height / 2., | ||
} | ||
} | ||
} | ||
|
||
/// A capsule primitive. | ||
/// A capsule is defined as a surface at a distance (radius) from a line | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct Capsule { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This probably isn't really an issue, but using the same capsule shape for 2D and 3D might make some helper methods a bit more annoying. For example, |
||
/// The radius of the capsule | ||
pub radius: f32, | ||
/// Half the height of the capsule, excluding the hemispheres | ||
pub half_length: f32, | ||
} | ||
impl super::Primitive2d for Capsule {} | ||
impl Primitive3d for Capsule {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//! This module defines primitive shapes. | ||
//! The origin is (0, 0) for 2D primitives and (0, 0, 0) for 3D primitives, | ||
//! unless stated otherwise. | ||
|
||
mod dim2; | ||
pub use dim2::*; | ||
mod dim3; | ||
pub use dim3::*; | ||
|
||
/// A marker trait for 2D primitives | ||
pub trait Primitive2d {} | ||
|
||
/// A marker trait for 3D primitives | ||
pub trait Primitive3d {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're using
Vec2
andfn
etc. in Rust, can we useDir2d
here and save some typing?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apart from
Quad
(quadrilateral), it'd be the only abbreviated primitive, but it does resemble a struct likeVec2
more than an actual primitive shape... if it will be used a lot in public APIs, then perhapsDir2d
would be good, but otherwise I think I preferDirection2d
since it's generally better not to abbreviate names in my eyes. Other opinions on this would be useful thoughThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure but have you been looking at Rust, didn't that ship float already? 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For very common types that appear everywhere like
Vec
,Vec2
,Quat
andRes
, it makes sense to abbreviate, but for most other types I don't consider it good practise. ButDir2d
could be considered to be a utility type similar toVec2
, so it could work here, not sure. (although we have the extrad
there...Dir2
would be more consistent with Glam math types, but not with Bevy's other types)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it's difficult to tell in advance. I don't mind either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to be consistent and explicit here. I don't think it's common enough to warrant shortening.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’d vote Direction2d for what it’s worth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we use
Normalized<T>(T)
, it could be used for T =Vec2
,Vec3
,Quat
, etc. Aliases could be defined as well.I think it's okay to abbreviate often-used types, such as
Vec2
/Vec3
(integers and floats only get a single letter), but using an expanded name for less common types avoids confusion and time spent searching documentation.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO
Direction2d
is better because it is more explicit. Since most users are usually usingrust-analyzer
, you can just typeDir2
and the fuzzy matching autocomplete will do the work for you, so you would type less anyways.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we can go for
Direction2d
since it's very explicit and consistent with other names. I kinda like theNormalized
suggestion too, but we only need it forVec2
/Vec3
for now, so I think it's fine if we go for the non-generic approach until there's a real need for it. It would probably be aliased underDirection2d
/Direction3d
anyway and the API would be basically the same, so changing it later if necessary should be pretty straightforward.