bevy_mesh/primitives/dim3/
sphere.rs

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