Define a basic set of Primitives (#10466)
# Objective - Implement a subset of https://github.com/bevyengine/rfcs/blob/main/rfcs/12-primitive-shapes.md#feature-name-primitive-shapes ## Solution - Define a very basic set of primitives in bevy_math - Assume a 0,0,0 origin for most shapes - Use radius and half extents to avoid unnecessary computational overhead wherever they get used - Provide both Boxed and const generics variants for shapes with variable sizes - Boxed is useful if a 3rd party crate wants to use something like enum-dispatch for all supported primitives - Const generics is useful when just working on a single primitive, as it causes no allocs #### Some discrepancies from the RFC: - Box was changed to Cuboid, because Box is already used for an alloc type - Skipped Cone because it's unclear where the origin should be for different uses - Skipped Wedge because it's too niche for an initial PR (we also don't implement Torus, Pyramid or a Death Star (there's an SDF for that!)) - Skipped Frustum because while it would be a useful math type, it's not really a common primitive - Skipped Triangle3d and Quad3d because those are just rotated 2D shapes ## Future steps - Add more primitives - Add helper methods to make primitives easier to construct (especially when half extents are involved) - Add methods to calculate AABBs for primitives (useful for physics, BVH construction, for the mesh AABBs, etc) - Add wrappers for common and cheap operations, like extruding 2D shapes and translating them - Use the primitives to generate meshes - Provide signed distance functions and gradients for primitives (maybe) --- ## Changelog - Added a collection of primitives to the bevy_math crate --------- Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
This commit is contained in:
		
							parent
							
								
									cbcd826612
								
							
						
					
					
						commit
						01b9ddd92c
					
				| @ -9,6 +9,7 @@ | |||||||
| 
 | 
 | ||||||
| mod affine3; | mod affine3; | ||||||
| pub mod cubic_splines; | pub mod cubic_splines; | ||||||
|  | pub mod primitives; | ||||||
| mod ray; | mod ray; | ||||||
| mod rects; | mod rects; | ||||||
| 
 | 
 | ||||||
| @ -24,8 +25,8 @@ pub mod prelude { | |||||||
|             CubicBSpline, CubicBezier, CubicCardinalSpline, CubicGenerator, CubicHermite, |             CubicBSpline, CubicBezier, CubicCardinalSpline, CubicGenerator, CubicHermite, | ||||||
|             CubicSegment, |             CubicSegment, | ||||||
|         }, |         }, | ||||||
|         BVec2, BVec3, BVec4, EulerRot, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, Quat, Ray, |         primitives, BVec2, BVec3, BVec4, EulerRot, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, | ||||||
|         Rect, URect, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, |         Quat, Ray, Rect, URect, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, | ||||||
|         Vec4Swizzles, |         Vec4Swizzles, | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										185
									
								
								crates/bevy_math/src/primitives/dim2.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								crates/bevy_math/src/primitives/dim2.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 { | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Get the position of the second point on the line segment
 | ||||||
|  |     pub fn point2(&self) -> Vec2 { | ||||||
|  |         *self.direction * self.half_length | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// 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 { | ||||||
|  |     /// The vertices of the polyline
 | ||||||
|  |     pub vertices: Box<[Vec2]>, | ||||||
|  | } | ||||||
|  | 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")] | ||||||
|  | #[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 { | ||||||
|  |     /// 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 { | ||||||
|  |         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 {} | ||||||
							
								
								
									
										173
									
								
								crates/bevy_math/src/primitives/dim3.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								crates/bevy_math/src/primitives/dim3.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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 { | ||||||
|  |     /// 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 { | ||||||
|  |     /// 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 {} | ||||||
							
								
								
									
										14
									
								
								crates/bevy_math/src/primitives/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								crates/bevy_math/src/primitives/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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 {} | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 NiseVoid
						NiseVoid