bevy_mesh/primitives/dim3/
cylinder.rs

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