Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_mesh/src/primitives/dim3/capsule.rs
6598 views
1
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
2
use bevy_asset::RenderAssetUsages;
3
use bevy_math::{ops, primitives::Capsule3d, Vec2, Vec3};
4
use bevy_reflect::prelude::*;
5
6
/// Manner in which UV coordinates are distributed vertically.
7
#[derive(Clone, Copy, Debug, Default, Reflect)]
8
#[reflect(Default, Debug, Clone)]
9
pub enum CapsuleUvProfile {
10
/// UV space is distributed by how much of the capsule consists of the hemispheres.
11
#[default]
12
Aspect,
13
/// Hemispheres get UV space according to the ratio of latitudes to rings.
14
Uniform,
15
/// Upper third of the texture goes to the northern hemisphere, middle third to the cylinder
16
/// and lower third to the southern one.
17
Fixed,
18
}
19
20
/// A builder used for creating a [`Mesh`] with a [`Capsule3d`] shape.
21
#[derive(Clone, Copy, Debug, Reflect)]
22
#[reflect(Default, Debug, Clone)]
23
pub struct Capsule3dMeshBuilder {
24
/// The [`Capsule3d`] shape.
25
pub capsule: Capsule3d,
26
/// The number of horizontal lines subdividing the cylindrical part of the capsule.
27
/// The default is `0`.
28
pub rings: u32,
29
/// The number of vertical lines subdividing the hemispheres of the capsule.
30
/// The default is `32`.
31
pub longitudes: u32,
32
/// The number of horizontal lines subdividing the hemispheres of the capsule.
33
/// The default is `16`.
34
pub latitudes: u32,
35
/// The manner in which UV coordinates are distributed vertically.
36
/// The default is [`CapsuleUvProfile::Aspect`].
37
pub uv_profile: CapsuleUvProfile,
38
}
39
40
impl Default for Capsule3dMeshBuilder {
41
fn default() -> Self {
42
Self {
43
capsule: Capsule3d::default(),
44
rings: 0,
45
longitudes: 32,
46
latitudes: 16,
47
uv_profile: CapsuleUvProfile::default(),
48
}
49
}
50
}
51
52
impl Capsule3dMeshBuilder {
53
/// Creates a new [`Capsule3dMeshBuilder`] from a given radius, height, longitudes, and latitudes.
54
///
55
/// Note that `height` is the distance between the centers of the hemispheres.
56
/// `radius` will be added to both ends to get the real height of the mesh.
57
#[inline]
58
pub fn new(radius: f32, height: f32, longitudes: u32, latitudes: u32) -> Self {
59
Self {
60
capsule: Capsule3d::new(radius, height),
61
longitudes,
62
latitudes,
63
..Default::default()
64
}
65
}
66
67
/// Sets the number of horizontal lines subdividing the cylindrical part of the capsule.
68
#[inline]
69
pub const fn rings(mut self, rings: u32) -> Self {
70
self.rings = rings;
71
self
72
}
73
74
/// Sets the number of vertical lines subdividing the hemispheres of the capsule.
75
#[inline]
76
pub const fn longitudes(mut self, longitudes: u32) -> Self {
77
self.longitudes = longitudes;
78
self
79
}
80
81
/// Sets the number of horizontal lines subdividing the hemispheres of the capsule.
82
#[inline]
83
pub const fn latitudes(mut self, latitudes: u32) -> Self {
84
self.latitudes = latitudes;
85
self
86
}
87
88
/// Sets the manner in which UV coordinates are distributed vertically.
89
#[inline]
90
pub const fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self {
91
self.uv_profile = uv_profile;
92
self
93
}
94
}
95
96
impl MeshBuilder for Capsule3dMeshBuilder {
97
fn build(&self) -> Mesh {
98
// code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db
99
let Capsule3dMeshBuilder {
100
capsule,
101
rings,
102
longitudes,
103
latitudes,
104
uv_profile,
105
} = *self;
106
let Capsule3d {
107
radius,
108
half_length,
109
} = capsule;
110
111
let calc_middle = rings > 0;
112
let half_lats = latitudes / 2;
113
let half_latsn1 = half_lats - 1;
114
let half_latsn2 = half_lats - 2;
115
let ringsp1 = rings + 1;
116
let lonsp1 = longitudes + 1;
117
let summit = half_length + radius;
118
119
// Vertex index offsets.
120
let vert_offset_north_hemi = longitudes;
121
let vert_offset_north_equator = vert_offset_north_hemi + lonsp1 * half_latsn1;
122
let vert_offset_cylinder = vert_offset_north_equator + lonsp1;
123
let vert_offset_south_equator = if calc_middle {
124
vert_offset_cylinder + lonsp1 * rings
125
} else {
126
vert_offset_cylinder
127
};
128
let vert_offset_south_hemi = vert_offset_south_equator + lonsp1;
129
let vert_offset_south_polar = vert_offset_south_hemi + lonsp1 * half_latsn2;
130
let vert_offset_south_cap = vert_offset_south_polar + lonsp1;
131
132
// Initialize arrays.
133
let vert_len = (vert_offset_south_cap + longitudes) as usize;
134
135
let mut vs: Vec<Vec3> = vec![Vec3::ZERO; vert_len];
136
let mut vts: Vec<Vec2> = vec![Vec2::ZERO; vert_len];
137
let mut vns: Vec<Vec3> = vec![Vec3::ZERO; vert_len];
138
139
let to_theta = 2.0 * core::f32::consts::PI / longitudes as f32;
140
let to_phi = core::f32::consts::PI / latitudes as f32;
141
let to_tex_horizontal = 1.0 / longitudes as f32;
142
let to_tex_vertical = 1.0 / half_lats as f32;
143
144
let vt_aspect_ratio = match uv_profile {
145
CapsuleUvProfile::Aspect => radius / (2.0 * half_length + radius + radius),
146
CapsuleUvProfile::Uniform => half_lats as f32 / (ringsp1 + latitudes) as f32,
147
CapsuleUvProfile::Fixed => 1.0 / 3.0,
148
};
149
let vt_aspect_north = 1.0 - vt_aspect_ratio;
150
let vt_aspect_south = vt_aspect_ratio;
151
152
let mut theta_cartesian: Vec<Vec2> = vec![Vec2::ZERO; longitudes as usize];
153
let mut rho_theta_cartesian: Vec<Vec2> = vec![Vec2::ZERO; longitudes as usize];
154
let mut s_texture_cache: Vec<f32> = vec![0.0; lonsp1 as usize];
155
156
for j in 0..longitudes as usize {
157
let jf = j as f32;
158
let s_texture_polar = 1.0 - ((jf + 0.5) * to_tex_horizontal);
159
let theta = jf * to_theta;
160
161
theta_cartesian[j] = Vec2::from_angle(theta);
162
rho_theta_cartesian[j] = radius * theta_cartesian[j];
163
164
// North.
165
vs[j] = Vec3::new(0.0, summit, 0.0);
166
vts[j] = Vec2::new(s_texture_polar, 1.0);
167
vns[j] = Vec3::Y;
168
169
// South.
170
let idx = vert_offset_south_cap as usize + j;
171
vs[idx] = Vec3::new(0.0, -summit, 0.0);
172
vts[idx] = Vec2::new(s_texture_polar, 0.0);
173
vns[idx] = Vec3::new(0.0, -1.0, 0.0);
174
}
175
176
// Equatorial vertices.
177
for (j, s_texture_cache_j) in s_texture_cache.iter_mut().enumerate().take(lonsp1 as usize) {
178
let s_texture = 1.0 - j as f32 * to_tex_horizontal;
179
*s_texture_cache_j = s_texture;
180
181
// Wrap to first element upon reaching last.
182
let j_mod = j % longitudes as usize;
183
let tc = theta_cartesian[j_mod];
184
let rtc = rho_theta_cartesian[j_mod];
185
186
// North equator.
187
let idxn = vert_offset_north_equator as usize + j;
188
vs[idxn] = Vec3::new(rtc.x, half_length, -rtc.y);
189
vts[idxn] = Vec2::new(s_texture, vt_aspect_north);
190
vns[idxn] = Vec3::new(tc.x, 0.0, -tc.y);
191
192
// South equator.
193
let idxs = vert_offset_south_equator as usize + j;
194
vs[idxs] = Vec3::new(rtc.x, -half_length, -rtc.y);
195
vts[idxs] = Vec2::new(s_texture, vt_aspect_south);
196
vns[idxs] = Vec3::new(tc.x, 0.0, -tc.y);
197
}
198
199
// Hemisphere vertices.
200
for i in 0..half_latsn1 {
201
let ip1f = i as f32 + 1.0;
202
let phi = ip1f * to_phi;
203
204
// For coordinates.
205
let (sin_phi_south, cos_phi_south) = ops::sin_cos(phi);
206
207
// Symmetrical hemispheres mean cosine and sine only needs
208
// to be calculated once.
209
let cos_phi_north = sin_phi_south;
210
let sin_phi_north = -cos_phi_south;
211
212
let rho_cos_phi_north = radius * cos_phi_north;
213
let rho_sin_phi_north = radius * sin_phi_north;
214
let z_offset_north = half_length - rho_sin_phi_north;
215
216
let rho_cos_phi_south = radius * cos_phi_south;
217
let rho_sin_phi_south = radius * sin_phi_south;
218
let z_offset_sout = -half_length - rho_sin_phi_south;
219
220
// For texture coordinates.
221
let t_tex_fac = ip1f * to_tex_vertical;
222
let cmpl_tex_fac = 1.0 - t_tex_fac;
223
let t_tex_north = cmpl_tex_fac + vt_aspect_north * t_tex_fac;
224
let t_tex_south = cmpl_tex_fac * vt_aspect_south;
225
226
let i_lonsp1 = i * lonsp1;
227
let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1;
228
let vert_curr_lat_south = vert_offset_south_hemi + i_lonsp1;
229
230
for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1 as usize) {
231
let j_mod = j % longitudes as usize;
232
233
let tc = theta_cartesian[j_mod];
234
235
// North hemisphere.
236
let idxn = vert_curr_lat_north as usize + j;
237
vs[idxn] = Vec3::new(
238
rho_cos_phi_north * tc.x,
239
z_offset_north,
240
-rho_cos_phi_north * tc.y,
241
);
242
vts[idxn] = Vec2::new(*s_texture, t_tex_north);
243
vns[idxn] = Vec3::new(cos_phi_north * tc.x, -sin_phi_north, -cos_phi_north * tc.y);
244
245
// South hemisphere.
246
let idxs = vert_curr_lat_south as usize + j;
247
vs[idxs] = Vec3::new(
248
rho_cos_phi_south * tc.x,
249
z_offset_sout,
250
-rho_cos_phi_south * tc.y,
251
);
252
vts[idxs] = Vec2::new(*s_texture, t_tex_south);
253
vns[idxs] = Vec3::new(cos_phi_south * tc.x, -sin_phi_south, -cos_phi_south * tc.y);
254
}
255
}
256
257
// Cylinder vertices.
258
if calc_middle {
259
// Exclude both origin and destination edges
260
// (North and South equators) from the interpolation.
261
let to_fac = 1.0 / ringsp1 as f32;
262
let mut idx_cyl_lat = vert_offset_cylinder as usize;
263
264
for h in 1..ringsp1 {
265
let fac = h as f32 * to_fac;
266
let cmpl_fac = 1.0 - fac;
267
let t_texture = cmpl_fac * vt_aspect_north + fac * vt_aspect_south;
268
let z = half_length - 2.0 * half_length * fac;
269
270
for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1 as usize) {
271
let j_mod = j % longitudes as usize;
272
let tc = theta_cartesian[j_mod];
273
let rtc = rho_theta_cartesian[j_mod];
274
275
vs[idx_cyl_lat] = Vec3::new(rtc.x, z, -rtc.y);
276
vts[idx_cyl_lat] = Vec2::new(*s_texture, t_texture);
277
vns[idx_cyl_lat] = Vec3::new(tc.x, 0.0, -tc.y);
278
279
idx_cyl_lat += 1;
280
}
281
}
282
}
283
284
// Triangle indices.
285
286
// Stride is 3 for polar triangles;
287
// stride is 6 for two triangles forming a quad.
288
let lons3 = longitudes * 3;
289
let lons6 = longitudes * 6;
290
let hemi_lons = half_latsn1 * lons6;
291
292
let tri_offset_north_hemi = lons3;
293
let tri_offset_cylinder = tri_offset_north_hemi + hemi_lons;
294
let tri_offset_south_hemi = tri_offset_cylinder + ringsp1 * lons6;
295
let tri_offset_south_cap = tri_offset_south_hemi + hemi_lons;
296
297
let fs_len = tri_offset_south_cap + lons3;
298
let mut tris: Vec<u32> = vec![0; fs_len as usize];
299
300
// Polar caps.
301
let mut i = 0;
302
let mut k = 0;
303
let mut m = tri_offset_south_cap as usize;
304
while i < longitudes {
305
// North.
306
tris[k] = i;
307
tris[k + 1] = vert_offset_north_hemi + i;
308
tris[k + 2] = vert_offset_north_hemi + i + 1;
309
310
// South.
311
tris[m] = vert_offset_south_cap + i;
312
tris[m + 1] = vert_offset_south_polar + i + 1;
313
tris[m + 2] = vert_offset_south_polar + i;
314
315
i += 1;
316
k += 3;
317
m += 3;
318
}
319
320
// Hemispheres.
321
322
let mut i = 0;
323
let mut k = tri_offset_north_hemi as usize;
324
let mut m = tri_offset_south_hemi as usize;
325
326
while i < half_latsn1 {
327
let i_lonsp1 = i * lonsp1;
328
329
let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1;
330
let vert_next_lat_north = vert_curr_lat_north + lonsp1;
331
332
let vert_curr_lat_south = vert_offset_south_equator + i_lonsp1;
333
let vert_next_lat_south = vert_curr_lat_south + lonsp1;
334
335
let mut j = 0;
336
while j < longitudes {
337
// North.
338
let north00 = vert_curr_lat_north + j;
339
let north01 = vert_next_lat_north + j;
340
let north11 = vert_next_lat_north + j + 1;
341
let north10 = vert_curr_lat_north + j + 1;
342
343
tris[k] = north00;
344
tris[k + 1] = north11;
345
tris[k + 2] = north10;
346
347
tris[k + 3] = north00;
348
tris[k + 4] = north01;
349
tris[k + 5] = north11;
350
351
// South.
352
let south00 = vert_curr_lat_south + j;
353
let south01 = vert_next_lat_south + j;
354
let south11 = vert_next_lat_south + j + 1;
355
let south10 = vert_curr_lat_south + j + 1;
356
357
tris[m] = south00;
358
tris[m + 1] = south11;
359
tris[m + 2] = south10;
360
361
tris[m + 3] = south00;
362
tris[m + 4] = south01;
363
tris[m + 5] = south11;
364
365
j += 1;
366
k += 6;
367
m += 6;
368
}
369
370
i += 1;
371
}
372
373
// Cylinder.
374
let mut i = 0;
375
let mut k = tri_offset_cylinder as usize;
376
377
while i < ringsp1 {
378
let vert_curr_lat = vert_offset_north_equator + i * lonsp1;
379
let vert_next_lat = vert_curr_lat + lonsp1;
380
381
let mut j = 0;
382
while j < longitudes {
383
let cy00 = vert_curr_lat + j;
384
let cy01 = vert_next_lat + j;
385
let cy11 = vert_next_lat + j + 1;
386
let cy10 = vert_curr_lat + j + 1;
387
388
tris[k] = cy00;
389
tris[k + 1] = cy11;
390
tris[k + 2] = cy10;
391
392
tris[k + 3] = cy00;
393
tris[k + 4] = cy01;
394
tris[k + 5] = cy11;
395
396
j += 1;
397
k += 6;
398
}
399
400
i += 1;
401
}
402
403
let vs: Vec<[f32; 3]> = vs.into_iter().map(Into::into).collect();
404
let vns: Vec<[f32; 3]> = vns.into_iter().map(Into::into).collect();
405
let vts: Vec<[f32; 2]> = vts.into_iter().map(Into::into).collect();
406
407
assert_eq!(vs.len(), vert_len);
408
assert_eq!(tris.len(), fs_len as usize);
409
410
Mesh::new(
411
PrimitiveTopology::TriangleList,
412
RenderAssetUsages::default(),
413
)
414
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs)
415
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns)
416
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts)
417
.with_inserted_indices(Indices::U32(tris))
418
}
419
}
420
421
impl Meshable for Capsule3d {
422
type Output = Capsule3dMeshBuilder;
423
424
fn mesh(&self) -> Self::Output {
425
Capsule3dMeshBuilder {
426
capsule: *self,
427
..Default::default()
428
}
429
}
430
}
431
432
impl From<Capsule3d> for Mesh {
433
fn from(capsule: Capsule3d) -> Self {
434
capsule.mesh().build()
435
}
436
}
437
438