bevy_mesh/primitives/
extrusion.rs

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