Path: blob/main/crates/bevy_mesh/src/primitives/dim2.rs
6596 views
use core::f32::consts::FRAC_PI_2;12use crate::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment};3use bevy_asset::RenderAssetUsages;45use super::{Extrudable, MeshBuilder, Meshable};6use bevy_math::prelude::Polyline2d;7use bevy_math::{8ops,9primitives::{10Annulus, Capsule2d, Circle, CircularSector, CircularSegment, ConvexPolygon, Ellipse,11Rectangle, RegularPolygon, Rhombus, Segment2d, Triangle2d, Triangle3d, WindingOrder,12},13FloatExt, Vec2,14};15use bevy_reflect::prelude::*;16use wgpu_types::PrimitiveTopology;1718/// A builder used for creating a [`Mesh`] with a [`Circle`] shape.19#[derive(Clone, Copy, Debug, Reflect)]20#[reflect(Default, Debug, Clone)]21pub struct CircleMeshBuilder {22/// The [`Circle`] shape.23pub circle: Circle,24/// The number of vertices used for the circle mesh.25/// The default is `32`.26#[doc(alias = "vertices")]27pub resolution: u32,28}2930impl Default for CircleMeshBuilder {31fn default() -> Self {32Self {33circle: Circle::default(),34resolution: 32,35}36}37}3839impl CircleMeshBuilder {40/// Creates a new [`CircleMeshBuilder`] from a given radius and vertex count.41#[inline]42pub const fn new(radius: f32, resolution: u32) -> Self {43Self {44circle: Circle { radius },45resolution,46}47}4849/// Sets the number of vertices used for the circle mesh.50#[inline]51#[doc(alias = "vertices")]52pub const fn resolution(mut self, resolution: u32) -> Self {53self.resolution = resolution;54self55}56}5758impl MeshBuilder for CircleMeshBuilder {59fn build(&self) -> Mesh {60Ellipse::new(self.circle.radius, self.circle.radius)61.mesh()62.resolution(self.resolution)63.build()64}65}6667impl Extrudable for CircleMeshBuilder {68fn perimeter(&self) -> Vec<PerimeterSegment> {69vec![PerimeterSegment::Smooth {70first_normal: Vec2::Y,71last_normal: Vec2::Y,72indices: (0..self.resolution).chain([0]).collect(),73}]74}75}7677impl Meshable for Circle {78type Output = CircleMeshBuilder;7980fn mesh(&self) -> Self::Output {81CircleMeshBuilder {82circle: *self,83..Default::default()84}85}86}8788impl From<Circle> for Mesh {89fn from(circle: Circle) -> Self {90circle.mesh().build()91}92}9394/// Specifies how to generate UV-mappings for the [`CircularSector`] and [`CircularSegment`] shapes.95///96/// Currently the only variant is `Mask`, which is good for showing a portion of a texture that includes97/// the entire circle, particularly the same texture will be displayed with different fractions of a98/// complete circle.99///100/// It's expected that more will be added in the future, such as a variant that causes the texture to be101/// scaled to fit the bounding box of the shape, which would be good for packed textures only including the102/// portion of the circle that is needed to display.103#[derive(Copy, Clone, Debug, PartialEq, Reflect)]104#[reflect(Default, Debug, Clone)]105#[non_exhaustive]106pub enum CircularMeshUvMode {107/// Treats the shape as a mask over a circle of equal size and radius,108/// with the center of the circle at the center of the texture.109Mask {110/// Angle by which to rotate the shape when generating the UV map.111angle: f32,112},113}114115impl Default for CircularMeshUvMode {116fn default() -> Self {117CircularMeshUvMode::Mask { angle: 0.0 }118}119}120121/// A builder used for creating a [`Mesh`] with a [`CircularSector`] shape.122///123/// The resulting mesh will have a UV-map such that the center of the circle is124/// at the center of the texture.125#[derive(Clone, Debug, Reflect)]126#[reflect(Default, Debug, Clone)]127pub struct CircularSectorMeshBuilder {128/// The sector shape.129pub sector: CircularSector,130/// The number of vertices used for the arc portion of the sector mesh.131/// The default is `32`.132#[doc(alias = "vertices")]133pub resolution: u32,134/// The UV mapping mode135pub uv_mode: CircularMeshUvMode,136}137138impl Default for CircularSectorMeshBuilder {139fn default() -> Self {140Self {141sector: CircularSector::default(),142resolution: 32,143uv_mode: CircularMeshUvMode::default(),144}145}146}147148impl CircularSectorMeshBuilder {149/// Creates a new [`CircularSectorMeshBuilder`] from a given sector150#[inline]151pub fn new(sector: CircularSector) -> Self {152Self {153sector,154..Self::default()155}156}157158/// Sets the number of vertices used for the sector mesh.159#[inline]160#[doc(alias = "vertices")]161pub const fn resolution(mut self, resolution: u32) -> Self {162self.resolution = resolution;163self164}165166/// Sets the uv mode used for the sector mesh167#[inline]168pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {169self.uv_mode = uv_mode;170self171}172}173174impl MeshBuilder for CircularSectorMeshBuilder {175fn build(&self) -> Mesh {176let resolution = self.resolution as usize;177let mut indices = Vec::with_capacity((resolution - 1) * 3);178let mut positions = Vec::with_capacity(resolution + 1);179let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];180let mut uvs = Vec::with_capacity(resolution + 1);181182let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;183184// Push the center of the circle.185positions.push([0.0; 3]);186uvs.push([0.5; 2]);187188let first_angle = FRAC_PI_2 - self.sector.half_angle();189let last_angle = FRAC_PI_2 + self.sector.half_angle();190let last_i = (self.resolution - 1) as f32;191for i in 0..self.resolution {192let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);193194// Compute the vertex195let vertex = self.sector.radius() * Vec2::from_angle(angle);196// 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).197// We accomplish the Y axis flip by negating the angle.198let uv =199Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));200201positions.push([vertex.x, vertex.y, 0.0]);202uvs.push([uv.x, uv.y]);203}204205for i in 1..self.resolution {206// Index 0 is the center.207indices.extend_from_slice(&[0, i, i + 1]);208}209210Mesh::new(211PrimitiveTopology::TriangleList,212RenderAssetUsages::default(),213)214.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)215.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)216.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)217.with_inserted_indices(Indices::U32(indices))218}219}220221impl Extrudable for CircularSectorMeshBuilder {222fn perimeter(&self) -> Vec<PerimeterSegment> {223let (sin, cos) = ops::sin_cos(self.sector.arc.half_angle);224let first_normal = Vec2::new(sin, cos);225let last_normal = Vec2::new(-sin, cos);226vec![227PerimeterSegment::Flat {228indices: vec![self.resolution, 0, 1],229},230PerimeterSegment::Smooth {231first_normal,232last_normal,233indices: (1..=self.resolution).collect(),234},235]236}237}238239impl Meshable for CircularSector {240type Output = CircularSectorMeshBuilder;241242fn mesh(&self) -> Self::Output {243CircularSectorMeshBuilder {244sector: *self,245..Default::default()246}247}248}249250impl From<CircularSector> for Mesh {251/// Converts this sector into a [`Mesh`] using a default [`CircularSectorMeshBuilder`].252///253/// See the documentation of [`CircularSectorMeshBuilder`] for more details.254fn from(sector: CircularSector) -> Self {255sector.mesh().build()256}257}258259/// A builder used for creating a [`Mesh`] with a [`CircularSegment`] shape.260///261/// The resulting mesh will have a UV-map such that the center of the circle is262/// at the center of the texture.263#[derive(Clone, Copy, Debug, Reflect)]264#[reflect(Default, Debug, Clone)]265pub struct CircularSegmentMeshBuilder {266/// The segment shape.267pub segment: CircularSegment,268/// The number of vertices used for the arc portion of the segment mesh.269/// The default is `32`.270#[doc(alias = "vertices")]271pub resolution: u32,272/// The UV mapping mode273pub uv_mode: CircularMeshUvMode,274}275276impl Default for CircularSegmentMeshBuilder {277fn default() -> Self {278Self {279segment: CircularSegment::default(),280resolution: 32,281uv_mode: CircularMeshUvMode::default(),282}283}284}285286impl CircularSegmentMeshBuilder {287/// Creates a new [`CircularSegmentMeshBuilder`] from a given segment288#[inline]289pub fn new(segment: CircularSegment) -> Self {290Self {291segment,292..Self::default()293}294}295296/// Sets the number of vertices used for the segment mesh.297#[inline]298#[doc(alias = "vertices")]299pub const fn resolution(mut self, resolution: u32) -> Self {300self.resolution = resolution;301self302}303304/// Sets the uv mode used for the segment mesh305#[inline]306pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {307self.uv_mode = uv_mode;308self309}310}311312impl MeshBuilder for CircularSegmentMeshBuilder {313fn build(&self) -> Mesh {314let resolution = self.resolution as usize;315let mut indices = Vec::with_capacity((resolution - 1) * 3);316let mut positions = Vec::with_capacity(resolution + 1);317let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];318let mut uvs = Vec::with_capacity(resolution + 1);319320let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;321322// Push the center of the chord.323let midpoint_vertex = self.segment.chord_midpoint();324positions.push([midpoint_vertex.x, midpoint_vertex.y, 0.0]);325// Compute the UV coordinate of the midpoint vertex.326// This is similar to the computation inside the loop for the arc vertices,327// but the vertex angle is PI/2, and we must scale by the ratio of the apothem to the radius328// to correctly position the vertex.329let midpoint_uv = Vec2::from_angle(-uv_angle - FRAC_PI_2).mul_add(330Vec2::splat(0.5 * (self.segment.apothem() / self.segment.radius())),331Vec2::splat(0.5),332);333uvs.push([midpoint_uv.x, midpoint_uv.y]);334335let first_angle = FRAC_PI_2 - self.segment.half_angle();336let last_angle = FRAC_PI_2 + self.segment.half_angle();337let last_i = (self.resolution - 1) as f32;338for i in 0..self.resolution {339let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);340341// Compute the vertex342let vertex = self.segment.radius() * Vec2::from_angle(angle);343// 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).344// We accomplish the Y axis flip by negating the angle.345let uv =346Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));347348positions.push([vertex.x, vertex.y, 0.0]);349uvs.push([uv.x, uv.y]);350}351352for i in 1..self.resolution {353// Index 0 is the midpoint of the chord.354indices.extend_from_slice(&[0, i, i + 1]);355}356357Mesh::new(358PrimitiveTopology::TriangleList,359RenderAssetUsages::default(),360)361.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)362.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)363.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)364.with_inserted_indices(Indices::U32(indices))365}366}367368impl Extrudable for CircularSegmentMeshBuilder {369fn perimeter(&self) -> Vec<PerimeterSegment> {370let (sin, cos) = ops::sin_cos(self.segment.arc.half_angle);371let first_normal = Vec2::new(sin, cos);372let last_normal = Vec2::new(-sin, cos);373vec![374PerimeterSegment::Flat {375indices: vec![self.resolution, 0, 1],376},377PerimeterSegment::Smooth {378first_normal,379last_normal,380indices: (1..=self.resolution).collect(),381},382]383}384}385386impl Meshable for CircularSegment {387type Output = CircularSegmentMeshBuilder;388389fn mesh(&self) -> Self::Output {390CircularSegmentMeshBuilder {391segment: *self,392..Default::default()393}394}395}396397impl From<CircularSegment> for Mesh {398/// Converts this sector into a [`Mesh`] using a default [`CircularSegmentMeshBuilder`].399///400/// See the documentation of [`CircularSegmentMeshBuilder`] for more details.401fn from(segment: CircularSegment) -> Self {402segment.mesh().build()403}404}405406/// A builder used for creating a [`Mesh`] with a [`ConvexPolygon`] shape.407///408/// You must verify that the `vertices` are not concave when constructing this type. You can409/// guarantee this by creating a [`ConvexPolygon`] first, then calling [`ConvexPolygon::mesh()`].410#[derive(Clone, Debug, Reflect)]411#[reflect(Debug, Clone)]412pub struct ConvexPolygonMeshBuilder {413pub vertices: Vec<Vec2>,414}415416impl Meshable for ConvexPolygon {417type Output = ConvexPolygonMeshBuilder;418419fn mesh(&self) -> Self::Output {420Self::Output {421vertices: self.vertices().to_vec(),422}423}424}425426impl MeshBuilder for ConvexPolygonMeshBuilder {427fn build(&self) -> Mesh {428let len = self.vertices.len();429let mut indices = Vec::with_capacity((len - 2) * 3);430let mut positions = Vec::with_capacity(len);431432for vertex in &self.vertices {433positions.push([vertex.x, vertex.y, 0.0]);434}435for i in 2..len as u32 {436indices.extend_from_slice(&[0, i - 1, i]);437}438Mesh::new(439PrimitiveTopology::TriangleList,440RenderAssetUsages::default(),441)442.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)443.with_inserted_indices(Indices::U32(indices))444}445}446447impl Extrudable for ConvexPolygonMeshBuilder {448fn perimeter(&self) -> Vec<PerimeterSegment> {449vec![PerimeterSegment::Flat {450indices: (0..self.vertices.len() as u32).chain([0]).collect(),451}]452}453}454455impl From<ConvexPolygon> for Mesh {456fn from(polygon: ConvexPolygon) -> Self {457polygon.mesh().build()458}459}460461/// A builder used for creating a [`Mesh`] with a [`RegularPolygon`] shape.462#[derive(Clone, Copy, Debug, Reflect)]463#[reflect(Default, Debug, Clone)]464pub struct RegularPolygonMeshBuilder {465circumradius: f32,466sides: u32,467}468469impl Default for RegularPolygonMeshBuilder {470/// Returns the default [`RegularPolygonMeshBuilder`] with six sides (a hexagon) and a circumradius of `0.5`.471fn default() -> Self {472Self {473circumradius: 0.5,474sides: 6,475}476}477}478479impl RegularPolygonMeshBuilder {480/// Creates a new [`RegularPolygonMeshBuilder`] from the radius of a circumcircle and a number481/// of sides.482///483/// # Panics484///485/// Panics in debug mode if `circumradius` is negative, or if `sides` is less than 3.486pub const fn new(circumradius: f32, sides: u32) -> Self {487debug_assert!(488circumradius.is_sign_positive(),489"polygon has a negative radius"490);491debug_assert!(sides > 2, "polygon has less than 3 sides");492493Self {494circumradius,495sides,496}497}498}499500impl Meshable for RegularPolygon {501type Output = RegularPolygonMeshBuilder;502503fn mesh(&self) -> Self::Output {504Self::Output {505circumradius: self.circumcircle.radius,506sides: self.sides,507}508}509}510511impl MeshBuilder for RegularPolygonMeshBuilder {512fn build(&self) -> Mesh {513// The ellipse mesh is just a regular polygon with two radii514Ellipse::new(self.circumradius, self.circumradius)515.mesh()516.resolution(self.sides)517.build()518}519}520521impl Extrudable for RegularPolygonMeshBuilder {522fn perimeter(&self) -> Vec<PerimeterSegment> {523vec![PerimeterSegment::Flat {524indices: (0..self.sides).chain([0]).collect(),525}]526}527}528529impl From<RegularPolygon> for Mesh {530fn from(polygon: RegularPolygon) -> Self {531polygon.mesh().build()532}533}534535/// A builder used for creating a [`Mesh`] with an [`Ellipse`] shape.536#[derive(Clone, Copy, Debug, Reflect)]537#[reflect(Default, Debug, Clone)]538pub struct EllipseMeshBuilder {539/// The [`Ellipse`] shape.540pub ellipse: Ellipse,541/// The number of vertices used for the ellipse mesh.542/// The default is `32`.543#[doc(alias = "vertices")]544pub resolution: u32,545}546547impl Default for EllipseMeshBuilder {548fn default() -> Self {549Self {550ellipse: Ellipse::default(),551resolution: 32,552}553}554}555556impl EllipseMeshBuilder {557/// Creates a new [`EllipseMeshBuilder`] from a given half width and half height and a vertex count.558#[inline]559pub const fn new(half_width: f32, half_height: f32, resolution: u32) -> Self {560Self {561ellipse: Ellipse::new(half_width, half_height),562resolution,563}564}565566/// Sets the number of vertices used for the ellipse mesh.567#[inline]568#[doc(alias = "vertices")]569pub const fn resolution(mut self, resolution: u32) -> Self {570self.resolution = resolution;571self572}573}574575impl MeshBuilder for EllipseMeshBuilder {576fn build(&self) -> Mesh {577let resolution = self.resolution as usize;578let mut indices = Vec::with_capacity((resolution - 2) * 3);579let mut positions = Vec::with_capacity(resolution);580let normals = vec![[0.0, 0.0, 1.0]; resolution];581let mut uvs = Vec::with_capacity(resolution);582583// Add pi/2 so that there is a vertex at the top (sin is 1.0 and cos is 0.0)584let start_angle = FRAC_PI_2;585let step = core::f32::consts::TAU / self.resolution as f32;586587for i in 0..self.resolution {588// Compute vertex position at angle theta589let theta = start_angle + i as f32 * step;590let (sin, cos) = ops::sin_cos(theta);591let x = cos * self.ellipse.half_size.x;592let y = sin * self.ellipse.half_size.y;593594positions.push([x, y, 0.0]);595uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);596}597598for i in 1..(self.resolution - 1) {599indices.extend_from_slice(&[0, i, i + 1]);600}601602Mesh::new(603PrimitiveTopology::TriangleList,604RenderAssetUsages::default(),605)606.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)607.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)608.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)609.with_inserted_indices(Indices::U32(indices))610}611}612613impl Extrudable for EllipseMeshBuilder {614fn perimeter(&self) -> Vec<PerimeterSegment> {615vec![PerimeterSegment::Smooth {616first_normal: Vec2::Y,617last_normal: Vec2::Y,618indices: (0..self.resolution).chain([0]).collect(),619}]620}621}622623impl Meshable for Ellipse {624type Output = EllipseMeshBuilder;625626fn mesh(&self) -> Self::Output {627EllipseMeshBuilder {628ellipse: *self,629..Default::default()630}631}632}633634impl From<Ellipse> for Mesh {635fn from(ellipse: Ellipse) -> Self {636ellipse.mesh().build()637}638}639640/// A builder used for creating a [`Mesh`] with a [`Segment2d`].641pub struct Segment2dMeshBuilder {642/// The [`Segment2d`] shape.643pub segment: Segment2d,644}645646impl Segment2dMeshBuilder {647/// Creates a new [`Segment2dMeshBuilder`] from a given segment.648#[inline]649pub const fn new(line: Segment2d) -> Self {650Self { segment: line }651}652}653654impl MeshBuilder for Segment2dMeshBuilder {655fn build(&self) -> Mesh {656let positions = self.segment.vertices.map(|v| v.extend(0.0)).to_vec();657let indices = Indices::U32(vec![0, 1]);658659Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default())660.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)661.with_inserted_indices(indices)662}663}664665impl Meshable for Segment2d {666type Output = Segment2dMeshBuilder;667668fn mesh(&self) -> Self::Output {669Segment2dMeshBuilder::new(*self)670}671}672673impl From<Segment2d> for Mesh {674/// Converts this segment into a [`Mesh`] using a default [`Segment2dMeshBuilder`].675fn from(segment: Segment2d) -> Self {676segment.mesh().build()677}678}679680/// A builder used for creating a [`Mesh`] with a [`Polyline2d`] shape.681#[derive(Clone, Debug, Default, Reflect)]682#[reflect(Default, Debug, Clone)]683pub struct Polyline2dMeshBuilder {684polyline: Polyline2d,685}686687impl MeshBuilder for Polyline2dMeshBuilder {688fn build(&self) -> Mesh {689let positions: Vec<_> = self690.polyline691.vertices692.iter()693.map(|v| v.extend(0.0))694.collect();695696let indices = Indices::U32(697(0..self.polyline.vertices.len() as u32 - 1)698.flat_map(|i| [i, i + 1])699.collect(),700);701702Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default())703.with_inserted_indices(indices)704.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)705}706}707708impl Meshable for Polyline2d {709type Output = Polyline2dMeshBuilder;710711fn mesh(&self) -> Self::Output {712Polyline2dMeshBuilder {713polyline: self.clone(),714}715}716}717718impl From<Polyline2d> for Mesh {719fn from(polyline: Polyline2d) -> Self {720polyline.mesh().build()721}722}723724/// A builder for creating a [`Mesh`] with an [`Annulus`] shape.725#[derive(Clone, Copy, Debug, Reflect)]726#[reflect(Default, Debug, Clone)]727pub struct AnnulusMeshBuilder {728/// The [`Annulus`] shape.729pub annulus: Annulus,730731/// The number of vertices used in constructing each concentric circle of the annulus mesh.732/// The default is `32`.733pub resolution: u32,734}735736impl Default for AnnulusMeshBuilder {737fn default() -> Self {738Self {739annulus: Annulus::default(),740resolution: 32,741}742}743}744745impl AnnulusMeshBuilder {746/// Create an [`AnnulusMeshBuilder`] with the given inner radius, outer radius, and angular vertex count.747#[inline]748pub fn new(inner_radius: f32, outer_radius: f32, resolution: u32) -> Self {749Self {750annulus: Annulus::new(inner_radius, outer_radius),751resolution,752}753}754755/// Sets the number of vertices used in constructing the concentric circles of the annulus mesh.756#[inline]757pub fn resolution(mut self, resolution: u32) -> Self {758self.resolution = resolution;759self760}761}762763impl MeshBuilder for AnnulusMeshBuilder {764fn build(&self) -> Mesh {765let inner_radius = self.annulus.inner_circle.radius;766let outer_radius = self.annulus.outer_circle.radius;767768let num_vertices = (self.resolution as usize + 1) * 2;769let mut indices = Vec::with_capacity(self.resolution as usize * 6);770let mut positions = Vec::with_capacity(num_vertices);771let mut uvs = Vec::with_capacity(num_vertices);772let normals = vec![[0.0, 0.0, 1.0]; num_vertices];773774// We have one more set of vertices than might be naïvely expected;775// the vertices at `start_angle` are duplicated for the purposes of UV776// mapping. Here, each iteration places a pair of vertices at a fixed777// angle from the center of the annulus.778let start_angle = FRAC_PI_2;779let step = core::f32::consts::TAU / self.resolution as f32;780for i in 0..=self.resolution {781let theta = start_angle + (i % self.resolution) as f32 * step;782let (sin, cos) = ops::sin_cos(theta);783let inner_pos = [cos * inner_radius, sin * inner_radius, 0.];784let outer_pos = [cos * outer_radius, sin * outer_radius, 0.];785positions.push(inner_pos);786positions.push(outer_pos);787788// The first UV direction is radial and the second is angular;789// i.e., a single UV rectangle is stretched around the annulus, with790// its top and bottom meeting as the circle closes. Lines of constant791// U map to circles, and lines of constant V map to radial line segments.792let inner_uv = [0., i as f32 / self.resolution as f32];793let outer_uv = [1., i as f32 / self.resolution as f32];794uvs.push(inner_uv);795uvs.push(outer_uv);796}797798// Adjacent pairs of vertices form two triangles with each other; here,799// we are just making sure that they both have the right orientation,800// which is the CCW order of801// `inner_vertex` -> `outer_vertex` -> `next_outer` -> `next_inner`802for i in 0..self.resolution {803let inner_vertex = 2 * i;804let outer_vertex = 2 * i + 1;805let next_inner = inner_vertex + 2;806let next_outer = outer_vertex + 2;807indices.extend_from_slice(&[inner_vertex, outer_vertex, next_outer]);808indices.extend_from_slice(&[next_outer, next_inner, inner_vertex]);809}810811Mesh::new(812PrimitiveTopology::TriangleList,813RenderAssetUsages::default(),814)815.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)816.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)817.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)818.with_inserted_indices(Indices::U32(indices))819}820}821822impl Extrudable for AnnulusMeshBuilder {823fn perimeter(&self) -> Vec<PerimeterSegment> {824let vert_count = 2 * self.resolution;825vec![826PerimeterSegment::Smooth {827first_normal: Vec2::NEG_Y,828last_normal: Vec2::NEG_Y,829indices: (0..vert_count).step_by(2).chain([0]).rev().collect(), // Inner hole830},831PerimeterSegment::Smooth {832first_normal: Vec2::Y,833last_normal: Vec2::Y,834indices: (1..vert_count).step_by(2).chain([1]).collect(), // Outer perimeter835},836]837}838}839840impl Meshable for Annulus {841type Output = AnnulusMeshBuilder;842843fn mesh(&self) -> Self::Output {844AnnulusMeshBuilder {845annulus: *self,846..Default::default()847}848}849}850851impl From<Annulus> for Mesh {852fn from(annulus: Annulus) -> Self {853annulus.mesh().build()854}855}856857/// A builder for creating a [`Mesh`] with an [`Rhombus`] shape.858#[derive(Clone, Copy, Debug, Reflect)]859#[reflect(Default, Debug, Clone)]860pub struct RhombusMeshBuilder {861half_diagonals: Vec2,862}863864impl Default for RhombusMeshBuilder {865/// Returns the default [`RhombusMeshBuilder`] with a half-horizontal and half-vertical diagonal of `0.5`.866fn default() -> Self {867Self {868half_diagonals: Vec2::splat(0.5),869}870}871}872873impl RhombusMeshBuilder {874/// Creates a new [`RhombusMeshBuilder`] from a horizontal and vertical diagonal size.875///876/// # Panics877///878/// Panics in debug mode if `horizontal_diagonal` or `vertical_diagonal` is negative.879pub const fn new(horizontal_diagonal: f32, vertical_diagonal: f32) -> Self {880debug_assert!(881horizontal_diagonal >= 0.0,882"rhombus has a negative horizontal size",883);884debug_assert!(885vertical_diagonal >= 0.0,886"rhombus has a negative vertical size"887);888889Self {890half_diagonals: Vec2::new(horizontal_diagonal / 2.0, vertical_diagonal / 2.0),891}892}893}894895impl MeshBuilder for RhombusMeshBuilder {896fn build(&self) -> Mesh {897let [hhd, vhd] = [self.half_diagonals.x, self.half_diagonals.y];898let positions = vec![899[hhd, 0.0, 0.0],900[-hhd, 0.0, 0.0],901[0.0, vhd, 0.0],902[0.0, -vhd, 0.0],903];904let normals = vec![[0.0, 0.0, 1.0]; 4];905let uvs = vec![[1.0, 0.5], [0.0, 0.5], [0.5, 0.0], [0.5, 1.0]];906let indices = Indices::U32(vec![1, 0, 2, 1, 3, 0]);907908Mesh::new(909PrimitiveTopology::TriangleList,910RenderAssetUsages::default(),911)912.with_inserted_indices(indices)913.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)914.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)915.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)916}917}918919impl Extrudable for RhombusMeshBuilder {920fn perimeter(&self) -> Vec<PerimeterSegment> {921vec![PerimeterSegment::Flat {922indices: vec![0, 2, 1, 3, 0],923}]924}925}926927impl Meshable for Rhombus {928type Output = RhombusMeshBuilder;929930fn mesh(&self) -> Self::Output {931Self::Output {932half_diagonals: self.half_diagonals,933}934}935}936937impl From<Rhombus> for Mesh {938fn from(rhombus: Rhombus) -> Self {939rhombus.mesh().build()940}941}942943/// A builder used for creating a [`Mesh`] with a [`Triangle2d`] shape.944#[derive(Clone, Copy, Debug, Default, Reflect)]945#[reflect(Default, Debug, Clone)]946pub struct Triangle2dMeshBuilder {947triangle: Triangle2d,948}949950impl Triangle2dMeshBuilder {951/// Creates a new [`Triangle2dMeshBuilder`] from the points `a`, `b`, and `c`.952pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {953Self {954triangle: Triangle2d::new(a, b, c),955}956}957}958959impl Meshable for Triangle2d {960type Output = Triangle2dMeshBuilder;961962fn mesh(&self) -> Self::Output {963Self::Output { triangle: *self }964}965}966967impl MeshBuilder for Triangle2dMeshBuilder {968fn build(&self) -> Mesh {969let vertices_3d = self.triangle.vertices.map(|v| v.extend(0.));970971let positions: Vec<_> = vertices_3d.into();972let normals = vec![[0.0, 0.0, 1.0]; 3];973974let uvs: Vec<_> = triangle3d::uv_coords(&Triangle3d::new(975vertices_3d[0],976vertices_3d[1],977vertices_3d[2],978))979.into();980981let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;982let indices = if is_ccw {983Indices::U32(vec![0, 1, 2])984} else {985Indices::U32(vec![2, 1, 0])986};987988Mesh::new(989PrimitiveTopology::TriangleList,990RenderAssetUsages::default(),991)992.with_inserted_indices(indices)993.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)994.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)995.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)996}997}998999impl Extrudable for Triangle2dMeshBuilder {1000fn perimeter(&self) -> Vec<PerimeterSegment> {1001let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;1002if is_ccw {1003vec![PerimeterSegment::Flat {1004indices: vec![0, 1, 2, 0],1005}]1006} else {1007vec![PerimeterSegment::Flat {1008indices: vec![2, 1, 0, 2],1009}]1010}1011}1012}10131014impl From<Triangle2d> for Mesh {1015fn from(triangle: Triangle2d) -> Self {1016triangle.mesh().build()1017}1018}10191020/// A builder used for creating a [`Mesh`] with a [`Rectangle`] shape.1021#[derive(Clone, Copy, Debug, Reflect)]1022#[reflect(Default, Debug, Clone)]1023pub struct RectangleMeshBuilder {1024half_size: Vec2,1025}10261027impl Default for RectangleMeshBuilder {1028/// Returns the default [`RectangleMeshBuilder`] with a half-width and half-height of `0.5`.1029fn default() -> Self {1030Self {1031half_size: Vec2::splat(0.5),1032}1033}1034}10351036impl RectangleMeshBuilder {1037/// Creates a new [`RectangleMeshBuilder`] from a full width and height.1038///1039/// # Panics1040///1041/// Panics in debug mode if `width` or `height` is negative.1042pub const fn new(width: f32, height: f32) -> Self {1043debug_assert!(width >= 0.0, "rectangle has a negative width");1044debug_assert!(height >= 0.0, "rectangle has a negative height");10451046Self {1047half_size: Vec2::new(width / 2.0, height / 2.0),1048}1049}1050}10511052impl MeshBuilder for RectangleMeshBuilder {1053fn build(&self) -> Mesh {1054let [hw, hh] = [self.half_size.x, self.half_size.y];1055let positions = vec![1056[hw, hh, 0.0],1057[-hw, hh, 0.0],1058[-hw, -hh, 0.0],1059[hw, -hh, 0.0],1060];1061let normals = vec![[0.0, 0.0, 1.0]; 4];1062let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]];1063let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]);10641065Mesh::new(1066PrimitiveTopology::TriangleList,1067RenderAssetUsages::default(),1068)1069.with_inserted_indices(indices)1070.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)1071.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)1072.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)1073}1074}10751076impl Extrudable for RectangleMeshBuilder {1077fn perimeter(&self) -> Vec<PerimeterSegment> {1078vec![PerimeterSegment::Flat {1079indices: vec![0, 1, 2, 3, 0],1080}]1081}1082}10831084impl Meshable for Rectangle {1085type Output = RectangleMeshBuilder;10861087fn mesh(&self) -> Self::Output {1088RectangleMeshBuilder {1089half_size: self.half_size,1090}1091}1092}10931094impl From<Rectangle> for Mesh {1095fn from(rectangle: Rectangle) -> Self {1096rectangle.mesh().build()1097}1098}10991100/// A builder used for creating a [`Mesh`] with a [`Capsule2d`] shape.1101#[derive(Clone, Copy, Debug, Reflect)]1102#[reflect(Default, Debug, Clone)]1103pub struct Capsule2dMeshBuilder {1104/// The [`Capsule2d`] shape.1105pub capsule: Capsule2d,1106/// The number of vertices used for one hemicircle.1107/// The total number of vertices for the capsule mesh will be two times the resolution.1108///1109/// The default is `16`.1110pub resolution: u32,1111}11121113impl Default for Capsule2dMeshBuilder {1114fn default() -> Self {1115Self {1116capsule: Capsule2d::default(),1117resolution: 16,1118}1119}1120}11211122impl Capsule2dMeshBuilder {1123/// Creates a new [`Capsule2dMeshBuilder`] from a given radius, length, and the number of vertices1124/// used for one hemicircle. The total number of vertices for the capsule mesh will be two times the resolution.1125#[inline]1126pub fn new(radius: f32, length: f32, resolution: u32) -> Self {1127Self {1128capsule: Capsule2d::new(radius, length),1129resolution,1130}1131}11321133/// Sets the number of vertices used for one hemicircle.1134/// The total number of vertices for the capsule mesh will be two times the resolution.1135#[inline]1136pub const fn resolution(mut self, resolution: u32) -> Self {1137self.resolution = resolution;1138self1139}1140}11411142impl MeshBuilder for Capsule2dMeshBuilder {1143fn build(&self) -> Mesh {1144// The resolution is the number of vertices for one semicircle1145let resolution = self.resolution;1146let vertex_count = 2 * resolution;11471148// Six extra indices for the two triangles between the semicircles1149let mut indices = Vec::with_capacity((resolution as usize - 2) * 2 * 3 + 6);1150let mut positions = Vec::with_capacity(vertex_count as usize);1151let normals = vec![[0.0, 0.0, 1.0]; vertex_count as usize];1152let mut uvs = Vec::with_capacity(vertex_count as usize);11531154let radius = self.capsule.radius;1155let step = core::f32::consts::TAU / vertex_count as f32;11561157// If the vertex count is even, offset starting angle of top semicircle by half a step1158// to position the vertices evenly.1159let start_angle = if vertex_count.is_multiple_of(2) {1160step / 2.01161} else {11620.01163};11641165// How much the hemicircle radius is of the total half-height of the capsule.1166// This is used to prevent the UVs from stretching between the semicircles.1167let radius_frac = self.capsule.radius / (self.capsule.half_length + self.capsule.radius);11681169// Create top semicircle1170for i in 0..resolution {1171// Compute vertex position at angle theta1172let theta = start_angle + i as f32 * step;1173let (sin, cos) = ops::sin_cos(theta);1174let (x, y) = (cos * radius, sin * radius + self.capsule.half_length);11751176positions.push([x, y, 0.0]);1177uvs.push([0.5 * (cos + 1.0), radius_frac * (1.0 - 0.5 * (sin + 1.0))]);1178}11791180// Add top semicircle indices1181for i in 1..resolution - 1 {1182indices.extend_from_slice(&[0, i, i + 1]);1183}11841185// Add indices for top left triangle of the part between the semicircles1186indices.extend_from_slice(&[0, resolution - 1, resolution]);11871188// Create bottom semicircle1189for i in resolution..vertex_count {1190// Compute vertex position at angle theta1191let theta = start_angle + i as f32 * step;1192let (sin, cos) = ops::sin_cos(theta);1193let (x, y) = (cos * radius, sin * radius - self.capsule.half_length);11941195positions.push([x, y, 0.0]);1196uvs.push([0.5 * (cos + 1.0), 1.0 - radius_frac * 0.5 * (sin + 1.0)]);1197}11981199// Add bottom semicircle indices1200for i in 1..resolution - 1 {1201indices.extend_from_slice(&[resolution, resolution + i, resolution + i + 1]);1202}12031204// Add indices for bottom right triangle of the part between the semicircles1205indices.extend_from_slice(&[resolution, vertex_count - 1, 0]);12061207Mesh::new(1208PrimitiveTopology::TriangleList,1209RenderAssetUsages::default(),1210)1211.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)1212.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)1213.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)1214.with_inserted_indices(Indices::U32(indices))1215}1216}12171218impl Extrudable for Capsule2dMeshBuilder {1219fn perimeter(&self) -> Vec<PerimeterSegment> {1220let resolution = self.resolution;1221let top_semi_indices = (0..resolution).collect();1222let bottom_semi_indices = (resolution..(2 * resolution)).collect();1223vec![1224PerimeterSegment::Smooth {1225first_normal: Vec2::X,1226last_normal: Vec2::NEG_X,1227indices: top_semi_indices,1228}, // Top semi-circle1229PerimeterSegment::Flat {1230indices: vec![resolution - 1, resolution],1231}, // Left edge1232PerimeterSegment::Smooth {1233first_normal: Vec2::NEG_X,1234last_normal: Vec2::X,1235indices: bottom_semi_indices,1236}, // Bottom semi-circle1237PerimeterSegment::Flat {1238indices: vec![2 * resolution - 1, 0],1239}, // Right edge1240]1241}1242}12431244impl Meshable for Capsule2d {1245type Output = Capsule2dMeshBuilder;12461247fn mesh(&self) -> Self::Output {1248Capsule2dMeshBuilder {1249capsule: *self,1250..Default::default()1251}1252}1253}12541255impl From<Capsule2d> for Mesh {1256fn from(capsule: Capsule2d) -> Self {1257capsule.mesh().build()1258}1259}12601261#[cfg(test)]1262mod tests {1263use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd};1264use bevy_platform::collections::HashSet;12651266use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};12671268fn count_distinct_positions(points: &[[f32; 3]]) -> usize {1269let mut map = <HashSet<_>>::default();1270for point in points {1271map.insert(point.map(FloatOrd));1272}1273map.len()1274}12751276#[test]1277fn test_annulus() {1278let mesh = Annulus::new(1.0, 1.2).mesh().resolution(16).build();12791280assert_eq!(128132,1282count_distinct_positions(1283mesh.attribute(Mesh::ATTRIBUTE_POSITION)1284.unwrap()1285.as_float3()1286.unwrap()1287)1288);1289}12901291/// Sin/cos and multiplication computations result in numbers like 0.4999999.1292/// Round these to numbers we expect like 0.5.1293fn fix_floats<const N: usize>(points: &mut [[f32; N]]) {1294for point in points.iter_mut() {1295for coord in point.iter_mut() {1296let round = (*coord * 2.).round() / 2.;1297if (*coord - round).abs() < 0.00001 {1298*coord = round;1299}1300}1301}1302}13031304#[test]1305fn test_regular_polygon() {1306let mut mesh = Mesh::from(RegularPolygon::new(7.0, 4));13071308let Some(VertexAttributeValues::Float32x3(mut positions)) =1309mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)1310else {1311panic!("Expected positions f32x3");1312};1313let Some(VertexAttributeValues::Float32x2(mut uvs)) =1314mesh.remove_attribute(Mesh::ATTRIBUTE_UV_0)1315else {1316panic!("Expected uvs f32x2");1317};1318let Some(VertexAttributeValues::Float32x3(normals)) =1319mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)1320else {1321panic!("Expected normals f32x3");1322};13231324fix_floats(&mut positions);1325fix_floats(&mut uvs);13261327assert_eq!(1328[1329[0.0, 7.0, 0.0],1330[-7.0, 0.0, 0.0],1331[0.0, -7.0, 0.0],1332[7.0, 0.0, 0.0],1333],1334&positions[..]1335);13361337// Note V coordinate increases in the opposite direction to the Y coordinate.1338assert_eq!([[0.5, 0.0], [0.0, 0.5], [0.5, 1.0], [1.0, 0.5],], &uvs[..]);13391340assert_eq!(&[[0.0, 0.0, 1.0]; 4], &normals[..]);1341}1342}134313441345