bevy_mesh/primitives/dim3/
cone.rs1use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
2use bevy_asset::RenderAssetUsages;
3use bevy_math::{ops, primitives::Cone, Vec3};
4use bevy_reflect::prelude::*;
5
6#[derive(Debug, Copy, Clone, Default, Reflect)]
8#[reflect(Default, Debug, Clone)]
9pub enum ConeAnchor {
10 #[default]
11 MidPoint,
13 Tip,
15 Base,
17}
18
19#[derive(Clone, Copy, Debug, Reflect)]
21#[reflect(Default, Debug, Clone)]
22pub struct ConeMeshBuilder {
23 pub cone: Cone,
25 pub resolution: u32,
29 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 #[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 #[inline]
58 pub const fn resolution(mut self, resolution: u32) -> Self {
59 self.resolution = resolution;
60 self
61 }
62
63 #[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 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 positions.push([0.0, half_height, 0.0]);
87
88 normals.push([0.0, 0.0, 0.0]);
96
97 uvs.push([0.5, 0.5]);
100
101 let normal_slope = self.cone.radius / self.cone.height;
109 let normalization_factor = (1.0 + normal_slope * normal_slope).sqrt().recip();
111
112 let step_theta = core::f32::consts::TAU / self.resolution as f32;
114
115 for segment in 0..self.resolution {
117 let theta = segment as f32 * step_theta;
118 let (sin, cos) = ops::sin_cos(theta);
119
120 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 for j in 1..self.resolution {
131 indices.extend_from_slice(&[0, j + 1, j]);
132 }
133
134 indices.extend_from_slice(&[0, 1, self.resolution]);
136
137 let index_offset = positions.len() as u32;
140
141 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 for i in 1..(self.resolution - 1) {
153 indices.extend_from_slice(&[index_offset, index_offset + i, index_offset + i + 1]);
154 }
155
156 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 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 assert_eq!(
234 [
235 [0.0, 0.5, 0.0],
237 [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 [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 let [x, y] = Vec2::new(0.5, -1.0).perp().normalize().to_array();
253 assert_eq!(
254 &[
255 [0.0, 0.0, 0.0],
257 [x, y, 0.0],
259 [0.0, y, x],
260 [-x, y, 0.0],
261 [0.0, y, -x],
262 [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}