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