Skip to main content

bevy_mesh/primitives/
extrusion.rs

1use bevy_math::{Vec2, Vec3};
2use bevy_shape::{Annulus, Capsule2d, Circle, Ellipse, Extrusion, Primitive2d};
3
4use super::{MeshBuilder, Meshable};
5use crate::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};
6
7/// A type representing a segment of the perimeter of an extrudable mesh.
8pub enum PerimeterSegment {
9    /// This segment of the perimeter will be shaded smooth.
10    ///
11    /// This has the effect of rendering the segment's faces with softened edges, so it is appropriate for curved shapes.
12    ///
13    /// The normals for the vertices that are part of this segment will be calculated based on the positions of their neighbors.
14    /// Each normal is interpolated between the normals of the two line segments connecting it with its neighbors.
15    /// Closer vertices have a stronger effect on the normal than more distant ones.
16    ///
17    /// Since the vertices corresponding to the first and last indices do not have two neighboring vertices, their normals must be provided manually.
18    Smooth {
19        /// The normal of the first vertex.
20        first_normal: Vec2,
21        /// The normal of the last vertex.
22        last_normal: Vec2,
23        /// A list of indices representing this segment of the perimeter of the mesh.
24        ///
25        /// The `indices` refer to the indices of the vertices generated by the `MeshBuilder` of the underlying 2D primitive.
26        /// For example, a triangle has 3 vertices with indices 0, 1 and 2.
27        ///
28        /// The indices must be ordered such that the *outside* of the mesh is to the right
29        /// when walking along the vertices of the mesh in the order provided by the indices.
30        ///
31        /// For geometry to be rendered, you must provide at least two indices.
32        indices: Vec<u32>,
33    },
34    /// This segment of the perimeter will be shaded flat.
35    ///
36    /// This has the effect of rendering the segment's faces with hard edges.
37    Flat {
38        /// A list of indices representing this segment of the perimeter of the mesh.
39        ///
40        /// The `indices` refer to the indices of the vertices generated by the `MeshBuilder` of the underlying 2D primitive.
41        /// For example, a triangle has 3 vertices with indices 0, 1 and 2.
42        ///
43        /// The indices must be ordered such that the *outside* of the mesh is to the right
44        /// when walking along the vertices of the mesh in the order provided by indices.
45        ///
46        /// For geometry to be rendered, you must provide at least two indices.
47        indices: Vec<u32>,
48    },
49}
50
51impl PerimeterSegment {
52    /// Returns the amount of vertices each 'layer' of the extrusion should include for this perimeter segment.
53    ///
54    /// A layer is the set of vertices sharing a common Z value or depth.
55    fn vertices_per_layer(&self) -> u32 {
56        match self {
57            PerimeterSegment::Smooth { indices, .. } => indices.len() as u32,
58            PerimeterSegment::Flat { indices } => 2 * (indices.len() as u32 - 1),
59        }
60    }
61
62    /// Returns the amount of indices each 'segment' of the extrusion should include for this perimeter segment.
63    ///
64    /// A segment is the set of faces on the mantel of the extrusion between two layers of vertices.
65    fn indices_per_segment(&self) -> usize {
66        match self {
67            PerimeterSegment::Smooth { indices, .. } | PerimeterSegment::Flat { indices } => {
68                6 * (indices.len() - 1)
69            }
70        }
71    }
72}
73
74/// A trait required for implementing `Meshable` for `Extrusion<T>`.
75///
76/// ## Warning
77///
78/// By implementing this trait you guarantee that the `primitive_topology` of the mesh returned by
79/// this builder is [`PrimitiveTopology::TriangleList`]
80/// and that your mesh has a [`Mesh::ATTRIBUTE_POSITION`] attribute.
81pub trait Extrudable: MeshBuilder {
82    /// A list of the indices each representing a part of the perimeter of the mesh.
83    fn perimeter(&self) -> Vec<PerimeterSegment>;
84}
85
86impl<P> Meshable for Extrusion<P>
87where
88    P: Primitive2d + Meshable,
89    P::Output: Extrudable,
90{
91    type Output = ExtrusionBuilder<P>;
92
93    fn mesh(&self) -> Self::Output {
94        ExtrusionBuilder {
95            base_builder: self.base_shape.mesh(),
96            half_depth: self.half_depth,
97            segments: 1,
98        }
99    }
100}
101
102/// A builder used for creating a [`Mesh`] with an [`Extrusion`] shape.
103pub struct ExtrusionBuilder<P>
104where
105    P: Primitive2d + Meshable,
106    P::Output: Extrudable,
107{
108    pub base_builder: P::Output,
109    pub half_depth: f32,
110    pub segments: usize,
111}
112
113impl<P> ExtrusionBuilder<P>
114where
115    P: Primitive2d + Meshable,
116    P::Output: Extrudable,
117{
118    /// Create a new `ExtrusionBuilder<P>` from a given `base_shape` and the full `depth` of the extrusion.
119    pub fn new(base_shape: &P, depth: f32) -> Self {
120        Self {
121            base_builder: base_shape.mesh(),
122            half_depth: depth / 2.,
123            segments: 1,
124        }
125    }
126
127    /// Sets the number of segments along the depth of the extrusion.
128    /// Must be greater than `0` for the geometry of the mantel to be generated.
129    pub fn segments(mut self, segments: usize) -> Self {
130        self.segments = segments;
131        self
132    }
133
134    /// Apply a function to the inner builder
135    pub fn with_inner(mut self, func: impl Fn(P::Output) -> P::Output) -> Self {
136        self.base_builder = func(self.base_builder);
137        self
138    }
139}
140
141impl ExtrusionBuilder<Circle> {
142    /// Sets the number of vertices used for the circle mesh at each end of the extrusion.
143    pub fn resolution(mut self, resolution: u32) -> Self {
144        self.base_builder.resolution = resolution;
145        self
146    }
147}
148
149impl ExtrusionBuilder<Ellipse> {
150    /// Sets the number of vertices used for the ellipse mesh at each end of the extrusion.
151    pub fn resolution(mut self, resolution: u32) -> Self {
152        self.base_builder.resolution = resolution;
153        self
154    }
155}
156
157impl ExtrusionBuilder<Annulus> {
158    /// Sets the number of vertices used in constructing the concentric circles of the annulus mesh at each end of the extrusion.
159    pub fn resolution(mut self, resolution: u32) -> Self {
160        self.base_builder.resolution = resolution;
161        self
162    }
163}
164
165impl ExtrusionBuilder<Capsule2d> {
166    /// Sets the number of vertices used for each hemicircle at the ends of the extrusion.
167    pub fn resolution(mut self, resolution: u32) -> Self {
168        self.base_builder.resolution = resolution;
169        self
170    }
171}
172
173impl<P> MeshBuilder for ExtrusionBuilder<P>
174where
175    P: Primitive2d + Meshable,
176    P::Output: Extrudable,
177{
178    fn build(&self) -> Mesh {
179        // Create and move the base mesh to the front
180        let mut front_face =
181            self.base_builder
182                .build()
183                .translated_by(Vec3::new(0., 0., self.half_depth));
184
185        // Move the uvs of the front face to be between (0., 0.) and (0.5, 0.5)
186        if let Some(VertexAttributeValues::Float32x2(uvs)) =
187            front_face.attribute_mut(Mesh::ATTRIBUTE_UV_0)
188        {
189            for uv in uvs {
190                *uv = uv.map(|coord| coord * 0.5);
191            }
192        }
193
194        let back_face = {
195            let topology = front_face.primitive_topology();
196            // Flip the normals, etc. and move mesh to the back
197            let mut back_face = front_face.clone().scaled_by(Vec3::new(1., 1., -1.));
198
199            // Move the uvs of the back face to be between (0.5, 0.) and (1., 0.5)
200            if let Some(VertexAttributeValues::Float32x2(uvs)) =
201                back_face.attribute_mut(Mesh::ATTRIBUTE_UV_0)
202            {
203                for uv in uvs {
204                    *uv = [uv[0] + 0.5, uv[1]];
205                }
206            }
207
208            // By swapping the first and second indices of each triangle we invert the winding order thus making the mesh visible from the other side
209            if let Some(indices) = back_face.indices_mut() {
210                match topology {
211                    PrimitiveTopology::TriangleList => match indices {
212                        Indices::U16(indices) => {
213                            for [a, b, _] in indices.as_chunks_mut().0 {
214                                core::mem::swap(a, b);
215                            }
216                        }
217                        Indices::U32(indices) => {
218                            for [a, b, _] in indices.as_chunks_mut().0 {
219                                core::mem::swap(a, b);
220                            }
221                        }
222                    },
223                    _ => {
224                        panic!("Meshes used with Extrusions must have a primitive topology of `PrimitiveTopology::TriangleList`");
225                    }
226                };
227            }
228            back_face
229        };
230
231        // An extrusion of depth 0 does not need a mantel
232        if self.half_depth == 0. {
233            front_face.merge(&back_face).unwrap();
234            return front_face;
235        }
236
237        let mantel = {
238            let Some(VertexAttributeValues::Float32x3(cap_verts)) =
239                front_face.attribute(Mesh::ATTRIBUTE_POSITION)
240            else {
241                panic!("The base mesh did not have vertex positions");
242            };
243
244            debug_assert!(self.segments > 0);
245
246            let layers = self.segments + 1;
247            let layer_depth_delta = self.half_depth * 2.0 / self.segments as f32;
248
249            let perimeter = self.base_builder.perimeter();
250            let (vert_count, index_count) =
251                perimeter
252                    .iter()
253                    .fold((0, 0), |(verts, indices), perimeter| {
254                        (
255                            verts + layers * perimeter.vertices_per_layer() as usize,
256                            indices + self.segments * perimeter.indices_per_segment(),
257                        )
258                    });
259            let mut positions = Vec::with_capacity(vert_count);
260            let mut normals = Vec::with_capacity(vert_count);
261            let mut indices = Vec::with_capacity(index_count);
262            let mut uvs = Vec::with_capacity(vert_count);
263
264            // Compute the amount of horizontal space allocated to each segment of the perimeter.
265            let uv_segment_delta = 1. / perimeter.len() as f32;
266            for (i, segment) in perimeter.into_iter().enumerate() {
267                // The start of the x range of the area of the current perimeter-segment.
268                let uv_start = i as f32 * uv_segment_delta;
269
270                match segment {
271                    PerimeterSegment::Flat {
272                        indices: segment_indices,
273                    } => {
274                        let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;
275                        for i in 0..(segment_indices.len() - 1) {
276                            let uv_x = uv_start + uv_delta * i as f32;
277                            // Get the positions for the current and the next index.
278                            let a = cap_verts[segment_indices[i] as usize];
279                            let b = cap_verts[segment_indices[i + 1] as usize];
280
281                            // Get the index of the next vertex added to the mantel.
282                            let index = positions.len() as u32;
283
284                            // Push the positions of the two indices and their equivalent points on each layer.
285                            for i in 0..layers {
286                                let i = i as f32;
287                                let z = a[2] - layer_depth_delta * i;
288                                positions.push([a[0], a[1], z]);
289                                positions.push([b[0], b[1], z]);
290
291                                // UVs for the mantel are between (0, 0.5) and (1, 1).
292                                let uv_y = 0.5 + 0.5 * i / self.segments as f32;
293                                uvs.push([uv_x, uv_y]);
294                                uvs.push([uv_x + uv_delta, uv_y]);
295                            }
296
297                            // The normal is calculated to be the normal of the line segment connecting a and b.
298                            let n = Vec3::from_array([b[1] - a[1], a[0] - b[0], 0.])
299                                .normalize_or_zero()
300                                .to_array();
301                            normals.extend_from_slice(&vec![n; 2 * layers]);
302
303                            // Add the indices for the vertices created above to the mesh.
304                            for i in 0..self.segments as u32 {
305                                let base_index = index + 2 * i;
306                                indices.extend_from_slice(&[
307                                    base_index,
308                                    base_index + 2,
309                                    base_index + 1,
310                                    base_index + 1,
311                                    base_index + 2,
312                                    base_index + 3,
313                                ]);
314                            }
315                        }
316                    }
317                    PerimeterSegment::Smooth {
318                        first_normal,
319                        last_normal,
320                        indices: segment_indices,
321                    } => {
322                        let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;
323
324                        // Since the indices for this segment will be added after its vertices have been added,
325                        // we need to store the index of the first vertex that is part of this segment.
326                        let base_index = positions.len() as u32;
327
328                        // If there is a first vertex, we need to add it and its counterparts on each layer.
329                        // The normal is provided by `segment.first_normal`.
330                        if let Some(i) = segment_indices.first() {
331                            let p = cap_verts[*i as usize];
332                            for i in 0..layers {
333                                let i = i as f32;
334                                let z = p[2] - layer_depth_delta * i;
335                                positions.push([p[0], p[1], z]);
336
337                                let uv_y = 0.5 + 0.5 * i / self.segments as f32;
338                                uvs.push([uv_start, uv_y]);
339                            }
340                            normals.extend_from_slice(&vec![
341                                first_normal.extend(0.).to_array();
342                                layers
343                            ]);
344                        }
345
346                        // For all points inbetween the first and last vertices, we can automatically compute the normals.
347                        for i in 1..(segment_indices.len() - 1) {
348                            let uv_x = uv_start + uv_delta * i as f32;
349
350                            // Get the positions for the last, current and the next index.
351                            let a = cap_verts[segment_indices[i - 1] as usize];
352                            let b = cap_verts[segment_indices[i] as usize];
353                            let c = cap_verts[segment_indices[i + 1] as usize];
354
355                            // Add the current vertex and its counterparts on each layer.
356                            for i in 0..layers {
357                                let i = i as f32;
358                                let z = b[2] - layer_depth_delta * i;
359                                positions.push([b[0], b[1], z]);
360
361                                let uv_y = 0.5 + 0.5 * i / self.segments as f32;
362                                uvs.push([uv_x, uv_y]);
363                            }
364
365                            // The normal for the current vertices can be calculated based on the two neighboring vertices.
366                            // The normal is interpolated between the normals of the two line segments connecting the current vertex with its neighbors.
367                            // Closer vertices have a stronger effect on the normal than more distant ones.
368                            let n = {
369                                let ab = Vec2::from_slice(&b) - Vec2::from_slice(&a);
370                                let bc = Vec2::from_slice(&c) - Vec2::from_slice(&b);
371                                let n = ab.normalize_or_zero() + bc.normalize_or_zero();
372                                Vec2::new(n.y, -n.x)
373                                    .normalize_or_zero()
374                                    .extend(0.)
375                                    .to_array()
376                            };
377                            normals.extend_from_slice(&vec![n; layers]);
378                        }
379
380                        // If there is a last vertex, we need to add it and its counterparts on each layer.
381                        // The normal is provided by `segment.last_normal`.
382                        if let Some(i) = segment_indices.last() {
383                            let p = cap_verts[*i as usize];
384                            for i in 0..layers {
385                                let i = i as f32;
386                                let z = p[2] - layer_depth_delta * i;
387                                positions.push([p[0], p[1], z]);
388
389                                let uv_y = 0.5 + 0.5 * i / self.segments as f32;
390                                uvs.push([uv_start + uv_segment_delta, uv_y]);
391                            }
392                            normals.extend_from_slice(&vec![
393                                last_normal.extend(0.).to_array();
394                                layers
395                            ]);
396                        }
397
398                        let columns = segment_indices.len() as u32;
399                        let segments = self.segments as u32;
400                        let layers = segments + 1;
401                        for s in 0..segments {
402                            for column in 0..(columns - 1) {
403                                let index = base_index + s + column * layers;
404                                indices.extend_from_slice(&[
405                                    index,
406                                    index + 1,
407                                    index + layers,
408                                    index + layers,
409                                    index + 1,
410                                    index + layers + 1,
411                                ]);
412                            }
413                        }
414                    }
415                }
416            }
417
418            Mesh::new(PrimitiveTopology::TriangleList, front_face.asset_usage)
419                .with_inserted_indices(Indices::U32(indices))
420                .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
421                .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
422                .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
423        };
424
425        front_face.merge(&back_face).unwrap();
426        front_face.merge(&mantel).unwrap();
427        front_face
428    }
429}
430
431impl<P> From<Extrusion<P>> for Mesh
432where
433    P: Primitive2d + Meshable,
434    P::Output: Extrudable,
435{
436    fn from(value: Extrusion<P>) -> Self {
437        value.mesh().build()
438    }
439}