bevy_mesh/primitives/dim3/
cone.rs1use crate::{Indices, Mesh, MeshBuilder, Meshable};
2use bevy_asset::RenderAssetUsages;
3use bevy_math::{ops, primitives::Cone, Vec3};
4use wgpu::PrimitiveTopology;
5
6#[derive(Debug, Copy, Clone, Default)]
8pub enum ConeAnchor {
9 #[default]
10 MidPoint,
12 Tip,
14 Base,
16}
17
18#[derive(Clone, Copy, Debug)]
20pub struct ConeMeshBuilder {
21 pub cone: Cone,
23 pub resolution: u32,
27 pub anchor: ConeAnchor,
30}
31
32impl Default for ConeMeshBuilder {
33 fn default() -> Self {
34 Self {
35 cone: Cone::default(),
36 resolution: 32,
37 anchor: ConeAnchor::default(),
38 }
39 }
40}
41
42impl ConeMeshBuilder {
43 #[inline]
46 pub const fn new(radius: f32, height: f32, resolution: u32) -> Self {
47 Self {
48 cone: Cone { radius, height },
49 resolution,
50 anchor: ConeAnchor::MidPoint,
51 }
52 }
53
54 #[inline]
56 pub const fn resolution(mut self, resolution: u32) -> Self {
57 self.resolution = resolution;
58 self
59 }
60
61 #[inline]
63 pub const fn anchor(mut self, anchor: ConeAnchor) -> Self {
64 self.anchor = anchor;
65 self
66 }
67}
68
69impl MeshBuilder for ConeMeshBuilder {
70 fn build(&self) -> Mesh {
71 let half_height = self.cone.height / 2.0;
72
73 let num_vertices = self.resolution as usize * 2 + 1;
76 let num_indices = self.resolution as usize * 6 - 6;
77
78 let mut positions = Vec::with_capacity(num_vertices);
79 let mut normals = Vec::with_capacity(num_vertices);
80 let mut uvs = Vec::with_capacity(num_vertices);
81 let mut indices = Vec::with_capacity(num_indices);
82
83 positions.push([0.0, half_height, 0.0]);
85
86 normals.push([0.0, 0.0, 0.0]);
94
95 uvs.push([0.5, 0.5]);
98
99 let normal_slope = self.cone.radius / self.cone.height;
107 let normalization_factor = (1.0 + normal_slope * normal_slope).sqrt().recip();
109
110 let step_theta = core::f32::consts::TAU / self.resolution as f32;
112
113 for segment in 0..self.resolution {
115 let theta = segment as f32 * step_theta;
116 let (sin, cos) = ops::sin_cos(theta);
117
118 let normal = Vec3::new(cos, normal_slope, sin) * normalization_factor;
120
121 positions.push([self.cone.radius * cos, -half_height, self.cone.radius * sin]);
122 normals.push(normal.to_array());
123 uvs.push([0.5 + cos * 0.5, 0.5 + sin * 0.5]);
124 }
125
126 for j in 1..self.resolution {
129 indices.extend_from_slice(&[0, j + 1, j]);
130 }
131
132 indices.extend_from_slice(&[0, 1, self.resolution]);
134
135 let index_offset = positions.len() as u32;
138
139 for i in 0..self.resolution {
141 let theta = i as f32 * step_theta;
142 let (sin, cos) = ops::sin_cos(theta);
143
144 positions.push([cos * self.cone.radius, -half_height, sin * self.cone.radius]);
145 normals.push([0.0, -1.0, 0.0]);
146 uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
147 }
148
149 for i in 1..(self.resolution - 1) {
151 indices.extend_from_slice(&[index_offset, index_offset + i, index_offset + i + 1]);
152 }
153
154 match self.anchor {
156 ConeAnchor::Tip => positions.iter_mut().for_each(|p| p[1] -= half_height),
157 ConeAnchor::Base => positions.iter_mut().for_each(|p| p[1] += half_height),
158 ConeAnchor::MidPoint => (),
159 };
160
161 Mesh::new(
162 PrimitiveTopology::TriangleList,
163 RenderAssetUsages::default(),
164 )
165 .with_inserted_indices(Indices::U32(indices))
166 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
167 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
168 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
169 }
170}
171
172impl Meshable for Cone {
173 type Output = ConeMeshBuilder;
174
175 fn mesh(&self) -> Self::Output {
176 ConeMeshBuilder {
177 cone: *self,
178 ..Default::default()
179 }
180 }
181}
182
183impl From<Cone> for Mesh {
184 fn from(cone: Cone) -> Self {
185 cone.mesh().build()
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};
192 use bevy_math::{primitives::Cone, Vec2};
193
194 fn round_floats<const N: usize>(points: &mut [[f32; N]]) {
196 for point in points.iter_mut() {
197 for coord in point.iter_mut() {
198 let round = (*coord * 100.0).round() / 100.0;
199 if (*coord - round).abs() < 0.00001 {
200 *coord = round;
201 }
202 }
203 }
204 }
205
206 #[test]
207 fn cone_mesh() {
208 let mut mesh = Cone {
209 radius: 0.5,
210 height: 1.0,
211 }
212 .mesh()
213 .resolution(4)
214 .build();
215
216 let Some(VertexAttributeValues::Float32x3(mut positions)) =
217 mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)
218 else {
219 panic!("Expected positions f32x3");
220 };
221 let Some(VertexAttributeValues::Float32x3(mut normals)) =
222 mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)
223 else {
224 panic!("Expected normals f32x3");
225 };
226
227 round_floats(&mut positions);
228 round_floats(&mut normals);
229
230 assert_eq!(
232 [
233 [0.0, 0.5, 0.0],
235 [0.5, -0.5, 0.0],
237 [0.0, -0.5, 0.5],
238 [-0.5, -0.5, 0.0],
239 [0.0, -0.5, -0.5],
240 [0.5, -0.5, 0.0],
242 [0.0, -0.5, 0.5],
243 [-0.5, -0.5, 0.0],
244 [0.0, -0.5, -0.5],
245 ],
246 &positions[..]
247 );
248
249 let [x, y] = Vec2::new(0.5, -1.0).perp().normalize().to_array();
251 assert_eq!(
252 &[
253 [0.0, 0.0, 0.0],
255 [x, y, 0.0],
257 [0.0, y, x],
258 [-x, y, 0.0],
259 [0.0, y, -x],
260 [0.0, -1.0, 0.0],
262 [0.0, -1.0, 0.0],
263 [0.0, -1.0, 0.0],
264 [0.0, -1.0, 0.0],
265 ],
266 &normals[..]
267 );
268 }
269}