Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_mesh/src/primitives/dim3/cone.rs
6598 views
1
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
2
use bevy_asset::RenderAssetUsages;
3
use bevy_math::{ops, primitives::Cone, Vec3};
4
use bevy_reflect::prelude::*;
5
6
/// Anchoring options for [`ConeMeshBuilder`]
7
#[derive(Debug, Copy, Clone, Default, Reflect)]
8
#[reflect(Default, Debug, Clone)]
9
pub 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)]
22
pub 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
34
impl Default for ConeMeshBuilder {
35
fn default() -> Self {
36
Self {
37
cone: Cone::default(),
38
resolution: 32,
39
anchor: ConeAnchor::default(),
40
}
41
}
42
}
43
44
impl 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
71
impl 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
174
impl 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
185
impl From<Cone> for Mesh {
186
fn from(cone: Cone) -> Self {
187
cone.mesh().build()
188
}
189
}
190
191
#[cfg(test)]
192
mod 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
}
272
273