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