Skip to main content

bevy_mesh/primitives/dim3/
capsule.rs

1use 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/// Manner in which UV coordinates are distributed vertically.
8#[derive(Clone, Copy, Debug, Default, Reflect)]
9#[reflect(Default, Debug, Clone)]
10pub enum CapsuleUvProfile {
11    /// UV space is distributed by how much of the capsule consists of the hemispheres.
12    #[default]
13    Aspect,
14    /// Hemispheres get UV space according to the ratio of latitudes to rings.
15    Uniform,
16    /// Upper third of the texture goes to the northern hemisphere, middle third to the cylinder
17    /// and lower third to the southern one.
18    Fixed,
19}
20
21/// A builder used for creating a [`Mesh`] with a [`Capsule3d`] shape.
22#[derive(Clone, Copy, Debug, Reflect)]
23#[reflect(Default, Debug, Clone)]
24pub struct Capsule3dMeshBuilder {
25    /// The [`Capsule3d`] shape.
26    pub capsule: Capsule3d,
27    /// The number of horizontal lines subdividing the cylindrical part of the capsule.
28    /// The default is `0`.
29    pub rings: u32,
30    /// The number of vertical lines subdividing the hemispheres of the capsule.
31    /// The default is `32`.
32    pub longitudes: u32,
33    /// The number of horizontal lines subdividing the hemispheres of the capsule.
34    /// The default is `16`.
35    pub latitudes: u32,
36    /// The manner in which UV coordinates are distributed vertically.
37    /// The default is [`CapsuleUvProfile::Aspect`].
38    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    /// Creates a new [`Capsule3dMeshBuilder`] from a given radius, height, longitudes, and latitudes.
55    ///
56    /// Note that `height` is the distance between the centers of the hemispheres.
57    /// `radius` will be added to both ends to get the real height of the mesh.
58    #[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    /// Sets the number of horizontal lines subdividing the cylindrical part of the capsule.
69    #[inline]
70    pub const fn rings(mut self, rings: u32) -> Self {
71        self.rings = rings;
72        self
73    }
74
75    /// Sets the number of vertical lines subdividing the hemispheres of the capsule.
76    #[inline]
77    pub const fn longitudes(mut self, longitudes: u32) -> Self {
78        self.longitudes = longitudes;
79        self
80    }
81
82    /// Sets the number of horizontal lines subdividing the hemispheres of the capsule.
83    #[inline]
84    pub const fn latitudes(mut self, latitudes: u32) -> Self {
85        self.latitudes = latitudes;
86        self
87    }
88
89    /// Sets the manner in which UV coordinates are distributed vertically.
90    #[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        // code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db
100        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        // Vertex index offsets.
121        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        // Initialize arrays.
134        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            // North.
166            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            // South.
171            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        // Equatorial vertices.
178        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            // Wrap to first element upon reaching last.
183            let j_mod = j % longitudes as usize;
184            let tc = theta_cartesian[j_mod];
185            let rtc = rho_theta_cartesian[j_mod];
186
187            // North equator.
188            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            // South equator.
194            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        // Hemisphere vertices.
201        for i in 0..half_latsn1 {
202            let ip1f = i as f32 + 1.0;
203            let phi = ip1f * to_phi;
204
205            // For coordinates.
206            let (sin_phi_south, cos_phi_south) = ops::sin_cos(phi);
207
208            // Symmetrical hemispheres mean cosine and sine only needs
209            // to be calculated once.
210            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            // For texture coordinates.
222            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                // North hemisphere.
237                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                // South hemisphere.
247                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        // Cylinder vertices.
259        if calc_middle {
260            // Exclude both origin and destination edges
261            // (North and South equators) from the interpolation.
262            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        // Triangle indices.
286
287        // Stride is 3 for polar triangles;
288        // stride is 6 for two triangles forming a quad.
289        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        // Polar caps.
302        let mut i = 0;
303        let mut k = 0;
304        let mut m = tri_offset_south_cap as usize;
305        while i < longitudes {
306            // North.
307            tris[k] = i;
308            tris[k + 1] = vert_offset_north_hemi + i;
309            tris[k + 2] = vert_offset_north_hemi + i + 1;
310
311            // South.
312            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        // Hemispheres.
322
323        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                // North.
339                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                // South.
353                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        // Cylinder.
375        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}