Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_mesh/src/primitives/extrusion.rs
9423 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` 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
53
impl 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.
83
pub 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
88
impl<P> Meshable for Extrusion<P>
89
where
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.
105
pub struct ExtrusionBuilder<P>
106
where
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
115
impl<P> ExtrusionBuilder<P>
116
where
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
143
impl 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
151
impl 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
159
impl 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
167
impl 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
175
impl<P> MeshBuilder for ExtrusionBuilder<P>
176
where
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
429
impl<P> From<Extrusion<P>> for Mesh
430
where
431
P: Primitive2d + Meshable,
432
P::Output: Extrudable,
433
{
434
fn from(value: Extrusion<P>) -> Self {
435
value.mesh().build()
436
}
437
}
438
439