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}