Skip to main content

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