bevy_mesh/primitives/dim3/
capsule.rs

1use crate::{Indices, Mesh, MeshBuilder, Meshable};
2use bevy_asset::RenderAssetUsages;
3use bevy_math::{ops, primitives::Capsule3d, Vec2, Vec3};
4use wgpu::PrimitiveTopology;
5
6/// Manner in which UV coordinates are distributed vertically.
7#[derive(Clone, Copy, Debug, Default)]
8pub enum CapsuleUvProfile {
9    /// UV space is distributed by how much of the capsule consists of the hemispheres.
10    #[default]
11    Aspect,
12    /// Hemispheres get UV space according to the ratio of latitudes to rings.
13    Uniform,
14    /// Upper third of the texture goes to the northern hemisphere, middle third to the cylinder
15    /// and lower third to the southern one.
16    Fixed,
17}
18
19/// A builder used for creating a [`Mesh`] with a [`Capsule3d`] shape.
20#[derive(Clone, Copy, Debug)]
21pub struct Capsule3dMeshBuilder {
22    /// The [`Capsule3d`] shape.
23    pub capsule: Capsule3d,
24    /// The number of horizontal lines subdividing the cylindrical part of the capsule.
25    /// The default is `0`.
26    pub rings: u32,
27    /// The number of vertical lines subdividing the hemispheres of the capsule.
28    /// The default is `32`.
29    pub longitudes: u32,
30    /// The number of horizontal lines subdividing the hemispheres of the capsule.
31    /// The default is `16`.
32    pub latitudes: u32,
33    /// The manner in which UV coordinates are distributed vertically.
34    /// The default is [`CapsuleUvProfile::Aspect`].
35    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    /// Creates a new [`Capsule3dMeshBuilder`] from a given radius, height, longitudes, and latitudes.
52    ///
53    /// Note that `height` is the distance between the centers of the hemispheres.
54    /// `radius` will be added to both ends to get the real height of the mesh.
55    #[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    /// Sets the number of horizontal lines subdividing the cylindrical part of the capsule.
66    #[inline]
67    pub const fn rings(mut self, rings: u32) -> Self {
68        self.rings = rings;
69        self
70    }
71
72    /// Sets the number of vertical lines subdividing the hemispheres of the capsule.
73    #[inline]
74    pub const fn longitudes(mut self, longitudes: u32) -> Self {
75        self.longitudes = longitudes;
76        self
77    }
78
79    /// Sets the number of horizontal lines subdividing the hemispheres of the capsule.
80    #[inline]
81    pub const fn latitudes(mut self, latitudes: u32) -> Self {
82        self.latitudes = latitudes;
83        self
84    }
85
86    /// Sets the manner in which UV coordinates are distributed vertically.
87    #[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        // code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db
97        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        // Vertex index offsets.
118        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        // Initialize arrays.
131        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            // North.
163            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            // South.
168            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        // Equatorial vertices.
175        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            // Wrap to first element upon reaching last.
180            let j_mod = j % longitudes as usize;
181            let tc = theta_cartesian[j_mod];
182            let rtc = rho_theta_cartesian[j_mod];
183
184            // North equator.
185            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            // South equator.
191            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        // Hemisphere vertices.
198        for i in 0..half_latsn1 {
199            let ip1f = i as f32 + 1.0;
200            let phi = ip1f * to_phi;
201
202            // For coordinates.
203            let (sin_phi_south, cos_phi_south) = ops::sin_cos(phi);
204
205            // Symmetrical hemispheres mean cosine and sine only needs
206            // to be calculated once.
207            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            // For texture coordinates.
219            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                // North hemisphere.
234                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                // South hemisphere.
244                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        // Cylinder vertices.
256        if calc_middle {
257            // Exclude both origin and destination edges
258            // (North and South equators) from the interpolation.
259            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        // Triangle indices.
283
284        // Stride is 3 for polar triangles;
285        // stride is 6 for two triangles forming a quad.
286        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        // Polar caps.
299        let mut i = 0;
300        let mut k = 0;
301        let mut m = tri_offset_south_cap as usize;
302        while i < longitudes {
303            // North.
304            tris[k] = i;
305            tris[k + 1] = vert_offset_north_hemi + i;
306            tris[k + 2] = vert_offset_north_hemi + i + 1;
307
308            // South.
309            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        // Hemispheres.
319
320        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                // North.
336                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                // South.
350                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        // Cylinder.
372        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}