Skip to content

Instantly share code, notes, and snippets.

@RJ
Created April 15, 2025 19:30
Show Gist options
  • Save RJ/4db7c0c30cefa5634ccaa5d9b677cc75 to your computer and use it in GitHub Desktop.
Save RJ/4db7c0c30cefa5634ccaa5d9b677cc75 to your computer and use it in GitHub Desktop.
bevy annular_sector
use bevy::{
asset::RenderAssetUsages,
prelude::*,
render::mesh::{Indices, PrimitiveTopology},
};
use std::f32::consts::FRAC_PI_2;
/// A builder for creating a [`Mesh`] with an [`Annulus`] shape.
pub struct AnnularSectorMeshBuilder {
/// 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: u32,
/// Half the angle defining the arc of the annular sector.
/// It is created starting from `Vec2::Y`, extending by `half_angle` radians on either side.
pub half_angle: f32,
}
impl AnnularSectorMeshBuilder {
/// Create an [`AnnularSectorMeshBuilder`] with the given inner radius, outer radius, half angle, and angular vertex count.
#[inline]
pub fn new(inner_radius: f32, outer_radius: f32, half_angle: f32, resolution: u32) -> Self {
Self {
annulus: Annulus::new(inner_radius, outer_radius),
half_angle,
resolution,
}
}
/// Sets the number of vertices used in constructing the concentric circles of the annulus mesh.
#[inline]
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl MeshBuilder for AnnularSectorMeshBuilder {
fn build(&self) -> Mesh {
let inner_radius = self.annulus.inner_circle.radius;
let outer_radius = self.annulus.outer_circle.radius;
let resolution = self.resolution as usize;
let mut positions = Vec::with_capacity((resolution + 1) * 2);
let mut uvs = Vec::with_capacity((resolution + 1) * 2);
let normals = vec![[0.0, 0.0, 1.0]; (resolution + 1) * 2];
let mut indices = Vec::with_capacity(resolution * 6);
// Angular range: we center around Vec2::Y (FRAC_PI_2) and extend by the half_angle on both sides.
let start_angle = FRAC_PI_2 - self.half_angle;
let end_angle = FRAC_PI_2 + self.half_angle;
let arc_extent = end_angle - start_angle;
let step = arc_extent / self.resolution as f32;
// Create vertices (each step creates an inner and an outer vertex).
for i in 0..=resolution {
// For a full circle we wrap the index to duplicate the first vertex at the end.
let theta = if self.half_angle == FRAC_PI_2 {
start_angle + ((i % resolution) as f32) * step
} else {
start_angle + i as f32 * step
};
let (sin, cos) = ops::sin_cos(theta);
let inner_pos = [cos * inner_radius, sin * inner_radius, 0.0];
let outer_pos = [cos * outer_radius, sin * outer_radius, 0.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 {
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))
}
}
@RJ
Copy link
Author

RJ commented Apr 15, 2025

not sure it'll get merged. bevyengine/bevy#17928

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment