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 indices.chunks_exact_mut(3).for_each(|arr| arr.swap(1, 0));
216 }
217 Indices::U32(indices) => {
218 indices.chunks_exact_mut(3).for_each(|arr| arr.swap(1, 0));
219 }
220 },
221 _ => {
222 panic!("Meshes used with Extrusions must have a primitive topology of `PrimitiveTopology::TriangleList`");
223 }
224 };
225 }
226 back_face
227 };
228
229 // An extrusion of depth 0 does not need a mantel
230 if self.half_depth == 0. {
231 front_face.merge(&back_face).unwrap();
232 return front_face;
233 }
234
235 let mantel = {
236 let Some(VertexAttributeValues::Float32x3(cap_verts)) =
237 front_face.attribute(Mesh::ATTRIBUTE_POSITION)
238 else {
239 panic!("The base mesh did not have vertex positions");
240 };
241
242 debug_assert!(self.segments > 0);
243
244 let layers = self.segments + 1;
245 let layer_depth_delta = self.half_depth * 2.0 / self.segments as f32;
246
247 let perimeter = self.base_builder.perimeter();
248 let (vert_count, index_count) =
249 perimeter
250 .iter()
251 .fold((0, 0), |(verts, indices), perimeter| {
252 (
253 verts + layers * perimeter.vertices_per_layer() as usize,
254 indices + self.segments * perimeter.indices_per_segment(),
255 )
256 });
257 let mut positions = Vec::with_capacity(vert_count);
258 let mut normals = Vec::with_capacity(vert_count);
259 let mut indices = Vec::with_capacity(index_count);
260 let mut uvs = Vec::with_capacity(vert_count);
261
262 // Compute the amount of horizontal space allocated to each segment of the perimeter.
263 let uv_segment_delta = 1. / perimeter.len() as f32;
264 for (i, segment) in perimeter.into_iter().enumerate() {
265 // The start of the x range of the area of the current perimeter-segment.
266 let uv_start = i as f32 * uv_segment_delta;
267
268 match segment {
269 PerimeterSegment::Flat {
270 indices: segment_indices,
271 } => {
272 let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;
273 for i in 0..(segment_indices.len() - 1) {
274 let uv_x = uv_start + uv_delta * i as f32;
275 // Get the positions for the current and the next index.
276 let a = cap_verts[segment_indices[i] as usize];
277 let b = cap_verts[segment_indices[i + 1] as usize];
278
279 // Get the index of the next vertex added to the mantel.
280 let index = positions.len() as u32;
281
282 // Push the positions of the two indices and their equivalent points on each layer.
283 for i in 0..layers {
284 let i = i as f32;
285 let z = a[2] - layer_depth_delta * i;
286 positions.push([a[0], a[1], z]);
287 positions.push([b[0], b[1], z]);
288
289 // UVs for the mantel are between (0, 0.5) and (1, 1).
290 let uv_y = 0.5 + 0.5 * i / self.segments as f32;
291 uvs.push([uv_x, uv_y]);
292 uvs.push([uv_x + uv_delta, uv_y]);
293 }
294
295 // The normal is calculated to be the normal of the line segment connecting a and b.
296 let n = Vec3::from_array([b[1] - a[1], a[0] - b[0], 0.])
297 .normalize_or_zero()
298 .to_array();
299 normals.extend_from_slice(&vec![n; 2 * layers]);
300
301 // Add the indices for the vertices created above to the mesh.
302 for i in 0..self.segments as u32 {
303 let base_index = index + 2 * i;
304 indices.extend_from_slice(&[
305 base_index,
306 base_index + 2,
307 base_index + 1,
308 base_index + 1,
309 base_index + 2,
310 base_index + 3,
311 ]);
312 }
313 }
314 }
315 PerimeterSegment::Smooth {
316 first_normal,
317 last_normal,
318 indices: segment_indices,
319 } => {
320 let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;
321
322 // Since the indices for this segment will be added after its vertices have been added,
323 // we need to store the index of the first vertex that is part of this segment.
324 let base_index = positions.len() as u32;
325
326 // If there is a first vertex, we need to add it and its counterparts on each layer.
327 // The normal is provided by `segment.first_normal`.
328 if let Some(i) = segment_indices.first() {
329 let p = cap_verts[*i as usize];
330 for i in 0..layers {
331 let i = i as f32;
332 let z = p[2] - layer_depth_delta * i;
333 positions.push([p[0], p[1], z]);
334
335 let uv_y = 0.5 + 0.5 * i / self.segments as f32;
336 uvs.push([uv_start, uv_y]);
337 }
338 normals.extend_from_slice(&vec![
339 first_normal.extend(0.).to_array();
340 layers
341 ]);
342 }
343
344 // For all points inbetween the first and last vertices, we can automatically compute the normals.
345 for i in 1..(segment_indices.len() - 1) {
346 let uv_x = uv_start + uv_delta * i as f32;
347
348 // Get the positions for the last, current and the next index.
349 let a = cap_verts[segment_indices[i - 1] as usize];
350 let b = cap_verts[segment_indices[i] as usize];
351 let c = cap_verts[segment_indices[i + 1] as usize];
352
353 // Add the current vertex and its counterparts on each layer.
354 for i in 0..layers {
355 let i = i as f32;
356 let z = b[2] - layer_depth_delta * i;
357 positions.push([b[0], b[1], z]);
358
359 let uv_y = 0.5 + 0.5 * i / self.segments as f32;
360 uvs.push([uv_x, uv_y]);
361 }
362
363 // The normal for the current vertices can be calculated based on the two neighboring vertices.
364 // The normal is interpolated between the normals of the two line segments connecting the current vertex with its neighbors.
365 // Closer vertices have a stronger effect on the normal than more distant ones.
366 let n = {
367 let ab = Vec2::from_slice(&b) - Vec2::from_slice(&a);
368 let bc = Vec2::from_slice(&c) - Vec2::from_slice(&b);
369 let n = ab.normalize_or_zero() + bc.normalize_or_zero();
370 Vec2::new(n.y, -n.x)
371 .normalize_or_zero()
372 .extend(0.)
373 .to_array()
374 };
375 normals.extend_from_slice(&vec![n; layers]);
376 }
377
378 // If there is a last vertex, we need to add it and its counterparts on each layer.
379 // The normal is provided by `segment.last_normal`.
380 if let Some(i) = segment_indices.last() {
381 let p = cap_verts[*i as usize];
382 for i in 0..layers {
383 let i = i as f32;
384 let z = p[2] - layer_depth_delta * i;
385 positions.push([p[0], p[1], z]);
386
387 let uv_y = 0.5 + 0.5 * i / self.segments as f32;
388 uvs.push([uv_start + uv_segment_delta, uv_y]);
389 }
390 normals.extend_from_slice(&vec![
391 last_normal.extend(0.).to_array();
392 layers
393 ]);
394 }
395
396 let columns = segment_indices.len() as u32;
397 let segments = self.segments as u32;
398 let layers = segments + 1;
399 for s in 0..segments {
400 for column in 0..(columns - 1) {
401 let index = base_index + s + column * layers;
402 indices.extend_from_slice(&[
403 index,
404 index + 1,
405 index + layers,
406 index + layers,
407 index + 1,
408 index + layers + 1,
409 ]);
410 }
411 }
412 }
413 }
414 }
415
416 Mesh::new(PrimitiveTopology::TriangleList, front_face.asset_usage)
417 .with_inserted_indices(Indices::U32(indices))
418 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
419 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
420 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
421 };
422
423 front_face.merge(&back_face).unwrap();
424 front_face.merge(&mantel).unwrap();
425 front_face
426 }
427}
428
429impl<P> From<Extrusion<P>> for Mesh
430where
431 P: Primitive2d + Meshable,
432 P::Output: Extrudable,
433{
434 fn from(value: Extrusion<P>) -> Self {
435 value.mesh().build()
436 }
437}