Skip to content

Commit

Permalink
Define a basic set of Primitives (#10466)
Browse files Browse the repository at this point in the history
# 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 <[email protected]>
  • Loading branch information
NiseVoid and Jondolf authored Nov 15, 2023
1 parent cbcd826 commit 01b9ddd
Show file tree
Hide file tree
Showing 4 changed files with 375 additions and 2 deletions.
5 changes: 3 additions & 2 deletions crates/bevy_math/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

mod affine3;
pub mod cubic_splines;
pub mod primitives;
mod ray;
mod rects;

Expand All @@ -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,
};
}
Expand Down
185 changes: 185 additions & 0 deletions crates/bevy_math/src/primitives/dim2.rs
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)
}
}

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 changes: 173 additions & 0 deletions crates/bevy_math/src/primitives/dim3.rs
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 {
/// 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 changes: 14 additions & 0 deletions crates/bevy_math/src/primitives/mod.rs
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 {}

0 comments on commit 01b9ddd

Please sign in to comment.