Skip to content

Commit

Permalink
Meshable extrusions (bevyengine#13478)
Browse files Browse the repository at this point in the history
# 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:

![extrusion
uvs](https://github.com/bevyengine/bevy/assets/62256001/4e54e421-bfda-44b9-8571-412525cebddf)

The `3d_shapes` example now looks like this:


![image_2024-05-22_235915753](https://github.com/bevyengine/bevy/assets/62256001/3d8bc86d-9ed1-47f2-899a-27aac0a265dd)

---------

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>
  • Loading branch information
4 people authored Jun 4, 2024
1 parent 5e1c841 commit fd82ef8
Show file tree
Hide file tree
Showing 10 changed files with 680 additions and 82 deletions.
202 changes: 168 additions & 34 deletions crates/bevy_render/src/mesh/primitives/dim2.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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(),
}]
}
}

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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 = Mesh;
type Output = RegularPolygonMeshBuilder;

fn mesh(&self) -> Self::Output {
Self::Output {
circumradius: self.circumcircle.radius,
sides: self.sides,
}
}
}

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()
}
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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],
Expand All @@ -609,17 +662,36 @@ impl Meshable for Rhombus {
}
}

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()
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];
Expand All @@ -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 {
Expand All @@ -649,16 +721,34 @@ impl Meshable for Triangle2d {
}
}

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 From<Triangle2d> for Mesh {
fn from(triangle: Triangle2d) -> Self {
triangle.mesh()
triangle.mesh().build()
}
}

impl Meshable for Rectangle {
type Output = Mesh;
/// A builder used for creating a [`Mesh`] with a [`Rectangle`] shape.
pub struct RectangleMeshBuilder {
half_size: Vec2,
}

fn mesh(&self) -> Self::Output {
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],
Expand All @@ -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()
}
}

Expand Down Expand Up @@ -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;

Expand Down
13 changes: 4 additions & 9 deletions crates/bevy_render/src/mesh/primitives/dim3/conical_frustum.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
mesh::{Indices, Mesh, Meshable},
mesh::{Indices, Mesh, MeshBuilder, Meshable},
render_asset::RenderAssetUsages,
};
use bevy_math::{primitives::ConicalFrustum, Vec3};
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -182,9 +183,3 @@ impl From<ConicalFrustum> for Mesh {
frustum.mesh().build()
}
}

impl From<ConicalFrustumMeshBuilder> for Mesh {
fn from(frustum: ConicalFrustumMeshBuilder) -> Self {
frustum.build()
}
}
Loading

0 comments on commit fd82ef8

Please sign in to comment.