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}