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;
|
||||
pub mod cubic_splines;
|
||||
pub mod primitives;
|
||||
mod ray;
|
||||
mod rects;
|
||||
|
||||
@ -24,8 +25,8 @@ pub mod prelude {
|
||||
CubicBSpline, CubicBezier, CubicCardinalSpline, CubicGenerator, CubicHermite,
|
||||
CubicSegment,
|
||||
},
|
||||
BVec2, BVec3, BVec4, EulerRot, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, Quat, Ray,
|
||||
Rect, URect, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4,
|
||||
primitives, BVec2, BVec3, BVec4, EulerRot, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4,
|
||||
Quat, Ray, Rect, URect, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4,
|
||||
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