diff --git a/assets/docs/Cone.drawio b/assets/docs/Cone.drawio
new file mode 100644
index 0000000000000..54cb92c04690f
--- /dev/null
+++ b/assets/docs/Cone.drawio
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/docs/Cone.png b/assets/docs/Cone.png
new file mode 100644
index 0000000000000..37025fea3b29d
Binary files /dev/null and b/assets/docs/Cone.png differ
diff --git a/crates/bevy_render/src/mesh/shape/cone.rs b/crates/bevy_render/src/mesh/shape/cone.rs
new file mode 100644
index 0000000000000..cf9f5bf573763
--- /dev/null
+++ b/crates/bevy_render/src/mesh/shape/cone.rs
@@ -0,0 +1,173 @@
+use crate::mesh::{Indices, Mesh};
+use bevy_math::Vec3;
+use wgpu::PrimitiveTopology;
+
+/// A cone which stands on the XZ plane.
+///
+/// If either radius is 0., the tip will be sharp. Otherwise, the cone will appear truncated, with flat surfaces on either end.
+///
+/// # Diagram
+///
+/// See an illustrative diagram [here](https://github.com/bevyengine/bevy/blob/main/assets/docs/Cone.png).
+/// The diagram illustrates the following properties:
+///
+/// * The height of the cone is evenly split by the XZ plane, so half height above, half below.
+/// * A resolution of 8 means each horizontal (XZ) face has 8 vertices.
+/// * A top radius of 0.0 means a sharp tip.
+/// * 3 segments (divided by the dotted lines) means there are 3 segments of quads enveloping the cone.
+///
+/// Note that the sharp tip still contains the same amount of vertices as the bottom.
+#[derive(Clone, Copy, Debug)]
+pub struct Cone {
+ /// Radius in the XZ plane at the upper face.
+ ///
+ /// Can be zero for a sharp tip, but both top and bottom should not be zero at the same time.
+ pub top_radius: f32,
+
+ /// Radius in the XZ plane at the bottom face.
+ ///
+ /// Can be zero for a sharp tip, but both top and bottom should not be zero at the same time.
+ pub bottom_radius: f32,
+
+ /// Height of the cone in the Y axis.
+ ///
+ /// Should be greater than zero.
+ pub height: f32,
+
+ /// The number of vertices around each horizontal slice of the cone. If you are looking at the cone from
+ /// above, this is the number of points you will see on the circle.
+ /// A higher number will make it appear more circular.
+ ///
+ /// Should be above two.
+ pub resolution: u32,
+
+ /// The number of segments between the two ends. Setting this to 1 will have triangles spanning the full
+ /// height of the cone. Setting it to 2 will have two sets of triangles with a horizontal slice in the middle of
+ /// the cone. Greater numbers increase triangles/slices in the same way.
+ ///
+ /// Should be greater than zero.
+ pub segments: u32,
+}
+
+impl Default for Cone {
+ fn default() -> Self {
+ Self {
+ top_radius: 0.0,
+ bottom_radius: 0.5,
+ height: 1.0,
+ resolution: 64,
+ segments: 4,
+ }
+ }
+}
+
+impl From for Mesh {
+ fn from(c: Cone) -> Self {
+ debug_assert!(c.top_radius >= 0.0);
+ debug_assert!(c.bottom_radius >= 0.0);
+ debug_assert!(!(c.bottom_radius == 0.0 && c.top_radius == 0.0));
+ debug_assert!(c.height > 0.0);
+ debug_assert!(c.resolution > 2);
+ debug_assert!(c.segments > 0);
+
+ let num_rings = c.segments + 1;
+ let num_vertices = c.resolution * 2 + num_rings * (c.resolution + 1);
+ let num_faces = c.resolution * (num_rings - 2);
+ let num_indices = (2 * num_faces + 2 * (c.resolution - 1) * 2) * 3;
+
+ 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 / c.resolution as f32;
+ let step_y = c.height / c.segments as f32;
+ let step_radius = (c.top_radius - c.bottom_radius) / c.segments as f32;
+
+ // rings
+
+ for ring in 0..num_rings {
+ let y = -c.height / 2.0 + ring as f32 * step_y;
+ let radius = c.bottom_radius + ring as f32 * step_radius;
+
+ for segment in 0..=c.resolution {
+ let theta = segment as f32 * step_theta;
+ let (sin, cos) = theta.sin_cos();
+
+ positions.push([radius * cos, y, radius * sin]);
+ normals.push(
+ Vec3::new(cos, (c.bottom_radius - c.top_radius) / c.height, sin)
+ .normalize()
+ .to_array(),
+ );
+ uvs.push([
+ segment as f32 / c.resolution as f32,
+ ring as f32 / c.segments as f32,
+ ]);
+ }
+ }
+
+ // barrel skin
+
+ for i in 0..c.segments {
+ let ring = i * (c.resolution + 1);
+ let next_ring = (i + 1) * (c.resolution + 1);
+
+ for j in 0..c.resolution {
+ indices.extend_from_slice(&[
+ ring + j,
+ next_ring + j,
+ ring + j + 1,
+ next_ring + j,
+ next_ring + j + 1,
+ ring + j + 1,
+ ]);
+ }
+ }
+
+ // caps
+
+ let mut build_cap = |top: bool| {
+ let offset = positions.len() as u32;
+ let (y, normal_y, winding) = if top {
+ (c.height / 2., 1., (1, 0))
+ } else {
+ (c.height / -2., -1., (0, 1))
+ };
+
+ let radius = if top { c.top_radius } else { c.bottom_radius };
+
+ if radius == 0.0 {
+ return;
+ }
+
+ for i in 0..c.resolution {
+ let theta = i as f32 * step_theta;
+ let (sin, cos) = theta.sin_cos();
+
+ positions.push([cos * radius, y, sin * radius]);
+ normals.push([0.0, normal_y, 0.0]);
+ uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
+ }
+
+ for i in 1..(c.resolution - 1) {
+ indices.extend_from_slice(&[
+ offset,
+ offset + i + winding.0,
+ offset + i + winding.1,
+ ]);
+ }
+ };
+
+ // top
+
+ build_cap(true);
+ build_cap(false);
+
+ Mesh::new(PrimitiveTopology::TriangleList)
+ .with_indices(Some(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)
+ }
+}
diff --git a/crates/bevy_render/src/mesh/shape/cylinder.rs b/crates/bevy_render/src/mesh/shape/cylinder.rs
index a4d517ac73952..a5a97ee094c48 100644
--- a/crates/bevy_render/src/mesh/shape/cylinder.rs
+++ b/crates/bevy_render/src/mesh/shape/cylinder.rs
@@ -1,11 +1,13 @@
-use crate::mesh::{Indices, Mesh};
-use wgpu::PrimitiveTopology;
+use crate::mesh::Mesh;
+
+use super::Cone;
/// A cylinder which stands on the XZ plane
#[derive(Clone, Copy, Debug)]
pub struct Cylinder {
/// Radius in the XZ plane.
pub radius: f32,
+
/// Height of the cylinder in the Y axis.
pub height: f32,
/// The number of vertices around each horizontal slice of the cylinder. If you are looking at the cylinder from
@@ -31,97 +33,13 @@ impl Default for Cylinder {
impl From for Mesh {
fn from(c: Cylinder) -> Self {
- debug_assert!(c.radius > 0.0);
- debug_assert!(c.height > 0.0);
- debug_assert!(c.resolution > 2);
- debug_assert!(c.segments > 0);
-
- let num_rings = c.segments + 1;
- let num_vertices = c.resolution * 2 + num_rings * (c.resolution + 1);
- let num_faces = c.resolution * (num_rings - 2);
- let num_indices = (2 * num_faces + 2 * (c.resolution - 1) * 2) * 3;
-
- 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 / c.resolution as f32;
- let step_y = c.height / c.segments as f32;
-
- // rings
-
- for ring in 0..num_rings {
- let y = -c.height / 2.0 + ring as f32 * step_y;
-
- for segment in 0..=c.resolution {
- let theta = segment as f32 * step_theta;
- let (sin, cos) = theta.sin_cos();
-
- positions.push([c.radius * cos, y, c.radius * sin]);
- normals.push([cos, 0., sin]);
- uvs.push([
- segment as f32 / c.resolution as f32,
- ring as f32 / c.segments as f32,
- ]);
- }
+ Cone {
+ top_radius: c.radius,
+ bottom_radius: c.radius,
+ height: c.height,
+ resolution: c.resolution,
+ segments: c.segments,
}
-
- // barrel skin
-
- for i in 0..c.segments {
- let ring = i * (c.resolution + 1);
- let next_ring = (i + 1) * (c.resolution + 1);
-
- for j in 0..c.resolution {
- indices.extend_from_slice(&[
- ring + j,
- next_ring + j,
- ring + j + 1,
- next_ring + j,
- next_ring + j + 1,
- ring + j + 1,
- ]);
- }
- }
-
- // caps
-
- let mut build_cap = |top: bool| {
- let offset = positions.len() as u32;
- let (y, normal_y, winding) = if top {
- (c.height / 2., 1., (1, 0))
- } else {
- (c.height / -2., -1., (0, 1))
- };
-
- for i in 0..c.resolution {
- let theta = i as f32 * step_theta;
- let (sin, cos) = theta.sin_cos();
-
- positions.push([cos * c.radius, y, sin * c.radius]);
- normals.push([0.0, normal_y, 0.0]);
- uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
- }
-
- for i in 1..(c.resolution - 1) {
- indices.extend_from_slice(&[
- offset,
- offset + i + winding.0,
- offset + i + winding.1,
- ]);
- }
- };
-
- // top
-
- build_cap(true);
- build_cap(false);
-
- Mesh::new(PrimitiveTopology::TriangleList)
- .with_indices(Some(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)
+ .into()
}
}
diff --git a/crates/bevy_render/src/mesh/shape/mod.rs b/crates/bevy_render/src/mesh/shape/mod.rs
index c9f6b9e1492bf..10927bd650d3d 100644
--- a/crates/bevy_render/src/mesh/shape/mod.rs
+++ b/crates/bevy_render/src/mesh/shape/mod.rs
@@ -262,6 +262,7 @@ impl From for Mesh {
}
mod capsule;
+mod cone;
mod cylinder;
mod icosphere;
mod regular_polygon;
@@ -269,6 +270,7 @@ mod torus;
mod uvsphere;
pub use capsule::{Capsule, CapsuleUvProfile};
+pub use cone::Cone;
pub use cylinder::Cylinder;
pub use icosphere::Icosphere;
pub use regular_polygon::{Circle, RegularPolygon};
diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs
index 999d11a74c878..0123b7b888e44 100644
--- a/examples/3d/3d_shapes.rs
+++ b/examples/3d/3d_shapes.rs
@@ -20,7 +20,7 @@ fn main() {
#[derive(Component)]
struct Shape;
-const X_EXTENT: f32 = 14.5;
+const X_EXTENT: f32 = 16.0;
fn setup(
mut commands: Commands,
@@ -39,6 +39,7 @@ fn setup(
meshes.add(shape::Capsule::default().into()),
meshes.add(shape::Torus::default().into()),
meshes.add(shape::Cylinder::default().into()),
+ meshes.add(shape::Cone::default().into()),
meshes.add(shape::Icosphere::default().try_into().unwrap()),
meshes.add(shape::UVSphere::default().into()),
];