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}