bevy_render/mesh/primitives/
extrusion.rs

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