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

Add meshing for ConicalFrustum #11819

Merged
merged 2 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 11 additions & 0 deletions crates/bevy_math/src/primitives/dim3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,17 @@ pub struct ConicalFrustum {
}
impl Primitive3d for ConicalFrustum {}

impl Default for ConicalFrustum {
/// Returns the default [`ConicalFrustum`] with a top radius of `0.25`, bottom radius of `0.5`, and a height of `0.5`.
fn default() -> Self {
Self {
radius_top: 0.25,
radius_bottom: 0.5,
height: 0.5,
}
}
}

/// The type of torus determined by the minor and major radii
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TorusKind {
Expand Down
190 changes: 190 additions & 0 deletions crates/bevy_render/src/mesh/primitives/dim3/conical_frustum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use crate::{
mesh::{Indices, Mesh, Meshable},
render_asset::RenderAssetUsages,
};
use bevy_math::{primitives::ConicalFrustum, Vec3};
use wgpu::PrimitiveTopology;

/// A builder used for creating a [`Mesh`] with a [`ConicalFrustum`] shape.
#[derive(Clone, Copy, Debug)]
pub struct ConicalFrustumMeshBuilder {
/// The [`ConicalFrustum`] shape.
pub frustum: ConicalFrustum,
/// The number of vertices used for the top and bottom of the conical frustum.
///
/// The default is `32`.
pub resolution: u32,
/// The number of horizontal lines subdividing the lateral surface of the conical frustum.
///
/// The default is `1`.
pub segments: u32,
}

impl Default for ConicalFrustumMeshBuilder {
fn default() -> Self {
Self {
frustum: ConicalFrustum::default(),
resolution: 32,
segments: 1,
}
}
}

impl ConicalFrustumMeshBuilder {
/// Creates a new [`ConicalFrustumMeshBuilder`] from the given top and bottom radii, a height,
/// and a resolution used for the top and bottom.
#[inline]
pub const fn new(radius_top: f32, radius_bottom: f32, height: f32, resolution: u32) -> Self {
Self {
frustum: ConicalFrustum {
radius_top,
radius_bottom,
height,
},
resolution,
segments: 1,
}
}

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

/// Sets the number of horizontal lines subdividing the lateral surface of the conical frustum.
#[inline]
pub const fn segments(mut self, segments: u32) -> Self {
self.segments = segments;
self
}

/// Builds a [`Mesh`] based on the configuration in `self`.
pub fn build(&self) -> Mesh {
debug_assert!(self.resolution > 2);
debug_assert!(self.segments > 0);

let ConicalFrustum {
radius_top,
radius_bottom,
height,
} = self.frustum;
let half_height = height / 2.0;

let num_rings = self.segments + 1;
let num_vertices = (self.resolution * 2 + num_rings * (self.resolution + 1)) as usize;
let num_faces = self.resolution * (num_rings - 2);
let num_indices = ((2 * num_faces + 2 * (self.resolution - 1) * 2) * 3) as usize;

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

let step_theta = std::f32::consts::TAU / self.resolution as f32;
let step_y = height / self.segments as f32;
let step_radius = (radius_top - radius_bottom) / self.segments as f32;

// Rings
for ring in 0..num_rings {
let y = -half_height + ring as f32 * step_y;
let radius = radius_bottom + ring as f32 * step_radius;

for segment in 0..=self.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, (radius_bottom - radius_top) / height, sin)
.normalize()
.to_array(),
);
uvs.push([
segment as f32 / self.resolution as f32,
ring as f32 / self.segments as f32,
]);
}
}

// Lateral surface
for i in 0..self.segments {
let ring = i * (self.resolution + 1);
let next_ring = (i + 1) * (self.resolution + 1);

for j in 0..self.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, radius: f32| {
let offset = positions.len() as u32;
let (y, normal_y, winding) = if top {
(half_height, 1.0, (1, 0))
} else {
(-half_height, -1.0, (0, 1))
};

for i in 0..self.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..(self.resolution - 1) {
indices.extend_from_slice(&[
offset,
offset + i + winding.0,
offset + i + winding.1,
]);
}
};

build_cap(true, radius_top);
build_cap(false, radius_bottom);

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 ConicalFrustum {
type Output = ConicalFrustumMeshBuilder;

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

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

impl From<ConicalFrustumMeshBuilder> for Mesh {
fn from(frustum: ConicalFrustumMeshBuilder) -> Self {
frustum.build()
}
}
2 changes: 0 additions & 2 deletions crates/bevy_render/src/mesh/primitives/dim3/cylinder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,6 @@ impl CylinderMeshBuilder {
}
};

// top

build_cap(true);
build_cap(false);

Expand Down
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,11 +1,13 @@
mod capsule;
mod conical_frustum;
mod cuboid;
mod cylinder;
mod plane;
mod sphere;
mod torus;

pub use capsule::*;
pub use conical_frustum::*;
pub use cylinder::*;
pub use plane::*;
pub use sphere::*;
Expand Down
3 changes: 2 additions & 1 deletion examples/3d/3d_shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn main() {
#[derive(Component)]
struct Shape;

const X_EXTENT: f32 = 12.0;
const X_EXTENT: f32 = 14.0;

fn setup(
mut commands: Commands,
Expand All @@ -41,6 +41,7 @@ fn setup(
meshes.add(Capsule3d::default()),
meshes.add(Torus::default()),
meshes.add(Cylinder::default()),
meshes.add(ConicalFrustum::default()),
meshes.add(Sphere::default().mesh().ico(5).unwrap()),
meshes.add(Sphere::default().mesh().uv(32, 18)),
];
Expand Down