Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_mesh/src/primitives/extrusion.rs
6596 views
1
use bevy_math::{
2
primitives::{Annulus, Capsule2d, Circle, Ellipse, Extrusion, Primitive2d},
3
Vec2, Vec3,
4
};
5
6
use super::{MeshBuilder, Meshable};
7
use crate::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};
8
9
/// A type representing a segment of the perimeter of an extrudable mesh.
10
pub 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
47
impl 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.
77
pub 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
82
impl<P> Meshable for Extrusion<P>
83
where
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.
99
pub struct ExtrusionBuilder<P>
100
where
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
109
impl<P> ExtrusionBuilder<P>
110
where
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
131
impl 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
139
impl 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
147
impl 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
155
impl 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
163
impl<P> MeshBuilder for ExtrusionBuilder<P>
164
where
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
417
impl<P> From<Extrusion<P>> for Mesh
418
where
419
P: Primitive2d + Meshable,
420
P::Output: Extrudable,
421
{
422
fn from(value: Extrusion<P>) -> Self {
423
value.mesh().build()
424
}
425
}
426
427