Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cone Mesh #13157

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions crates/bevy_math/src/primitives/dim3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,9 +518,25 @@ pub struct Cone {
/// The height of the cone
pub height: f32,
}

impl Default for Cone {
fn default() -> Self {
Self {
radius: 0.5,
height: 1.0,
}
}
}

impl Primitive3d for Cone {}

impl Cone {
/// Create a new `Cone` from a radius and full height
#[inline(always)]
pub const fn new(radius: f32, height: f32) -> Self {
Self { radius, height }
}

/// Get the base of the cone as a [`Circle`]
#[inline(always)]
pub fn base(&self) -> Circle {
Expand Down
135 changes: 135 additions & 0 deletions crates/bevy_render/src/mesh/primitives/dim3/cone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use bevy_math::primitives::Cone;
use wgpu::PrimitiveTopology;

use crate::{
mesh::{Indices, Mesh, Meshable},
render_asset::RenderAssetUsages,
};

/// A builder used for creating a [`Mesh`] with a [`Cone`] shape.
#[derive(Clone, Copy, Debug)]
pub struct ConeMeshBuilder {
/// The [`Cone`] shape.
pub cone: Cone,
/// The number of vertices used for the bottom of the cone.
///
/// The default is `32`.
pub resolution: u32,
}

impl Default for ConeMeshBuilder {
fn default() -> Self {
Self {
cone: Cone::default(),
resolution: 32,
}
}
}

impl ConeMeshBuilder {
/// Creates a new [`ConeMeshBuilder`] from the given radius, a height,
/// and a resolution used for the top and bottom.
#[inline]
pub fn new(radius: f32, height: f32, resolution: u32) -> Self {
Self {
cone: Cone::new(radius, height),
resolution,
}
}

/// Sets the number of vertices used for the top and bottom of the cone.
#[inline]
pub const fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}

/// Builds a [`Mesh`] based on the configuration in `self`.
pub fn build(&self) -> Mesh {
let resolution = self.resolution;

debug_assert!(resolution > 2);

let num_vertices = resolution * 2 + resolution + 1;
let num_indices = resolution * 3 * 2;

let mut positions = Vec::with_capacity(num_vertices as usize);
let mut normals = Vec::with_capacity(num_vertices as usize);
let mut uvs = Vec::with_capacity(num_vertices as usize);
let mut indices = Vec::with_capacity(num_indices as usize);

let step_theta = std::f32::consts::TAU / resolution as f32;
let half_height = self.cone.height / 2.0;

// Center of the base
positions.push([0.0, -half_height, 0.0]);
normals.push([0.0, -1.0, 0.0]);
uvs.push([0.5, 0.5]);

// Base circle vertices
for i in 1..=resolution {
let theta = i as f32 * step_theta;
let (sin, cos) = theta.sin_cos();

positions.push([self.cone.radius * cos, -half_height, self.cone.radius * sin]);
normals.push([0.0, -1.0, 0.0]);
uvs.push([0.5 * (cos + 1.0), 0.5 * (sin + 1.0)]);
indices.extend_from_slice(&[0, i, i % resolution + 1]);
}

let tip_idx = resolution + 1;

// Tip of the cone
positions.push([0.0, half_height, 0.0]);
// The normal is zero, instead of 0.0, 1.0, 0.0 which causes bad lighting
normals.push([0.0, 0.0, 0.0]);
uvs.push([0.5, 0.5]);

// Cone vertices
for i in 1..=resolution {
// vertex
let theta = i as f32 * step_theta;
let (sin, cos) = theta.sin_cos();

positions.push([self.cone.radius * cos, -half_height, self.cone.radius * sin]);
normals.push([cos, 0.0, sin]);
uvs.push([0.5 * (cos + 1.0), 0.5 * (sin + 1.0)]);
}

// Indices for the base using fan triangulation
for i in 1..=resolution {
indices.extend_from_slice(&[tip_idx + i, tip_idx, tip_idx + i % resolution + 1]);
}
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_indices(Indices::U32(indices))
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
}
}

impl Meshable for Cone {
type Output = ConeMeshBuilder;

fn mesh(&self) -> Self::Output {
ConeMeshBuilder {
cone: *self,
..Default::default()
}
}
}

impl From<Cone> for Mesh {
fn from(cone: Cone) -> Self {
cone.mesh().build()
}
}

impl From<ConeMeshBuilder> for Mesh {
fn from(cone: ConeMeshBuilder) -> Self {
cone.build()
}
}
2 changes: 2 additions & 0 deletions crates/bevy_render/src/mesh/primitives/dim3/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod capsule;
mod cone;
mod cuboid;
mod cylinder;
mod plane;
Expand All @@ -7,6 +8,7 @@ mod torus;
pub(crate) mod triangle3d;

pub use capsule::*;
pub use cone::*;
pub use cylinder::*;
pub use plane::*;
pub use sphere::*;
Expand Down
1 change: 1 addition & 0 deletions examples/3d/3d_shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ fn setup(
meshes.add(Cylinder::default()),
meshes.add(Sphere::default().mesh().ico(5).unwrap()),
meshes.add(Sphere::default().mesh().uv(32, 18)),
meshes.add(Cone::default()),
];

let num_shapes = shapes.len();
Expand Down