Path: blob/main/crates/bevy_mesh/src/primitives/dim3/sphere.rs
6598 views
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};1use bevy_asset::RenderAssetUsages;2use bevy_math::{ops, primitives::Sphere};3use bevy_reflect::prelude::*;4use core::f32::consts::PI;5use hexasphere::shapes::IcoSphere;6use thiserror::Error;78/// An error when creating an icosphere [`Mesh`] from a [`SphereMeshBuilder`].9#[derive(Clone, Copy, Debug, Error)]10pub enum IcosphereError {11/// The icosphere has too many vertices.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)")]13TooManyVertices {14/// The number of subdivisions used. 79 is the largest allowed value for a mesh to be generated.15subdivisions: u32,16/// The number of vertices generated. 65535 is the largest allowed value for a mesh to be generated.17number_of_resulting_points: u32,18},19}2021/// A type of sphere mesh.22#[derive(Clone, Copy, Debug, Reflect)]23#[reflect(Default, Debug, Clone)]24pub enum SphereKind {25/// An icosphere, a spherical mesh that consists of similar sized triangles.26Ico {27/// The number of subdivisions applied.28/// The number of faces quadruples with each subdivision.29subdivisions: u32,30},31/// A UV sphere, a spherical mesh that consists of quadrilaterals32/// apart from triangles at the top and bottom.33Uv {34/// The number of longitudinal sectors, aka the horizontal resolution.35#[doc(alias = "horizontal_resolution")]36sectors: u32,37/// The number of latitudinal stacks, aka the vertical resolution.38#[doc(alias = "vertical_resolution")]39stacks: u32,40},41}4243impl Default for SphereKind {44fn default() -> Self {45Self::Ico { subdivisions: 5 }46}47}4849/// A builder used for creating a [`Mesh`] with an [`Sphere`] shape.50#[derive(Clone, Copy, Debug, Default, Reflect)]51#[reflect(Default, Debug, Clone)]52pub struct SphereMeshBuilder {53/// The [`Sphere`] shape.54pub sphere: Sphere,55/// The type of sphere mesh that will be built.56pub kind: SphereKind,57}5859impl SphereMeshBuilder {60/// Creates a new [`SphereMeshBuilder`] from a radius and [`SphereKind`].61#[inline]62pub const fn new(radius: f32, kind: SphereKind) -> Self {63Self {64sphere: Sphere { radius },65kind,66}67}6869/// Sets the [`SphereKind`] that will be used for building the mesh.70#[inline]71pub const fn kind(mut self, kind: SphereKind) -> Self {72self.kind = kind;73self74}7576/// Creates an icosphere mesh with the given number of subdivisions.77///78/// The number of faces quadruples with each subdivision.79/// If there are `80` or more subdivisions, the vertex count will be too large,80/// and an [`IcosphereError`] is returned.81///82/// A good default is `5` subdivisions.83pub fn ico(&self, subdivisions: u32) -> Result<Mesh, IcosphereError> {84if subdivisions >= 80 {85/*86Number of triangles:87N = 208889Number of edges:90E = 309192Number of vertices:93V = 129495Number of points within a triangle (triangular numbers):96inner(s) = (s^2 + s) / 29798Number of points on an edge:99edges(s) = s100101Add up all vertices on the surface:102vertices(s) = edges(s) * E + inner(s - 1) * N + V103104Expand and simplify. Notice that the triangular number formula has roots at -1, and 0, so translating it one to the right fixes it.105subdivisions(s) = 30s + 20((s^2 - 2s + 1 + s - 1) / 2) + 12106subdivisions(s) = 30s + 10s^2 - 10s + 12107subdivisions(s) = 10(s^2 + 2s) + 12108109Factor an (s + 1) term to simplify in terms of calculation110subdivisions(s) = 10(s + 1)^2 + 12 - 10111resulting_vertices(s) = 10(s + 1)^2 + 2112*/113let temp = subdivisions + 1;114let number_of_resulting_points = temp * temp * 10 + 2;115return Err(IcosphereError::TooManyVertices {116subdivisions,117number_of_resulting_points,118});119}120let generated = IcoSphere::new(subdivisions as usize, |point| {121let inclination = ops::acos(point.y);122let azimuth = ops::atan2(point.z, point.x);123124let norm_inclination = inclination / PI;125let norm_azimuth = 0.5 - (azimuth / core::f32::consts::TAU);126127[norm_azimuth, norm_inclination]128});129130let raw_points = generated.raw_points();131132let points = raw_points133.iter()134.map(|&p| (p * self.sphere.radius).into())135.collect::<Vec<[f32; 3]>>();136137let normals = raw_points138.iter()139.copied()140.map(Into::into)141.collect::<Vec<[f32; 3]>>();142143let uvs = generated.raw_data().to_owned();144145let mut indices = Vec::with_capacity(generated.indices_per_main_triangle() * 20);146147for i in 0..20 {148generated.get_indices(i, &mut indices);149}150151let indices = Indices::U32(indices);152153Ok(Mesh::new(154PrimitiveTopology::TriangleList,155RenderAssetUsages::default(),156)157.with_inserted_indices(indices)158.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, points)159.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)160.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs))161}162163/// Creates a UV sphere [`Mesh`] with the given number of164/// longitudinal sectors and latitudinal stacks, aka horizontal and vertical resolution.165///166/// A good default is `32` sectors and `18` stacks.167pub fn uv(&self, sectors: u32, stacks: u32) -> Mesh {168// Largely inspired from http://www.songho.ca/opengl/gl_sphere.html169170let sectors_f32 = sectors as f32;171let stacks_f32 = stacks as f32;172let length_inv = 1. / self.sphere.radius;173let sector_step = 2. * PI / sectors_f32;174let stack_step = PI / stacks_f32;175176let n_vertices = (stacks * sectors) as usize;177let mut vertices: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);178let mut normals: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);179let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(n_vertices);180let mut indices: Vec<u32> = Vec::with_capacity(n_vertices * 2 * 3);181182for i in 0..stacks + 1 {183let stack_angle = PI / 2. - (i as f32) * stack_step;184let xy = self.sphere.radius * ops::cos(stack_angle);185let z = self.sphere.radius * ops::sin(stack_angle);186187for j in 0..sectors + 1 {188let sector_angle = (j as f32) * sector_step;189let x = xy * ops::cos(sector_angle);190let y = xy * ops::sin(sector_angle);191192vertices.push([x, y, z]);193normals.push([x * length_inv, y * length_inv, z * length_inv]);194uvs.push([(j as f32) / sectors_f32, (i as f32) / stacks_f32]);195}196}197198// indices199// k1--k1+1200// | / |201// | / |202// k2--k2+1203for i in 0..stacks {204let mut k1 = i * (sectors + 1);205let mut k2 = k1 + sectors + 1;206for _j in 0..sectors {207if i != 0 {208indices.push(k1);209indices.push(k2);210indices.push(k1 + 1);211}212if i != stacks - 1 {213indices.push(k1 + 1);214indices.push(k2);215indices.push(k2 + 1);216}217k1 += 1;218k2 += 1;219}220}221222Mesh::new(223PrimitiveTopology::TriangleList,224RenderAssetUsages::default(),225)226.with_inserted_indices(Indices::U32(indices))227.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices)228.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)229.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)230}231}232233impl MeshBuilder for SphereMeshBuilder {234/// Builds a [`Mesh`] according to the configuration in `self`.235///236/// # Panics237///238/// Panics if the sphere is a [`SphereKind::Ico`] with a subdivision count239/// that is greater than or equal to `80` because there will be too many vertices.240fn build(&self) -> Mesh {241match self.kind {242SphereKind::Ico { subdivisions } => self.ico(subdivisions).unwrap(),243SphereKind::Uv { sectors, stacks } => self.uv(sectors, stacks),244}245}246}247248impl Meshable for Sphere {249type Output = SphereMeshBuilder;250251fn mesh(&self) -> Self::Output {252SphereMeshBuilder {253sphere: *self,254..Default::default()255}256}257}258259impl From<Sphere> for Mesh {260fn from(sphere: Sphere) -> Self {261sphere.mesh().build()262}263}264265266