mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 22:18:33 +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)]
|
#[reflect(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
#[type_path = "bevy_math::primitives"]
|
#[type_path = "bevy_math::primitives"]
|
||||||
struct Ellipse {
|
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!(
|
impl_reflect!(
|
||||||
#[reflect(Debug, PartialEq, Serialize, Deserialize)]
|
#[reflect(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
#[type_path = "bevy_math::primitives"]
|
#[type_path = "bevy_math::primitives"]
|
||||||
|
|
|
@ -5,7 +5,9 @@ use crate::{
|
||||||
|
|
||||||
use super::Meshable;
|
use super::Meshable;
|
||||||
use bevy_math::{
|
use bevy_math::{
|
||||||
primitives::{Capsule2d, Circle, Ellipse, Rectangle, RegularPolygon, Triangle2d, WindingOrder},
|
primitives::{
|
||||||
|
Annulus, Capsule2d, Circle, Ellipse, Rectangle, RegularPolygon, Triangle2d, WindingOrder,
|
||||||
|
},
|
||||||
Vec2,
|
Vec2,
|
||||||
};
|
};
|
||||||
use wgpu::PrimitiveTopology;
|
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 {
|
impl Meshable for Triangle2d {
|
||||||
type Output = Mesh;
|
type Output = Mesh;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue