diff --git a/crates/bevy_reflect/src/impls/math/primitives2d.rs b/crates/bevy_reflect/src/impls/math/primitives2d.rs index 03364fb5f2fbd..c9d9a5b50dc09 100644 --- a/crates/bevy_reflect/src/impls/math/primitives2d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives2d.rs @@ -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, } ); diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index e9b025529cfd8..a691446067994 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -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"] diff --git a/crates/bevy_render/src/mesh/primitives/dim2.rs b/crates/bevy_render/src/mesh/primitives/dim2.rs index 40e018602670c..e10d7c845ce6a 100644 --- a/crates/bevy_render/src/mesh/primitives/dim2.rs +++ b/crates/bevy_render/src/mesh/primitives/dim2.rs @@ -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 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 for Mesh { + fn from(annulus: Annulus) -> Self { + annulus.mesh().build() + } +} + +impl From for Mesh { + fn from(builder: AnnulusMeshBuilder) -> Self { + builder.build() + } +} + impl Meshable for Triangle2d { type Output = Mesh;