bevy_render/mesh/primitives/dim3/
capsule.rs1use crate::{
2 mesh::{Indices, Mesh, MeshBuilder, Meshable},
3 render_asset::RenderAssetUsages,
4};
5use bevy_math::{primitives::Capsule3d, Vec2, Vec3};
6use wgpu::PrimitiveTopology;
7
8#[derive(Clone, Copy, Debug, Default)]
10pub enum CapsuleUvProfile {
11 #[default]
13 Aspect,
14 Uniform,
16 Fixed,
19}
20
21#[derive(Clone, Copy, Debug)]
23pub struct Capsule3dMeshBuilder {
24 pub capsule: Capsule3d,
26 pub rings: usize,
29 pub longitudes: usize,
32 pub latitudes: usize,
35 pub uv_profile: CapsuleUvProfile,
38}
39
40impl 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
52impl Capsule3dMeshBuilder {
53 #[inline]
58 pub fn new(radius: f32, height: f32, longitudes: usize, latitudes: usize) -> Self {
59 Self {
60 capsule: Capsule3d::new(radius, height),
61 longitudes,
62 latitudes,
63 ..Default::default()
64 }
65 }
66
67 #[inline]
69 pub const fn rings(mut self, rings: usize) -> Self {
70 self.rings = rings;
71 self
72 }
73
74 #[inline]
76 pub const fn longitudes(mut self, longitudes: usize) -> Self {
77 self.longitudes = longitudes;
78 self
79 }
80
81 #[inline]
83 pub const fn latitudes(mut self, latitudes: usize) -> Self {
84 self.latitudes = latitudes;
85 self
86 }
87
88 #[inline]
90 pub const fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self {
91 self.uv_profile = uv_profile;
92 self
93 }
94}
95
96impl MeshBuilder for Capsule3dMeshBuilder {
97 fn build(&self) -> Mesh {
98 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 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 let vert_len = vert_offset_south_cap + longitudes;
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 * std::f32::consts::PI / longitudes as f32;
140 let to_phi = std::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];
153 let mut rho_theta_cartesian: Vec<Vec2> = vec![Vec2::ZERO; longitudes];
154 let mut s_texture_cache: Vec<f32> = vec![0.0; lonsp1];
155
156 for j in 0..longitudes {
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 let cos_theta = theta.cos();
162 let sin_theta = theta.sin();
163
164 theta_cartesian[j] = Vec2::new(cos_theta, sin_theta);
165 rho_theta_cartesian[j] = Vec2::new(radius * cos_theta, radius * sin_theta);
166
167 vs[j] = Vec3::new(0.0, summit, 0.0);
169 vts[j] = Vec2::new(s_texture_polar, 1.0);
170 vns[j] = Vec3::Y;
171
172 let idx = vert_offset_south_cap + j;
174 vs[idx] = Vec3::new(0.0, -summit, 0.0);
175 vts[idx] = Vec2::new(s_texture_polar, 0.0);
176 vns[idx] = Vec3::new(0.0, -1.0, 0.0);
177 }
178
179 for (j, s_texture_cache_j) in s_texture_cache.iter_mut().enumerate().take(lonsp1) {
181 let s_texture = 1.0 - j as f32 * to_tex_horizontal;
182 *s_texture_cache_j = s_texture;
183
184 let j_mod = j % longitudes;
186 let tc = theta_cartesian[j_mod];
187 let rtc = rho_theta_cartesian[j_mod];
188
189 let idxn = vert_offset_north_equator + j;
191 vs[idxn] = Vec3::new(rtc.x, half_length, -rtc.y);
192 vts[idxn] = Vec2::new(s_texture, vt_aspect_north);
193 vns[idxn] = Vec3::new(tc.x, 0.0, -tc.y);
194
195 let idxs = vert_offset_south_equator + j;
197 vs[idxs] = Vec3::new(rtc.x, -half_length, -rtc.y);
198 vts[idxs] = Vec2::new(s_texture, vt_aspect_south);
199 vns[idxs] = Vec3::new(tc.x, 0.0, -tc.y);
200 }
201
202 for i in 0..half_latsn1 {
204 let ip1f = i as f32 + 1.0;
205 let phi = ip1f * to_phi;
206
207 let cos_phi_south = phi.cos();
209 let sin_phi_south = phi.sin();
210
211 let cos_phi_north = sin_phi_south;
214 let sin_phi_north = -cos_phi_south;
215
216 let rho_cos_phi_north = radius * cos_phi_north;
217 let rho_sin_phi_north = radius * sin_phi_north;
218 let z_offset_north = half_length - rho_sin_phi_north;
219
220 let rho_cos_phi_south = radius * cos_phi_south;
221 let rho_sin_phi_south = radius * sin_phi_south;
222 let z_offset_sout = -half_length - rho_sin_phi_south;
223
224 let t_tex_fac = ip1f * to_tex_vertical;
226 let cmpl_tex_fac = 1.0 - t_tex_fac;
227 let t_tex_north = cmpl_tex_fac + vt_aspect_north * t_tex_fac;
228 let t_tex_south = cmpl_tex_fac * vt_aspect_south;
229
230 let i_lonsp1 = i * lonsp1;
231 let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1;
232 let vert_curr_lat_south = vert_offset_south_hemi + i_lonsp1;
233
234 for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1) {
235 let j_mod = j % longitudes;
236
237 let tc = theta_cartesian[j_mod];
238
239 let idxn = vert_curr_lat_north + j;
241 vs[idxn] = Vec3::new(
242 rho_cos_phi_north * tc.x,
243 z_offset_north,
244 -rho_cos_phi_north * tc.y,
245 );
246 vts[idxn] = Vec2::new(*s_texture, t_tex_north);
247 vns[idxn] = Vec3::new(cos_phi_north * tc.x, -sin_phi_north, -cos_phi_north * tc.y);
248
249 let idxs = vert_curr_lat_south + j;
251 vs[idxs] = Vec3::new(
252 rho_cos_phi_south * tc.x,
253 z_offset_sout,
254 -rho_cos_phi_south * tc.y,
255 );
256 vts[idxs] = Vec2::new(*s_texture, t_tex_south);
257 vns[idxs] = Vec3::new(cos_phi_south * tc.x, -sin_phi_south, -cos_phi_south * tc.y);
258 }
259 }
260
261 if calc_middle {
263 let to_fac = 1.0 / ringsp1 as f32;
266 let mut idx_cyl_lat = vert_offset_cylinder;
267
268 for h in 1..ringsp1 {
269 let fac = h as f32 * to_fac;
270 let cmpl_fac = 1.0 - fac;
271 let t_texture = cmpl_fac * vt_aspect_north + fac * vt_aspect_south;
272 let z = half_length - 2.0 * half_length * fac;
273
274 for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1) {
275 let j_mod = j % longitudes;
276 let tc = theta_cartesian[j_mod];
277 let rtc = rho_theta_cartesian[j_mod];
278
279 vs[idx_cyl_lat] = Vec3::new(rtc.x, z, -rtc.y);
280 vts[idx_cyl_lat] = Vec2::new(*s_texture, t_texture);
281 vns[idx_cyl_lat] = Vec3::new(tc.x, 0.0, -tc.y);
282
283 idx_cyl_lat += 1;
284 }
285 }
286 }
287
288 let lons3 = longitudes * 3;
293 let lons6 = longitudes * 6;
294 let hemi_lons = half_latsn1 * lons6;
295
296 let tri_offset_north_hemi = lons3;
297 let tri_offset_cylinder = tri_offset_north_hemi + hemi_lons;
298 let tri_offset_south_hemi = tri_offset_cylinder + ringsp1 * lons6;
299 let tri_offset_south_cap = tri_offset_south_hemi + hemi_lons;
300
301 let fs_len = tri_offset_south_cap + lons3;
302 let mut tris: Vec<u32> = vec![0; fs_len];
303
304 let mut i = 0;
306 let mut k = 0;
307 let mut m = tri_offset_south_cap;
308 while i < longitudes {
309 tris[k] = i as u32;
311 tris[k + 1] = (vert_offset_north_hemi + i) as u32;
312 tris[k + 2] = (vert_offset_north_hemi + i + 1) as u32;
313
314 tris[m] = (vert_offset_south_cap + i) as u32;
316 tris[m + 1] = (vert_offset_south_polar + i + 1) as u32;
317 tris[m + 2] = (vert_offset_south_polar + i) as u32;
318
319 i += 1;
320 k += 3;
321 m += 3;
322 }
323
324 let mut i = 0;
327 let mut k = tri_offset_north_hemi;
328 let mut m = tri_offset_south_hemi;
329
330 while i < half_latsn1 {
331 let i_lonsp1 = i * lonsp1;
332
333 let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1;
334 let vert_next_lat_north = vert_curr_lat_north + lonsp1;
335
336 let vert_curr_lat_south = vert_offset_south_equator + i_lonsp1;
337 let vert_next_lat_south = vert_curr_lat_south + lonsp1;
338
339 let mut j = 0;
340 while j < longitudes {
341 let north00 = vert_curr_lat_north + j;
343 let north01 = vert_next_lat_north + j;
344 let north11 = vert_next_lat_north + j + 1;
345 let north10 = vert_curr_lat_north + j + 1;
346
347 tris[k] = north00 as u32;
348 tris[k + 1] = north11 as u32;
349 tris[k + 2] = north10 as u32;
350
351 tris[k + 3] = north00 as u32;
352 tris[k + 4] = north01 as u32;
353 tris[k + 5] = north11 as u32;
354
355 let south00 = vert_curr_lat_south + j;
357 let south01 = vert_next_lat_south + j;
358 let south11 = vert_next_lat_south + j + 1;
359 let south10 = vert_curr_lat_south + j + 1;
360
361 tris[m] = south00 as u32;
362 tris[m + 1] = south11 as u32;
363 tris[m + 2] = south10 as u32;
364
365 tris[m + 3] = south00 as u32;
366 tris[m + 4] = south01 as u32;
367 tris[m + 5] = south11 as u32;
368
369 j += 1;
370 k += 6;
371 m += 6;
372 }
373
374 i += 1;
375 }
376
377 let mut i = 0;
379 let mut k = tri_offset_cylinder;
380
381 while i < ringsp1 {
382 let vert_curr_lat = vert_offset_north_equator + i * lonsp1;
383 let vert_next_lat = vert_curr_lat + lonsp1;
384
385 let mut j = 0;
386 while j < longitudes {
387 let cy00 = vert_curr_lat + j;
388 let cy01 = vert_next_lat + j;
389 let cy11 = vert_next_lat + j + 1;
390 let cy10 = vert_curr_lat + j + 1;
391
392 tris[k] = cy00 as u32;
393 tris[k + 1] = cy11 as u32;
394 tris[k + 2] = cy10 as u32;
395
396 tris[k + 3] = cy00 as u32;
397 tris[k + 4] = cy01 as u32;
398 tris[k + 5] = cy11 as u32;
399
400 j += 1;
401 k += 6;
402 }
403
404 i += 1;
405 }
406
407 let vs: Vec<[f32; 3]> = vs.into_iter().map(Into::into).collect();
408 let vns: Vec<[f32; 3]> = vns.into_iter().map(Into::into).collect();
409 let vts: Vec<[f32; 2]> = vts.into_iter().map(Into::into).collect();
410
411 assert_eq!(vs.len(), vert_len);
412 assert_eq!(tris.len(), fs_len);
413
414 Mesh::new(
415 PrimitiveTopology::TriangleList,
416 RenderAssetUsages::default(),
417 )
418 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs)
419 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns)
420 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts)
421 .with_inserted_indices(Indices::U32(tris))
422 }
423}
424
425impl Meshable for Capsule3d {
426 type Output = Capsule3dMeshBuilder;
427
428 fn mesh(&self) -> Self::Output {
429 Capsule3dMeshBuilder {
430 capsule: *self,
431 ..Default::default()
432 }
433 }
434}
435
436impl From<Capsule3d> for Mesh {
437 fn from(capsule: Capsule3d) -> Self {
438 capsule.mesh().build()
439 }
440}