bevy_mesh/primitives/dim3/
cone.rs

1use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
2use bevy_asset::RenderAssetUsages;
3use bevy_math::{ops, primitives::Cone, Vec3};
4use bevy_reflect::prelude::*;
5
6/// Anchoring options for [`ConeMeshBuilder`]
7#[derive(Debug, Copy, Clone, Default, Reflect)]
8#[reflect(Default, Debug, Clone)]
9pub enum ConeAnchor {
10    #[default]
11    /// Midpoint between the tip of the cone and the center of its base.
12    MidPoint,
13    /// The Tip of the triangle
14    Tip,
15    /// The center of the base circle
16    Base,
17}
18
19/// A builder used for creating a [`Mesh`] with a [`Cone`] shape.
20#[derive(Clone, Copy, Debug, Reflect)]
21#[reflect(Default, Debug, Clone)]
22pub struct ConeMeshBuilder {
23    /// The [`Cone`] shape.
24    pub cone: Cone,
25    /// The number of vertices used for the base of the cone.
26    ///
27    /// The default is `32`.
28    pub resolution: u32,
29    /// The anchor point for the cone mesh, defaults to the midpoint between
30    /// the tip of the cone and the center of its base
31    pub anchor: ConeAnchor,
32}
33
34impl Default for ConeMeshBuilder {
35    fn default() -> Self {
36        Self {
37            cone: Cone::default(),
38            resolution: 32,
39            anchor: ConeAnchor::default(),
40        }
41    }
42}
43
44impl ConeMeshBuilder {
45    /// Creates a new [`ConeMeshBuilder`] from a given radius, height,
46    /// and number of vertices used for the base of the cone.
47    #[inline]
48    pub const fn new(radius: f32, height: f32, resolution: u32) -> Self {
49        Self {
50            cone: Cone { radius, height },
51            resolution,
52            anchor: ConeAnchor::MidPoint,
53        }
54    }
55
56    /// Sets the number of vertices used for the base of the cone.
57    #[inline]
58    pub const fn resolution(mut self, resolution: u32) -> Self {
59        self.resolution = resolution;
60        self
61    }
62
63    /// Sets a custom anchor point for the mesh
64    #[inline]
65    pub const fn anchor(mut self, anchor: ConeAnchor) -> Self {
66        self.anchor = anchor;
67        self
68    }
69}
70
71impl MeshBuilder for ConeMeshBuilder {
72    fn build(&self) -> Mesh {
73        let half_height = self.cone.height / 2.0;
74
75        // `resolution` vertices for the base, `resolution` vertices for the bottom of the lateral surface,
76        // and one vertex for the tip.
77        let num_vertices = self.resolution as usize * 2 + 1;
78        let num_indices = self.resolution as usize * 6 - 6;
79
80        let mut positions = Vec::with_capacity(num_vertices);
81        let mut normals = Vec::with_capacity(num_vertices);
82        let mut uvs = Vec::with_capacity(num_vertices);
83        let mut indices = Vec::with_capacity(num_indices);
84
85        // Tip
86        positions.push([0.0, half_height, 0.0]);
87
88        // The tip doesn't have a singular normal that works correctly.
89        // We use an invalid normal here so that it becomes NaN in the fragment shader
90        // and doesn't affect the overall shading. This might seem hacky, but it's one of
91        // the only ways to get perfectly smooth cones without creases or other shading artifacts.
92        //
93        // Note that this requires that normals are not normalized in the vertex shader,
94        // as that would make the entire triangle invalid and make the cone appear as black.
95        normals.push([0.0, 0.0, 0.0]);
96
97        // The UVs of the cone are in polar coordinates, so it's like projecting a circle texture from above.
98        // The center of the texture is at the center of the lateral surface, at the tip of the cone.
99        uvs.push([0.5, 0.5]);
100
101        // Now we build the lateral surface, the side of the cone.
102
103        // The vertex normals will be perpendicular to the surface.
104        //
105        // Here we get the slope of a normal and use it for computing
106        // the multiplicative inverse of the length of a vector in the direction
107        // of the normal. This allows us to normalize vertex normals efficiently.
108        let normal_slope = self.cone.radius / self.cone.height;
109        // Equivalent to Vec2::new(1.0, slope).length().recip()
110        let normalization_factor = (1.0 + normal_slope * normal_slope).sqrt().recip();
111
112        // How much the angle changes at each step
113        let step_theta = core::f32::consts::TAU / self.resolution as f32;
114
115        // Add vertices for the bottom of the lateral surface.
116        for segment in 0..self.resolution {
117            let theta = segment as f32 * step_theta;
118            let (sin, cos) = ops::sin_cos(theta);
119
120            // The vertex normal perpendicular to the side
121            let normal = Vec3::new(cos, normal_slope, sin) * normalization_factor;
122
123            positions.push([self.cone.radius * cos, -half_height, self.cone.radius * sin]);
124            normals.push(normal.to_array());
125            uvs.push([0.5 + cos * 0.5, 0.5 + sin * 0.5]);
126        }
127
128        // Add indices for the lateral surface. Each triangle is formed by the tip
129        // and two vertices at the base.
130        for j in 1..self.resolution {
131            indices.extend_from_slice(&[0, j + 1, j]);
132        }
133
134        // Close the surface with a triangle between the tip, first base vertex, and last base vertex.
135        indices.extend_from_slice(&[0, 1, self.resolution]);
136
137        // Now we build the actual base of the cone.
138
139        let index_offset = positions.len() as u32;
140
141        // Add base vertices.
142        for i in 0..self.resolution {
143            let theta = i as f32 * step_theta;
144            let (sin, cos) = ops::sin_cos(theta);
145
146            positions.push([cos * self.cone.radius, -half_height, sin * self.cone.radius]);
147            normals.push([0.0, -1.0, 0.0]);
148            uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
149        }
150
151        // Add base indices.
152        for i in 1..(self.resolution - 1) {
153            indices.extend_from_slice(&[index_offset, index_offset + i, index_offset + i + 1]);
154        }
155
156        // Offset the vertex positions Y axis to match the anchor
157        match self.anchor {
158            ConeAnchor::Tip => positions.iter_mut().for_each(|p| p[1] -= half_height),
159            ConeAnchor::Base => positions.iter_mut().for_each(|p| p[1] += half_height),
160            ConeAnchor::MidPoint => (),
161        };
162
163        Mesh::new(
164            PrimitiveTopology::TriangleList,
165            RenderAssetUsages::default(),
166        )
167        .with_inserted_indices(Indices::U32(indices))
168        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
169        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
170        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
171    }
172}
173
174impl Meshable for Cone {
175    type Output = ConeMeshBuilder;
176
177    fn mesh(&self) -> Self::Output {
178        ConeMeshBuilder {
179            cone: *self,
180            ..Default::default()
181        }
182    }
183}
184
185impl From<Cone> for Mesh {
186    fn from(cone: Cone) -> Self {
187        cone.mesh().build()
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};
194    use bevy_math::{primitives::Cone, Vec2};
195
196    /// Rounds floats to handle floating point error in tests.
197    fn round_floats<const N: usize>(points: &mut [[f32; N]]) {
198        for point in points.iter_mut() {
199            for coord in point.iter_mut() {
200                let round = (*coord * 100.0).round() / 100.0;
201                if (*coord - round).abs() < 0.00001 {
202                    *coord = round;
203                }
204            }
205        }
206    }
207
208    #[test]
209    fn cone_mesh() {
210        let mut mesh = Cone {
211            radius: 0.5,
212            height: 1.0,
213        }
214        .mesh()
215        .resolution(4)
216        .build();
217
218        let Some(VertexAttributeValues::Float32x3(mut positions)) =
219            mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)
220        else {
221            panic!("Expected positions f32x3");
222        };
223        let Some(VertexAttributeValues::Float32x3(mut normals)) =
224            mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)
225        else {
226            panic!("Expected normals f32x3");
227        };
228
229        round_floats(&mut positions);
230        round_floats(&mut normals);
231
232        // Vertex positions
233        assert_eq!(
234            [
235                // Tip
236                [0.0, 0.5, 0.0],
237                // Lateral surface
238                [0.5, -0.5, 0.0],
239                [0.0, -0.5, 0.5],
240                [-0.5, -0.5, 0.0],
241                [0.0, -0.5, -0.5],
242                // Base
243                [0.5, -0.5, 0.0],
244                [0.0, -0.5, 0.5],
245                [-0.5, -0.5, 0.0],
246                [0.0, -0.5, -0.5],
247            ],
248            &positions[..]
249        );
250
251        // Vertex normals
252        let [x, y] = Vec2::new(0.5, -1.0).perp().normalize().to_array();
253        assert_eq!(
254            &[
255                // Tip
256                [0.0, 0.0, 0.0],
257                // Lateral surface
258                [x, y, 0.0],
259                [0.0, y, x],
260                [-x, y, 0.0],
261                [0.0, y, -x],
262                // Base
263                [0.0, -1.0, 0.0],
264                [0.0, -1.0, 0.0],
265                [0.0, -1.0, 0.0],
266                [0.0, -1.0, 0.0],
267            ],
268            &normals[..]
269        );
270    }
271}