Path: blob/main/crates/bevy_mesh/src/primitives/dim2.rs
9294 views
use core::f32::consts::FRAC_PI_2;1use core::mem;23use crate::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment, VertexAttributeValues};4use bevy_asset::RenderAssetUsages;56use super::{Extrudable, MeshBuilder, Meshable};7use bevy_math::prelude::Polyline2d;8use bevy_math::{9ops,10primitives::{11Annulus, Capsule2d, Circle, CircularSector, CircularSegment, ConvexPolygon, Ellipse,12Primitive2d, Rectangle, RegularPolygon, Rhombus, Ring, Segment2d, Triangle2d, Triangle3d,13WindingOrder,14},15FloatExt, Vec2, Vec3,16};17use bevy_reflect::prelude::*;18use wgpu_types::PrimitiveTopology;1920/// A builder used for creating a [`Mesh`] with a [`Circle`] shape.21#[derive(Clone, Copy, Debug, Reflect)]22#[reflect(Default, Debug, Clone)]23pub struct CircleMeshBuilder {24/// The [`Circle`] shape.25pub circle: Circle,26/// The number of vertices used for the circle mesh.27/// The default is `32`.28#[doc(alias = "vertices")]29pub resolution: u32,30}3132impl Default for CircleMeshBuilder {33fn default() -> Self {34Self {35circle: Circle::default(),36resolution: 32,37}38}39}4041impl CircleMeshBuilder {42/// Creates a new [`CircleMeshBuilder`] from a given radius and vertex count.43#[inline]44pub const fn new(radius: f32, resolution: u32) -> Self {45Self {46circle: Circle { radius },47resolution,48}49}5051/// Sets the number of vertices used for the circle mesh.52#[inline]53#[doc(alias = "vertices")]54pub const fn resolution(mut self, resolution: u32) -> Self {55self.resolution = resolution;56self57}58}5960impl MeshBuilder for CircleMeshBuilder {61fn build(&self) -> Mesh {62Ellipse::new(self.circle.radius, self.circle.radius)63.mesh()64.resolution(self.resolution)65.build()66}67}6869impl Extrudable for CircleMeshBuilder {70fn perimeter(&self) -> Vec<PerimeterSegment> {71vec![PerimeterSegment::Smooth {72first_normal: Vec2::Y,73last_normal: Vec2::Y,74indices: (0..self.resolution).chain([0]).collect(),75}]76}77}7879impl Meshable for Circle {80type Output = CircleMeshBuilder;8182fn mesh(&self) -> Self::Output {83CircleMeshBuilder {84circle: *self,85..Default::default()86}87}88}8990impl From<Circle> for Mesh {91fn from(circle: Circle) -> Self {92circle.mesh().build()93}94}9596/// Specifies how to generate UV-mappings for the [`CircularSector`] and [`CircularSegment`] shapes.97///98/// Currently the only variant is `Mask`, which is good for showing a portion of a texture that includes99/// the entire circle, particularly the same texture will be displayed with different fractions of a100/// complete circle.101///102/// It's expected that more will be added in the future, such as a variant that causes the texture to be103/// scaled to fit the bounding box of the shape, which would be good for packed textures only including the104/// portion of the circle that is needed to display.105#[derive(Copy, Clone, Debug, PartialEq, Reflect)]106#[reflect(Default, Debug, Clone)]107#[non_exhaustive]108pub enum CircularMeshUvMode {109/// Treats the shape as a mask over a circle of equal size and radius,110/// with the center of the circle at the center of the texture.111Mask {112/// Angle by which to rotate the shape when generating the UV map.113angle: f32,114},115}116117impl Default for CircularMeshUvMode {118fn default() -> Self {119CircularMeshUvMode::Mask { angle: 0.0 }120}121}122123/// A builder used for creating a [`Mesh`] with a [`CircularSector`] shape.124///125/// The resulting mesh will have a UV-map such that the center of the circle is126/// at the center of the texture.127#[derive(Clone, Debug, Reflect)]128#[reflect(Default, Debug, Clone)]129pub struct CircularSectorMeshBuilder {130/// The sector shape.131pub sector: CircularSector,132/// The number of vertices used for the arc portion of the sector mesh.133/// The default is `32`.134#[doc(alias = "vertices")]135pub resolution: u32,136/// The UV mapping mode137pub uv_mode: CircularMeshUvMode,138}139140impl Default for CircularSectorMeshBuilder {141fn default() -> Self {142Self {143sector: CircularSector::default(),144resolution: 32,145uv_mode: CircularMeshUvMode::default(),146}147}148}149150impl CircularSectorMeshBuilder {151/// Creates a new [`CircularSectorMeshBuilder`] from a given sector152#[inline]153pub fn new(sector: CircularSector) -> Self {154Self {155sector,156..Self::default()157}158}159160/// Sets the number of vertices used for the sector mesh.161#[inline]162#[doc(alias = "vertices")]163pub const fn resolution(mut self, resolution: u32) -> Self {164self.resolution = resolution;165self166}167168/// Sets the uv mode used for the sector mesh169#[inline]170pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {171self.uv_mode = uv_mode;172self173}174}175176impl MeshBuilder for CircularSectorMeshBuilder {177fn build(&self) -> Mesh {178let resolution = self.resolution as usize;179let mut indices = Vec::with_capacity((resolution - 1) * 3);180let mut positions = Vec::with_capacity(resolution + 1);181let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];182let mut uvs = Vec::with_capacity(resolution + 1);183184let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;185186// Push the center of the circle.187positions.push([0.0; 3]);188uvs.push([0.5; 2]);189190let first_angle = FRAC_PI_2 - self.sector.half_angle();191let last_angle = FRAC_PI_2 + self.sector.half_angle();192let last_i = (self.resolution - 1) as f32;193for i in 0..self.resolution {194let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);195196// Compute the vertex197let vertex = self.sector.radius() * Vec2::from_angle(angle);198// Compute the UV coordinate by taking the modified angle's unit vector, negating the Y axis, and rescaling and centering it at (0.5, 0.5).199// We accomplish the Y axis flip by negating the angle.200let uv =201Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));202203positions.push([vertex.x, vertex.y, 0.0]);204uvs.push([uv.x, uv.y]);205}206207for i in 1..self.resolution {208// Index 0 is the center.209indices.extend_from_slice(&[0, i, i + 1]);210}211212Mesh::new(213PrimitiveTopology::TriangleList,214RenderAssetUsages::default(),215)216.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)217.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)218.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)219.with_inserted_indices(Indices::U32(indices))220}221}222223impl Extrudable for CircularSectorMeshBuilder {224fn perimeter(&self) -> Vec<PerimeterSegment> {225let (sin, cos) = ops::sin_cos(self.sector.arc.half_angle);226let first_normal = Vec2::new(sin, cos);227let last_normal = Vec2::new(-sin, cos);228vec![229PerimeterSegment::Flat {230indices: vec![self.resolution, 0, 1],231},232PerimeterSegment::Smooth {233first_normal,234last_normal,235indices: (1..=self.resolution).collect(),236},237]238}239}240241impl Meshable for CircularSector {242type Output = CircularSectorMeshBuilder;243244fn mesh(&self) -> Self::Output {245CircularSectorMeshBuilder {246sector: *self,247..Default::default()248}249}250}251252impl From<CircularSector> for Mesh {253/// Converts this sector into a [`Mesh`] using a default [`CircularSectorMeshBuilder`].254///255/// See the documentation of [`CircularSectorMeshBuilder`] for more details.256fn from(sector: CircularSector) -> Self {257sector.mesh().build()258}259}260261/// A builder used for creating a [`Mesh`] with a [`CircularSegment`] shape.262///263/// The resulting mesh will have a UV-map such that the center of the circle is264/// at the center of the texture.265#[derive(Clone, Copy, Debug, Reflect)]266#[reflect(Default, Debug, Clone)]267pub struct CircularSegmentMeshBuilder {268/// The segment shape.269pub segment: CircularSegment,270/// The number of vertices used for the arc portion of the segment mesh.271/// The default is `32`.272#[doc(alias = "vertices")]273pub resolution: u32,274/// The UV mapping mode275pub uv_mode: CircularMeshUvMode,276}277278impl Default for CircularSegmentMeshBuilder {279fn default() -> Self {280Self {281segment: CircularSegment::default(),282resolution: 32,283uv_mode: CircularMeshUvMode::default(),284}285}286}287288impl CircularSegmentMeshBuilder {289/// Creates a new [`CircularSegmentMeshBuilder`] from a given segment290#[inline]291pub fn new(segment: CircularSegment) -> Self {292Self {293segment,294..Self::default()295}296}297298/// Sets the number of vertices used for the segment mesh.299#[inline]300#[doc(alias = "vertices")]301pub const fn resolution(mut self, resolution: u32) -> Self {302self.resolution = resolution;303self304}305306/// Sets the uv mode used for the segment mesh307#[inline]308pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {309self.uv_mode = uv_mode;310self311}312}313314impl MeshBuilder for CircularSegmentMeshBuilder {315fn build(&self) -> Mesh {316let resolution = self.resolution as usize;317let mut indices = Vec::with_capacity((resolution - 1) * 3);318let mut positions = Vec::with_capacity(resolution + 1);319let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];320let mut uvs = Vec::with_capacity(resolution + 1);321322let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;323324// Push the center of the chord.325let midpoint_vertex = self.segment.chord_midpoint();326positions.push([midpoint_vertex.x, midpoint_vertex.y, 0.0]);327// Compute the UV coordinate of the midpoint vertex.328// This is similar to the computation inside the loop for the arc vertices,329// but the vertex angle is PI/2, and we must scale by the ratio of the apothem to the radius330// to correctly position the vertex.331let midpoint_uv = Vec2::from_angle(-uv_angle - FRAC_PI_2).mul_add(332Vec2::splat(0.5 * (self.segment.apothem() / self.segment.radius())),333Vec2::splat(0.5),334);335uvs.push([midpoint_uv.x, midpoint_uv.y]);336337let first_angle = FRAC_PI_2 - self.segment.half_angle();338let last_angle = FRAC_PI_2 + self.segment.half_angle();339let last_i = (self.resolution - 1) as f32;340for i in 0..self.resolution {341let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);342343// Compute the vertex344let vertex = self.segment.radius() * Vec2::from_angle(angle);345// Compute the UV coordinate by taking the modified angle's unit vector, negating the Y axis, and rescaling and centering it at (0.5, 0.5).346// We accomplish the Y axis flip by negating the angle.347let uv =348Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));349350positions.push([vertex.x, vertex.y, 0.0]);351uvs.push([uv.x, uv.y]);352}353354for i in 1..self.resolution {355// Index 0 is the midpoint of the chord.356indices.extend_from_slice(&[0, i, i + 1]);357}358359Mesh::new(360PrimitiveTopology::TriangleList,361RenderAssetUsages::default(),362)363.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)364.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)365.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)366.with_inserted_indices(Indices::U32(indices))367}368}369370impl Extrudable for CircularSegmentMeshBuilder {371fn perimeter(&self) -> Vec<PerimeterSegment> {372let (sin, cos) = ops::sin_cos(self.segment.arc.half_angle);373let first_normal = Vec2::new(sin, cos);374let last_normal = Vec2::new(-sin, cos);375vec![376PerimeterSegment::Flat {377indices: vec![self.resolution, 0, 1],378},379PerimeterSegment::Smooth {380first_normal,381last_normal,382indices: (1..=self.resolution).collect(),383},384]385}386}387388impl Meshable for CircularSegment {389type Output = CircularSegmentMeshBuilder;390391fn mesh(&self) -> Self::Output {392CircularSegmentMeshBuilder {393segment: *self,394..Default::default()395}396}397}398399impl From<CircularSegment> for Mesh {400/// Converts this sector into a [`Mesh`] using a default [`CircularSegmentMeshBuilder`].401///402/// See the documentation of [`CircularSegmentMeshBuilder`] for more details.403fn from(segment: CircularSegment) -> Self {404segment.mesh().build()405}406}407408/// A builder used for creating a [`Mesh`] with a [`ConvexPolygon`] shape.409///410/// You must verify that the `vertices` are not concave when constructing this type. You can411/// guarantee this by creating a [`ConvexPolygon`] first, then calling [`ConvexPolygon::mesh()`].412#[derive(Clone, Debug, Reflect)]413#[reflect(Debug, Clone)]414pub struct ConvexPolygonMeshBuilder {415pub vertices: Vec<Vec2>,416}417418impl Meshable for ConvexPolygon {419type Output = ConvexPolygonMeshBuilder;420421fn mesh(&self) -> Self::Output {422Self::Output {423vertices: self.vertices().to_vec(),424}425}426}427428impl MeshBuilder for ConvexPolygonMeshBuilder {429fn build(&self) -> Mesh {430let len = self.vertices.len();431let mut indices = Vec::with_capacity((len - 2) * 3);432let mut positions = Vec::with_capacity(len);433434for vertex in &self.vertices {435positions.push([vertex.x, vertex.y, 0.0]);436}437for i in 2..len as u32 {438indices.extend_from_slice(&[0, i - 1, i]);439}440Mesh::new(441PrimitiveTopology::TriangleList,442RenderAssetUsages::default(),443)444.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)445.with_inserted_indices(Indices::U32(indices))446}447}448449impl Extrudable for ConvexPolygonMeshBuilder {450fn perimeter(&self) -> Vec<PerimeterSegment> {451vec![PerimeterSegment::Flat {452indices: (0..self.vertices.len() as u32).chain([0]).collect(),453}]454}455}456457impl From<ConvexPolygon> for Mesh {458fn from(polygon: ConvexPolygon) -> Self {459polygon.mesh().build()460}461}462463/// A builder used for creating a [`Mesh`] with a [`RegularPolygon`] shape.464#[derive(Clone, Copy, Debug, Reflect)]465#[reflect(Default, Debug, Clone)]466pub struct RegularPolygonMeshBuilder {467circumradius: f32,468sides: u32,469}470471impl Default for RegularPolygonMeshBuilder {472/// Returns the default [`RegularPolygonMeshBuilder`] with six sides (a hexagon) and a circumradius of `0.5`.473fn default() -> Self {474Self {475circumradius: 0.5,476sides: 6,477}478}479}480481impl RegularPolygonMeshBuilder {482/// Creates a new [`RegularPolygonMeshBuilder`] from the radius of a circumcircle and a number483/// of sides.484///485/// # Panics486///487/// Panics in debug mode if `circumradius` is negative, or if `sides` is less than 3.488pub const fn new(circumradius: f32, sides: u32) -> Self {489debug_assert!(490circumradius.is_sign_positive(),491"polygon has a negative radius"492);493debug_assert!(sides > 2, "polygon has less than 3 sides");494495Self {496circumradius,497sides,498}499}500}501502impl Meshable for RegularPolygon {503type Output = RegularPolygonMeshBuilder;504505fn mesh(&self) -> Self::Output {506Self::Output {507circumradius: self.circumcircle.radius,508sides: self.sides,509}510}511}512513impl MeshBuilder for RegularPolygonMeshBuilder {514fn build(&self) -> Mesh {515// The ellipse mesh is just a regular polygon with two radii516Ellipse::new(self.circumradius, self.circumradius)517.mesh()518.resolution(self.sides)519.build()520}521}522523impl Extrudable for RegularPolygonMeshBuilder {524fn perimeter(&self) -> Vec<PerimeterSegment> {525vec![PerimeterSegment::Flat {526indices: (0..self.sides).chain([0]).collect(),527}]528}529}530531impl From<RegularPolygon> for Mesh {532fn from(polygon: RegularPolygon) -> Self {533polygon.mesh().build()534}535}536537/// A builder used for creating a [`Mesh`] with an [`Ellipse`] shape.538#[derive(Clone, Copy, Debug, Reflect)]539#[reflect(Default, Debug, Clone)]540pub struct EllipseMeshBuilder {541/// The [`Ellipse`] shape.542pub ellipse: Ellipse,543/// The number of vertices used for the ellipse mesh.544/// The default is `32`.545#[doc(alias = "vertices")]546pub resolution: u32,547}548549impl Default for EllipseMeshBuilder {550fn default() -> Self {551Self {552ellipse: Ellipse::default(),553resolution: 32,554}555}556}557558impl EllipseMeshBuilder {559/// Creates a new [`EllipseMeshBuilder`] from a given half width and half height and a vertex count.560#[inline]561pub const fn new(half_width: f32, half_height: f32, resolution: u32) -> Self {562Self {563ellipse: Ellipse::new(half_width, half_height),564resolution,565}566}567568/// Sets the number of vertices used for the ellipse mesh.569#[inline]570#[doc(alias = "vertices")]571pub const fn resolution(mut self, resolution: u32) -> Self {572self.resolution = resolution;573self574}575}576577impl MeshBuilder for EllipseMeshBuilder {578fn build(&self) -> Mesh {579let resolution = self.resolution as usize;580let mut indices = Vec::with_capacity((resolution - 2) * 3);581let mut positions = Vec::with_capacity(resolution);582let normals = vec![[0.0, 0.0, 1.0]; resolution];583let mut uvs = Vec::with_capacity(resolution);584585// Add pi/2 so that there is a vertex at the top (sin is 1.0 and cos is 0.0)586let start_angle = FRAC_PI_2;587let step = core::f32::consts::TAU / self.resolution as f32;588589for i in 0..self.resolution {590// Compute vertex position at angle theta591let theta = start_angle + i as f32 * step;592let (sin, cos) = ops::sin_cos(theta);593let x = cos * self.ellipse.half_size.x;594let y = sin * self.ellipse.half_size.y;595596positions.push([x, y, 0.0]);597uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);598}599600for i in 1..(self.resolution - 1) {601indices.extend_from_slice(&[0, i, i + 1]);602}603604Mesh::new(605PrimitiveTopology::TriangleList,606RenderAssetUsages::default(),607)608.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)609.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)610.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)611.with_inserted_indices(Indices::U32(indices))612}613}614615impl Extrudable for EllipseMeshBuilder {616fn perimeter(&self) -> Vec<PerimeterSegment> {617vec![PerimeterSegment::Smooth {618first_normal: Vec2::Y,619last_normal: Vec2::Y,620indices: (0..self.resolution).chain([0]).collect(),621}]622}623}624625impl Meshable for Ellipse {626type Output = EllipseMeshBuilder;627628fn mesh(&self) -> Self::Output {629EllipseMeshBuilder {630ellipse: *self,631..Default::default()632}633}634}635636impl From<Ellipse> for Mesh {637fn from(ellipse: Ellipse) -> Self {638ellipse.mesh().build()639}640}641642/// A builder used for creating a [`Mesh`] with a [`Segment2d`].643pub struct Segment2dMeshBuilder {644/// The [`Segment2d`] shape.645pub segment: Segment2d,646}647648impl Segment2dMeshBuilder {649/// Creates a new [`Segment2dMeshBuilder`] from a given segment.650#[inline]651pub const fn new(line: Segment2d) -> Self {652Self { segment: line }653}654}655656impl MeshBuilder for Segment2dMeshBuilder {657fn build(&self) -> Mesh {658let positions = self.segment.vertices.map(|v| v.extend(0.0)).to_vec();659let indices = Indices::U32(vec![0, 1]);660661Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default())662.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)663.with_inserted_indices(indices)664}665}666667impl Meshable for Segment2d {668type Output = Segment2dMeshBuilder;669670fn mesh(&self) -> Self::Output {671Segment2dMeshBuilder::new(*self)672}673}674675impl From<Segment2d> for Mesh {676/// Converts this segment into a [`Mesh`] using a default [`Segment2dMeshBuilder`].677fn from(segment: Segment2d) -> Self {678segment.mesh().build()679}680}681682/// A builder used for creating a [`Mesh`] with a [`Polyline2d`] shape.683#[derive(Clone, Debug, Default, Reflect)]684#[reflect(Default, Debug, Clone)]685pub struct Polyline2dMeshBuilder {686polyline: Polyline2d,687}688689impl MeshBuilder for Polyline2dMeshBuilder {690fn build(&self) -> Mesh {691let positions: Vec<_> = self692.polyline693.vertices694.iter()695.map(|v| v.extend(0.0))696.collect();697698let indices = Indices::U32(699(0..self.polyline.vertices.len() as u32 - 1)700.flat_map(|i| [i, i + 1])701.collect(),702);703704Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default())705.with_inserted_indices(indices)706.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)707}708}709710impl Meshable for Polyline2d {711type Output = Polyline2dMeshBuilder;712713fn mesh(&self) -> Self::Output {714Polyline2dMeshBuilder {715polyline: self.clone(),716}717}718}719720impl From<Polyline2d> for Mesh {721fn from(polyline: Polyline2d) -> Self {722polyline.mesh().build()723}724}725726/// A builder for creating a [`Mesh`] with an [`Annulus`] shape.727#[derive(Clone, Copy, Debug, Reflect)]728#[reflect(Default, Debug, Clone)]729pub struct AnnulusMeshBuilder {730/// The [`Annulus`] shape.731pub annulus: Annulus,732733/// The number of vertices used in constructing each concentric circle of the annulus mesh.734/// The default is `32`.735pub resolution: u32,736}737738impl Default for AnnulusMeshBuilder {739fn default() -> Self {740Self {741annulus: Annulus::default(),742resolution: 32,743}744}745}746747impl AnnulusMeshBuilder {748/// Create an [`AnnulusMeshBuilder`] with the given inner radius, outer radius, and angular vertex count.749#[inline]750pub fn new(inner_radius: f32, outer_radius: f32, resolution: u32) -> Self {751Self {752annulus: Annulus::new(inner_radius, outer_radius),753resolution,754}755}756757/// Sets the number of vertices used in constructing the concentric circles of the annulus mesh.758#[inline]759pub fn resolution(mut self, resolution: u32) -> Self {760self.resolution = resolution;761self762}763}764765impl MeshBuilder for AnnulusMeshBuilder {766fn build(&self) -> Mesh {767let inner_radius = self.annulus.inner_circle.radius;768let outer_radius = self.annulus.outer_circle.radius;769770let num_vertices = (self.resolution as usize + 1) * 2;771let mut indices = Vec::with_capacity(self.resolution as usize * 6);772let mut positions = Vec::with_capacity(num_vertices);773let mut uvs = Vec::with_capacity(num_vertices);774let normals = vec![[0.0, 0.0, 1.0]; num_vertices];775776// We have one more set of vertices than might be naïvely expected;777// the vertices at `start_angle` are duplicated for the purposes of UV778// mapping. Here, each iteration places a pair of vertices at a fixed779// angle from the center of the annulus.780let start_angle = FRAC_PI_2;781let step = core::f32::consts::TAU / self.resolution as f32;782for i in 0..=self.resolution {783let theta = start_angle + (i % self.resolution) as f32 * step;784let (sin, cos) = ops::sin_cos(theta);785let inner_pos = [cos * inner_radius, sin * inner_radius, 0.];786let outer_pos = [cos * outer_radius, sin * outer_radius, 0.];787positions.push(inner_pos);788positions.push(outer_pos);789790// The first UV direction is radial and the second is angular;791// i.e., a single UV rectangle is stretched around the annulus, with792// its top and bottom meeting as the circle closes. Lines of constant793// U map to circles, and lines of constant V map to radial line segments.794let inner_uv = [0., i as f32 / self.resolution as f32];795let outer_uv = [1., i as f32 / self.resolution as f32];796uvs.push(inner_uv);797uvs.push(outer_uv);798}799800// Adjacent pairs of vertices form two triangles with each other; here,801// we are just making sure that they both have the right orientation,802// which is the CCW order of803// `inner_vertex` -> `outer_vertex` -> `next_outer` -> `next_inner`804for i in 0..self.resolution {805let inner_vertex = 2 * i;806let outer_vertex = 2 * i + 1;807let next_inner = inner_vertex + 2;808let next_outer = outer_vertex + 2;809indices.extend_from_slice(&[inner_vertex, outer_vertex, next_outer]);810indices.extend_from_slice(&[next_outer, next_inner, inner_vertex]);811}812813Mesh::new(814PrimitiveTopology::TriangleList,815RenderAssetUsages::default(),816)817.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)818.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)819.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)820.with_inserted_indices(Indices::U32(indices))821}822}823824impl Extrudable for AnnulusMeshBuilder {825fn perimeter(&self) -> Vec<PerimeterSegment> {826let vert_count = 2 * self.resolution;827vec![828PerimeterSegment::Smooth {829first_normal: Vec2::NEG_Y,830last_normal: Vec2::NEG_Y,831indices: (0..vert_count).step_by(2).chain([0]).rev().collect(), // Inner hole832},833PerimeterSegment::Smooth {834first_normal: Vec2::Y,835last_normal: Vec2::Y,836indices: (1..vert_count).step_by(2).chain([1]).collect(), // Outer perimeter837},838]839}840}841842impl Meshable for Annulus {843type Output = AnnulusMeshBuilder;844845fn mesh(&self) -> Self::Output {846AnnulusMeshBuilder {847annulus: *self,848..Default::default()849}850}851}852853impl From<Annulus> for Mesh {854fn from(annulus: Annulus) -> Self {855annulus.mesh().build()856}857}858859/// A builder for creating a [`Mesh`] with an [`Rhombus`] shape.860#[derive(Clone, Copy, Debug, Reflect)]861#[reflect(Default, Debug, Clone)]862pub struct RhombusMeshBuilder {863half_diagonals: Vec2,864}865866impl Default for RhombusMeshBuilder {867/// Returns the default [`RhombusMeshBuilder`] with a half-horizontal and half-vertical diagonal of `0.5`.868fn default() -> Self {869Self {870half_diagonals: Vec2::splat(0.5),871}872}873}874875impl RhombusMeshBuilder {876/// Creates a new [`RhombusMeshBuilder`] from a horizontal and vertical diagonal size.877///878/// # Panics879///880/// Panics in debug mode if `horizontal_diagonal` or `vertical_diagonal` is negative.881pub const fn new(horizontal_diagonal: f32, vertical_diagonal: f32) -> Self {882debug_assert!(883horizontal_diagonal >= 0.0,884"rhombus has a negative horizontal size",885);886debug_assert!(887vertical_diagonal >= 0.0,888"rhombus has a negative vertical size"889);890891Self {892half_diagonals: Vec2::new(horizontal_diagonal / 2.0, vertical_diagonal / 2.0),893}894}895}896897impl MeshBuilder for RhombusMeshBuilder {898fn build(&self) -> Mesh {899let [hhd, vhd] = [self.half_diagonals.x, self.half_diagonals.y];900let positions = vec![901[hhd, 0.0, 0.0],902[0.0, vhd, 0.0],903[-hhd, 0.0, 0.0],904[0.0, -vhd, 0.0],905];906let normals = vec![[0.0, 0.0, 1.0]; 4];907let uvs = vec![[1.0, 0.5], [0.5, 0.0], [0.0, 0.5], [0.5, 1.0]];908let indices = Indices::U32(vec![2, 0, 1, 2, 3, 0]);909910Mesh::new(911PrimitiveTopology::TriangleList,912RenderAssetUsages::default(),913)914.with_inserted_indices(indices)915.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)916.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)917.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)918}919}920921impl Extrudable for RhombusMeshBuilder {922fn perimeter(&self) -> Vec<PerimeterSegment> {923vec![PerimeterSegment::Flat {924indices: vec![0, 1, 2, 3, 0],925}]926}927}928929impl Meshable for Rhombus {930type Output = RhombusMeshBuilder;931932fn mesh(&self) -> Self::Output {933Self::Output {934half_diagonals: self.half_diagonals,935}936}937}938939impl From<Rhombus> for Mesh {940fn from(rhombus: Rhombus) -> Self {941rhombus.mesh().build()942}943}944945/// A builder used for creating a [`Mesh`] with a [`Triangle2d`] shape.946#[derive(Clone, Copy, Debug, Default, Reflect)]947#[reflect(Default, Debug, Clone)]948pub struct Triangle2dMeshBuilder {949triangle: Triangle2d,950}951952impl Triangle2dMeshBuilder {953/// Creates a new [`Triangle2dMeshBuilder`] from the points `a`, `b`, and `c`.954pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {955Self {956triangle: Triangle2d::new(a, b, c),957}958}959}960961impl Meshable for Triangle2d {962type Output = Triangle2dMeshBuilder;963964fn mesh(&self) -> Self::Output {965Self::Output { triangle: *self }966}967}968969impl MeshBuilder for Triangle2dMeshBuilder {970fn build(&self) -> Mesh {971let vertices_3d = self.triangle.vertices.map(|v| v.extend(0.));972973let positions: Vec<_> = vertices_3d.into();974let normals = vec![[0.0, 0.0, 1.0]; 3];975976let uvs: Vec<_> = triangle3d::uv_coords(&Triangle3d::new(977vertices_3d[0],978vertices_3d[1],979vertices_3d[2],980))981.into();982983let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;984let indices = if is_ccw {985Indices::U32(vec![0, 1, 2])986} else {987Indices::U32(vec![2, 1, 0])988};989990Mesh::new(991PrimitiveTopology::TriangleList,992RenderAssetUsages::default(),993)994.with_inserted_indices(indices)995.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)996.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)997.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)998}999}10001001impl Extrudable for Triangle2dMeshBuilder {1002fn perimeter(&self) -> Vec<PerimeterSegment> {1003let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;1004if is_ccw {1005vec![PerimeterSegment::Flat {1006indices: vec![0, 1, 2, 0],1007}]1008} else {1009vec![PerimeterSegment::Flat {1010indices: vec![2, 1, 0, 2],1011}]1012}1013}1014}10151016impl From<Triangle2d> for Mesh {1017fn from(triangle: Triangle2d) -> Self {1018triangle.mesh().build()1019}1020}10211022/// A builder used for creating a [`Mesh`] with a [`Rectangle`] shape.1023#[derive(Clone, Copy, Debug, Reflect)]1024#[reflect(Default, Debug, Clone)]1025pub struct RectangleMeshBuilder {1026half_size: Vec2,1027}10281029impl Default for RectangleMeshBuilder {1030/// Returns the default [`RectangleMeshBuilder`] with a half-width and half-height of `0.5`.1031fn default() -> Self {1032Self {1033half_size: Vec2::splat(0.5),1034}1035}1036}10371038impl RectangleMeshBuilder {1039/// Creates a new [`RectangleMeshBuilder`] from a full width and height.1040///1041/// # Panics1042///1043/// Panics in debug mode if `width` or `height` is negative.1044pub const fn new(width: f32, height: f32) -> Self {1045debug_assert!(width >= 0.0, "rectangle has a negative width");1046debug_assert!(height >= 0.0, "rectangle has a negative height");10471048Self {1049half_size: Vec2::new(width / 2.0, height / 2.0),1050}1051}1052}10531054impl MeshBuilder for RectangleMeshBuilder {1055fn build(&self) -> Mesh {1056let [hw, hh] = [self.half_size.x, self.half_size.y];1057let positions = vec![1058[hw, hh, 0.0],1059[-hw, hh, 0.0],1060[-hw, -hh, 0.0],1061[hw, -hh, 0.0],1062];1063let normals = vec![[0.0, 0.0, 1.0]; 4];1064let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]];1065let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]);10661067Mesh::new(1068PrimitiveTopology::TriangleList,1069RenderAssetUsages::default(),1070)1071.with_inserted_indices(indices)1072.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)1073.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)1074.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)1075}1076}10771078impl Extrudable for RectangleMeshBuilder {1079fn perimeter(&self) -> Vec<PerimeterSegment> {1080vec![PerimeterSegment::Flat {1081indices: vec![0, 1, 2, 3, 0],1082}]1083}1084}10851086impl Meshable for Rectangle {1087type Output = RectangleMeshBuilder;10881089fn mesh(&self) -> Self::Output {1090RectangleMeshBuilder {1091half_size: self.half_size,1092}1093}1094}10951096impl From<Rectangle> for Mesh {1097fn from(rectangle: Rectangle) -> Self {1098rectangle.mesh().build()1099}1100}11011102/// A builder used for creating a [`Mesh`] with a [`Capsule2d`] shape.1103#[derive(Clone, Copy, Debug, Reflect)]1104#[reflect(Default, Debug, Clone)]1105pub struct Capsule2dMeshBuilder {1106/// The [`Capsule2d`] shape.1107pub capsule: Capsule2d,1108/// The number of vertices used for one hemicircle.1109/// The total number of vertices for the capsule mesh will be two times the resolution.1110///1111/// The default is `16`.1112pub resolution: u32,1113}11141115impl Default for Capsule2dMeshBuilder {1116fn default() -> Self {1117Self {1118capsule: Capsule2d::default(),1119resolution: 16,1120}1121}1122}11231124impl Capsule2dMeshBuilder {1125/// Creates a new [`Capsule2dMeshBuilder`] from a given radius, length, and the number of vertices1126/// used for one hemicircle. The total number of vertices for the capsule mesh will be two times the resolution.1127#[inline]1128pub fn new(radius: f32, length: f32, resolution: u32) -> Self {1129Self {1130capsule: Capsule2d::new(radius, length),1131resolution,1132}1133}11341135/// Sets the number of vertices used for one hemicircle.1136/// The total number of vertices for the capsule mesh will be two times the resolution.1137#[inline]1138pub const fn resolution(mut self, resolution: u32) -> Self {1139self.resolution = resolution;1140self1141}1142}11431144impl MeshBuilder for Capsule2dMeshBuilder {1145fn build(&self) -> Mesh {1146// The resolution is the number of vertices for one semicircle1147let resolution = self.resolution;1148let vertex_count = 2 * resolution;11491150// Six extra indices for the two triangles between the semicircles1151let mut indices = Vec::with_capacity((resolution as usize - 2) * 2 * 3 + 6);1152let mut positions = Vec::with_capacity(vertex_count as usize);1153let normals = vec![[0.0, 0.0, 1.0]; vertex_count as usize];1154let mut uvs = Vec::with_capacity(vertex_count as usize);11551156let radius = self.capsule.radius;1157let step = core::f32::consts::TAU / vertex_count as f32;11581159// If the vertex count is even, offset starting angle of top semicircle by half a step1160// to position the vertices evenly.1161let start_angle = if vertex_count.is_multiple_of(2) {1162step / 2.01163} else {11640.01165};11661167// How much the hemicircle radius is of the total half-height of the capsule.1168// This is used to prevent the UVs from stretching between the semicircles.1169let radius_frac = self.capsule.radius / (self.capsule.half_length + self.capsule.radius);11701171// Create top semicircle1172for i in 0..resolution {1173// Compute vertex position at angle theta1174let theta = start_angle + i as f32 * step;1175let (sin, cos) = ops::sin_cos(theta);1176let (x, y) = (cos * radius, sin * radius + self.capsule.half_length);11771178positions.push([x, y, 0.0]);1179uvs.push([0.5 * (cos + 1.0), radius_frac * (1.0 - 0.5 * (sin + 1.0))]);1180}11811182// Add top semicircle indices1183for i in 1..resolution - 1 {1184indices.extend_from_slice(&[0, i, i + 1]);1185}11861187// Add indices for top left triangle of the part between the semicircles1188indices.extend_from_slice(&[0, resolution - 1, resolution]);11891190// Create bottom semicircle1191for i in resolution..vertex_count {1192// Compute vertex position at angle theta1193let theta = start_angle + i as f32 * step;1194let (sin, cos) = ops::sin_cos(theta);1195let (x, y) = (cos * radius, sin * radius - self.capsule.half_length);11961197positions.push([x, y, 0.0]);1198uvs.push([0.5 * (cos + 1.0), 1.0 - radius_frac * 0.5 * (sin + 1.0)]);1199}12001201// Add bottom semicircle indices1202for i in 1..resolution - 1 {1203indices.extend_from_slice(&[resolution, resolution + i, resolution + i + 1]);1204}12051206// Add indices for bottom right triangle of the part between the semicircles1207indices.extend_from_slice(&[resolution, vertex_count - 1, 0]);12081209Mesh::new(1210PrimitiveTopology::TriangleList,1211RenderAssetUsages::default(),1212)1213.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)1214.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)1215.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)1216.with_inserted_indices(Indices::U32(indices))1217}1218}12191220impl Extrudable for Capsule2dMeshBuilder {1221fn perimeter(&self) -> Vec<PerimeterSegment> {1222let resolution = self.resolution;1223let top_semi_indices = (0..resolution).collect();1224let bottom_semi_indices = (resolution..(2 * resolution)).collect();1225vec![1226PerimeterSegment::Smooth {1227first_normal: Vec2::X,1228last_normal: Vec2::NEG_X,1229indices: top_semi_indices,1230}, // Top semi-circle1231PerimeterSegment::Flat {1232indices: vec![resolution - 1, resolution],1233}, // Left edge1234PerimeterSegment::Smooth {1235first_normal: Vec2::NEG_X,1236last_normal: Vec2::X,1237indices: bottom_semi_indices,1238}, // Bottom semi-circle1239PerimeterSegment::Flat {1240indices: vec![2 * resolution - 1, 0],1241}, // Right edge1242]1243}1244}12451246impl Meshable for Capsule2d {1247type Output = Capsule2dMeshBuilder;12481249fn mesh(&self) -> Self::Output {1250Capsule2dMeshBuilder {1251capsule: *self,1252..Default::default()1253}1254}1255}12561257impl From<Capsule2d> for Mesh {1258fn from(capsule: Capsule2d) -> Self {1259capsule.mesh().build()1260}1261}12621263/// A builder used for creating a [`Mesh`] with a [`Ring`] shape.1264pub struct RingMeshBuilder<P>1265where1266P: Primitive2d + Meshable,1267{1268pub outer_shape_builder: P::Output,1269pub inner_shape_builder: P::Output,1270}12711272impl<P> RingMeshBuilder<P>1273where1274P: Primitive2d + Meshable,1275{1276/// Create a new `RingMeshBuilder<P>` from a given `Ring<P>` shape.1277pub fn new(ring: &Ring<P>) -> Self {1278Self {1279outer_shape_builder: ring.outer_shape.mesh(),1280inner_shape_builder: ring.inner_shape.mesh(),1281}1282}12831284/// Apply a function to the inner builders1285pub fn with_inner(mut self, func: impl Fn(P::Output) -> P::Output) -> Self {1286self.outer_shape_builder = func(self.outer_shape_builder);1287self.inner_shape_builder = func(self.inner_shape_builder);1288self1289}12901291fn get_vertex_attributes(&self) -> Option<RingMeshBuilderVertexAttributes> {1292fn get_positions(mesh: &mut Mesh) -> Option<&mut Vec<[f32; 3]>> {1293if let VertexAttributeValues::Float32x3(data) =1294mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)?1295{1296Some(data)1297} else {1298None1299}1300}13011302fn get_uvs(mesh: &mut Mesh) -> Option<&mut Vec<[f32; 2]>> {1303if let VertexAttributeValues::Float32x2(data) =1304mesh.attribute_mut(Mesh::ATTRIBUTE_UV_0)?1305{1306Some(data)1307} else {1308None1309}1310}13111312fn get_normals(mesh: &mut Mesh) -> Option<&mut Vec<[f32; 3]>> {1313if let VertexAttributeValues::Float32x3(data) =1314mesh.attribute_mut(Mesh::ATTRIBUTE_NORMAL)?1315{1316Some(data)1317} else {1318None1319}1320}13211322let mut outer = self.outer_shape_builder.build();1323let mut inner = self.inner_shape_builder.build();13241325assert_eq!(1326outer.primitive_topology(),1327PrimitiveTopology::TriangleList,1328"PrimitiveTopology must be a TriangleList, mesh builder not compatible"1329);1330assert_eq!(1331inner.primitive_topology(),1332PrimitiveTopology::TriangleList,1333"PrimitiveTopology must be a TriangleList, mesh builder not compatible"1334);13351336Some(RingMeshBuilderVertexAttributes {1337outer_positions: mem::take(get_positions(&mut outer)?),1338inner_positions: mem::take(get_positions(&mut inner)?),1339outer_normals: mem::take(get_normals(&mut outer)?),1340inner_normals: mem::take(get_normals(&mut inner)?),1341outer_uvs: mem::take(get_uvs(&mut outer)?),1342inner_uvs: mem::take(get_uvs(&mut inner)?),1343})1344}1345}13461347struct RingMeshBuilderVertexAttributes {1348outer_positions: Vec<[f32; 3]>,1349inner_positions: Vec<[f32; 3]>,1350outer_normals: Vec<[f32; 3]>,1351inner_normals: Vec<[f32; 3]>,1352outer_uvs: Vec<[f32; 2]>,1353inner_uvs: Vec<[f32; 2]>,1354}13551356impl<P> MeshBuilder for RingMeshBuilder<P>1357where1358P: Primitive2d + Meshable,1359{1360/// Builds a [`Mesh`] based on the configuration in `self`.1361///1362/// # Panics1363///1364/// Panics if the following assumptions are not met.1365///1366/// It is assumed that the inner and outer meshes have the same number of vertices.1367/// If not, then the [`MeshBuilder`] of the underlying 2d primitive has generated1368/// a different number of vertices for the inner and outer instances of the primitive.1369///1370/// It is assumed that the `primitive_topology` of the mesh returned by1371/// the underlying builder is [`PrimitiveTopology::TriangleList`]1372/// and that the mesh has [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes.1373fn build(&self) -> Mesh {1374if let Some(RingMeshBuilderVertexAttributes {1375outer_uvs,1376inner_uvs,1377outer_positions,1378inner_positions,1379outer_normals,1380inner_normals,1381}) = self.get_vertex_attributes()1382&& outer_uvs.len() == inner_uvs.len()1383&& outer_positions.len() == inner_positions.len()1384&& outer_normals.len() == inner_normals.len()1385{1386let mut uvs = outer_uvs;1387let inner_uvs = inner_positions1388.iter()1389.zip(&outer_positions)1390.zip(&inner_uvs)1391.map(|((inner_position, outer_position), inner_uv)| -> [f32; 2] {1392const UV_CENTER: Vec2 = Vec2::splat(0.5);1393let ip = Vec3::from(*inner_position).truncate();1394let op = Vec3::from(*outer_position).truncate();1395let uv = Vec2::from(*inner_uv) - UV_CENTER;1396(uv * ip.length() / op.length() + UV_CENTER).into()1397});1398uvs.extend(inner_uvs);13991400let mut normals = outer_normals;1401normals.extend(inner_normals);14021403let points = outer_positions.len() as u32;1404let mut indices = Vec::with_capacity(outer_positions.len() * 6);1405for i in 0..points {1406// for five points:1407indices.push(i); // 0 1 2 3 41408indices.push(i + 1); // 1 2 3 4 0 <-1409indices.push(points + i); // 0' 1' 2' 3' 4'1410indices.push(points + i); // 0' 1' 2' 3' 4'1411indices.push(i + 1); // 1 2 3 4 0 <-1412indices.push(points + i + 1); // 1' 2' 3' 4' 0' <-1413}1414let indices_length = indices.len();1415// Fix up the last pair of triangles (return to start)1416if let (_, [_, b, _, _, e, f]) = indices.split_at_mut(indices_length.saturating_sub(6))1417{1418*b = 0;1419*e = 0;1420*f = points;1421}14221423let mut positions = outer_positions;1424positions.extend_from_slice(&inner_positions);14251426Mesh::new(1427PrimitiveTopology::TriangleList,1428RenderAssetUsages::default(),1429)1430.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)1431.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)1432.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)1433.with_inserted_indices(Indices::U32(indices))1434} else {1435panic!("The inner and outer meshes should have the same number of vertices, and have required attributes");1436}1437}1438}14391440impl<P> Extrudable for RingMeshBuilder<P>1441where1442P: Primitive2d + Meshable,1443P::Output: Extrudable,1444{1445/// A list of the indices each representing a part of the perimeter of the mesh.1446///1447/// # Panics1448///1449/// Panics if the following assumptions are not met.1450///1451/// It is assumed that the inner and outer meshes have the same number of vertices.1452/// If not, then the [`MeshBuilder`] of the underlying 2d primitive has generated1453/// a different number of vertices for the inner and outer instances of the primitive.1454///1455/// It is assumed that the `primitive_topology` of the mesh returned by1456/// the underlying builder is [`PrimitiveTopology::TriangleList`]1457/// and that the mesh has [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes.1458fn perimeter(&self) -> Vec<PerimeterSegment> {1459let outer_vertex_count = self1460.get_vertex_attributes()1461.filter(|r| r.outer_positions.len() == r.inner_positions.len())1462.expect("The inner and outer meshes should have the same number of vertices, and have required attributes")1463.outer_positions1464.len();14651466let mut outer_perimeter = self.outer_shape_builder.perimeter();1467let inner_perimeter =1468self.inner_shape_builder1469.perimeter()1470.into_iter()1471.rev()1472.map(|segment| match segment {1473PerimeterSegment::Smooth {1474first_normal,1475last_normal,1476mut indices,1477} => PerimeterSegment::Smooth {1478first_normal: -last_normal,1479last_normal: -first_normal,1480indices: {1481let outer_perimeter_vertex_count = outer_vertex_count as u32;1482indices.reverse();1483for i in &mut indices {1484*i += outer_perimeter_vertex_count;1485}1486indices1487},1488},1489PerimeterSegment::Flat { mut indices } => PerimeterSegment::Flat {1490indices: {1491let outer_perimeter_vertex_count = outer_vertex_count as u32;1492indices.reverse();1493for i in &mut indices {1494*i += outer_perimeter_vertex_count;1495}1496indices1497},1498},1499});15001501outer_perimeter.extend(inner_perimeter);1502outer_perimeter1503}1504}15051506impl<P> Meshable for Ring<P>1507where1508P: Primitive2d + Meshable,1509{1510type Output = RingMeshBuilder<P>;15111512fn mesh(&self) -> Self::Output {1513RingMeshBuilder::new(self)1514}1515}15161517impl<P> From<Ring<P>> for Mesh1518where1519P: Primitive2d + Meshable,1520{1521fn from(ring: Ring<P>) -> Self {1522ring.mesh().build()1523}1524}15251526#[cfg(test)]1527mod tests {1528use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd};1529use bevy_platform::collections::HashSet;15301531use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};15321533fn count_distinct_positions(points: &[[f32; 3]]) -> usize {1534let mut map = <HashSet<_>>::default();1535for point in points {1536map.insert(point.map(FloatOrd));1537}1538map.len()1539}15401541#[test]1542fn test_annulus() {1543let mesh = Annulus::new(1.0, 1.2).mesh().resolution(16).build();15441545assert_eq!(154632,1547count_distinct_positions(1548mesh.attribute(Mesh::ATTRIBUTE_POSITION)1549.unwrap()1550.as_float3()1551.unwrap()1552)1553);1554}15551556/// Sin/cos and multiplication computations result in numbers like 0.4999999.1557/// Round these to numbers we expect like 0.5.1558fn fix_floats<const N: usize>(points: &mut [[f32; N]]) {1559for point in points.iter_mut() {1560for coord in point.iter_mut() {1561let round = (*coord * 2.).round() / 2.;1562if (*coord - round).abs() < 0.00001 {1563*coord = round;1564}1565}1566}1567}15681569#[test]1570fn test_regular_polygon() {1571let mut mesh = Mesh::from(RegularPolygon::new(7.0, 4));15721573let Some(VertexAttributeValues::Float32x3(mut positions)) =1574mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)1575else {1576panic!("Expected positions f32x3");1577};1578let Some(VertexAttributeValues::Float32x2(mut uvs)) =1579mesh.remove_attribute(Mesh::ATTRIBUTE_UV_0)1580else {1581panic!("Expected uvs f32x2");1582};1583let Some(VertexAttributeValues::Float32x3(normals)) =1584mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)1585else {1586panic!("Expected normals f32x3");1587};15881589fix_floats(&mut positions);1590fix_floats(&mut uvs);15911592assert_eq!(1593[1594[0.0, 7.0, 0.0],1595[-7.0, 0.0, 0.0],1596[0.0, -7.0, 0.0],1597[7.0, 0.0, 0.0],1598],1599&positions[..]1600);16011602// Note V coordinate increases in the opposite direction to the Y coordinate.1603assert_eq!([[0.5, 0.0], [0.0, 0.5], [0.5, 1.0], [1.0, 0.5],], &uvs[..]);16041605assert_eq!(&[[0.0, 0.0, 1.0]; 4], &normals[..]);1606}1607}160816091610