bevy_mesh/primitives/dim3/
sphere.rs1use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
2use bevy_asset::RenderAssetUsages;
3use bevy_math::{ops, primitives::Sphere};
4use bevy_reflect::prelude::*;
5use core::f32::consts::PI;
6use hexasphere::shapes::IcoSphere;
7use thiserror::Error;
8
9#[derive(Clone, Copy, Debug, Error)]
11pub enum IcosphereError {
12 #[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 subdivisions: u32,
17 number_of_resulting_points: u32,
19 },
20}
21
22#[derive(Clone, Copy, Debug, Reflect)]
24#[reflect(Default, Debug, Clone)]
25pub enum SphereKind {
26 Ico {
28 subdivisions: u32,
31 },
32 Uv {
35 #[doc(alias = "horizontal_resolution")]
37 sectors: u32,
38 #[doc(alias = "vertical_resolution")]
40 stacks: u32,
41 },
42}
43
44impl Default for SphereKind {
45 fn default() -> Self {
46 Self::Ico { subdivisions: 5 }
47 }
48}
49
50#[derive(Clone, Copy, Debug, Default, Reflect)]
52#[reflect(Default, Debug, Clone)]
53pub struct SphereMeshBuilder {
54 pub sphere: Sphere,
56 pub kind: SphereKind,
58}
59
60impl SphereMeshBuilder {
61 #[inline]
63 pub const fn new(radius: f32, kind: SphereKind) -> Self {
64 Self {
65 sphere: Sphere { radius },
66 kind,
67 }
68 }
69
70 #[inline]
72 pub const fn kind(mut self, kind: SphereKind) -> Self {
73 self.kind = kind;
74 self
75 }
76
77 pub fn ico(&self, subdivisions: u32) -> Result<Mesh, IcosphereError> {
85 if subdivisions >= 80 {
86 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 #[expect(
169 clippy::explicit_counter_loop,
170 reason = "Clippy suggestion was much less clear."
171 )]
172 pub fn uv(&self, sectors: u32, stacks: u32) -> Mesh {
173 let sectors_f32 = sectors as f32;
176 let stacks_f32 = stacks as f32;
177 let length_inv = 1. / self.sphere.radius;
178 let sector_step = 2. * PI / sectors_f32;
179 let stack_step = PI / stacks_f32;
180
181 let n_vertices = (stacks * sectors) as usize;
182 let mut vertices: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
183 let mut normals: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
184 let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(n_vertices);
185 let mut indices: Vec<u32> = Vec::with_capacity(n_vertices * 2 * 3);
186
187 for i in 0..stacks + 1 {
188 let stack_angle = PI / 2. - (i as f32) * stack_step;
189 let xy = self.sphere.radius * ops::cos(stack_angle);
190 let z = self.sphere.radius * ops::sin(stack_angle);
191
192 for j in 0..sectors + 1 {
193 let sector_angle = (j as f32) * sector_step;
194 let x = xy * ops::cos(sector_angle);
195 let y = xy * ops::sin(sector_angle);
196
197 vertices.push([x, y, z]);
198 normals.push([x * length_inv, y * length_inv, z * length_inv]);
199 uvs.push([(j as f32) / sectors_f32, (i as f32) / stacks_f32]);
200 }
201 }
202
203 for i in 0..stacks {
209 let mut k1 = i * (sectors + 1);
210 let mut k2 = k1 + sectors + 1;
211 for _j in 0..sectors {
212 if i != 0 {
213 indices.push(k1);
214 indices.push(k2);
215 indices.push(k1 + 1);
216 }
217 if i != stacks - 1 {
218 indices.push(k1 + 1);
219 indices.push(k2);
220 indices.push(k2 + 1);
221 }
222 k1 += 1;
223 k2 += 1;
224 }
225 }
226
227 Mesh::new(
228 PrimitiveTopology::TriangleList,
229 RenderAssetUsages::default(),
230 )
231 .with_inserted_indices(Indices::U32(indices))
232 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices)
233 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
234 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
235 }
236}
237
238impl MeshBuilder for SphereMeshBuilder {
239 fn build(&self) -> Mesh {
246 match self.kind {
247 SphereKind::Ico { subdivisions } => self.ico(subdivisions).unwrap(),
248 SphereKind::Uv { sectors, stacks } => self.uv(sectors, stacks),
249 }
250 }
251}
252
253impl Meshable for Sphere {
254 type Output = SphereMeshBuilder;
255
256 fn mesh(&self) -> Self::Output {
257 SphereMeshBuilder {
258 sphere: *self,
259 ..Default::default()
260 }
261 }
262}
263
264impl From<Sphere> for Mesh {
265 fn from(sphere: Sphere) -> Self {
266 sphere.mesh().build()
267 }
268}