bevy_mesh/primitives/dim3/
sphere.rs1use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
2use bevy_asset::RenderAssetUsages;
3use bevy_math::ops;
4use bevy_reflect::prelude::*;
5use bevy_shape::Sphere;
6use core::f32::consts::PI;
7use hexasphere::shapes::IcoSphere;
8use thiserror::Error;
9
10#[derive(Clone, Copy, Debug, Error)]
12pub enum IcosphereError {
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)")]
15 TooManyVertices {
16 subdivisions: u32,
18 number_of_resulting_points: u32,
20 },
21}
22
23#[derive(Clone, Copy, Debug, Reflect)]
25#[reflect(Default, Debug, Clone)]
26pub enum SphereKind {
27 Ico {
29 subdivisions: u32,
32 },
33 Uv {
36 #[doc(alias = "horizontal_resolution")]
38 sectors: u32,
39 #[doc(alias = "vertical_resolution")]
41 stacks: u32,
42 },
43}
44
45impl Default for SphereKind {
46 fn default() -> Self {
47 Self::Ico { subdivisions: 5 }
48 }
49}
50
51#[derive(Clone, Copy, Debug, Default, Reflect)]
53#[reflect(Default, Debug, Clone)]
54pub struct SphereMeshBuilder {
55 pub sphere: Sphere,
57 pub kind: SphereKind,
59}
60
61impl SphereMeshBuilder {
62 #[inline]
64 pub const fn new(radius: f32, kind: SphereKind) -> Self {
65 Self {
66 sphere: Sphere { radius },
67 kind,
68 }
69 }
70
71 #[inline]
73 pub const fn kind(mut self, kind: SphereKind) -> Self {
74 self.kind = kind;
75 self
76 }
77
78 pub fn ico(&self, subdivisions: u32) -> Result<Mesh, IcosphereError> {
86 if subdivisions >= 80 {
87 let temp = subdivisions + 1;
116 let number_of_resulting_points = temp * temp * 10 + 2;
117 return Err(IcosphereError::TooManyVertices {
118 subdivisions,
119 number_of_resulting_points,
120 });
121 }
122 let generated = IcoSphere::new(subdivisions as usize, |point| {
123 let inclination = ops::acos(point.y);
124 let azimuth = ops::atan2(point.z, point.x);
125
126 let norm_inclination = inclination / PI;
127 let norm_azimuth = 0.5 - (azimuth / core::f32::consts::TAU);
128
129 [norm_azimuth, norm_inclination]
130 });
131
132 let raw_points = generated.raw_points();
133
134 let points = raw_points
135 .iter()
136 .map(|&p| (p * self.sphere.radius).into())
137 .collect::<Vec<[f32; 3]>>();
138
139 let normals = raw_points
140 .iter()
141 .copied()
142 .map(Into::into)
143 .collect::<Vec<[f32; 3]>>();
144
145 let uvs = generated.raw_data().to_owned();
146
147 let mut indices = Vec::with_capacity(generated.indices_per_main_triangle() * 20);
148
149 for i in 0..20 {
150 generated.get_indices(i, &mut indices);
151 }
152
153 let indices = Indices::U32(indices);
154
155 Ok(Mesh::new(
156 PrimitiveTopology::TriangleList,
157 RenderAssetUsages::default(),
158 )
159 .with_inserted_indices(indices)
160 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, points)
161 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
162 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs))
163 }
164
165 #[expect(
170 clippy::explicit_counter_loop,
171 reason = "Clippy suggestion was much less clear."
172 )]
173 pub fn uv(&self, sectors: u32, stacks: u32) -> Mesh {
174 let sectors_f32 = sectors as f32;
177 let stacks_f32 = stacks as f32;
178 let length_inv = 1. / self.sphere.radius;
179 let sector_step = 2. * PI / sectors_f32;
180 let stack_step = PI / stacks_f32;
181
182 let n_vertices = (stacks * sectors) as usize;
183 let mut vertices: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
184 let mut normals: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
185 let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(n_vertices);
186 let mut indices: Vec<u32> = Vec::with_capacity(n_vertices * 2 * 3);
187
188 for i in 0..stacks + 1 {
189 let stack_angle = PI / 2. - (i as f32) * stack_step;
190 let xy = self.sphere.radius * ops::cos(stack_angle);
191 let z = self.sphere.radius * ops::sin(stack_angle);
192
193 for j in 0..sectors + 1 {
194 let sector_angle = (j as f32) * sector_step;
195 let x = xy * ops::cos(sector_angle);
196 let y = xy * ops::sin(sector_angle);
197
198 vertices.push([x, y, z]);
199 normals.push([x * length_inv, y * length_inv, z * length_inv]);
200 uvs.push([(j as f32) / sectors_f32, (i as f32) / stacks_f32]);
201 }
202 }
203
204 for i in 0..stacks {
210 let mut k1 = i * (sectors + 1);
211 let mut k2 = k1 + sectors + 1;
212 for _j in 0..sectors {
213 if i != 0 {
214 indices.push(k1);
215 indices.push(k2);
216 indices.push(k1 + 1);
217 }
218 if i != stacks - 1 {
219 indices.push(k1 + 1);
220 indices.push(k2);
221 indices.push(k2 + 1);
222 }
223 k1 += 1;
224 k2 += 1;
225 }
226 }
227
228 Mesh::new(
229 PrimitiveTopology::TriangleList,
230 RenderAssetUsages::default(),
231 )
232 .with_inserted_indices(Indices::U32(indices))
233 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices)
234 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
235 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
236 }
237}
238
239impl MeshBuilder for SphereMeshBuilder {
240 fn build(&self) -> Mesh {
247 match self.kind {
248 SphereKind::Ico { subdivisions } => self.ico(subdivisions).unwrap(),
249 SphereKind::Uv { sectors, stacks } => self.uv(sectors, stacks),
250 }
251 }
252}
253
254impl Meshable for Sphere {
255 type Output = SphereMeshBuilder;
256
257 fn mesh(&self) -> Self::Output {
258 SphereMeshBuilder {
259 sphere: *self,
260 ..Default::default()
261 }
262 }
263}
264
265impl From<Sphere> for Mesh {
266 fn from(sphere: Sphere) -> Self {
267 sphere.mesh().build()
268 }
269}