Path: blob/main/crates/bevy_mesh/src/primitives/dim3/cone.rs
6598 views
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};1use bevy_asset::RenderAssetUsages;2use bevy_math::{ops, primitives::Cone, Vec3};3use bevy_reflect::prelude::*;45/// Anchoring options for [`ConeMeshBuilder`]6#[derive(Debug, Copy, Clone, Default, Reflect)]7#[reflect(Default, Debug, Clone)]8pub enum ConeAnchor {9#[default]10/// Midpoint between the tip of the cone and the center of its base.11MidPoint,12/// The Tip of the triangle13Tip,14/// The center of the base circle15Base,16}1718/// A builder used for creating a [`Mesh`] with a [`Cone`] shape.19#[derive(Clone, Copy, Debug, Reflect)]20#[reflect(Default, Debug, Clone)]21pub struct ConeMeshBuilder {22/// The [`Cone`] shape.23pub cone: Cone,24/// The number of vertices used for the base of the cone.25///26/// The default is `32`.27pub resolution: u32,28/// The anchor point for the cone mesh, defaults to the midpoint between29/// the tip of the cone and the center of its base30pub anchor: ConeAnchor,31}3233impl Default for ConeMeshBuilder {34fn default() -> Self {35Self {36cone: Cone::default(),37resolution: 32,38anchor: ConeAnchor::default(),39}40}41}4243impl ConeMeshBuilder {44/// Creates a new [`ConeMeshBuilder`] from a given radius, height,45/// and number of vertices used for the base of the cone.46#[inline]47pub const fn new(radius: f32, height: f32, resolution: u32) -> Self {48Self {49cone: Cone { radius, height },50resolution,51anchor: ConeAnchor::MidPoint,52}53}5455/// Sets the number of vertices used for the base of the cone.56#[inline]57pub const fn resolution(mut self, resolution: u32) -> Self {58self.resolution = resolution;59self60}6162/// Sets a custom anchor point for the mesh63#[inline]64pub const fn anchor(mut self, anchor: ConeAnchor) -> Self {65self.anchor = anchor;66self67}68}6970impl MeshBuilder for ConeMeshBuilder {71fn build(&self) -> Mesh {72let half_height = self.cone.height / 2.0;7374// `resolution` vertices for the base, `resolution` vertices for the bottom of the lateral surface,75// and one vertex for the tip.76let num_vertices = self.resolution as usize * 2 + 1;77let num_indices = self.resolution as usize * 6 - 6;7879let mut positions = Vec::with_capacity(num_vertices);80let mut normals = Vec::with_capacity(num_vertices);81let mut uvs = Vec::with_capacity(num_vertices);82let mut indices = Vec::with_capacity(num_indices);8384// Tip85positions.push([0.0, half_height, 0.0]);8687// The tip doesn't have a singular normal that works correctly.88// We use an invalid normal here so that it becomes NaN in the fragment shader89// and doesn't affect the overall shading. This might seem hacky, but it's one of90// the only ways to get perfectly smooth cones without creases or other shading artifacts.91//92// Note that this requires that normals are not normalized in the vertex shader,93// as that would make the entire triangle invalid and make the cone appear as black.94normals.push([0.0, 0.0, 0.0]);9596// The UVs of the cone are in polar coordinates, so it's like projecting a circle texture from above.97// The center of the texture is at the center of the lateral surface, at the tip of the cone.98uvs.push([0.5, 0.5]);99100// Now we build the lateral surface, the side of the cone.101102// The vertex normals will be perpendicular to the surface.103//104// Here we get the slope of a normal and use it for computing105// the multiplicative inverse of the length of a vector in the direction106// of the normal. This allows us to normalize vertex normals efficiently.107let normal_slope = self.cone.radius / self.cone.height;108// Equivalent to Vec2::new(1.0, slope).length().recip()109let normalization_factor = (1.0 + normal_slope * normal_slope).sqrt().recip();110111// How much the angle changes at each step112let step_theta = core::f32::consts::TAU / self.resolution as f32;113114// Add vertices for the bottom of the lateral surface.115for segment in 0..self.resolution {116let theta = segment as f32 * step_theta;117let (sin, cos) = ops::sin_cos(theta);118119// The vertex normal perpendicular to the side120let normal = Vec3::new(cos, normal_slope, sin) * normalization_factor;121122positions.push([self.cone.radius * cos, -half_height, self.cone.radius * sin]);123normals.push(normal.to_array());124uvs.push([0.5 + cos * 0.5, 0.5 + sin * 0.5]);125}126127// Add indices for the lateral surface. Each triangle is formed by the tip128// and two vertices at the base.129for j in 1..self.resolution {130indices.extend_from_slice(&[0, j + 1, j]);131}132133// Close the surface with a triangle between the tip, first base vertex, and last base vertex.134indices.extend_from_slice(&[0, 1, self.resolution]);135136// Now we build the actual base of the cone.137138let index_offset = positions.len() as u32;139140// Add base vertices.141for i in 0..self.resolution {142let theta = i as f32 * step_theta;143let (sin, cos) = ops::sin_cos(theta);144145positions.push([cos * self.cone.radius, -half_height, sin * self.cone.radius]);146normals.push([0.0, -1.0, 0.0]);147uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);148}149150// Add base indices.151for i in 1..(self.resolution - 1) {152indices.extend_from_slice(&[index_offset, index_offset + i, index_offset + i + 1]);153}154155// Offset the vertex positions Y axis to match the anchor156match self.anchor {157ConeAnchor::Tip => positions.iter_mut().for_each(|p| p[1] -= half_height),158ConeAnchor::Base => positions.iter_mut().for_each(|p| p[1] += half_height),159ConeAnchor::MidPoint => (),160};161162Mesh::new(163PrimitiveTopology::TriangleList,164RenderAssetUsages::default(),165)166.with_inserted_indices(Indices::U32(indices))167.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)168.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)169.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)170}171}172173impl Meshable for Cone {174type Output = ConeMeshBuilder;175176fn mesh(&self) -> Self::Output {177ConeMeshBuilder {178cone: *self,179..Default::default()180}181}182}183184impl From<Cone> for Mesh {185fn from(cone: Cone) -> Self {186cone.mesh().build()187}188}189190#[cfg(test)]191mod tests {192use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};193use bevy_math::{primitives::Cone, Vec2};194195/// Rounds floats to handle floating point error in tests.196fn round_floats<const N: usize>(points: &mut [[f32; N]]) {197for point in points.iter_mut() {198for coord in point.iter_mut() {199let round = (*coord * 100.0).round() / 100.0;200if (*coord - round).abs() < 0.00001 {201*coord = round;202}203}204}205}206207#[test]208fn cone_mesh() {209let mut mesh = Cone {210radius: 0.5,211height: 1.0,212}213.mesh()214.resolution(4)215.build();216217let Some(VertexAttributeValues::Float32x3(mut positions)) =218mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)219else {220panic!("Expected positions f32x3");221};222let Some(VertexAttributeValues::Float32x3(mut normals)) =223mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)224else {225panic!("Expected normals f32x3");226};227228round_floats(&mut positions);229round_floats(&mut normals);230231// Vertex positions232assert_eq!(233[234// Tip235[0.0, 0.5, 0.0],236// Lateral surface237[0.5, -0.5, 0.0],238[0.0, -0.5, 0.5],239[-0.5, -0.5, 0.0],240[0.0, -0.5, -0.5],241// Base242[0.5, -0.5, 0.0],243[0.0, -0.5, 0.5],244[-0.5, -0.5, 0.0],245[0.0, -0.5, -0.5],246],247&positions[..]248);249250// Vertex normals251let [x, y] = Vec2::new(0.5, -1.0).perp().normalize().to_array();252assert_eq!(253&[254// Tip255[0.0, 0.0, 0.0],256// Lateral surface257[x, y, 0.0],258[0.0, y, x],259[-x, y, 0.0],260[0.0, y, -x],261// Base262[0.0, -1.0, 0.0],263[0.0, -1.0, 0.0],264[0.0, -1.0, 0.0],265[0.0, -1.0, 0.0],266],267&normals[..]268);269}270}271272273