Extrusion (#13270)
# Objective - Adds a basic `Extrusion<T: Primitive2d>` shape, suggestion of #10572 ## Solution - Adds `Measured2d` and `Measured3d` traits for getting the perimeter/area or area/volume of shapes. This allows implementing `.volume()` and `.area()` for all extrusions `Extrusion<T: Primitive2d + Measured2d>` within `bevy_math` - All existing perimeter, area and volume implementations for primitves have been moved into implementations of `Measured2d` and `Measured3d` - Shapes should be extruded along the Z-axis since an extrusion of depth `0.` should be equivalent in everything but name to the base shape ## Caviats - I am not sure about the naming. `Extrusion<T>` could also be `Prism<T>` and the `MeasuredNd` could also be something like `MeasuredPrimitiveNd`. If you have any other suggestions, please fell free to share them :) ## Future work This PR adds a basic `Extrusion` shape and does not implement a lot of things you might want it to. Some of the future possibilities include: - [ ] bounding for extrusions - [ ] making extrusions work with gizmos - [ ] meshing --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
22305acf66
commit
03f4cc5dde
@ -1,6 +1,6 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use super::{Primitive2d, WindingOrder};
|
||||
use super::{Measured2d, Primitive2d, WindingOrder};
|
||||
use crate::{Dir2, Vec2};
|
||||
|
||||
/// A circle primitive
|
||||
@ -32,19 +32,6 @@ impl Circle {
|
||||
2.0 * self.radius
|
||||
}
|
||||
|
||||
/// Get the area of the circle
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
PI * self.radius.powi(2)
|
||||
}
|
||||
|
||||
/// Get the perimeter or circumference of the circle
|
||||
#[inline(always)]
|
||||
#[doc(alias = "circumference")]
|
||||
pub fn perimeter(&self) -> f32 {
|
||||
2.0 * PI * self.radius
|
||||
}
|
||||
|
||||
/// Finds the point on the circle that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the circle, the returned point will be on the perimeter of the circle.
|
||||
@ -65,6 +52,21 @@ impl Circle {
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured2d for Circle {
|
||||
/// Get the area of the circle
|
||||
#[inline(always)]
|
||||
fn area(&self) -> f32 {
|
||||
PI * self.radius.powi(2)
|
||||
}
|
||||
|
||||
/// Get the perimeter or circumference of the circle
|
||||
#[inline(always)]
|
||||
#[doc(alias = "circumference")]
|
||||
fn perimeter(&self) -> f32 {
|
||||
2.0 * PI * self.radius
|
||||
}
|
||||
}
|
||||
|
||||
/// An ellipse primitive
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
@ -129,11 +131,31 @@ impl Ellipse {
|
||||
(a * a - b * b).sqrt()
|
||||
}
|
||||
|
||||
/// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.
|
||||
#[inline(always)]
|
||||
pub fn semi_major(&self) -> f32 {
|
||||
self.half_size.max_element()
|
||||
}
|
||||
|
||||
/// Returns the length of the semi-minor axis. This corresponds to the shortest radius of the ellipse.
|
||||
#[inline(always)]
|
||||
pub fn semi_minor(&self) -> f32 {
|
||||
self.half_size.min_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured2d for Ellipse {
|
||||
/// Get the area of the ellipse
|
||||
#[inline(always)]
|
||||
fn area(&self) -> f32 {
|
||||
PI * self.half_size.x * self.half_size.y
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
/// Get an approximation for the perimeter or circumference of the ellipse.
|
||||
///
|
||||
/// The approximation is reasonably precise with a relative error less than 0.007%, getting more precise as the eccentricity of the ellipse decreases.
|
||||
pub fn perimeter(&self) -> f32 {
|
||||
fn perimeter(&self) -> f32 {
|
||||
let a = self.semi_major();
|
||||
let b = self.semi_minor();
|
||||
|
||||
@ -184,24 +206,6 @@ impl Ellipse {
|
||||
.map(|i| BINOMIAL_COEFFICIENTS[i] * h.powi(i as i32))
|
||||
.sum::<f32>()
|
||||
}
|
||||
|
||||
/// Returns the length of the semi-major axis. This corresponds to the longest radius of the ellipse.
|
||||
#[inline(always)]
|
||||
pub fn semi_major(&self) -> f32 {
|
||||
self.half_size.max_element()
|
||||
}
|
||||
|
||||
/// Returns the length of the semi-minor axis. This corresponds to the shortest radius of the ellipse.
|
||||
#[inline(always)]
|
||||
pub fn semi_minor(&self) -> f32 {
|
||||
self.half_size.min_element()
|
||||
}
|
||||
|
||||
/// Get the area of the ellipse
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
PI * self.half_size.x * self.half_size.y
|
||||
}
|
||||
}
|
||||
|
||||
/// A primitive shape formed by the region between two circles, also known as a ring.
|
||||
@ -248,20 +252,6 @@ impl Annulus {
|
||||
self.outer_circle.radius - self.inner_circle.radius
|
||||
}
|
||||
|
||||
/// Get the area of the annulus
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
PI * (self.outer_circle.radius.powi(2) - self.inner_circle.radius.powi(2))
|
||||
}
|
||||
|
||||
/// Get the perimeter or circumference of the annulus,
|
||||
/// which is the sum of the perimeters of the inner and outer circles.
|
||||
#[inline(always)]
|
||||
#[doc(alias = "circumference")]
|
||||
pub fn perimeter(&self) -> f32 {
|
||||
2.0 * PI * (self.outer_circle.radius + self.inner_circle.radius)
|
||||
}
|
||||
|
||||
/// Finds the point on the annulus that is closest to the given `point`:
|
||||
///
|
||||
/// - If the point is outside of the annulus completely, the returned point will be on the outer perimeter.
|
||||
@ -290,6 +280,22 @@ impl Annulus {
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured2d for Annulus {
|
||||
/// Get the area of the annulus
|
||||
#[inline(always)]
|
||||
fn area(&self) -> f32 {
|
||||
PI * (self.outer_circle.radius.powi(2) - self.inner_circle.radius.powi(2))
|
||||
}
|
||||
|
||||
/// Get the perimeter or circumference of the annulus,
|
||||
/// which is the sum of the perimeters of the inner and outer circles.
|
||||
#[inline(always)]
|
||||
#[doc(alias = "circumference")]
|
||||
fn perimeter(&self) -> f32 {
|
||||
2.0 * PI * (self.outer_circle.radius + self.inner_circle.radius)
|
||||
}
|
||||
}
|
||||
|
||||
/// An unbounded plane in 2D space. It forms a separating surface through the origin,
|
||||
/// stretching infinitely far
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@ -471,25 +477,6 @@ impl Triangle2d {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the area of the triangle
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
let [a, b, c] = self.vertices;
|
||||
(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)).abs() / 2.0
|
||||
}
|
||||
|
||||
/// Get the perimeter of the triangle
|
||||
#[inline(always)]
|
||||
pub fn perimeter(&self) -> f32 {
|
||||
let [a, b, c] = self.vertices;
|
||||
|
||||
let ab = a.distance(b);
|
||||
let bc = b.distance(c);
|
||||
let ca = c.distance(a);
|
||||
|
||||
ab + bc + ca
|
||||
}
|
||||
|
||||
/// Get the [`WindingOrder`] of the triangle
|
||||
#[inline(always)]
|
||||
#[doc(alias = "orientation")]
|
||||
@ -548,6 +535,27 @@ impl Triangle2d {
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured2d for Triangle2d {
|
||||
/// Get the area of the triangle
|
||||
#[inline(always)]
|
||||
fn area(&self) -> f32 {
|
||||
let [a, b, c] = self.vertices;
|
||||
(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)).abs() / 2.0
|
||||
}
|
||||
|
||||
/// Get the perimeter of the triangle
|
||||
#[inline(always)]
|
||||
fn perimeter(&self) -> f32 {
|
||||
let [a, b, c] = self.vertices;
|
||||
|
||||
let ab = a.distance(b);
|
||||
let bc = b.distance(c);
|
||||
let ca = c.distance(a);
|
||||
|
||||
ab + bc + ca
|
||||
}
|
||||
}
|
||||
|
||||
/// A rectangle primitive
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
@ -605,18 +613,6 @@ impl Rectangle {
|
||||
2.0 * self.half_size
|
||||
}
|
||||
|
||||
/// Get the area of the rectangle
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
4.0 * self.half_size.x * self.half_size.y
|
||||
}
|
||||
|
||||
/// Get the perimeter of the rectangle
|
||||
#[inline(always)]
|
||||
pub fn perimeter(&self) -> f32 {
|
||||
4.0 * (self.half_size.x + self.half_size.y)
|
||||
}
|
||||
|
||||
/// Finds the point on the rectangle that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the rectangle, the returned point will be on the perimeter of the rectangle.
|
||||
@ -628,6 +624,20 @@ impl Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured2d for Rectangle {
|
||||
/// Get the area of the rectangle
|
||||
#[inline(always)]
|
||||
fn area(&self) -> f32 {
|
||||
4.0 * self.half_size.x * self.half_size.y
|
||||
}
|
||||
|
||||
/// Get the perimeter of the rectangle
|
||||
#[inline(always)]
|
||||
fn perimeter(&self) -> f32 {
|
||||
4.0 * (self.half_size.x + self.half_size.y)
|
||||
}
|
||||
}
|
||||
|
||||
/// A polygon with N vertices.
|
||||
///
|
||||
/// For a version without generics: [`BoxedPolygon`]
|
||||
@ -749,20 +759,6 @@ impl RegularPolygon {
|
||||
2.0 * self.circumradius() * (PI / self.sides as f32).sin()
|
||||
}
|
||||
|
||||
/// Get the area of the regular polygon
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
let angle: f32 = 2.0 * PI / (self.sides as f32);
|
||||
(self.sides as f32) * self.circumradius().powi(2) * angle.sin() / 2.0
|
||||
}
|
||||
|
||||
/// Get the perimeter of the regular polygon.
|
||||
/// This is the sum of its sides
|
||||
#[inline(always)]
|
||||
pub fn perimeter(&self) -> f32 {
|
||||
self.sides as f32 * self.side_length()
|
||||
}
|
||||
|
||||
/// Get the internal angle of the regular polygon in degrees.
|
||||
///
|
||||
/// This is the angle formed by two adjacent sides with points
|
||||
@ -816,6 +812,22 @@ impl RegularPolygon {
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured2d for RegularPolygon {
|
||||
/// Get the area of the regular polygon
|
||||
#[inline(always)]
|
||||
fn area(&self) -> f32 {
|
||||
let angle: f32 = 2.0 * PI / (self.sides as f32);
|
||||
(self.sides as f32) * self.circumradius().powi(2) * angle.sin() / 2.0
|
||||
}
|
||||
|
||||
/// Get the perimeter of the regular polygon.
|
||||
/// This is the sum of its sides
|
||||
#[inline(always)]
|
||||
fn perimeter(&self) -> f32 {
|
||||
self.sides as f32 * self.side_length()
|
||||
}
|
||||
}
|
||||
|
||||
/// A 2D capsule primitive, also known as a stadium or pill shape.
|
||||
///
|
||||
/// A two-dimensional capsule is defined as a neighborhood of points at a distance (radius) from a line
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::f32::consts::{FRAC_PI_3, PI};
|
||||
|
||||
use super::{Circle, Primitive3d};
|
||||
use super::{Circle, Measured2d, Measured3d, Primitive2d, Primitive3d};
|
||||
use crate::{Dir3, InvalidDirectionError, Mat3, Vec2, Vec3};
|
||||
|
||||
/// A sphere primitive
|
||||
@ -32,18 +32,6 @@ impl Sphere {
|
||||
2.0 * self.radius
|
||||
}
|
||||
|
||||
/// Get the surface area of the sphere
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
4.0 * PI * self.radius.powi(2)
|
||||
}
|
||||
|
||||
/// Get the volume of the sphere
|
||||
#[inline(always)]
|
||||
pub fn volume(&self) -> f32 {
|
||||
4.0 * FRAC_PI_3 * self.radius.powi(3)
|
||||
}
|
||||
|
||||
/// Finds the point on the sphere that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the sphere, the returned point will be on the surface of the sphere.
|
||||
@ -64,6 +52,20 @@ impl Sphere {
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured3d for Sphere {
|
||||
/// Get the surface area of the sphere
|
||||
#[inline(always)]
|
||||
fn area(&self) -> f32 {
|
||||
4.0 * PI * self.radius.powi(2)
|
||||
}
|
||||
|
||||
/// Get the volume of the sphere
|
||||
#[inline(always)]
|
||||
fn volume(&self) -> f32 {
|
||||
4.0 * FRAC_PI_3 * self.radius.powi(3)
|
||||
}
|
||||
}
|
||||
|
||||
/// A bounded plane in 3D space. It forms a surface starting from the origin with a defined height and width.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
@ -360,20 +362,6 @@ impl Cuboid {
|
||||
2.0 * self.half_size
|
||||
}
|
||||
|
||||
/// Get the surface area of the cuboid
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
8.0 * (self.half_size.x * self.half_size.y
|
||||
+ self.half_size.y * self.half_size.z
|
||||
+ self.half_size.x * self.half_size.z)
|
||||
}
|
||||
|
||||
/// Get the volume of the cuboid
|
||||
#[inline(always)]
|
||||
pub fn volume(&self) -> f32 {
|
||||
8.0 * self.half_size.x * self.half_size.y * self.half_size.z
|
||||
}
|
||||
|
||||
/// Finds the point on the cuboid that is closest to the given `point`.
|
||||
///
|
||||
/// If the point is outside the cuboid, the returned point will be on the surface of the cuboid.
|
||||
@ -385,6 +373,22 @@ impl Cuboid {
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured3d for Cuboid {
|
||||
/// Get the surface area of the cuboid
|
||||
#[inline(always)]
|
||||
fn area(&self) -> f32 {
|
||||
8.0 * (self.half_size.x * self.half_size.y
|
||||
+ self.half_size.y * self.half_size.z
|
||||
+ self.half_size.x * self.half_size.z)
|
||||
}
|
||||
|
||||
/// Get the volume of the cuboid
|
||||
#[inline(always)]
|
||||
fn volume(&self) -> f32 {
|
||||
8.0 * self.half_size.x * self.half_size.y * self.half_size.z
|
||||
}
|
||||
}
|
||||
|
||||
/// A cylinder primitive
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
@ -437,16 +441,18 @@ impl Cylinder {
|
||||
pub fn base_area(&self) -> f32 {
|
||||
PI * self.radius.powi(2)
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured3d for Cylinder {
|
||||
/// Get the total surface area of the cylinder
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
fn area(&self) -> f32 {
|
||||
2.0 * PI * self.radius * (self.radius + 2.0 * self.half_height)
|
||||
}
|
||||
|
||||
/// Get the volume of the cylinder
|
||||
#[inline(always)]
|
||||
pub fn volume(&self) -> f32 {
|
||||
fn volume(&self) -> f32 {
|
||||
self.base_area() * 2.0 * self.half_height
|
||||
}
|
||||
}
|
||||
@ -492,17 +498,19 @@ impl Capsule3d {
|
||||
half_height: self.half_length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured3d for Capsule3d {
|
||||
/// Get the surface area of the capsule
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
fn area(&self) -> f32 {
|
||||
// Modified version of 2pi * r * (2r + h)
|
||||
4.0 * PI * self.radius * (self.radius + self.half_length)
|
||||
}
|
||||
|
||||
/// Get the volume of the capsule
|
||||
#[inline(always)]
|
||||
pub fn volume(&self) -> f32 {
|
||||
fn volume(&self) -> f32 {
|
||||
// Modified version of pi * r^2 * (4/3 * r + a)
|
||||
let diameter = self.radius * 2.0;
|
||||
PI * self.radius * diameter * (diameter / 3.0 + self.half_length)
|
||||
@ -550,16 +558,18 @@ impl Cone {
|
||||
pub fn base_area(&self) -> f32 {
|
||||
PI * self.radius.powi(2)
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured3d for Cone {
|
||||
/// Get the total surface area of the cone
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
fn area(&self) -> f32 {
|
||||
self.base_area() + self.lateral_area()
|
||||
}
|
||||
|
||||
/// Get the volume of the cone
|
||||
#[inline(always)]
|
||||
pub fn volume(&self) -> f32 {
|
||||
fn volume(&self) -> f32 {
|
||||
(self.base_area() * self.height) / 3.0
|
||||
}
|
||||
}
|
||||
@ -681,18 +691,20 @@ impl Torus {
|
||||
std::cmp::Ordering::Less => TorusKind::Spindle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured3d for Torus {
|
||||
/// Get the surface area of the torus. Note that this only produces
|
||||
/// the expected result when the torus has a ring and isn't self-intersecting
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
fn area(&self) -> f32 {
|
||||
4.0 * PI.powi(2) * self.major_radius * self.minor_radius
|
||||
}
|
||||
|
||||
/// Get the volume of the torus. Note that this only produces
|
||||
/// the expected result when the torus has a ring and isn't self-intersecting
|
||||
#[inline(always)]
|
||||
pub fn volume(&self) -> f32 {
|
||||
fn volume(&self) -> f32 {
|
||||
2.0 * PI.powi(2) * self.major_radius * self.minor_radius.powi(2)
|
||||
}
|
||||
}
|
||||
@ -729,22 +741,6 @@ impl Triangle3d {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the area of the triangle.
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
let [a, b, c] = self.vertices;
|
||||
let ab = b - a;
|
||||
let ac = c - a;
|
||||
ab.cross(ac).length() / 2.0
|
||||
}
|
||||
|
||||
/// Get the perimeter of the triangle.
|
||||
#[inline(always)]
|
||||
pub fn perimeter(&self) -> f32 {
|
||||
let [a, b, c] = self.vertices;
|
||||
a.distance(b) + b.distance(c) + c.distance(a)
|
||||
}
|
||||
|
||||
/// Get the normal of the triangle in the direction of the right-hand rule, assuming
|
||||
/// the vertices are ordered in a counter-clockwise direction.
|
||||
///
|
||||
@ -835,6 +831,24 @@ impl Triangle3d {
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured2d for Triangle3d {
|
||||
/// Get the area of the triangle.
|
||||
#[inline(always)]
|
||||
fn area(&self) -> f32 {
|
||||
let [a, b, c] = self.vertices;
|
||||
let ab = b - a;
|
||||
let ac = c - a;
|
||||
ab.cross(ac).length() / 2.0
|
||||
}
|
||||
|
||||
/// Get the perimeter of the triangle.
|
||||
#[inline(always)]
|
||||
fn perimeter(&self) -> f32 {
|
||||
let [a, b, c] = self.vertices;
|
||||
a.distance(b) + b.distance(c) + c.distance(a)
|
||||
}
|
||||
}
|
||||
|
||||
/// A tetrahedron primitive.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
@ -868,28 +882,6 @@ impl Tetrahedron {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the surface area of the tetrahedron.
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
let [a, b, c, d] = self.vertices;
|
||||
let ab = b - a;
|
||||
let ac = c - a;
|
||||
let ad = d - a;
|
||||
let bc = c - b;
|
||||
let bd = d - b;
|
||||
(ab.cross(ac).length()
|
||||
+ ab.cross(ad).length()
|
||||
+ ac.cross(ad).length()
|
||||
+ bc.cross(bd).length())
|
||||
/ 2.0
|
||||
}
|
||||
|
||||
/// Get the volume of the tetrahedron.
|
||||
#[inline(always)]
|
||||
pub fn volume(&self) -> f32 {
|
||||
self.signed_volume().abs()
|
||||
}
|
||||
|
||||
/// Get the signed volume of the tetrahedron.
|
||||
///
|
||||
/// If it's negative, the normal vector of the face defined by
|
||||
@ -915,6 +907,70 @@ impl Tetrahedron {
|
||||
}
|
||||
}
|
||||
|
||||
impl Measured3d for Tetrahedron {
|
||||
/// Get the surface area of the tetrahedron.
|
||||
#[inline(always)]
|
||||
fn area(&self) -> f32 {
|
||||
let [a, b, c, d] = self.vertices;
|
||||
let ab = b - a;
|
||||
let ac = c - a;
|
||||
let ad = d - a;
|
||||
let bc = c - b;
|
||||
let bd = d - b;
|
||||
(ab.cross(ac).length()
|
||||
+ ab.cross(ad).length()
|
||||
+ ac.cross(ad).length()
|
||||
+ bc.cross(bd).length())
|
||||
/ 2.0
|
||||
}
|
||||
|
||||
/// Get the volume of the tetrahedron.
|
||||
#[inline(always)]
|
||||
fn volume(&self) -> f32 {
|
||||
self.signed_volume().abs()
|
||||
}
|
||||
}
|
||||
|
||||
/// A 3D shape representing an extruded 2D `base_shape`.
|
||||
///
|
||||
/// Extruding a shape effectively "thickens" a 2D shapes,
|
||||
/// creating a shape with the same cross-section over the entire depth.
|
||||
///
|
||||
/// The resulting volumes are prisms.
|
||||
/// For example, a triangle becomes a triangular prism, while a circle becomes a cylinder.
|
||||
#[doc(alias = "Prism")]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Extrusion<T: Primitive2d> {
|
||||
/// The base shape of the extrusion
|
||||
pub base_shape: T,
|
||||
/// Half of the depth of the extrusion
|
||||
pub half_depth: f32,
|
||||
}
|
||||
impl<T: Primitive2d> Primitive3d for Extrusion<T> {}
|
||||
|
||||
impl<T: Primitive2d> Extrusion<T> {
|
||||
/// Create a new `Extrusion<T>` from a given `base_shape` and `depth`
|
||||
pub fn new(base_shape: T, depth: f32) -> Self {
|
||||
Self {
|
||||
base_shape,
|
||||
half_depth: depth / 2.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Primitive2d + Measured2d> Measured3d for Extrusion<T> {
|
||||
/// Get the surface area of the extrusion
|
||||
fn area(&self) -> f32 {
|
||||
2. * (self.base_shape.area() + self.half_depth * self.base_shape.perimeter())
|
||||
}
|
||||
|
||||
/// Get the volume of the extrusion
|
||||
fn volume(&self) -> f32 {
|
||||
2. * self.base_shape.area() * self.half_depth
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Reference values were computed by hand and/or with external tools
|
||||
@ -1146,4 +1202,22 @@ mod tests {
|
||||
let degenerate = Triangle3d::new(Vec3::NEG_ONE, Vec3::ZERO, Vec3::ONE);
|
||||
assert!(degenerate.is_degenerate(), "did not find degenerate");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extrusion_math() {
|
||||
let circle = Circle::new(0.75);
|
||||
let cylinder = Extrusion::new(circle, 2.5);
|
||||
assert_eq!(cylinder.area(), 15.315264, "incorrect surface area");
|
||||
assert_eq!(cylinder.volume(), 4.417865, "incorrect volume");
|
||||
|
||||
let annulus = crate::primitives::Annulus::new(0.25, 1.375);
|
||||
let tube = Extrusion::new(annulus, 0.333);
|
||||
assert_eq!(tube.area(), 14.886437, "incorrect surface area");
|
||||
assert_eq!(tube.volume(), 1.9124937, "incorrect volume");
|
||||
|
||||
let polygon = crate::primitives::RegularPolygon::new(3.8, 7);
|
||||
let regular_prism = Extrusion::new(polygon, 1.25);
|
||||
assert_eq!(regular_prism.area(), 107.8808, "incorrect surface area");
|
||||
assert_eq!(regular_prism.volume(), 49.392204, "incorrect volume");
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,3 +29,21 @@ pub enum WindingOrder {
|
||||
#[doc(alias("Degenerate", "Collinear"))]
|
||||
Invalid,
|
||||
}
|
||||
|
||||
/// A trait for getting measurements of 2D shapes
|
||||
pub trait Measured2d {
|
||||
/// Get the perimeter of the shape
|
||||
fn perimeter(&self) -> f32;
|
||||
|
||||
/// Get the area of the shape
|
||||
fn area(&self) -> f32;
|
||||
}
|
||||
|
||||
/// A trait for getting measurements of 3D shapes
|
||||
pub trait Measured3d {
|
||||
/// Get the surface area of the shape
|
||||
fn area(&self) -> f32;
|
||||
|
||||
/// Get the volume of the shape
|
||||
fn volume(&self) -> f32;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user