Meshable extrusions (#13478)
# Objective - Implement `Meshable` for `Extrusion<T>` ## Solution - `Meshable` requires `Meshable::Output: MeshBuilder` now. This means that all `some_primitive.mesh()` calls now return a `MeshBuilder`. These were added for primitives that did not have one prior to this. - A new trait `Extrudable: MeshBuilder` has been added. This trait allows you to specify the indices of the perimeter of the mesh created by this `MeshBuilder` and whether they are to be shaded smooth or flat. - `Extrusion<P: Primitive2d + Meshable>` is now `Meshable` aswell. The associated `MeshBuilder` is `ExtrusionMeshBuilder` which is generic over `P` and uses the `MeshBuilder` of its baseshape internally. - `ExtrusionMeshBuilder` exposes the configuration functions of its base-shapes builder. - Updated the `3d_shapes` example to include `Extrusion`s ## Migration Guide - Depending on the context, you may need to explicitly call `.mesh().build()` on primitives where you have previously called `.mesh()` - The `Output` type of custom `Meshable` implementations must now derive `MeshBuilder`. ## Additional information - The extrusions UVs are done so that - the front face (`+Z`) is in the area between `(0, 0)` and `(0.5, 0.5)`, - the back face (`-Z`) is in the area between `(0.5, 0)` and `(1, 0.5)` - the mantle is in the area between `(0, 0.5)` and `(1, 1)`. Each `PerimeterSegment` you specified in the `Extrudable` implementation will be allocated an equal portion of this area. - The UVs of the base shape are scaled to be in the front/back area so whatever method of filling the full UV-space the base shape used is how these areas will be filled. Here is an example of what that looks like on a capsule: https://github.com/bevyengine/bevy/assets/62256001/425ad288-fbbc-4634-9d3f-5e846cdce85f This is the texture used:  The `3d_shapes` example now looks like this:  --------- Co-authored-by: Lynn Büttgenbach <62256001+solis-lumine-vorago@users.noreply.github.com> Co-authored-by: Matty <weatherleymatthew@gmail.com> Co-authored-by: Matty <2975848+mweatherley@users.noreply.github.com>
This commit is contained in:
parent
5e1c841f4e
commit
fd82ef8956
@ -1,12 +1,11 @@
|
||||
use std::f32::consts::FRAC_PI_2;
|
||||
|
||||
use crate::{
|
||||
mesh::primitives::dim3::triangle3d,
|
||||
mesh::{Indices, Mesh},
|
||||
mesh::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment},
|
||||
render_asset::RenderAssetUsages,
|
||||
};
|
||||
|
||||
use super::{MeshBuilder, Meshable};
|
||||
use super::{Extrudable, MeshBuilder, Meshable};
|
||||
use bevy_math::{
|
||||
primitives::{
|
||||
Annulus, Capsule2d, Circle, CircularSector, CircularSegment, Ellipse, Rectangle,
|
||||
@ -57,7 +56,19 @@ impl CircleMeshBuilder {
|
||||
|
||||
impl MeshBuilder for CircleMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
RegularPolygon::new(self.circle.radius, self.resolution).mesh()
|
||||
RegularPolygon::new(self.circle.radius, self.resolution)
|
||||
.mesh()
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Extrudable for CircleMeshBuilder {
|
||||
fn perimeter(&self) -> Vec<PerimeterSegment> {
|
||||
vec![PerimeterSegment::Smooth {
|
||||
first_normal: Vec2::Y,
|
||||
last_normal: Vec2::Y,
|
||||
indices: (0..self.resolution as u32).chain([0]).collect(),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,9 +165,10 @@ impl CircularSectorMeshBuilder {
|
||||
self.uv_mode = uv_mode;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a [`Mesh`] based on the configuration in `self`.
|
||||
pub fn build(&self) -> Mesh {
|
||||
impl MeshBuilder for CircularSectorMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
let mut indices = Vec::with_capacity((self.resolution - 1) * 3);
|
||||
let mut positions = Vec::with_capacity(self.resolution + 1);
|
||||
let normals = vec![[0.0, 0.0, 1.0]; self.resolution + 1];
|
||||
@ -221,12 +233,6 @@ impl From<CircularSector> for Mesh {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CircularSectorMeshBuilder> for Mesh {
|
||||
fn from(sector: CircularSectorMeshBuilder) -> Self {
|
||||
sector.build()
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder used for creating a [`Mesh`] with a [`CircularSegment`] shape.
|
||||
///
|
||||
/// The resulting mesh will have a UV-map such that the center of the circle is
|
||||
@ -277,9 +283,10 @@ impl CircularSegmentMeshBuilder {
|
||||
self.uv_mode = uv_mode;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a [`Mesh`] based on the configuration in `self`.
|
||||
pub fn build(&self) -> Mesh {
|
||||
impl MeshBuilder for CircularSegmentMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
let mut indices = Vec::with_capacity((self.resolution - 1) * 3);
|
||||
let mut positions = Vec::with_capacity(self.resolution + 1);
|
||||
let normals = vec![[0.0, 0.0, 1.0]; self.resolution + 1];
|
||||
@ -353,27 +360,43 @@ impl From<CircularSegment> for Mesh {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CircularSegmentMeshBuilder> for Mesh {
|
||||
fn from(sector: CircularSegmentMeshBuilder) -> Self {
|
||||
sector.build()
|
||||
/// A builder used for creating a [`Mesh`] with a [`RegularPolygon`] shape.
|
||||
pub struct RegularPolygonMeshBuilder {
|
||||
circumradius: f32,
|
||||
sides: usize,
|
||||
}
|
||||
impl Meshable for RegularPolygon {
|
||||
type Output = RegularPolygonMeshBuilder;
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
Self::Output {
|
||||
circumradius: self.circumcircle.radius,
|
||||
sides: self.sides,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Meshable for RegularPolygon {
|
||||
type Output = Mesh;
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
impl MeshBuilder for RegularPolygonMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
// The ellipse mesh is just a regular polygon with two radii
|
||||
Ellipse::new(self.circumcircle.radius, self.circumcircle.radius)
|
||||
Ellipse::new(self.circumradius, self.circumradius)
|
||||
.mesh()
|
||||
.resolution(self.sides)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Extrudable for RegularPolygonMeshBuilder {
|
||||
fn perimeter(&self) -> Vec<PerimeterSegment> {
|
||||
vec![PerimeterSegment::Flat {
|
||||
indices: (0..self.sides as u32).chain([0]).collect(),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RegularPolygon> for Mesh {
|
||||
fn from(polygon: RegularPolygon) -> Self {
|
||||
polygon.mesh()
|
||||
polygon.mesh().build()
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,6 +476,16 @@ impl MeshBuilder for EllipseMeshBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
impl Extrudable for EllipseMeshBuilder {
|
||||
fn perimeter(&self) -> Vec<PerimeterSegment> {
|
||||
vec![PerimeterSegment::Smooth {
|
||||
first_normal: Vec2::Y,
|
||||
last_normal: Vec2::Y,
|
||||
indices: (0..self.resolution as u32).chain([0]).collect(),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
impl Meshable for Ellipse {
|
||||
type Output = EllipseMeshBuilder;
|
||||
|
||||
@ -566,6 +599,24 @@ impl MeshBuilder for AnnulusMeshBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
impl Extrudable for AnnulusMeshBuilder {
|
||||
fn perimeter(&self) -> Vec<PerimeterSegment> {
|
||||
let vert_count = 2 * self.resolution as u32;
|
||||
vec![
|
||||
PerimeterSegment::Smooth {
|
||||
first_normal: Vec2::NEG_Y,
|
||||
last_normal: Vec2::NEG_Y,
|
||||
indices: (0..vert_count).step_by(2).chain([0]).rev().collect(), // Inner hole
|
||||
},
|
||||
PerimeterSegment::Smooth {
|
||||
first_normal: Vec2::Y,
|
||||
last_normal: Vec2::Y,
|
||||
indices: (1..vert_count).step_by(2).chain([1]).collect(), // Outer perimeter
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Meshable for Annulus {
|
||||
type Output = AnnulusMeshBuilder;
|
||||
|
||||
@ -583,10 +634,12 @@ impl From<Annulus> for Mesh {
|
||||
}
|
||||
}
|
||||
|
||||
impl Meshable for Rhombus {
|
||||
type Output = Mesh;
|
||||
pub struct RhombusMeshBuilder {
|
||||
half_diagonals: Vec2,
|
||||
}
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
impl MeshBuilder for RhombusMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
let [hhd, vhd] = [self.half_diagonals.x, self.half_diagonals.y];
|
||||
let positions = vec![
|
||||
[hhd, 0.0, 0.0],
|
||||
@ -609,17 +662,36 @@ impl Meshable for Rhombus {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rhombus> for Mesh {
|
||||
fn from(rhombus: Rhombus) -> Self {
|
||||
rhombus.mesh()
|
||||
impl Meshable for Rhombus {
|
||||
type Output = RhombusMeshBuilder;
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
Self::Output {
|
||||
half_diagonals: self.half_diagonals,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rhombus> for Mesh {
|
||||
fn from(rhombus: Rhombus) -> Self {
|
||||
rhombus.mesh().build()
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder used for creating a [`Mesh`] with a [`Triangle2d`] shape.
|
||||
pub struct Triangle2dMeshBuilder {
|
||||
triangle: Triangle2d,
|
||||
}
|
||||
impl Meshable for Triangle2d {
|
||||
type Output = Mesh;
|
||||
type Output = Triangle2dMeshBuilder;
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
let vertices_3d = self.vertices.map(|v| v.extend(0.));
|
||||
Self::Output { triangle: *self }
|
||||
}
|
||||
}
|
||||
impl MeshBuilder for Triangle2dMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
let vertices_3d = self.triangle.vertices.map(|v| v.extend(0.));
|
||||
|
||||
let positions: Vec<_> = vertices_3d.into();
|
||||
let normals = vec![[0.0, 0.0, 1.0]; 3];
|
||||
@ -631,7 +703,7 @@ impl Meshable for Triangle2d {
|
||||
))
|
||||
.into();
|
||||
|
||||
let is_ccw = self.winding_order() == WindingOrder::CounterClockwise;
|
||||
let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
|
||||
let indices = if is_ccw {
|
||||
Indices::U32(vec![0, 1, 2])
|
||||
} else {
|
||||
@ -649,16 +721,34 @@ impl Meshable for Triangle2d {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Triangle2d> for Mesh {
|
||||
fn from(triangle: Triangle2d) -> Self {
|
||||
triangle.mesh()
|
||||
impl Extrudable for Triangle2dMeshBuilder {
|
||||
fn perimeter(&self) -> Vec<PerimeterSegment> {
|
||||
let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
|
||||
if is_ccw {
|
||||
vec![PerimeterSegment::Flat {
|
||||
indices: vec![0, 1, 2, 0],
|
||||
}]
|
||||
} else {
|
||||
vec![PerimeterSegment::Flat {
|
||||
indices: vec![2, 1, 0, 2],
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Meshable for Rectangle {
|
||||
type Output = Mesh;
|
||||
impl From<Triangle2d> for Mesh {
|
||||
fn from(triangle: Triangle2d) -> Self {
|
||||
triangle.mesh().build()
|
||||
}
|
||||
}
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
/// A builder used for creating a [`Mesh`] with a [`Rectangle`] shape.
|
||||
pub struct RectangleMeshBuilder {
|
||||
half_size: Vec2,
|
||||
}
|
||||
|
||||
impl MeshBuilder for RectangleMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
let [hw, hh] = [self.half_size.x, self.half_size.y];
|
||||
let positions = vec![
|
||||
[hw, hh, 0.0],
|
||||
@ -681,9 +771,27 @@ impl Meshable for Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
impl Extrudable for RectangleMeshBuilder {
|
||||
fn perimeter(&self) -> Vec<PerimeterSegment> {
|
||||
vec![PerimeterSegment::Flat {
|
||||
indices: vec![0, 1, 2, 3, 0],
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
impl Meshable for Rectangle {
|
||||
type Output = RectangleMeshBuilder;
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
RectangleMeshBuilder {
|
||||
half_size: self.half_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rectangle> for Mesh {
|
||||
fn from(rectangle: Rectangle) -> Self {
|
||||
rectangle.mesh()
|
||||
rectangle.mesh().build()
|
||||
}
|
||||
}
|
||||
|
||||
@ -804,6 +912,32 @@ impl MeshBuilder for Capsule2dMeshBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
impl Extrudable for Capsule2dMeshBuilder {
|
||||
fn perimeter(&self) -> Vec<PerimeterSegment> {
|
||||
let resolution = self.resolution as u32;
|
||||
let top_semi_indices = (0..resolution).collect();
|
||||
let bottom_semi_indices = (resolution..(2 * resolution)).collect();
|
||||
vec![
|
||||
PerimeterSegment::Smooth {
|
||||
first_normal: Vec2::X,
|
||||
last_normal: Vec2::NEG_X,
|
||||
indices: top_semi_indices,
|
||||
}, // Top semi-circle
|
||||
PerimeterSegment::Flat {
|
||||
indices: vec![resolution - 1, resolution],
|
||||
}, // Left edge
|
||||
PerimeterSegment::Smooth {
|
||||
first_normal: Vec2::NEG_X,
|
||||
last_normal: Vec2::X,
|
||||
indices: bottom_semi_indices,
|
||||
}, // Bottom semi-circle
|
||||
PerimeterSegment::Flat {
|
||||
indices: vec![2 * resolution - 1, 0],
|
||||
}, // Right edge
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Meshable for Capsule2d {
|
||||
type Output = Capsule2dMeshBuilder;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
mesh::{Indices, Mesh, Meshable},
|
||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
||||
render_asset::RenderAssetUsages,
|
||||
};
|
||||
use bevy_math::{primitives::ConicalFrustum, Vec3};
|
||||
@ -59,9 +59,10 @@ impl ConicalFrustumMeshBuilder {
|
||||
self.segments = segments;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a [`Mesh`] based on the configuration in `self`.
|
||||
pub fn build(&self) -> Mesh {
|
||||
impl MeshBuilder for ConicalFrustumMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
debug_assert!(self.resolution > 2);
|
||||
debug_assert!(self.segments > 0);
|
||||
|
||||
@ -182,9 +183,3 @@ impl From<ConicalFrustum> for Mesh {
|
||||
frustum.mesh().build()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConicalFrustumMeshBuilder> for Mesh {
|
||||
fn from(frustum: ConicalFrustumMeshBuilder) -> Self {
|
||||
frustum.build()
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,18 @@
|
||||
use bevy_math::primitives::Cuboid;
|
||||
use bevy_math::{primitives::Cuboid, Vec3};
|
||||
use wgpu::PrimitiveTopology;
|
||||
|
||||
use crate::{
|
||||
mesh::{Indices, Mesh, Meshable},
|
||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
||||
render_asset::RenderAssetUsages,
|
||||
};
|
||||
|
||||
impl Meshable for Cuboid {
|
||||
type Output = Mesh;
|
||||
/// A builder used for creating a [`Mesh`] with a [`Cuboid`] shape.
|
||||
pub struct CuboidMeshBuilder {
|
||||
half_size: Vec3,
|
||||
}
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
impl MeshBuilder for CuboidMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
let min = -self.half_size;
|
||||
let max = self.half_size;
|
||||
|
||||
@ -71,8 +74,18 @@ impl Meshable for Cuboid {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cuboid> for Mesh {
|
||||
fn from(cuboid: Cuboid) -> Self {
|
||||
cuboid.mesh()
|
||||
impl Meshable for Cuboid {
|
||||
type Output = CuboidMeshBuilder;
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
CuboidMeshBuilder {
|
||||
half_size: self.half_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cuboid> for Mesh {
|
||||
fn from(cuboid: Cuboid) -> Self {
|
||||
cuboid.mesh().build()
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,23 @@
|
||||
use super::triangle3d;
|
||||
use crate::{
|
||||
mesh::{Indices, Mesh, Meshable},
|
||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
||||
render_asset::RenderAssetUsages,
|
||||
};
|
||||
use bevy_math::primitives::{Tetrahedron, Triangle3d};
|
||||
use wgpu::PrimitiveTopology;
|
||||
|
||||
impl Meshable for Tetrahedron {
|
||||
type Output = Mesh;
|
||||
/// A builder used for creating a [`Mesh`] with a [`Tetrahedron`] shape.
|
||||
pub struct TetrahedronMeshBuilder {
|
||||
tetrahedron: Tetrahedron,
|
||||
}
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
let mut faces: Vec<_> = self.faces().into();
|
||||
impl MeshBuilder for TetrahedronMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
let mut faces: Vec<_> = self.tetrahedron.faces().into();
|
||||
|
||||
// If the tetrahedron has negative orientation, reverse all the triangles so that
|
||||
// they still face outward.
|
||||
if self.signed_volume().is_sign_negative() {
|
||||
if self.tetrahedron.signed_volume().is_sign_negative() {
|
||||
faces.iter_mut().for_each(Triangle3d::reverse);
|
||||
}
|
||||
|
||||
@ -48,8 +51,16 @@ impl Meshable for Tetrahedron {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tetrahedron> for Mesh {
|
||||
fn from(tetrahedron: Tetrahedron) -> Self {
|
||||
tetrahedron.mesh()
|
||||
impl Meshable for Tetrahedron {
|
||||
type Output = TetrahedronMeshBuilder;
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
TetrahedronMeshBuilder { tetrahedron: *self }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tetrahedron> for Mesh {
|
||||
fn from(tetrahedron: Tetrahedron) -> Self {
|
||||
tetrahedron.mesh().build()
|
||||
}
|
||||
}
|
||||
|
@ -2,19 +2,22 @@ use bevy_math::{primitives::Triangle3d, Vec3};
|
||||
use wgpu::PrimitiveTopology;
|
||||
|
||||
use crate::{
|
||||
mesh::{Indices, Mesh, Meshable},
|
||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
||||
render_asset::RenderAssetUsages,
|
||||
};
|
||||
|
||||
impl Meshable for Triangle3d {
|
||||
type Output = Mesh;
|
||||
/// A builder used for creating a [`Mesh`] with a [`Triangle3d`] shape.
|
||||
pub struct Triangle3dMeshBuilder {
|
||||
triangle: Triangle3d,
|
||||
}
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
let positions: Vec<_> = self.vertices.into();
|
||||
let uvs: Vec<_> = uv_coords(self).into();
|
||||
impl MeshBuilder for Triangle3dMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
let positions: Vec<_> = self.triangle.vertices.into();
|
||||
let uvs: Vec<_> = uv_coords(&self.triangle).into();
|
||||
|
||||
// Every vertex has the normal of the face of the triangle (or zero if the triangle is degenerate).
|
||||
let normal: Vec3 = normal_vec(self);
|
||||
let normal: Vec3 = normal_vec(&self.triangle);
|
||||
let normals = vec![normal; 3];
|
||||
|
||||
let indices = Indices::U32(vec![0, 1, 2]);
|
||||
@ -30,6 +33,14 @@ impl Meshable for Triangle3d {
|
||||
}
|
||||
}
|
||||
|
||||
impl Meshable for Triangle3d {
|
||||
type Output = Triangle3dMeshBuilder;
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
Triangle3dMeshBuilder { triangle: *self }
|
||||
}
|
||||
}
|
||||
|
||||
/// The normal of a [`Triangle3d`] with zeroing so that a [`Vec3`] is always obtained for meshing.
|
||||
#[inline]
|
||||
pub(crate) fn normal_vec(triangle: &Triangle3d) -> Vec3 {
|
||||
@ -86,7 +97,7 @@ pub(crate) fn uv_coords(triangle: &Triangle3d) -> [[f32; 2]; 3] {
|
||||
|
||||
impl From<Triangle3d> for Mesh {
|
||||
fn from(triangle: Triangle3d) -> Self {
|
||||
triangle.mesh()
|
||||
triangle.mesh().build()
|
||||
}
|
||||
}
|
||||
|
||||
|
391
crates/bevy_render/src/mesh/primitives/extrusion.rs
Normal file
391
crates/bevy_render/src/mesh/primitives/extrusion.rs
Normal file
@ -0,0 +1,391 @@
|
||||
use bevy_math::{
|
||||
primitives::{Annulus, Capsule2d, Circle, Ellipse, Extrusion, Primitive2d},
|
||||
Vec2, Vec3,
|
||||
};
|
||||
|
||||
use crate::mesh::{Indices, Mesh, VertexAttributeValues};
|
||||
|
||||
use super::{MeshBuilder, Meshable};
|
||||
|
||||
/// A type representing a segment of the perimeter of an extrudable mesh.
|
||||
pub enum PerimeterSegment {
|
||||
/// This segment of the perimeter will be shaded smooth.
|
||||
///
|
||||
/// This has the effect of rendering the segment's faces with softened edges, so it is appropriate for curved shapes.
|
||||
///
|
||||
/// The normals for the vertices that are part of this segment will be calculated based on the positions of their neighbours.
|
||||
/// Each normal is interpolated between the normals of the two line segments connecting it with its neighbours.
|
||||
/// Closer vertices have a stronger effect on the normal than more distant ones.
|
||||
///
|
||||
/// Since the vertices corresponding to the first and last indices do not have two neighbouring vertices, their normals must be provided manually.
|
||||
Smooth {
|
||||
/// The normal of the first vertex.
|
||||
first_normal: Vec2,
|
||||
/// The normal of the last vertex.
|
||||
last_normal: Vec2,
|
||||
/// A list of indices representing this segment of the perimeter of the mesh.
|
||||
///
|
||||
/// The indices must be ordered such that the *outside* of the mesh is to the right
|
||||
/// when walking along the vertices of the mesh in the order provided by the indices.
|
||||
///
|
||||
/// For geometry to be rendered, you must provide at least two indices.
|
||||
indices: Vec<u32>,
|
||||
},
|
||||
/// This segment of the perimeter will be shaded flat.
|
||||
///
|
||||
/// This has the effect of rendering the segment's faces with hard edges.
|
||||
Flat {
|
||||
/// A list of indices representing this segment of the perimeter of the mesh.
|
||||
///
|
||||
/// The indices must be ordered such that the *outside* of the mesh is to the right
|
||||
/// when walking along the vertices of the mesh in the order provided by indices.
|
||||
///
|
||||
/// For geometry to be rendered, you must provide at least two indices.
|
||||
indices: Vec<u32>,
|
||||
},
|
||||
}
|
||||
|
||||
impl PerimeterSegment {
|
||||
/// Returns the amount of vertices each 'layer' of the extrusion should include for this perimeter segment.
|
||||
///
|
||||
/// A layer is the set of vertices sharing a common Z value or depth.
|
||||
fn vertices_per_layer(&self) -> usize {
|
||||
match self {
|
||||
PerimeterSegment::Smooth { indices, .. } => indices.len(),
|
||||
PerimeterSegment::Flat { indices } => 2 * (indices.len() - 1),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the amount of indices each 'segment' of the extrusion should include for this perimeter segment.
|
||||
///
|
||||
/// A segment is the set of faces on the mantel of the extrusion between two layers of vertices.
|
||||
fn indices_per_segment(&self) -> usize {
|
||||
match self {
|
||||
PerimeterSegment::Smooth { indices, .. } | PerimeterSegment::Flat { indices } => {
|
||||
6 * (indices.len() - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for required for implementing `Meshable` for `Extrusion<T>`.
|
||||
///
|
||||
/// ## Warning
|
||||
///
|
||||
/// By implementing this trait you guarantee that the `primitive_topology` of the mesh returned by
|
||||
/// this builder is [`PrimitiveTopology::TriangleList`](wgpu::PrimitiveTopology::TriangleList)
|
||||
/// and that your mesh has a [`Mesh::ATTRIBUTE_POSITION`] attribute.
|
||||
pub trait Extrudable: MeshBuilder {
|
||||
/// A list of the indices each representing a part of the perimeter of the mesh.
|
||||
fn perimeter(&self) -> Vec<PerimeterSegment>;
|
||||
}
|
||||
|
||||
impl<P> Meshable for Extrusion<P>
|
||||
where
|
||||
P: Primitive2d + Meshable,
|
||||
P::Output: Extrudable,
|
||||
{
|
||||
type Output = ExtrusionBuilder<P>;
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
ExtrusionBuilder {
|
||||
base_builder: self.base_shape.mesh(),
|
||||
half_depth: self.half_depth,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder used for creating a [`Mesh`] with an [`Extrusion`] shape.
|
||||
pub struct ExtrusionBuilder<P>
|
||||
where
|
||||
P: Primitive2d + Meshable,
|
||||
P::Output: Extrudable,
|
||||
{
|
||||
base_builder: P::Output,
|
||||
half_depth: f32,
|
||||
}
|
||||
|
||||
impl<P> ExtrusionBuilder<P>
|
||||
where
|
||||
P: Primitive2d + Meshable,
|
||||
P::Output: Extrudable,
|
||||
{
|
||||
/// Create a new `ExtrusionBuilder<P>` from a given `base_shape` and the full `depth` of the extrusion.
|
||||
pub fn new(base_shape: &P, depth: f32) -> Self {
|
||||
Self {
|
||||
base_builder: base_shape.mesh(),
|
||||
half_depth: depth / 2.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrusionBuilder<Circle> {
|
||||
/// Sets the number of vertices used for the circle mesh at each end of the extrusion.
|
||||
pub fn resolution(mut self, resolution: usize) -> Self {
|
||||
self.base_builder.resolution = resolution;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrusionBuilder<Ellipse> {
|
||||
/// Sets the number of vertices used for the ellipse mesh at each end of the extrusion.
|
||||
pub fn resolution(mut self, resolution: usize) -> Self {
|
||||
self.base_builder.resolution = resolution;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrusionBuilder<Annulus> {
|
||||
/// Sets the number of vertices used in constructing the concentric circles of the annulus mesh at each end of the extrusion.
|
||||
pub fn resolution(mut self, resolution: usize) -> Self {
|
||||
self.base_builder.resolution = resolution;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrusionBuilder<Capsule2d> {
|
||||
/// Sets the number of vertices used for each hemicircle at the ends of the extrusion.
|
||||
pub fn resolution(mut self, resolution: usize) -> Self {
|
||||
self.base_builder.resolution = resolution;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> MeshBuilder for ExtrusionBuilder<P>
|
||||
where
|
||||
P: Primitive2d + Meshable,
|
||||
P::Output: Extrudable,
|
||||
{
|
||||
fn build(&self) -> Mesh {
|
||||
// Create and move the base mesh to the front
|
||||
let mut front_face =
|
||||
self.base_builder
|
||||
.build()
|
||||
.translated_by(Vec3::new(0., 0., self.half_depth));
|
||||
|
||||
// Move the uvs of the front face to be between (0., 0.) and (0.5, 0.5)
|
||||
if let Some(VertexAttributeValues::Float32x2(uvs)) =
|
||||
front_face.attribute_mut(Mesh::ATTRIBUTE_UV_0)
|
||||
{
|
||||
for uv in uvs {
|
||||
*uv = uv.map(|coord| coord * 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
let back_face = {
|
||||
let topology = front_face.primitive_topology();
|
||||
// Flip the normals, etc. and move mesh to the back
|
||||
let mut back_face = front_face.clone().scaled_by(Vec3::new(1., 1., -1.));
|
||||
|
||||
// Move the uvs of the back face to be between (0.5, 0.) and (1., 0.5)
|
||||
if let Some(VertexAttributeValues::Float32x2(uvs)) =
|
||||
back_face.attribute_mut(Mesh::ATTRIBUTE_UV_0)
|
||||
{
|
||||
for uv in uvs {
|
||||
*uv = [uv[0] + 0.5, uv[1]];
|
||||
}
|
||||
}
|
||||
|
||||
// By swapping the first and second indices of each triangle we invert the winding order thus making the mesh visible from the other side
|
||||
if let Some(indices) = back_face.indices_mut() {
|
||||
match topology {
|
||||
wgpu::PrimitiveTopology::TriangleList => match indices {
|
||||
Indices::U16(indices) => {
|
||||
indices.chunks_exact_mut(3).for_each(|arr| arr.swap(1, 0));
|
||||
}
|
||||
Indices::U32(indices) => {
|
||||
indices.chunks_exact_mut(3).for_each(|arr| arr.swap(1, 0));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
panic!("Meshes used with Extrusions must have a primitive topology of `PrimitiveTopology::TriangleList`");
|
||||
}
|
||||
};
|
||||
}
|
||||
back_face
|
||||
};
|
||||
|
||||
// An extrusion of depth 0 does not need a mantel
|
||||
if self.half_depth == 0. {
|
||||
front_face.merge(back_face);
|
||||
return front_face;
|
||||
}
|
||||
|
||||
let mantel = {
|
||||
let Some(VertexAttributeValues::Float32x3(cap_verts)) =
|
||||
front_face.attribute(Mesh::ATTRIBUTE_POSITION)
|
||||
else {
|
||||
panic!("The base mesh did not have vertex positions");
|
||||
};
|
||||
|
||||
let perimeter = self.base_builder.perimeter();
|
||||
let (vert_count, index_count) =
|
||||
perimeter
|
||||
.iter()
|
||||
.fold((0, 0), |(verts, indices), perimeter| {
|
||||
(
|
||||
verts + 2 * perimeter.vertices_per_layer(),
|
||||
indices + perimeter.indices_per_segment(),
|
||||
)
|
||||
});
|
||||
let mut positions = Vec::with_capacity(vert_count);
|
||||
let mut normals = Vec::with_capacity(vert_count);
|
||||
let mut indices = Vec::with_capacity(index_count);
|
||||
let mut uvs = Vec::with_capacity(vert_count);
|
||||
|
||||
// Compute the amount of horizontal space allocated to each segment of the perimeter.
|
||||
let uv_segment_delta = 1. / perimeter.len() as f32;
|
||||
for (i, segment) in perimeter.into_iter().enumerate() {
|
||||
// The start of the x range of the area of the current perimeter-segment.
|
||||
let uv_start = i as f32 * uv_segment_delta;
|
||||
|
||||
match segment {
|
||||
PerimeterSegment::Flat {
|
||||
indices: segment_indices,
|
||||
} => {
|
||||
let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;
|
||||
for i in 0..(segment_indices.len() - 1) {
|
||||
let uv_x = uv_start + uv_delta * i as f32;
|
||||
// Get the positions for the current and the next index.
|
||||
let a = cap_verts[segment_indices[i] as usize];
|
||||
let b = cap_verts[segment_indices[i + 1] as usize];
|
||||
|
||||
// Get the index of the next vertex added to the mantel.
|
||||
let index = positions.len() as u32;
|
||||
|
||||
// Push the positions of the two indices and their equivalent points on the back face.
|
||||
// This works, since the front face has already been moved to the correct depth.
|
||||
positions.push(a);
|
||||
positions.push(b);
|
||||
positions.push([a[0], a[1], -a[2]]);
|
||||
positions.push([b[0], b[1], -b[2]]);
|
||||
|
||||
// UVs for the mantel are between (0, 0.5) and (1, 1).
|
||||
uvs.extend_from_slice(&[
|
||||
[uv_x, 0.5],
|
||||
[uv_x + uv_delta, 0.5],
|
||||
[uv_x, 1.],
|
||||
[uv_x + uv_delta, 1.],
|
||||
]);
|
||||
|
||||
// The normal is calculated to be the normal of the line segment connecting a and b.
|
||||
let n = Vec3::from_array([b[1] - a[1], a[0] - b[0], 0.])
|
||||
.normalize_or_zero()
|
||||
.to_array();
|
||||
normals.extend_from_slice(&[n; 4]);
|
||||
|
||||
// Add the indices for the vertices created above to the mesh.
|
||||
indices.extend_from_slice(&[
|
||||
index,
|
||||
index + 2,
|
||||
index + 1,
|
||||
index + 1,
|
||||
index + 2,
|
||||
index + 3,
|
||||
]);
|
||||
}
|
||||
}
|
||||
PerimeterSegment::Smooth {
|
||||
first_normal,
|
||||
last_normal,
|
||||
indices: segment_indices,
|
||||
} => {
|
||||
let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;
|
||||
|
||||
// Since the indices for this segment will be added after its vertices have been added,
|
||||
// we need to store the index of the first vertex that is part of this segment.
|
||||
let base_index = positions.len() as u32;
|
||||
|
||||
// If there is a first vertex, we need to add it and its counterpart on the back face.
|
||||
// The normal is provided by `segment.first_normal`.
|
||||
if let Some(i) = segment_indices.first() {
|
||||
let p = cap_verts[*i as usize];
|
||||
positions.push(p);
|
||||
positions.push([p[0], p[1], -p[2]]);
|
||||
uvs.extend_from_slice(&[[uv_start, 0.5], [uv_start, 1.]]);
|
||||
normals.extend_from_slice(&[first_normal.extend(0.).to_array(); 2]);
|
||||
}
|
||||
|
||||
// For all points inbetween the first and last vertices, we can automatically compute the normals.
|
||||
for i in 1..(segment_indices.len() - 1) {
|
||||
let uv_x = uv_start + uv_delta * i as f32;
|
||||
|
||||
// Get the positions for the last, current and the next index.
|
||||
let a = cap_verts[segment_indices[i - 1] as usize];
|
||||
let b = cap_verts[segment_indices[i] as usize];
|
||||
let c = cap_verts[segment_indices[i + 1] as usize];
|
||||
|
||||
// Add the current vertex and its counterpart on the backface
|
||||
positions.push(b);
|
||||
positions.push([b[0], b[1], -b[2]]);
|
||||
|
||||
uvs.extend_from_slice(&[[uv_x, 0.5], [uv_x, 1.]]);
|
||||
|
||||
// The normal for the current vertices can be calculated based on the two neighbouring vertices.
|
||||
// The normal is interpolated between the normals of the two line segments connecting the current vertex with its neighbours.
|
||||
// Closer vertices have a stronger effect on the normal than more distant ones.
|
||||
let n = {
|
||||
let ab = Vec2::from_slice(&b) - Vec2::from_slice(&a);
|
||||
let bc = Vec2::from_slice(&c) - Vec2::from_slice(&b);
|
||||
let n = ab.normalize_or_zero() + bc.normalize_or_zero();
|
||||
Vec2::new(n.y, -n.x)
|
||||
.normalize_or_zero()
|
||||
.extend(0.)
|
||||
.to_array()
|
||||
};
|
||||
normals.extend_from_slice(&[n; 2]);
|
||||
}
|
||||
|
||||
// If there is a last vertex, we need to add it and its counterpart on the back face.
|
||||
// The normal is provided by `segment.last_normal`.
|
||||
if let Some(i) = segment_indices.last() {
|
||||
let p = cap_verts[*i as usize];
|
||||
positions.push(p);
|
||||
positions.push([p[0], p[1], -p[2]]);
|
||||
uvs.extend_from_slice(&[
|
||||
[uv_start + uv_segment_delta, 0.5],
|
||||
[uv_start + uv_segment_delta, 1.],
|
||||
]);
|
||||
normals.extend_from_slice(&[last_normal.extend(0.).to_array(); 2]);
|
||||
}
|
||||
|
||||
for i in 0..(segment_indices.len() as u32 - 1) {
|
||||
let index = base_index + 2 * i;
|
||||
indices.extend_from_slice(&[
|
||||
index,
|
||||
index + 1,
|
||||
index + 2,
|
||||
index + 2,
|
||||
index + 1,
|
||||
index + 3,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mesh::new(
|
||||
wgpu::PrimitiveTopology::TriangleList,
|
||||
front_face.asset_usage,
|
||||
)
|
||||
.with_inserted_indices(Indices::U32(indices))
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
|
||||
};
|
||||
|
||||
front_face.merge(back_face);
|
||||
front_face.merge(mantel);
|
||||
front_face
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> From<Extrusion<P>> for Mesh
|
||||
where
|
||||
P: Primitive2d + Meshable,
|
||||
P::Output: Extrudable,
|
||||
{
|
||||
fn from(value: Extrusion<P>) -> Self {
|
||||
value.mesh().build()
|
||||
}
|
||||
}
|
@ -25,13 +25,15 @@ pub use dim2::*;
|
||||
mod dim3;
|
||||
pub use dim3::*;
|
||||
|
||||
mod extrusion;
|
||||
pub use extrusion::*;
|
||||
|
||||
use super::Mesh;
|
||||
|
||||
/// A trait for shapes that can be turned into a [`Mesh`](super::Mesh).
|
||||
pub trait Meshable {
|
||||
/// The output of [`Self::mesh`]. This can either be a [`Mesh`](super::Mesh)
|
||||
/// or a [`MeshBuilder`] used for creating a [`Mesh`](super::Mesh).
|
||||
type Output;
|
||||
/// The output of [`Self::mesh`]. This will be a [`MeshBuilder`] used for creating a [`Mesh`](super::Mesh).
|
||||
type Output: MeshBuilder;
|
||||
|
||||
/// Creates a [`Mesh`](super::Mesh) for a shape.
|
||||
fn mesh(&self) -> Self::Output;
|
||||
|
@ -28,7 +28,9 @@ fn main() {
|
||||
#[derive(Component)]
|
||||
struct Shape;
|
||||
|
||||
const X_EXTENT: f32 = 14.0;
|
||||
const SHAPES_X_EXTENT: f32 = 14.0;
|
||||
const EXTRUSION_X_EXTENT: f32 = 16.0;
|
||||
const Z_EXTENT: f32 = 5.0;
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
@ -53,6 +55,16 @@ fn setup(
|
||||
meshes.add(Sphere::default().mesh().uv(32, 18)),
|
||||
];
|
||||
|
||||
let extrusions = [
|
||||
meshes.add(Extrusion::new(Rectangle::default(), 1.)),
|
||||
meshes.add(Extrusion::new(Capsule2d::default(), 1.)),
|
||||
meshes.add(Extrusion::new(Annulus::default(), 1.)),
|
||||
meshes.add(Extrusion::new(Circle::default(), 1.)),
|
||||
meshes.add(Extrusion::new(Ellipse::default(), 1.)),
|
||||
meshes.add(Extrusion::new(RegularPolygon::default(), 1.)),
|
||||
meshes.add(Extrusion::new(Triangle2d::default(), 1.)),
|
||||
];
|
||||
|
||||
let num_shapes = shapes.len();
|
||||
|
||||
for (i, shape) in shapes.into_iter().enumerate() {
|
||||
@ -61,9 +73,29 @@ fn setup(
|
||||
mesh: shape,
|
||||
material: debug_material.clone(),
|
||||
transform: Transform::from_xyz(
|
||||
-X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT,
|
||||
-SHAPES_X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * SHAPES_X_EXTENT,
|
||||
2.0,
|
||||
0.0,
|
||||
Z_EXTENT / 2.,
|
||||
)
|
||||
.with_rotation(Quat::from_rotation_x(-PI / 4.)),
|
||||
..default()
|
||||
},
|
||||
Shape,
|
||||
));
|
||||
}
|
||||
|
||||
let num_extrusions = extrusions.len();
|
||||
|
||||
for (i, shape) in extrusions.into_iter().enumerate() {
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: shape,
|
||||
material: debug_material.clone(),
|
||||
transform: Transform::from_xyz(
|
||||
-EXTRUSION_X_EXTENT / 2.
|
||||
+ i as f32 / (num_extrusions - 1) as f32 * EXTRUSION_X_EXTENT,
|
||||
2.0,
|
||||
-Z_EXTENT / 2.,
|
||||
)
|
||||
.with_rotation(Quat::from_rotation_x(-PI / 4.)),
|
||||
..default()
|
||||
@ -92,7 +124,7 @@ fn setup(
|
||||
});
|
||||
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(0.0, 6., 12.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y),
|
||||
transform: Transform::from_xyz(0.0, 7., 14.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
|
||||
|
@ -507,16 +507,16 @@ fn spawn_primitive_2d(
|
||||
let material: Handle<ColorMaterial> = materials.add(Color::WHITE);
|
||||
let camera_mode = CameraActive::Dim2;
|
||||
[
|
||||
Some(RECTANGLE.mesh()),
|
||||
Some(RECTANGLE.mesh().build()),
|
||||
Some(CIRCLE.mesh().build()),
|
||||
Some(ELLIPSE.mesh().build()),
|
||||
Some(TRIANGLE_2D.mesh()),
|
||||
Some(TRIANGLE_2D.mesh().build()),
|
||||
None, // plane
|
||||
None, // line
|
||||
None, // segment
|
||||
None, // polyline
|
||||
None, // polygon
|
||||
Some(REGULAR_POLYGON.mesh()),
|
||||
Some(REGULAR_POLYGON.mesh().build()),
|
||||
Some(CAPSULE_2D.mesh().build()),
|
||||
None, // cylinder
|
||||
None, // cone
|
||||
@ -554,10 +554,10 @@ fn spawn_primitive_3d(
|
||||
let material: Handle<StandardMaterial> = materials.add(Color::WHITE);
|
||||
let camera_mode = CameraActive::Dim3;
|
||||
[
|
||||
Some(CUBOID.mesh()),
|
||||
Some(CUBOID.mesh().build()),
|
||||
Some(SPHERE.mesh().build()),
|
||||
None, // ellipse
|
||||
Some(TRIANGLE_3D.mesh()),
|
||||
Some(TRIANGLE_3D.mesh().build()),
|
||||
Some(PLANE_3D.mesh().build()),
|
||||
None, // line
|
||||
None, // segment
|
||||
@ -569,7 +569,7 @@ fn spawn_primitive_3d(
|
||||
None, // cone
|
||||
None, // conical frustum
|
||||
Some(TORUS.mesh().build()),
|
||||
Some(TETRAHEDRON.mesh()),
|
||||
Some(TETRAHEDRON.mesh().build()),
|
||||
]
|
||||
.into_iter()
|
||||
.zip(PrimitiveSelected::ALL)
|
||||
|
@ -154,6 +154,9 @@ enum Shape {
|
||||
Tetrahedron,
|
||||
Triangle,
|
||||
}
|
||||
struct ShapeMeshBuilder {
|
||||
shape: Shape,
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
/// Return a vector containing all implemented shapes
|
||||
@ -195,16 +198,22 @@ impl ShapeSample for Shape {
|
||||
}
|
||||
|
||||
impl Meshable for Shape {
|
||||
type Output = Mesh;
|
||||
type Output = ShapeMeshBuilder;
|
||||
|
||||
fn mesh(&self) -> Self::Output {
|
||||
match self {
|
||||
Shape::Cuboid => CUBOID.mesh(),
|
||||
ShapeMeshBuilder { shape: *self }
|
||||
}
|
||||
}
|
||||
|
||||
impl MeshBuilder for ShapeMeshBuilder {
|
||||
fn build(&self) -> Mesh {
|
||||
match self.shape {
|
||||
Shape::Cuboid => CUBOID.mesh().into(),
|
||||
Shape::Sphere => SPHERE.mesh().into(),
|
||||
Shape::Capsule => CAPSULE_3D.mesh().into(),
|
||||
Shape::Cylinder => CYLINDER.mesh().into(),
|
||||
Shape::Tetrahedron => TETRAHEDRON.mesh(),
|
||||
Shape::Triangle => TRIANGLE_3D.mesh(),
|
||||
Shape::Tetrahedron => TETRAHEDRON.mesh().into(),
|
||||
Shape::Triangle => TRIANGLE_3D.mesh().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user