bevy_mesh/primitives/dim3/
conical_frustum.rs

1use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
2use bevy_asset::RenderAssetUsages;
3use bevy_math::{ops, primitives::ConicalFrustum, Vec3};
4use bevy_reflect::prelude::*;
5
6/// A builder used for creating a [`Mesh`] with a [`ConicalFrustum`] shape.
7#[derive(Clone, Copy, Debug, Reflect)]
8#[reflect(Default, Debug, Clone)]
9pub struct ConicalFrustumMeshBuilder {
10    /// The [`ConicalFrustum`] shape.
11    pub frustum: ConicalFrustum,
12    /// The number of vertices used for the top and bottom of the conical frustum.
13    ///
14    /// The default is `32`.
15    pub resolution: u32,
16    /// The number of horizontal lines subdividing the lateral surface of the conical frustum.
17    ///
18    /// The default is `1`.
19    pub segments: u32,
20}
21
22impl Default for ConicalFrustumMeshBuilder {
23    fn default() -> Self {
24        Self {
25            frustum: ConicalFrustum::default(),
26            resolution: 32,
27            segments: 1,
28        }
29    }
30}
31
32impl ConicalFrustumMeshBuilder {
33    /// Creates a new [`ConicalFrustumMeshBuilder`] from the given top and bottom radii, a height,
34    /// and a resolution used for the top and bottom.
35    #[inline]
36    pub const fn new(radius_top: f32, radius_bottom: f32, height: f32, resolution: u32) -> Self {
37        Self {
38            frustum: ConicalFrustum {
39                radius_top,
40                radius_bottom,
41                height,
42            },
43            resolution,
44            segments: 1,
45        }
46    }
47
48    /// Sets the number of vertices used for the top and bottom of the conical frustum.
49    #[inline]
50    pub const fn resolution(mut self, resolution: u32) -> Self {
51        self.resolution = resolution;
52        self
53    }
54
55    /// Sets the number of horizontal lines subdividing the lateral surface of the conical frustum.
56    #[inline]
57    pub const fn segments(mut self, segments: u32) -> Self {
58        self.segments = segments;
59        self
60    }
61}
62
63impl MeshBuilder for ConicalFrustumMeshBuilder {
64    fn build(&self) -> Mesh {
65        debug_assert!(self.resolution > 2);
66        debug_assert!(self.segments > 0);
67
68        let ConicalFrustum {
69            radius_top,
70            radius_bottom,
71            height,
72        } = self.frustum;
73        let half_height = height / 2.0;
74
75        let num_rings = self.segments + 1;
76        let num_vertices = (self.resolution * 2 + num_rings * (self.resolution + 1)) as usize;
77        let num_faces = self.resolution * (num_rings - 2);
78        let num_indices = ((2 * num_faces + 2 * (self.resolution - 1) * 2) * 3) as usize;
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        let step_theta = core::f32::consts::TAU / self.resolution as f32;
86        let step_y = height / self.segments as f32;
87        let step_radius = (radius_top - radius_bottom) / self.segments as f32;
88
89        // Rings
90        for ring in 0..num_rings {
91            let y = -half_height + ring as f32 * step_y;
92            let radius = radius_bottom + ring as f32 * step_radius;
93
94            for segment in 0..=self.resolution {
95                let theta = segment as f32 * step_theta;
96                let (sin, cos) = ops::sin_cos(theta);
97
98                positions.push([radius * cos, y, radius * sin]);
99                normals.push(
100                    Vec3::new(cos, (radius_bottom - radius_top) / height, sin)
101                        .normalize()
102                        .to_array(),
103                );
104                uvs.push([
105                    segment as f32 / self.resolution as f32,
106                    ring as f32 / self.segments as f32,
107                ]);
108            }
109        }
110
111        // Lateral surface
112        for i in 0..self.segments {
113            let ring = i * (self.resolution + 1);
114            let next_ring = (i + 1) * (self.resolution + 1);
115
116            for j in 0..self.resolution {
117                indices.extend_from_slice(&[
118                    ring + j,
119                    next_ring + j,
120                    ring + j + 1,
121                    next_ring + j,
122                    next_ring + j + 1,
123                    ring + j + 1,
124                ]);
125            }
126        }
127
128        // Caps
129        let mut build_cap = |top: bool, radius: f32| {
130            let offset = positions.len() as u32;
131            let (y, normal_y, winding) = if top {
132                (half_height, 1.0, (1, 0))
133            } else {
134                (-half_height, -1.0, (0, 1))
135            };
136
137            for i in 0..self.resolution {
138                let theta = i as f32 * step_theta;
139                let (sin, cos) = ops::sin_cos(theta);
140
141                positions.push([cos * radius, y, sin * radius]);
142                normals.push([0.0, normal_y, 0.0]);
143                uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
144            }
145
146            for i in 1..(self.resolution - 1) {
147                indices.extend_from_slice(&[
148                    offset,
149                    offset + i + winding.0,
150                    offset + i + winding.1,
151                ]);
152            }
153        };
154
155        build_cap(true, radius_top);
156        build_cap(false, radius_bottom);
157
158        Mesh::new(
159            PrimitiveTopology::TriangleList,
160            RenderAssetUsages::default(),
161        )
162        .with_inserted_indices(Indices::U32(indices))
163        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
164        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
165        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
166    }
167}
168
169impl Meshable for ConicalFrustum {
170    type Output = ConicalFrustumMeshBuilder;
171
172    fn mesh(&self) -> Self::Output {
173        ConicalFrustumMeshBuilder {
174            frustum: *self,
175            ..Default::default()
176        }
177    }
178}
179
180impl From<ConicalFrustum> for Mesh {
181    fn from(frustum: ConicalFrustum) -> Self {
182        frustum.mesh().build()
183    }
184}