bevy_render/mesh/primitives/dim3/
capsule.rs

1use crate::{
2    mesh::{Indices, Mesh, MeshBuilder, Meshable},
3    render_asset::RenderAssetUsages,
4};
5use bevy_math::{primitives::Capsule3d, Vec2, Vec3};
6use wgpu::PrimitiveTopology;
7
8/// Manner in which UV coordinates are distributed vertically.
9#[derive(Clone, Copy, Debug, Default)]
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)]
23pub 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: usize,
29    /// The number of vertical lines subdividing the hemispheres of the capsule.
30    /// The default is `32`.
31    pub longitudes: usize,
32    /// The number of horizontal lines subdividing the hemispheres of the capsule.
33    /// The default is `16`.
34    pub latitudes: usize,
35    /// The manner in which UV coordinates are distributed vertically.
36    /// The default is [`CapsuleUvProfile::Aspect`].
37    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    /// 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: usize, latitudes: usize) -> 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: usize) -> 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: usize) -> 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: usize) -> 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
96impl 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;
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            // North.
168            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            // South.
173            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        // Equatorial vertices.
180        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            // Wrap to first element upon reaching last.
185            let j_mod = j % longitudes;
186            let tc = theta_cartesian[j_mod];
187            let rtc = rho_theta_cartesian[j_mod];
188
189            // North equator.
190            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            // South equator.
196            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        // Hemisphere vertices.
203        for i in 0..half_latsn1 {
204            let ip1f = i as f32 + 1.0;
205            let phi = ip1f * to_phi;
206
207            // For coordinates.
208            let cos_phi_south = phi.cos();
209            let sin_phi_south = phi.sin();
210
211            // Symmetrical hemispheres mean cosine and sine only needs
212            // to be calculated once.
213            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            // For texture coordinates.
225            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                // North hemisphere.
240                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                // South hemisphere.
250                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        // Cylinder vertices.
262        if calc_middle {
263            // Exclude both origin and destination edges
264            // (North and South equators) from the interpolation.
265            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        // Triangle indices.
289
290        // Stride is 3 for polar triangles;
291        // stride is 6 for two triangles forming a quad.
292        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        // Polar caps.
305        let mut i = 0;
306        let mut k = 0;
307        let mut m = tri_offset_south_cap;
308        while i < longitudes {
309            // North.
310            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            // South.
315            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        // Hemispheres.
325
326        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                // North.
342                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                // South.
356                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        // Cylinder.
378        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}