Skip to content

Meshing for Annulus primitive #12734

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

Merged
merged 4 commits into from
Apr 1, 2024
Merged
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
11 changes: 10 additions & 1 deletion crates/bevy_reflect/src/impls/math/primitives2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@ impl_reflect!(
#[reflect(Debug, PartialEq, Serialize, Deserialize)]
#[type_path = "bevy_math::primitives"]
struct Ellipse {
pub half_size: Vec2,
half_size: Vec2,
}
);

impl_reflect!(
#[reflect(Debug, PartialEq, Serialize, Deserialize)]
#[type_path = "bevy_math::primitives"]
struct Annulus {
inner_circle: Circle,
outer_circle: Circle,
}
);

Expand Down
8 changes: 8 additions & 0 deletions crates/bevy_reflect/src/impls/math/primitives3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ impl_reflect!(
}
);

impl_reflect!(
#[reflect(Debug, PartialEq, Serialize, Deserialize)]
#[type_path = "bevy_math::primitives"]
struct Triangle3d {
vertices: [Vec3; 3],
}
);

impl_reflect!(
#[reflect(Debug, PartialEq, Serialize, Deserialize)]
#[type_path = "bevy_math::primitives"]
Expand Down
122 changes: 121 additions & 1 deletion crates/bevy_render/src/mesh/primitives/dim2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use crate::{

use super::Meshable;
use bevy_math::{
primitives::{Capsule2d, Circle, Ellipse, Rectangle, RegularPolygon, Triangle2d, WindingOrder},
primitives::{
Annulus, Capsule2d, Circle, Ellipse, Rectangle, RegularPolygon, Triangle2d, WindingOrder,
},
Vec2,
};
use wgpu::PrimitiveTopology;
Expand Down Expand Up @@ -193,6 +195,124 @@ impl From<EllipseMeshBuilder> for Mesh {
}
}

/// A builder for creating a [`Mesh`] with an [`Annulus`] shape.
pub struct AnnulusMeshBuilder {
/// The [`Annulus`] shape.
pub annulus: Annulus,

/// The number of vertices used in constructing each concentric circle of the annulus mesh.
/// The default is `32`.
pub resolution: usize,
}

impl Default for AnnulusMeshBuilder {
fn default() -> Self {
Self {
annulus: Annulus::default(),
resolution: 32,
}
}
}

impl AnnulusMeshBuilder {
/// Create an [`AnnulusMeshBuilder`] with the given inner radius, outer radius, and angular vertex count.
#[inline]
pub fn new(inner_radius: f32, outer_radius: f32, resolution: usize) -> Self {
Self {
annulus: Annulus::new(inner_radius, outer_radius),
resolution,
}
}

/// Sets the number of vertices used in constructing the concentric circles of the annulus mesh.
#[inline]
pub fn resolution(mut self, resolution: usize) -> Self {
self.resolution = resolution;
self
}

/// Builds a [`Mesh`] based on the configuration in `self`.
pub fn build(&self) -> Mesh {
let inner_radius = self.annulus.inner_circle.radius;
let outer_radius = self.annulus.outer_circle.radius;

let num_vertices = (self.resolution + 1) * 2;
let mut indices = Vec::with_capacity(self.resolution * 6);
let mut positions = Vec::with_capacity(num_vertices);
let mut uvs = Vec::with_capacity(num_vertices);
let normals = vec![[0.0, 0.0, 1.0]; num_vertices];

// We have one more set of vertices than might be naïvely expected;
// the vertices at `start_angle` are duplicated for the purposes of UV
// mapping. Here, each iteration places a pair of vertices at a fixed
// angle from the center of the annulus.
let start_angle = std::f32::consts::FRAC_PI_2;
let step = std::f32::consts::TAU / self.resolution as f32;
for i in 0..=self.resolution {
let theta = start_angle + i as f32 * step;
let (sin, cos) = theta.sin_cos();
let inner_pos = [cos * inner_radius, sin * inner_radius, 0.];
let outer_pos = [cos * outer_radius, sin * outer_radius, 0.];
positions.push(inner_pos);
positions.push(outer_pos);

// The first UV direction is radial and the second is angular;
// i.e., a single UV rectangle is stretched around the annulus, with
// its top and bottom meeting as the circle closes. Lines of constant
// U map to circles, and lines of constant V map to radial line segments.
let inner_uv = [0., i as f32 / self.resolution as f32];
let outer_uv = [1., i as f32 / self.resolution as f32];
uvs.push(inner_uv);
uvs.push(outer_uv);
}

// Adjacent pairs of vertices form two triangles with each other; here,
// we are just making sure that they both have the right orientation,
// which is the CCW order of
// `inner_vertex` -> `outer_vertex` -> `next_outer` -> `next_inner`
for i in 0..(self.resolution as u32) {
let inner_vertex = 2 * i;
let outer_vertex = 2 * i + 1;
let next_inner = inner_vertex + 2;
let next_outer = outer_vertex + 2;
indices.extend_from_slice(&[inner_vertex, outer_vertex, next_outer]);
indices.extend_from_slice(&[next_outer, next_inner, inner_vertex]);
}

Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
.with_inserted_indices(Indices::U32(indices))
}
}

impl Meshable for Annulus {
type Output = AnnulusMeshBuilder;

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

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

impl From<AnnulusMeshBuilder> for Mesh {
fn from(builder: AnnulusMeshBuilder) -> Self {
builder.build()
}
}

impl Meshable for Triangle2d {
type Output = Mesh;

Expand Down