bevy_mesh/primitives/dim3/
cylinder.rs

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