mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +00:00
Meshing for Annulus
primitive (#12734)
# Objective Related to #10572 Allow the `Annulus` primitive to be meshed. ## Solution We introduce a `Meshable` structure, `AnnulusMeshBuilder`, which allows the `Annulus` primitive to be meshed, leaving optional configuration of the number of angular sudivisions to the user. Here is a picture of the annulus's UV-mapping: <img width="1440" alt="Screenshot 2024-03-26 at 10 39 48 AM" src="https://github.com/bevyengine/bevy/assets/2975848/b170291d-cba7-441b-90ee-2ad6841eaedb"> Other features are essentially identical to the implementations for `Circle`/`Ellipse`. --- ## Changelog - Introduced `AnnulusMeshBuilder` - Implemented `Meshable` for `Annulus` with `Output = AnnulusMeshBuilder` - Implemented `From<Annulus>` and `From<AnnulusMeshBuilder>` for `Mesh` - Added `impl_reflect!` declaration for `Annulus` and `Triangle3d` in `bevy_reflect` --- ## Discussion ### Design considerations The only interesting wrinkle here is that the existing UV-mapping of `Ellipse` (and hence of `Circle` and `RegularPolygon`) is non-radial (it's skew-free, created by situating the mesh in a bounding rectangle), so the UV-mapping of `Annulus` doesn't limit to that of `Circle` as its inner radius tends to zero, for instance. I don't see this as a real issue for `Annulus`, which should almost certainly have this kind of UV-mapping, but I think we ought to at least consider allowing mesh configuration for `Circle`/`Ellipse` that performs radial UV-mapping instead. (In these cases in particular, it would be especially easy, since we wouldn't need a different parameter set in the builder.) --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
20ee56e719
commit
c8aa3ac7d1
3 changed files with 139 additions and 2 deletions
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue