bevy_mesh/primitives/dim3/
capsule.rs1use crate::{Indices, Mesh, MeshBuilder, Meshable};
2use bevy_asset::RenderAssetUsages;
3use bevy_math::{ops, primitives::Capsule3d, Vec2, Vec3};
4use wgpu::PrimitiveTopology;
5
6#[derive(Clone, Copy, Debug, Default)]
8pub enum CapsuleUvProfile {
9 #[default]
11 Aspect,
12 Uniform,
14 Fixed,
17}
18
19#[derive(Clone, Copy, Debug)]
21pub struct Capsule3dMeshBuilder {
22 pub capsule: Capsule3d,
24 pub rings: u32,
27 pub longitudes: u32,
30 pub latitudes: u32,
33 pub uv_profile: CapsuleUvProfile,
36}
37
38impl Default for Capsule3dMeshBuilder {
39 fn default() -> Self {
40 Self {
41 capsule: Capsule3d::default(),
42 rings: 0,
43 longitudes: 32,
44 latitudes: 16,
45 uv_profile: CapsuleUvProfile::default(),
46 }
47 }
48}
49
50impl Capsule3dMeshBuilder {
51 #[inline]
56 pub fn new(radius: f32, height: f32, longitudes: u32, latitudes: u32) -> Self {
57 Self {
58 capsule: Capsule3d::new(radius, height),
59 longitudes,
60 latitudes,
61 ..Default::default()
62 }
63 }
64
65 #[inline]
67 pub const fn rings(mut self, rings: u32) -> Self {
68 self.rings = rings;
69 self
70 }
71
72 #[inline]
74 pub const fn longitudes(mut self, longitudes: u32) -> Self {
75 self.longitudes = longitudes;
76 self
77 }
78
79 #[inline]
81 pub const fn latitudes(mut self, latitudes: u32) -> Self {
82 self.latitudes = latitudes;
83 self
84 }
85
86 #[inline]
88 pub const fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self {
89 self.uv_profile = uv_profile;
90 self
91 }
92}
93
94impl MeshBuilder for Capsule3dMeshBuilder {
95 fn build(&self) -> Mesh {
96 let Capsule3dMeshBuilder {
98 capsule,
99 rings,
100 longitudes,
101 latitudes,
102 uv_profile,
103 } = *self;
104 let Capsule3d {
105 radius,
106 half_length,
107 } = capsule;
108
109 let calc_middle = rings > 0;
110 let half_lats = latitudes / 2;
111 let half_latsn1 = half_lats - 1;
112 let half_latsn2 = half_lats - 2;
113 let ringsp1 = rings + 1;
114 let lonsp1 = longitudes + 1;
115 let summit = half_length + radius;
116
117 let vert_offset_north_hemi = longitudes;
119 let vert_offset_north_equator = vert_offset_north_hemi + lonsp1 * half_latsn1;
120 let vert_offset_cylinder = vert_offset_north_equator + lonsp1;
121 let vert_offset_south_equator = if calc_middle {
122 vert_offset_cylinder + lonsp1 * rings
123 } else {
124 vert_offset_cylinder
125 };
126 let vert_offset_south_hemi = vert_offset_south_equator + lonsp1;
127 let vert_offset_south_polar = vert_offset_south_hemi + lonsp1 * half_latsn2;
128 let vert_offset_south_cap = vert_offset_south_polar + lonsp1;
129
130 let vert_len = (vert_offset_south_cap + longitudes) as usize;
132
133 let mut vs: Vec<Vec3> = vec![Vec3::ZERO; vert_len];
134 let mut vts: Vec<Vec2> = vec![Vec2::ZERO; vert_len];
135 let mut vns: Vec<Vec3> = vec![Vec3::ZERO; vert_len];
136
137 let to_theta = 2.0 * core::f32::consts::PI / longitudes as f32;
138 let to_phi = core::f32::consts::PI / latitudes as f32;
139 let to_tex_horizontal = 1.0 / longitudes as f32;
140 let to_tex_vertical = 1.0 / half_lats as f32;
141
142 let vt_aspect_ratio = match uv_profile {
143 CapsuleUvProfile::Aspect => radius / (2.0 * half_length + radius + radius),
144 CapsuleUvProfile::Uniform => half_lats as f32 / (ringsp1 + latitudes) as f32,
145 CapsuleUvProfile::Fixed => 1.0 / 3.0,
146 };
147 let vt_aspect_north = 1.0 - vt_aspect_ratio;
148 let vt_aspect_south = vt_aspect_ratio;
149
150 let mut theta_cartesian: Vec<Vec2> = vec![Vec2::ZERO; longitudes as usize];
151 let mut rho_theta_cartesian: Vec<Vec2> = vec![Vec2::ZERO; longitudes as usize];
152 let mut s_texture_cache: Vec<f32> = vec![0.0; lonsp1 as usize];
153
154 for j in 0..longitudes as usize {
155 let jf = j as f32;
156 let s_texture_polar = 1.0 - ((jf + 0.5) * to_tex_horizontal);
157 let theta = jf * to_theta;
158
159 theta_cartesian[j] = Vec2::from_angle(theta);
160 rho_theta_cartesian[j] = radius * theta_cartesian[j];
161
162 vs[j] = Vec3::new(0.0, summit, 0.0);
164 vts[j] = Vec2::new(s_texture_polar, 1.0);
165 vns[j] = Vec3::Y;
166
167 let idx = vert_offset_south_cap as usize + j;
169 vs[idx] = Vec3::new(0.0, -summit, 0.0);
170 vts[idx] = Vec2::new(s_texture_polar, 0.0);
171 vns[idx] = Vec3::new(0.0, -1.0, 0.0);
172 }
173
174 for (j, s_texture_cache_j) in s_texture_cache.iter_mut().enumerate().take(lonsp1 as usize) {
176 let s_texture = 1.0 - j as f32 * to_tex_horizontal;
177 *s_texture_cache_j = s_texture;
178
179 let j_mod = j % longitudes as usize;
181 let tc = theta_cartesian[j_mod];
182 let rtc = rho_theta_cartesian[j_mod];
183
184 let idxn = vert_offset_north_equator as usize + j;
186 vs[idxn] = Vec3::new(rtc.x, half_length, -rtc.y);
187 vts[idxn] = Vec2::new(s_texture, vt_aspect_north);
188 vns[idxn] = Vec3::new(tc.x, 0.0, -tc.y);
189
190 let idxs = vert_offset_south_equator as usize + j;
192 vs[idxs] = Vec3::new(rtc.x, -half_length, -rtc.y);
193 vts[idxs] = Vec2::new(s_texture, vt_aspect_south);
194 vns[idxs] = Vec3::new(tc.x, 0.0, -tc.y);
195 }
196
197 for i in 0..half_latsn1 {
199 let ip1f = i as f32 + 1.0;
200 let phi = ip1f * to_phi;
201
202 let (sin_phi_south, cos_phi_south) = ops::sin_cos(phi);
204
205 let cos_phi_north = sin_phi_south;
208 let sin_phi_north = -cos_phi_south;
209
210 let rho_cos_phi_north = radius * cos_phi_north;
211 let rho_sin_phi_north = radius * sin_phi_north;
212 let z_offset_north = half_length - rho_sin_phi_north;
213
214 let rho_cos_phi_south = radius * cos_phi_south;
215 let rho_sin_phi_south = radius * sin_phi_south;
216 let z_offset_sout = -half_length - rho_sin_phi_south;
217
218 let t_tex_fac = ip1f * to_tex_vertical;
220 let cmpl_tex_fac = 1.0 - t_tex_fac;
221 let t_tex_north = cmpl_tex_fac + vt_aspect_north * t_tex_fac;
222 let t_tex_south = cmpl_tex_fac * vt_aspect_south;
223
224 let i_lonsp1 = i * lonsp1;
225 let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1;
226 let vert_curr_lat_south = vert_offset_south_hemi + i_lonsp1;
227
228 for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1 as usize) {
229 let j_mod = j % longitudes as usize;
230
231 let tc = theta_cartesian[j_mod];
232
233 let idxn = vert_curr_lat_north as usize + j;
235 vs[idxn] = Vec3::new(
236 rho_cos_phi_north * tc.x,
237 z_offset_north,
238 -rho_cos_phi_north * tc.y,
239 );
240 vts[idxn] = Vec2::new(*s_texture, t_tex_north);
241 vns[idxn] = Vec3::new(cos_phi_north * tc.x, -sin_phi_north, -cos_phi_north * tc.y);
242
243 let idxs = vert_curr_lat_south as usize + j;
245 vs[idxs] = Vec3::new(
246 rho_cos_phi_south * tc.x,
247 z_offset_sout,
248 -rho_cos_phi_south * tc.y,
249 );
250 vts[idxs] = Vec2::new(*s_texture, t_tex_south);
251 vns[idxs] = Vec3::new(cos_phi_south * tc.x, -sin_phi_south, -cos_phi_south * tc.y);
252 }
253 }
254
255 if calc_middle {
257 let to_fac = 1.0 / ringsp1 as f32;
260 let mut idx_cyl_lat = vert_offset_cylinder as usize;
261
262 for h in 1..ringsp1 {
263 let fac = h as f32 * to_fac;
264 let cmpl_fac = 1.0 - fac;
265 let t_texture = cmpl_fac * vt_aspect_north + fac * vt_aspect_south;
266 let z = half_length - 2.0 * half_length * fac;
267
268 for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1 as usize) {
269 let j_mod = j % longitudes as usize;
270 let tc = theta_cartesian[j_mod];
271 let rtc = rho_theta_cartesian[j_mod];
272
273 vs[idx_cyl_lat] = Vec3::new(rtc.x, z, -rtc.y);
274 vts[idx_cyl_lat] = Vec2::new(*s_texture, t_texture);
275 vns[idx_cyl_lat] = Vec3::new(tc.x, 0.0, -tc.y);
276
277 idx_cyl_lat += 1;
278 }
279 }
280 }
281
282 let lons3 = longitudes * 3;
287 let lons6 = longitudes * 6;
288 let hemi_lons = half_latsn1 * lons6;
289
290 let tri_offset_north_hemi = lons3;
291 let tri_offset_cylinder = tri_offset_north_hemi + hemi_lons;
292 let tri_offset_south_hemi = tri_offset_cylinder + ringsp1 * lons6;
293 let tri_offset_south_cap = tri_offset_south_hemi + hemi_lons;
294
295 let fs_len = tri_offset_south_cap + lons3;
296 let mut tris: Vec<u32> = vec![0; fs_len as usize];
297
298 let mut i = 0;
300 let mut k = 0;
301 let mut m = tri_offset_south_cap as usize;
302 while i < longitudes {
303 tris[k] = i;
305 tris[k + 1] = vert_offset_north_hemi + i;
306 tris[k + 2] = vert_offset_north_hemi + i + 1;
307
308 tris[m] = vert_offset_south_cap + i;
310 tris[m + 1] = vert_offset_south_polar + i + 1;
311 tris[m + 2] = vert_offset_south_polar + i;
312
313 i += 1;
314 k += 3;
315 m += 3;
316 }
317
318 let mut i = 0;
321 let mut k = tri_offset_north_hemi as usize;
322 let mut m = tri_offset_south_hemi as usize;
323
324 while i < half_latsn1 {
325 let i_lonsp1 = i * lonsp1;
326
327 let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1;
328 let vert_next_lat_north = vert_curr_lat_north + lonsp1;
329
330 let vert_curr_lat_south = vert_offset_south_equator + i_lonsp1;
331 let vert_next_lat_south = vert_curr_lat_south + lonsp1;
332
333 let mut j = 0;
334 while j < longitudes {
335 let north00 = vert_curr_lat_north + j;
337 let north01 = vert_next_lat_north + j;
338 let north11 = vert_next_lat_north + j + 1;
339 let north10 = vert_curr_lat_north + j + 1;
340
341 tris[k] = north00;
342 tris[k + 1] = north11;
343 tris[k + 2] = north10;
344
345 tris[k + 3] = north00;
346 tris[k + 4] = north01;
347 tris[k + 5] = north11;
348
349 let south00 = vert_curr_lat_south + j;
351 let south01 = vert_next_lat_south + j;
352 let south11 = vert_next_lat_south + j + 1;
353 let south10 = vert_curr_lat_south + j + 1;
354
355 tris[m] = south00;
356 tris[m + 1] = south11;
357 tris[m + 2] = south10;
358
359 tris[m + 3] = south00;
360 tris[m + 4] = south01;
361 tris[m + 5] = south11;
362
363 j += 1;
364 k += 6;
365 m += 6;
366 }
367
368 i += 1;
369 }
370
371 let mut i = 0;
373 let mut k = tri_offset_cylinder as usize;
374
375 while i < ringsp1 {
376 let vert_curr_lat = vert_offset_north_equator + i * lonsp1;
377 let vert_next_lat = vert_curr_lat + lonsp1;
378
379 let mut j = 0;
380 while j < longitudes {
381 let cy00 = vert_curr_lat + j;
382 let cy01 = vert_next_lat + j;
383 let cy11 = vert_next_lat + j + 1;
384 let cy10 = vert_curr_lat + j + 1;
385
386 tris[k] = cy00;
387 tris[k + 1] = cy11;
388 tris[k + 2] = cy10;
389
390 tris[k + 3] = cy00;
391 tris[k + 4] = cy01;
392 tris[k + 5] = cy11;
393
394 j += 1;
395 k += 6;
396 }
397
398 i += 1;
399 }
400
401 let vs: Vec<[f32; 3]> = vs.into_iter().map(Into::into).collect();
402 let vns: Vec<[f32; 3]> = vns.into_iter().map(Into::into).collect();
403 let vts: Vec<[f32; 2]> = vts.into_iter().map(Into::into).collect();
404
405 assert_eq!(vs.len(), vert_len);
406 assert_eq!(tris.len(), fs_len as usize);
407
408 Mesh::new(
409 PrimitiveTopology::TriangleList,
410 RenderAssetUsages::default(),
411 )
412 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs)
413 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns)
414 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts)
415 .with_inserted_indices(Indices::U32(tris))
416 }
417}
418
419impl Meshable for Capsule3d {
420 type Output = Capsule3dMeshBuilder;
421
422 fn mesh(&self) -> Self::Output {
423 Capsule3dMeshBuilder {
424 capsule: *self,
425 ..Default::default()
426 }
427 }
428}
429
430impl From<Capsule3d> for Mesh {
431 fn from(capsule: Capsule3d) -> Self {
432 capsule.mesh().build()
433 }
434}