Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_mesh/src/primitives/dim3/sphere.rs
6598 views
1
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
2
use bevy_asset::RenderAssetUsages;
3
use bevy_math::{ops, primitives::Sphere};
4
use bevy_reflect::prelude::*;
5
use core::f32::consts::PI;
6
use hexasphere::shapes::IcoSphere;
7
use thiserror::Error;
8
9
/// An error when creating an icosphere [`Mesh`] from a [`SphereMeshBuilder`].
10
#[derive(Clone, Copy, Debug, Error)]
11
pub enum IcosphereError {
12
/// The icosphere has too many vertices.
13
#[error("Cannot create an icosphere of {subdivisions} subdivisions due to there being too many vertices being generated: {number_of_resulting_points}. (Limited to 65535 vertices or 79 subdivisions)")]
14
TooManyVertices {
15
/// The number of subdivisions used. 79 is the largest allowed value for a mesh to be generated.
16
subdivisions: u32,
17
/// The number of vertices generated. 65535 is the largest allowed value for a mesh to be generated.
18
number_of_resulting_points: u32,
19
},
20
}
21
22
/// A type of sphere mesh.
23
#[derive(Clone, Copy, Debug, Reflect)]
24
#[reflect(Default, Debug, Clone)]
25
pub enum SphereKind {
26
/// An icosphere, a spherical mesh that consists of similar sized triangles.
27
Ico {
28
/// The number of subdivisions applied.
29
/// The number of faces quadruples with each subdivision.
30
subdivisions: u32,
31
},
32
/// A UV sphere, a spherical mesh that consists of quadrilaterals
33
/// apart from triangles at the top and bottom.
34
Uv {
35
/// The number of longitudinal sectors, aka the horizontal resolution.
36
#[doc(alias = "horizontal_resolution")]
37
sectors: u32,
38
/// The number of latitudinal stacks, aka the vertical resolution.
39
#[doc(alias = "vertical_resolution")]
40
stacks: u32,
41
},
42
}
43
44
impl Default for SphereKind {
45
fn default() -> Self {
46
Self::Ico { subdivisions: 5 }
47
}
48
}
49
50
/// A builder used for creating a [`Mesh`] with an [`Sphere`] shape.
51
#[derive(Clone, Copy, Debug, Default, Reflect)]
52
#[reflect(Default, Debug, Clone)]
53
pub struct SphereMeshBuilder {
54
/// The [`Sphere`] shape.
55
pub sphere: Sphere,
56
/// The type of sphere mesh that will be built.
57
pub kind: SphereKind,
58
}
59
60
impl SphereMeshBuilder {
61
/// Creates a new [`SphereMeshBuilder`] from a radius and [`SphereKind`].
62
#[inline]
63
pub const fn new(radius: f32, kind: SphereKind) -> Self {
64
Self {
65
sphere: Sphere { radius },
66
kind,
67
}
68
}
69
70
/// Sets the [`SphereKind`] that will be used for building the mesh.
71
#[inline]
72
pub const fn kind(mut self, kind: SphereKind) -> Self {
73
self.kind = kind;
74
self
75
}
76
77
/// Creates an icosphere mesh with the given number of subdivisions.
78
///
79
/// The number of faces quadruples with each subdivision.
80
/// If there are `80` or more subdivisions, the vertex count will be too large,
81
/// and an [`IcosphereError`] is returned.
82
///
83
/// A good default is `5` subdivisions.
84
pub fn ico(&self, subdivisions: u32) -> Result<Mesh, IcosphereError> {
85
if subdivisions >= 80 {
86
/*
87
Number of triangles:
88
N = 20
89
90
Number of edges:
91
E = 30
92
93
Number of vertices:
94
V = 12
95
96
Number of points within a triangle (triangular numbers):
97
inner(s) = (s^2 + s) / 2
98
99
Number of points on an edge:
100
edges(s) = s
101
102
Add up all vertices on the surface:
103
vertices(s) = edges(s) * E + inner(s - 1) * N + V
104
105
Expand and simplify. Notice that the triangular number formula has roots at -1, and 0, so translating it one to the right fixes it.
106
subdivisions(s) = 30s + 20((s^2 - 2s + 1 + s - 1) / 2) + 12
107
subdivisions(s) = 30s + 10s^2 - 10s + 12
108
subdivisions(s) = 10(s^2 + 2s) + 12
109
110
Factor an (s + 1) term to simplify in terms of calculation
111
subdivisions(s) = 10(s + 1)^2 + 12 - 10
112
resulting_vertices(s) = 10(s + 1)^2 + 2
113
*/
114
let temp = subdivisions + 1;
115
let number_of_resulting_points = temp * temp * 10 + 2;
116
return Err(IcosphereError::TooManyVertices {
117
subdivisions,
118
number_of_resulting_points,
119
});
120
}
121
let generated = IcoSphere::new(subdivisions as usize, |point| {
122
let inclination = ops::acos(point.y);
123
let azimuth = ops::atan2(point.z, point.x);
124
125
let norm_inclination = inclination / PI;
126
let norm_azimuth = 0.5 - (azimuth / core::f32::consts::TAU);
127
128
[norm_azimuth, norm_inclination]
129
});
130
131
let raw_points = generated.raw_points();
132
133
let points = raw_points
134
.iter()
135
.map(|&p| (p * self.sphere.radius).into())
136
.collect::<Vec<[f32; 3]>>();
137
138
let normals = raw_points
139
.iter()
140
.copied()
141
.map(Into::into)
142
.collect::<Vec<[f32; 3]>>();
143
144
let uvs = generated.raw_data().to_owned();
145
146
let mut indices = Vec::with_capacity(generated.indices_per_main_triangle() * 20);
147
148
for i in 0..20 {
149
generated.get_indices(i, &mut indices);
150
}
151
152
let indices = Indices::U32(indices);
153
154
Ok(Mesh::new(
155
PrimitiveTopology::TriangleList,
156
RenderAssetUsages::default(),
157
)
158
.with_inserted_indices(indices)
159
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, points)
160
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
161
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs))
162
}
163
164
/// Creates a UV sphere [`Mesh`] with the given number of
165
/// longitudinal sectors and latitudinal stacks, aka horizontal and vertical resolution.
166
///
167
/// A good default is `32` sectors and `18` stacks.
168
pub fn uv(&self, sectors: u32, stacks: u32) -> Mesh {
169
// Largely inspired from http://www.songho.ca/opengl/gl_sphere.html
170
171
let sectors_f32 = sectors as f32;
172
let stacks_f32 = stacks as f32;
173
let length_inv = 1. / self.sphere.radius;
174
let sector_step = 2. * PI / sectors_f32;
175
let stack_step = PI / stacks_f32;
176
177
let n_vertices = (stacks * sectors) as usize;
178
let mut vertices: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
179
let mut normals: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
180
let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(n_vertices);
181
let mut indices: Vec<u32> = Vec::with_capacity(n_vertices * 2 * 3);
182
183
for i in 0..stacks + 1 {
184
let stack_angle = PI / 2. - (i as f32) * stack_step;
185
let xy = self.sphere.radius * ops::cos(stack_angle);
186
let z = self.sphere.radius * ops::sin(stack_angle);
187
188
for j in 0..sectors + 1 {
189
let sector_angle = (j as f32) * sector_step;
190
let x = xy * ops::cos(sector_angle);
191
let y = xy * ops::sin(sector_angle);
192
193
vertices.push([x, y, z]);
194
normals.push([x * length_inv, y * length_inv, z * length_inv]);
195
uvs.push([(j as f32) / sectors_f32, (i as f32) / stacks_f32]);
196
}
197
}
198
199
// indices
200
// k1--k1+1
201
// | / |
202
// | / |
203
// k2--k2+1
204
for i in 0..stacks {
205
let mut k1 = i * (sectors + 1);
206
let mut k2 = k1 + sectors + 1;
207
for _j in 0..sectors {
208
if i != 0 {
209
indices.push(k1);
210
indices.push(k2);
211
indices.push(k1 + 1);
212
}
213
if i != stacks - 1 {
214
indices.push(k1 + 1);
215
indices.push(k2);
216
indices.push(k2 + 1);
217
}
218
k1 += 1;
219
k2 += 1;
220
}
221
}
222
223
Mesh::new(
224
PrimitiveTopology::TriangleList,
225
RenderAssetUsages::default(),
226
)
227
.with_inserted_indices(Indices::U32(indices))
228
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices)
229
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
230
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
231
}
232
}
233
234
impl MeshBuilder for SphereMeshBuilder {
235
/// Builds a [`Mesh`] according to the configuration in `self`.
236
///
237
/// # Panics
238
///
239
/// Panics if the sphere is a [`SphereKind::Ico`] with a subdivision count
240
/// that is greater than or equal to `80` because there will be too many vertices.
241
fn build(&self) -> Mesh {
242
match self.kind {
243
SphereKind::Ico { subdivisions } => self.ico(subdivisions).unwrap(),
244
SphereKind::Uv { sectors, stacks } => self.uv(sectors, stacks),
245
}
246
}
247
}
248
249
impl Meshable for Sphere {
250
type Output = SphereMeshBuilder;
251
252
fn mesh(&self) -> Self::Output {
253
SphereMeshBuilder {
254
sphere: *self,
255
..Default::default()
256
}
257
}
258
}
259
260
impl From<Sphere> for Mesh {
261
fn from(sphere: Sphere) -> Self {
262
sphere.mesh().build()
263
}
264
}
265
266