From d8192222934321cc0de0b46e027ad7edfdb3d31a Mon Sep 17 00:00:00 2001 From: Jay Pavlina Date: Sun, 11 Dec 2022 18:22:05 +0000 Subject: [PATCH] Add cylinder shape (#6809) # Objective Adds a cylinder shape. Fixes #2282. ## Solution - I added a custom cylinder shape, taken from [here](https://github.com/rparrett/typey_birb/blob/main/src/cylinder.rs) with permission from @rparrett. - I also added the cylinder shape to the `3d_shapes` example scene. --- ## Changelog - Added cylinder shape Co-Authored-By: Rob Parrett Co-Authored-By: davidhof <7483215+davidhof@users.noreply.github.com> --- crates/bevy_render/src/mesh/shape/cylinder.rs | 127 ++++++++++++++++++ crates/bevy_render/src/mesh/shape/mod.rs | 2 + examples/3d/3d_shapes.rs | 3 +- 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 crates/bevy_render/src/mesh/shape/cylinder.rs diff --git a/crates/bevy_render/src/mesh/shape/cylinder.rs b/crates/bevy_render/src/mesh/shape/cylinder.rs new file mode 100644 index 0000000000000..844be9ba6430e --- /dev/null +++ b/crates/bevy_render/src/mesh/shape/cylinder.rs @@ -0,0 +1,127 @@ +use crate::mesh::{Indices, Mesh}; +use wgpu::PrimitiveTopology; + +/// A cylinder which stands on the XZ plane +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 + /// above, this is the number of points you will see on the circle. + /// A higher number will make it appear more circular. + pub resolution: u32, + /// The number of segments between the two ends. Setting this to 1 will have triangles spanning the full + /// height of the cylinder. Setting it to 2 will have two sets of triangles with a horizontal slice in the middle of + /// cylinder. Greater numbers increase triangles/slices in the same way. + pub segments: u32, +} + +impl Default for Cylinder { + fn default() -> Self { + Self { + radius: 0.5, + height: 1.0, + resolution: 16, + segments: 1, + } + } +} + +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, + ]); + } + } + + // 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 as u32 - 1) { + indices.extend_from_slice(&[ + offset, + offset + i + winding.0, + offset + i + winding.1, + ]); + } + }; + + // top + + build_cap(true); + build_cap(false); + + let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); + mesh.set_indices(Some(Indices::U32(indices))); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + mesh + } +} diff --git a/crates/bevy_render/src/mesh/shape/mod.rs b/crates/bevy_render/src/mesh/shape/mod.rs index 0c3317d764075..a2a76ccd12fbc 100644 --- a/crates/bevy_render/src/mesh/shape/mod.rs +++ b/crates/bevy_render/src/mesh/shape/mod.rs @@ -221,12 +221,14 @@ impl From for Mesh { } mod capsule; +mod cylinder; mod icosphere; mod regular_polygon; mod torus; mod uvsphere; pub use capsule::{Capsule, CapsuleUvProfile}; +pub use cylinder::Cylinder; pub use icosphere::Icosphere; pub use regular_polygon::{Circle, RegularPolygon}; pub use torus::Torus; diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 61c86bbec94ac..d942505b56fad 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.; +const X_EXTENT: f32 = 14.5; fn setup( mut commands: Commands, @@ -38,6 +38,7 @@ fn setup( meshes.add(shape::Box::default().into()), meshes.add(shape::Capsule::default().into()), meshes.add(shape::Torus::default().into()), + meshes.add(shape::Cylinder::default().into()), meshes.add(shape::Icosphere::default().try_into().unwrap()), meshes.add(shape::UVSphere::default().into()), ];