bevy_render/mesh/primitives/dim3/
cylinder.rs

1use bevy_math::primitives::Cylinder;
2use wgpu::PrimitiveTopology;
3
4use crate::{
5    mesh::{Indices, Mesh, MeshBuilder, Meshable},
6    render_asset::RenderAssetUsages,
7};
8
9/// Anchoring options for [`CylinderMeshBuilder`]
10#[derive(Debug, Copy, Clone, Default)]
11pub enum CylinderAnchor {
12    #[default]
13    /// Midpoint between the top and bottom caps of the cylinder
14    MidPoint,
15    /// The center of the top circle cap
16    Top,
17    /// The center of the bottom circle cap
18    Bottom,
19}
20
21/// A builder used for creating a [`Mesh`] with a [`Cylinder`] shape.
22#[derive(Clone, Copy, Debug)]
23pub struct CylinderMeshBuilder {
24    /// The [`Cylinder`] shape.
25    pub cylinder: Cylinder,
26    /// The number of vertices used for the top and bottom of the cylinder.
27    ///
28    /// The default is `32`.
29    pub resolution: u32,
30    /// The number of segments along the height of the cylinder.
31    /// Must be greater than `0` for geometry to be generated.
32    ///
33    /// The default is `1`.
34    pub segments: u32,
35    /// If set to `true`, the cylinder caps (flat circle faces) are built,
36    /// otherwise the mesh will be a shallow tube
37    pub caps: bool,
38    /// The anchor point for the cylinder mesh, defaults to the midpoint between
39    /// the top and bottom caps
40    pub anchor: CylinderAnchor,
41}
42
43impl Default for CylinderMeshBuilder {
44    fn default() -> Self {
45        Self {
46            cylinder: Cylinder::default(),
47            resolution: 32,
48            segments: 1,
49            caps: true,
50            anchor: CylinderAnchor::default(),
51        }
52    }
53}
54
55impl CylinderMeshBuilder {
56    /// Creates a new [`CylinderMeshBuilder`] from the given radius, a height,
57    /// and a resolution used for the top and bottom.
58    #[inline]
59    pub fn new(radius: f32, height: f32, resolution: u32) -> Self {
60        Self {
61            cylinder: Cylinder::new(radius, height),
62            resolution,
63            ..Default::default()
64        }
65    }
66
67    /// Sets the number of vertices used for the top and bottom of the cylinder.
68    #[inline]
69    pub const fn resolution(mut self, resolution: u32) -> Self {
70        self.resolution = resolution;
71        self
72    }
73
74    /// Sets the number of segments along the height of the cylinder.
75    /// Must be greater than `0` for geometry to be generated.
76    #[inline]
77    pub const fn segments(mut self, segments: u32) -> Self {
78        self.segments = segments;
79        self
80    }
81
82    /// Ignore the cylinder caps, making the mesh a shallow tube instead
83    #[inline]
84    pub const fn without_caps(mut self) -> Self {
85        self.caps = false;
86        self
87    }
88
89    /// Sets a custom anchor point for the mesh
90    #[inline]
91    pub const fn anchor(mut self, anchor: CylinderAnchor) -> Self {
92        self.anchor = anchor;
93        self
94    }
95}
96
97impl MeshBuilder for CylinderMeshBuilder {
98    fn build(&self) -> Mesh {
99        let resolution = self.resolution;
100        let segments = self.segments;
101
102        debug_assert!(resolution > 2);
103        debug_assert!(segments > 0);
104
105        let num_rings = segments + 1;
106        let num_vertices = resolution * 2 + num_rings * (resolution + 1);
107        let num_faces = resolution * (num_rings - 2);
108        let num_indices = (2 * num_faces + 2 * (resolution - 1) * 2) * 3;
109
110        let mut positions = Vec::with_capacity(num_vertices as usize);
111        let mut normals = Vec::with_capacity(num_vertices as usize);
112        let mut uvs = Vec::with_capacity(num_vertices as usize);
113        let mut indices = Vec::with_capacity(num_indices as usize);
114
115        let step_theta = std::f32::consts::TAU / resolution as f32;
116        let step_y = 2.0 * self.cylinder.half_height / segments as f32;
117
118        // rings
119
120        for ring in 0..num_rings {
121            let y = -self.cylinder.half_height + ring as f32 * step_y;
122
123            for segment in 0..=resolution {
124                let theta = segment as f32 * step_theta;
125                let (sin, cos) = theta.sin_cos();
126
127                positions.push([self.cylinder.radius * cos, y, self.cylinder.radius * sin]);
128                normals.push([cos, 0., sin]);
129                uvs.push([
130                    segment as f32 / resolution as f32,
131                    ring as f32 / segments as f32,
132                ]);
133            }
134        }
135
136        // barrel skin
137
138        for i in 0..segments {
139            let ring = i * (resolution + 1);
140            let next_ring = (i + 1) * (resolution + 1);
141
142            for j in 0..resolution {
143                indices.extend_from_slice(&[
144                    ring + j,
145                    next_ring + j,
146                    ring + j + 1,
147                    next_ring + j,
148                    next_ring + j + 1,
149                    ring + j + 1,
150                ]);
151            }
152        }
153
154        // caps
155        if self.caps {
156            let mut build_cap = |top: bool| {
157                let offset = positions.len() as u32;
158                let (y, normal_y, winding) = if top {
159                    (self.cylinder.half_height, 1., (1, 0))
160                } else {
161                    (-self.cylinder.half_height, -1., (0, 1))
162                };
163
164                for i in 0..self.resolution {
165                    let theta = i as f32 * step_theta;
166                    let (sin, cos) = theta.sin_cos();
167
168                    positions.push([cos * self.cylinder.radius, y, sin * self.cylinder.radius]);
169                    normals.push([0.0, normal_y, 0.0]);
170                    uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
171                }
172
173                for i in 1..(self.resolution - 1) {
174                    indices.extend_from_slice(&[
175                        offset,
176                        offset + i + winding.0,
177                        offset + i + winding.1,
178                    ]);
179                }
180            };
181
182            build_cap(true);
183            build_cap(false);
184        }
185
186        // Offset the vertex positions Y axis to match the anchor
187        match self.anchor {
188            CylinderAnchor::Top => positions
189                .iter_mut()
190                .for_each(|p| p[1] -= self.cylinder.half_height),
191            CylinderAnchor::Bottom => positions
192                .iter_mut()
193                .for_each(|p| p[1] += self.cylinder.half_height),
194            CylinderAnchor::MidPoint => (),
195        };
196
197        Mesh::new(
198            PrimitiveTopology::TriangleList,
199            RenderAssetUsages::default(),
200        )
201        .with_inserted_indices(Indices::U32(indices))
202        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
203        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
204        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
205    }
206}
207
208impl Meshable for Cylinder {
209    type Output = CylinderMeshBuilder;
210
211    fn mesh(&self) -> Self::Output {
212        CylinderMeshBuilder {
213            cylinder: *self,
214            ..Default::default()
215        }
216    }
217}
218
219impl From<Cylinder> for Mesh {
220    fn from(cylinder: Cylinder) -> Self {
221        cylinder.mesh().build()
222    }
223}