bevy_mesh/primitives/
dim2.rs

1use core::f32::consts::FRAC_PI_2;
2
3use crate::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment};
4use bevy_asset::RenderAssetUsages;
5
6use super::{Extrudable, MeshBuilder, Meshable};
7use bevy_math::prelude::Polyline2d;
8use bevy_math::{
9    ops,
10    primitives::{
11        Annulus, Capsule2d, Circle, CircularSector, CircularSegment, ConvexPolygon, Ellipse,
12        Rectangle, RegularPolygon, Rhombus, Segment2d, Triangle2d, Triangle3d, WindingOrder,
13    },
14    FloatExt, Vec2,
15};
16use bevy_reflect::prelude::*;
17use wgpu_types::PrimitiveTopology;
18
19/// A builder used for creating a [`Mesh`] with a [`Circle`] shape.
20#[derive(Clone, Copy, Debug, Reflect)]
21#[reflect(Default, Debug, Clone)]
22pub struct CircleMeshBuilder {
23    /// The [`Circle`] shape.
24    pub circle: Circle,
25    /// The number of vertices used for the circle mesh.
26    /// The default is `32`.
27    #[doc(alias = "vertices")]
28    pub resolution: u32,
29}
30
31impl Default for CircleMeshBuilder {
32    fn default() -> Self {
33        Self {
34            circle: Circle::default(),
35            resolution: 32,
36        }
37    }
38}
39
40impl CircleMeshBuilder {
41    /// Creates a new [`CircleMeshBuilder`] from a given radius and vertex count.
42    #[inline]
43    pub const fn new(radius: f32, resolution: u32) -> Self {
44        Self {
45            circle: Circle { radius },
46            resolution,
47        }
48    }
49
50    /// Sets the number of vertices used for the circle mesh.
51    #[inline]
52    #[doc(alias = "vertices")]
53    pub const fn resolution(mut self, resolution: u32) -> Self {
54        self.resolution = resolution;
55        self
56    }
57}
58
59impl MeshBuilder for CircleMeshBuilder {
60    fn build(&self) -> Mesh {
61        Ellipse::new(self.circle.radius, self.circle.radius)
62            .mesh()
63            .resolution(self.resolution)
64            .build()
65    }
66}
67
68impl Extrudable for CircleMeshBuilder {
69    fn perimeter(&self) -> Vec<PerimeterSegment> {
70        vec![PerimeterSegment::Smooth {
71            first_normal: Vec2::Y,
72            last_normal: Vec2::Y,
73            indices: (0..self.resolution).chain([0]).collect(),
74        }]
75    }
76}
77
78impl Meshable for Circle {
79    type Output = CircleMeshBuilder;
80
81    fn mesh(&self) -> Self::Output {
82        CircleMeshBuilder {
83            circle: *self,
84            ..Default::default()
85        }
86    }
87}
88
89impl From<Circle> for Mesh {
90    fn from(circle: Circle) -> Self {
91        circle.mesh().build()
92    }
93}
94
95/// Specifies how to generate UV-mappings for the [`CircularSector`] and [`CircularSegment`] shapes.
96///
97/// Currently the only variant is `Mask`, which is good for showing a portion of a texture that includes
98/// the entire circle, particularly the same texture will be displayed with different fractions of a
99/// complete circle.
100///
101/// It's expected that more will be added in the future, such as a variant that causes the texture to be
102/// scaled to fit the bounding box of the shape, which would be good for packed textures only including the
103/// portion of the circle that is needed to display.
104#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
105#[reflect(Default, Debug, Clone)]
106#[non_exhaustive]
107pub enum CircularMeshUvMode {
108    /// Treats the shape as a mask over a circle of equal size and radius,
109    /// with the center of the circle at the center of the texture.
110    Mask {
111        /// Angle by which to rotate the shape when generating the UV map.
112        angle: f32,
113    },
114}
115
116impl Default for CircularMeshUvMode {
117    fn default() -> Self {
118        CircularMeshUvMode::Mask { angle: 0.0 }
119    }
120}
121
122/// A builder used for creating a [`Mesh`] with a [`CircularSector`] shape.
123///
124/// The resulting mesh will have a UV-map such that the center of the circle is
125/// at the center of the texture.
126#[derive(Clone, Debug, Reflect)]
127#[reflect(Default, Debug, Clone)]
128pub struct CircularSectorMeshBuilder {
129    /// The sector shape.
130    pub sector: CircularSector,
131    /// The number of vertices used for the arc portion of the sector mesh.
132    /// The default is `32`.
133    #[doc(alias = "vertices")]
134    pub resolution: u32,
135    /// The UV mapping mode
136    pub uv_mode: CircularMeshUvMode,
137}
138
139impl Default for CircularSectorMeshBuilder {
140    fn default() -> Self {
141        Self {
142            sector: CircularSector::default(),
143            resolution: 32,
144            uv_mode: CircularMeshUvMode::default(),
145        }
146    }
147}
148
149impl CircularSectorMeshBuilder {
150    /// Creates a new [`CircularSectorMeshBuilder`] from a given sector
151    #[inline]
152    pub fn new(sector: CircularSector) -> Self {
153        Self {
154            sector,
155            ..Self::default()
156        }
157    }
158
159    /// Sets the number of vertices used for the sector mesh.
160    #[inline]
161    #[doc(alias = "vertices")]
162    pub const fn resolution(mut self, resolution: u32) -> Self {
163        self.resolution = resolution;
164        self
165    }
166
167    /// Sets the uv mode used for the sector mesh
168    #[inline]
169    pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {
170        self.uv_mode = uv_mode;
171        self
172    }
173}
174
175impl MeshBuilder for CircularSectorMeshBuilder {
176    fn build(&self) -> Mesh {
177        let resolution = self.resolution as usize;
178        let mut indices = Vec::with_capacity((resolution - 1) * 3);
179        let mut positions = Vec::with_capacity(resolution + 1);
180        let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];
181        let mut uvs = Vec::with_capacity(resolution + 1);
182
183        let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;
184
185        // Push the center of the circle.
186        positions.push([0.0; 3]);
187        uvs.push([0.5; 2]);
188
189        let first_angle = FRAC_PI_2 - self.sector.half_angle();
190        let last_angle = FRAC_PI_2 + self.sector.half_angle();
191        let last_i = (self.resolution - 1) as f32;
192        for i in 0..self.resolution {
193            let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);
194
195            // Compute the vertex
196            let vertex = self.sector.radius() * Vec2::from_angle(angle);
197            // 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).
198            // We accomplish the Y axis flip by negating the angle.
199            let uv =
200                Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));
201
202            positions.push([vertex.x, vertex.y, 0.0]);
203            uvs.push([uv.x, uv.y]);
204        }
205
206        for i in 1..self.resolution {
207            // Index 0 is the center.
208            indices.extend_from_slice(&[0, i, i + 1]);
209        }
210
211        Mesh::new(
212            PrimitiveTopology::TriangleList,
213            RenderAssetUsages::default(),
214        )
215        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
216        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
217        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
218        .with_inserted_indices(Indices::U32(indices))
219    }
220}
221
222impl Extrudable for CircularSectorMeshBuilder {
223    fn perimeter(&self) -> Vec<PerimeterSegment> {
224        let (sin, cos) = ops::sin_cos(self.sector.arc.half_angle);
225        let first_normal = Vec2::new(sin, cos);
226        let last_normal = Vec2::new(-sin, cos);
227        vec![
228            PerimeterSegment::Flat {
229                indices: vec![self.resolution, 0, 1],
230            },
231            PerimeterSegment::Smooth {
232                first_normal,
233                last_normal,
234                indices: (1..=self.resolution).collect(),
235            },
236        ]
237    }
238}
239
240impl Meshable for CircularSector {
241    type Output = CircularSectorMeshBuilder;
242
243    fn mesh(&self) -> Self::Output {
244        CircularSectorMeshBuilder {
245            sector: *self,
246            ..Default::default()
247        }
248    }
249}
250
251impl From<CircularSector> for Mesh {
252    /// Converts this sector into a [`Mesh`] using a default [`CircularSectorMeshBuilder`].
253    ///
254    /// See the documentation of [`CircularSectorMeshBuilder`] for more details.
255    fn from(sector: CircularSector) -> Self {
256        sector.mesh().build()
257    }
258}
259
260/// A builder used for creating a [`Mesh`] with a [`CircularSegment`] shape.
261///
262/// The resulting mesh will have a UV-map such that the center of the circle is
263/// at the center of the texture.
264#[derive(Clone, Copy, Debug, Reflect)]
265#[reflect(Default, Debug, Clone)]
266pub struct CircularSegmentMeshBuilder {
267    /// The segment shape.
268    pub segment: CircularSegment,
269    /// The number of vertices used for the arc portion of the segment mesh.
270    /// The default is `32`.
271    #[doc(alias = "vertices")]
272    pub resolution: u32,
273    /// The UV mapping mode
274    pub uv_mode: CircularMeshUvMode,
275}
276
277impl Default for CircularSegmentMeshBuilder {
278    fn default() -> Self {
279        Self {
280            segment: CircularSegment::default(),
281            resolution: 32,
282            uv_mode: CircularMeshUvMode::default(),
283        }
284    }
285}
286
287impl CircularSegmentMeshBuilder {
288    /// Creates a new [`CircularSegmentMeshBuilder`] from a given segment
289    #[inline]
290    pub fn new(segment: CircularSegment) -> Self {
291        Self {
292            segment,
293            ..Self::default()
294        }
295    }
296
297    /// Sets the number of vertices used for the segment mesh.
298    #[inline]
299    #[doc(alias = "vertices")]
300    pub const fn resolution(mut self, resolution: u32) -> Self {
301        self.resolution = resolution;
302        self
303    }
304
305    /// Sets the uv mode used for the segment mesh
306    #[inline]
307    pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {
308        self.uv_mode = uv_mode;
309        self
310    }
311}
312
313impl MeshBuilder for CircularSegmentMeshBuilder {
314    fn build(&self) -> Mesh {
315        let resolution = self.resolution as usize;
316        let mut indices = Vec::with_capacity((resolution - 1) * 3);
317        let mut positions = Vec::with_capacity(resolution + 1);
318        let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];
319        let mut uvs = Vec::with_capacity(resolution + 1);
320
321        let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;
322
323        // Push the center of the chord.
324        let midpoint_vertex = self.segment.chord_midpoint();
325        positions.push([midpoint_vertex.x, midpoint_vertex.y, 0.0]);
326        // Compute the UV coordinate of the midpoint vertex.
327        // This is similar to the computation inside the loop for the arc vertices,
328        // but the vertex angle is PI/2, and we must scale by the ratio of the apothem to the radius
329        // to correctly position the vertex.
330        let midpoint_uv = Vec2::from_angle(-uv_angle - FRAC_PI_2).mul_add(
331            Vec2::splat(0.5 * (self.segment.apothem() / self.segment.radius())),
332            Vec2::splat(0.5),
333        );
334        uvs.push([midpoint_uv.x, midpoint_uv.y]);
335
336        let first_angle = FRAC_PI_2 - self.segment.half_angle();
337        let last_angle = FRAC_PI_2 + self.segment.half_angle();
338        let last_i = (self.resolution - 1) as f32;
339        for i in 0..self.resolution {
340            let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);
341
342            // Compute the vertex
343            let vertex = self.segment.radius() * Vec2::from_angle(angle);
344            // 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).
345            // We accomplish the Y axis flip by negating the angle.
346            let uv =
347                Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));
348
349            positions.push([vertex.x, vertex.y, 0.0]);
350            uvs.push([uv.x, uv.y]);
351        }
352
353        for i in 1..self.resolution {
354            // Index 0 is the midpoint of the chord.
355            indices.extend_from_slice(&[0, i, i + 1]);
356        }
357
358        Mesh::new(
359            PrimitiveTopology::TriangleList,
360            RenderAssetUsages::default(),
361        )
362        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
363        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
364        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
365        .with_inserted_indices(Indices::U32(indices))
366    }
367}
368
369impl Extrudable for CircularSegmentMeshBuilder {
370    fn perimeter(&self) -> Vec<PerimeterSegment> {
371        let (sin, cos) = ops::sin_cos(self.segment.arc.half_angle);
372        let first_normal = Vec2::new(sin, cos);
373        let last_normal = Vec2::new(-sin, cos);
374        vec![
375            PerimeterSegment::Flat {
376                indices: vec![self.resolution, 0, 1],
377            },
378            PerimeterSegment::Smooth {
379                first_normal,
380                last_normal,
381                indices: (1..=self.resolution).collect(),
382            },
383        ]
384    }
385}
386
387impl Meshable for CircularSegment {
388    type Output = CircularSegmentMeshBuilder;
389
390    fn mesh(&self) -> Self::Output {
391        CircularSegmentMeshBuilder {
392            segment: *self,
393            ..Default::default()
394        }
395    }
396}
397
398impl From<CircularSegment> for Mesh {
399    /// Converts this sector into a [`Mesh`] using a default [`CircularSegmentMeshBuilder`].
400    ///
401    /// See the documentation of [`CircularSegmentMeshBuilder`] for more details.
402    fn from(segment: CircularSegment) -> Self {
403        segment.mesh().build()
404    }
405}
406
407/// A builder used for creating a [`Mesh`] with a [`ConvexPolygon`] shape.
408///
409/// You must verify that the `vertices` are not concave when constructing this type. You can
410/// guarantee this by creating a [`ConvexPolygon`] first, then calling [`ConvexPolygon::mesh()`].
411#[derive(Clone, Debug, Reflect)]
412#[reflect(Debug, Clone)]
413pub struct ConvexPolygonMeshBuilder {
414    pub vertices: Vec<Vec2>,
415}
416
417impl Meshable for ConvexPolygon {
418    type Output = ConvexPolygonMeshBuilder;
419
420    fn mesh(&self) -> Self::Output {
421        Self::Output {
422            vertices: self.vertices().to_vec(),
423        }
424    }
425}
426
427impl MeshBuilder for ConvexPolygonMeshBuilder {
428    fn build(&self) -> Mesh {
429        let len = self.vertices.len();
430        let mut indices = Vec::with_capacity((len - 2) * 3);
431        let mut positions = Vec::with_capacity(len);
432
433        for vertex in &self.vertices {
434            positions.push([vertex.x, vertex.y, 0.0]);
435        }
436        for i in 2..len as u32 {
437            indices.extend_from_slice(&[0, i - 1, i]);
438        }
439        Mesh::new(
440            PrimitiveTopology::TriangleList,
441            RenderAssetUsages::default(),
442        )
443        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
444        .with_inserted_indices(Indices::U32(indices))
445    }
446}
447
448impl Extrudable for ConvexPolygonMeshBuilder {
449    fn perimeter(&self) -> Vec<PerimeterSegment> {
450        vec![PerimeterSegment::Flat {
451            indices: (0..self.vertices.len() as u32).chain([0]).collect(),
452        }]
453    }
454}
455
456impl From<ConvexPolygon> for Mesh {
457    fn from(polygon: ConvexPolygon) -> Self {
458        polygon.mesh().build()
459    }
460}
461
462/// A builder used for creating a [`Mesh`] with a [`RegularPolygon`] shape.
463#[derive(Clone, Copy, Debug, Reflect)]
464#[reflect(Default, Debug, Clone)]
465pub struct RegularPolygonMeshBuilder {
466    circumradius: f32,
467    sides: u32,
468}
469
470impl Default for RegularPolygonMeshBuilder {
471    /// Returns the default [`RegularPolygonMeshBuilder`] with six sides (a hexagon) and a circumradius of `0.5`.
472    fn default() -> Self {
473        Self {
474            circumradius: 0.5,
475            sides: 6,
476        }
477    }
478}
479
480impl RegularPolygonMeshBuilder {
481    /// Creates a new [`RegularPolygonMeshBuilder`] from the radius of a circumcircle and a number
482    /// of sides.
483    ///
484    /// # Panics
485    ///
486    /// Panics in debug mode if `circumradius` is negative, or if `sides` is less than 3.
487    pub const fn new(circumradius: f32, sides: u32) -> Self {
488        debug_assert!(
489            circumradius.is_sign_positive(),
490            "polygon has a negative radius"
491        );
492        debug_assert!(sides > 2, "polygon has less than 3 sides");
493
494        Self {
495            circumradius,
496            sides,
497        }
498    }
499}
500
501impl Meshable for RegularPolygon {
502    type Output = RegularPolygonMeshBuilder;
503
504    fn mesh(&self) -> Self::Output {
505        Self::Output {
506            circumradius: self.circumcircle.radius,
507            sides: self.sides,
508        }
509    }
510}
511
512impl MeshBuilder for RegularPolygonMeshBuilder {
513    fn build(&self) -> Mesh {
514        // The ellipse mesh is just a regular polygon with two radii
515        Ellipse::new(self.circumradius, self.circumradius)
516            .mesh()
517            .resolution(self.sides)
518            .build()
519    }
520}
521
522impl Extrudable for RegularPolygonMeshBuilder {
523    fn perimeter(&self) -> Vec<PerimeterSegment> {
524        vec![PerimeterSegment::Flat {
525            indices: (0..self.sides).chain([0]).collect(),
526        }]
527    }
528}
529
530impl From<RegularPolygon> for Mesh {
531    fn from(polygon: RegularPolygon) -> Self {
532        polygon.mesh().build()
533    }
534}
535
536/// A builder used for creating a [`Mesh`] with an [`Ellipse`] shape.
537#[derive(Clone, Copy, Debug, Reflect)]
538#[reflect(Default, Debug, Clone)]
539pub struct EllipseMeshBuilder {
540    /// The [`Ellipse`] shape.
541    pub ellipse: Ellipse,
542    /// The number of vertices used for the ellipse mesh.
543    /// The default is `32`.
544    #[doc(alias = "vertices")]
545    pub resolution: u32,
546}
547
548impl Default for EllipseMeshBuilder {
549    fn default() -> Self {
550        Self {
551            ellipse: Ellipse::default(),
552            resolution: 32,
553        }
554    }
555}
556
557impl EllipseMeshBuilder {
558    /// Creates a new [`EllipseMeshBuilder`] from a given half width and half height and a vertex count.
559    #[inline]
560    pub const fn new(half_width: f32, half_height: f32, resolution: u32) -> Self {
561        Self {
562            ellipse: Ellipse::new(half_width, half_height),
563            resolution,
564        }
565    }
566
567    /// Sets the number of vertices used for the ellipse mesh.
568    #[inline]
569    #[doc(alias = "vertices")]
570    pub const fn resolution(mut self, resolution: u32) -> Self {
571        self.resolution = resolution;
572        self
573    }
574}
575
576impl MeshBuilder for EllipseMeshBuilder {
577    fn build(&self) -> Mesh {
578        let resolution = self.resolution as usize;
579        let mut indices = Vec::with_capacity((resolution - 2) * 3);
580        let mut positions = Vec::with_capacity(resolution);
581        let normals = vec![[0.0, 0.0, 1.0]; resolution];
582        let mut uvs = Vec::with_capacity(resolution);
583
584        // Add pi/2 so that there is a vertex at the top (sin is 1.0 and cos is 0.0)
585        let start_angle = FRAC_PI_2;
586        let step = core::f32::consts::TAU / self.resolution as f32;
587
588        for i in 0..self.resolution {
589            // Compute vertex position at angle theta
590            let theta = start_angle + i as f32 * step;
591            let (sin, cos) = ops::sin_cos(theta);
592            let x = cos * self.ellipse.half_size.x;
593            let y = sin * self.ellipse.half_size.y;
594
595            positions.push([x, y, 0.0]);
596            uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
597        }
598
599        for i in 1..(self.resolution - 1) {
600            indices.extend_from_slice(&[0, i, i + 1]);
601        }
602
603        Mesh::new(
604            PrimitiveTopology::TriangleList,
605            RenderAssetUsages::default(),
606        )
607        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
608        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
609        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
610        .with_inserted_indices(Indices::U32(indices))
611    }
612}
613
614impl Extrudable for EllipseMeshBuilder {
615    fn perimeter(&self) -> Vec<PerimeterSegment> {
616        vec![PerimeterSegment::Smooth {
617            first_normal: Vec2::Y,
618            last_normal: Vec2::Y,
619            indices: (0..self.resolution).chain([0]).collect(),
620        }]
621    }
622}
623
624impl Meshable for Ellipse {
625    type Output = EllipseMeshBuilder;
626
627    fn mesh(&self) -> Self::Output {
628        EllipseMeshBuilder {
629            ellipse: *self,
630            ..Default::default()
631        }
632    }
633}
634
635impl From<Ellipse> for Mesh {
636    fn from(ellipse: Ellipse) -> Self {
637        ellipse.mesh().build()
638    }
639}
640
641/// A builder used for creating a [`Mesh`] with a [`Segment2d`].
642pub struct Segment2dMeshBuilder {
643    /// The [`Segment2d`] shape.
644    pub segment: Segment2d,
645}
646
647impl Segment2dMeshBuilder {
648    /// Creates a new [`Segment2dMeshBuilder`] from a given segment.
649    #[inline]
650    pub const fn new(line: Segment2d) -> Self {
651        Self { segment: line }
652    }
653}
654
655impl MeshBuilder for Segment2dMeshBuilder {
656    fn build(&self) -> Mesh {
657        let positions = self.segment.vertices.map(|v| v.extend(0.0)).to_vec();
658        let indices = Indices::U32(vec![0, 1]);
659
660        Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default())
661            .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
662            .with_inserted_indices(indices)
663    }
664}
665
666impl Meshable for Segment2d {
667    type Output = Segment2dMeshBuilder;
668
669    fn mesh(&self) -> Self::Output {
670        Segment2dMeshBuilder::new(*self)
671    }
672}
673
674impl From<Segment2d> for Mesh {
675    /// Converts this segment into a [`Mesh`] using a default [`Segment2dMeshBuilder`].
676    fn from(segment: Segment2d) -> Self {
677        segment.mesh().build()
678    }
679}
680
681/// A builder used for creating a [`Mesh`] with a [`Polyline2d`] shape.
682#[derive(Clone, Debug, Default, Reflect)]
683#[reflect(Default, Debug, Clone)]
684pub struct Polyline2dMeshBuilder {
685    polyline: Polyline2d,
686}
687
688impl MeshBuilder for Polyline2dMeshBuilder {
689    fn build(&self) -> Mesh {
690        let positions: Vec<_> = self
691            .polyline
692            .vertices
693            .iter()
694            .map(|v| v.extend(0.0))
695            .collect();
696
697        let indices = Indices::U32(
698            (0..self.polyline.vertices.len() as u32 - 1)
699                .flat_map(|i| [i, i + 1])
700                .collect(),
701        );
702
703        Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default())
704            .with_inserted_indices(indices)
705            .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
706    }
707}
708
709impl Meshable for Polyline2d {
710    type Output = Polyline2dMeshBuilder;
711
712    fn mesh(&self) -> Self::Output {
713        Polyline2dMeshBuilder {
714            polyline: self.clone(),
715        }
716    }
717}
718
719impl From<Polyline2d> for Mesh {
720    fn from(polyline: Polyline2d) -> Self {
721        polyline.mesh().build()
722    }
723}
724
725/// A builder for creating a [`Mesh`] with an [`Annulus`] shape.
726#[derive(Clone, Copy, Debug, Reflect)]
727#[reflect(Default, Debug, Clone)]
728pub struct AnnulusMeshBuilder {
729    /// The [`Annulus`] shape.
730    pub annulus: Annulus,
731
732    /// The number of vertices used in constructing each concentric circle of the annulus mesh.
733    /// The default is `32`.
734    pub resolution: u32,
735}
736
737impl Default for AnnulusMeshBuilder {
738    fn default() -> Self {
739        Self {
740            annulus: Annulus::default(),
741            resolution: 32,
742        }
743    }
744}
745
746impl AnnulusMeshBuilder {
747    /// Create an [`AnnulusMeshBuilder`] with the given inner radius, outer radius, and angular vertex count.
748    #[inline]
749    pub fn new(inner_radius: f32, outer_radius: f32, resolution: u32) -> Self {
750        Self {
751            annulus: Annulus::new(inner_radius, outer_radius),
752            resolution,
753        }
754    }
755
756    /// Sets the number of vertices used in constructing the concentric circles of the annulus mesh.
757    #[inline]
758    pub fn resolution(mut self, resolution: u32) -> Self {
759        self.resolution = resolution;
760        self
761    }
762}
763
764impl MeshBuilder for AnnulusMeshBuilder {
765    fn build(&self) -> Mesh {
766        let inner_radius = self.annulus.inner_circle.radius;
767        let outer_radius = self.annulus.outer_circle.radius;
768
769        let num_vertices = (self.resolution as usize + 1) * 2;
770        let mut indices = Vec::with_capacity(self.resolution as usize * 6);
771        let mut positions = Vec::with_capacity(num_vertices);
772        let mut uvs = Vec::with_capacity(num_vertices);
773        let normals = vec![[0.0, 0.0, 1.0]; num_vertices];
774
775        // We have one more set of vertices than might be naïvely expected;
776        // the vertices at `start_angle` are duplicated for the purposes of UV
777        // mapping. Here, each iteration places a pair of vertices at a fixed
778        // angle from the center of the annulus.
779        let start_angle = FRAC_PI_2;
780        let step = core::f32::consts::TAU / self.resolution as f32;
781        for i in 0..=self.resolution {
782            let theta = start_angle + (i % self.resolution) as f32 * step;
783            let (sin, cos) = ops::sin_cos(theta);
784            let inner_pos = [cos * inner_radius, sin * inner_radius, 0.];
785            let outer_pos = [cos * outer_radius, sin * outer_radius, 0.];
786            positions.push(inner_pos);
787            positions.push(outer_pos);
788
789            // The first UV direction is radial and the second is angular;
790            // i.e., a single UV rectangle is stretched around the annulus, with
791            // its top and bottom meeting as the circle closes. Lines of constant
792            // U map to circles, and lines of constant V map to radial line segments.
793            let inner_uv = [0., i as f32 / self.resolution as f32];
794            let outer_uv = [1., i as f32 / self.resolution as f32];
795            uvs.push(inner_uv);
796            uvs.push(outer_uv);
797        }
798
799        // Adjacent pairs of vertices form two triangles with each other; here,
800        // we are just making sure that they both have the right orientation,
801        // which is the CCW order of
802        // `inner_vertex` -> `outer_vertex` -> `next_outer` -> `next_inner`
803        for i in 0..self.resolution {
804            let inner_vertex = 2 * i;
805            let outer_vertex = 2 * i + 1;
806            let next_inner = inner_vertex + 2;
807            let next_outer = outer_vertex + 2;
808            indices.extend_from_slice(&[inner_vertex, outer_vertex, next_outer]);
809            indices.extend_from_slice(&[next_outer, next_inner, inner_vertex]);
810        }
811
812        Mesh::new(
813            PrimitiveTopology::TriangleList,
814            RenderAssetUsages::default(),
815        )
816        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
817        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
818        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
819        .with_inserted_indices(Indices::U32(indices))
820    }
821}
822
823impl Extrudable for AnnulusMeshBuilder {
824    fn perimeter(&self) -> Vec<PerimeterSegment> {
825        let vert_count = 2 * self.resolution;
826        vec![
827            PerimeterSegment::Smooth {
828                first_normal: Vec2::NEG_Y,
829                last_normal: Vec2::NEG_Y,
830                indices: (0..vert_count).step_by(2).chain([0]).rev().collect(), // Inner hole
831            },
832            PerimeterSegment::Smooth {
833                first_normal: Vec2::Y,
834                last_normal: Vec2::Y,
835                indices: (1..vert_count).step_by(2).chain([1]).collect(), // Outer perimeter
836            },
837        ]
838    }
839}
840
841impl Meshable for Annulus {
842    type Output = AnnulusMeshBuilder;
843
844    fn mesh(&self) -> Self::Output {
845        AnnulusMeshBuilder {
846            annulus: *self,
847            ..Default::default()
848        }
849    }
850}
851
852impl From<Annulus> for Mesh {
853    fn from(annulus: Annulus) -> Self {
854        annulus.mesh().build()
855    }
856}
857
858/// A builder for creating a [`Mesh`] with an [`Rhombus`] shape.
859#[derive(Clone, Copy, Debug, Reflect)]
860#[reflect(Default, Debug, Clone)]
861pub struct RhombusMeshBuilder {
862    half_diagonals: Vec2,
863}
864
865impl Default for RhombusMeshBuilder {
866    /// Returns the default [`RhombusMeshBuilder`] with a half-horizontal and half-vertical diagonal of `0.5`.
867    fn default() -> Self {
868        Self {
869            half_diagonals: Vec2::splat(0.5),
870        }
871    }
872}
873
874impl RhombusMeshBuilder {
875    /// Creates a new [`RhombusMeshBuilder`] from a horizontal and vertical diagonal size.
876    ///
877    /// # Panics
878    ///
879    /// Panics in debug mode if `horizontal_diagonal` or `vertical_diagonal` is negative.
880    pub const fn new(horizontal_diagonal: f32, vertical_diagonal: f32) -> Self {
881        debug_assert!(
882            horizontal_diagonal >= 0.0,
883            "rhombus has a negative horizontal size",
884        );
885        debug_assert!(
886            vertical_diagonal >= 0.0,
887            "rhombus has a negative vertical size"
888        );
889
890        Self {
891            half_diagonals: Vec2::new(horizontal_diagonal / 2.0, vertical_diagonal / 2.0),
892        }
893    }
894}
895
896impl MeshBuilder for RhombusMeshBuilder {
897    fn build(&self) -> Mesh {
898        let [hhd, vhd] = [self.half_diagonals.x, self.half_diagonals.y];
899        let positions = vec![
900            [hhd, 0.0, 0.0],
901            [-hhd, 0.0, 0.0],
902            [0.0, vhd, 0.0],
903            [0.0, -vhd, 0.0],
904        ];
905        let normals = vec![[0.0, 0.0, 1.0]; 4];
906        let uvs = vec![[1.0, 0.5], [0.0, 0.5], [0.5, 0.0], [0.5, 1.0]];
907        let indices = Indices::U32(vec![1, 0, 2, 1, 3, 0]);
908
909        Mesh::new(
910            PrimitiveTopology::TriangleList,
911            RenderAssetUsages::default(),
912        )
913        .with_inserted_indices(indices)
914        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
915        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
916        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
917    }
918}
919
920impl Extrudable for RhombusMeshBuilder {
921    fn perimeter(&self) -> Vec<PerimeterSegment> {
922        vec![PerimeterSegment::Flat {
923            indices: vec![0, 2, 1, 3, 0],
924        }]
925    }
926}
927
928impl Meshable for Rhombus {
929    type Output = RhombusMeshBuilder;
930
931    fn mesh(&self) -> Self::Output {
932        Self::Output {
933            half_diagonals: self.half_diagonals,
934        }
935    }
936}
937
938impl From<Rhombus> for Mesh {
939    fn from(rhombus: Rhombus) -> Self {
940        rhombus.mesh().build()
941    }
942}
943
944/// A builder used for creating a [`Mesh`] with a [`Triangle2d`] shape.
945#[derive(Clone, Copy, Debug, Default, Reflect)]
946#[reflect(Default, Debug, Clone)]
947pub struct Triangle2dMeshBuilder {
948    triangle: Triangle2d,
949}
950
951impl Triangle2dMeshBuilder {
952    /// Creates a new [`Triangle2dMeshBuilder`] from the points `a`, `b`, and `c`.
953    pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {
954        Self {
955            triangle: Triangle2d::new(a, b, c),
956        }
957    }
958}
959
960impl Meshable for Triangle2d {
961    type Output = Triangle2dMeshBuilder;
962
963    fn mesh(&self) -> Self::Output {
964        Self::Output { triangle: *self }
965    }
966}
967
968impl MeshBuilder for Triangle2dMeshBuilder {
969    fn build(&self) -> Mesh {
970        let vertices_3d = self.triangle.vertices.map(|v| v.extend(0.));
971
972        let positions: Vec<_> = vertices_3d.into();
973        let normals = vec![[0.0, 0.0, 1.0]; 3];
974
975        let uvs: Vec<_> = triangle3d::uv_coords(&Triangle3d::new(
976            vertices_3d[0],
977            vertices_3d[1],
978            vertices_3d[2],
979        ))
980        .into();
981
982        let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
983        let indices = if is_ccw {
984            Indices::U32(vec![0, 1, 2])
985        } else {
986            Indices::U32(vec![2, 1, 0])
987        };
988
989        Mesh::new(
990            PrimitiveTopology::TriangleList,
991            RenderAssetUsages::default(),
992        )
993        .with_inserted_indices(indices)
994        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
995        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
996        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
997    }
998}
999
1000impl Extrudable for Triangle2dMeshBuilder {
1001    fn perimeter(&self) -> Vec<PerimeterSegment> {
1002        let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
1003        if is_ccw {
1004            vec![PerimeterSegment::Flat {
1005                indices: vec![0, 1, 2, 0],
1006            }]
1007        } else {
1008            vec![PerimeterSegment::Flat {
1009                indices: vec![2, 1, 0, 2],
1010            }]
1011        }
1012    }
1013}
1014
1015impl From<Triangle2d> for Mesh {
1016    fn from(triangle: Triangle2d) -> Self {
1017        triangle.mesh().build()
1018    }
1019}
1020
1021/// A builder used for creating a [`Mesh`] with a [`Rectangle`] shape.
1022#[derive(Clone, Copy, Debug, Reflect)]
1023#[reflect(Default, Debug, Clone)]
1024pub struct RectangleMeshBuilder {
1025    half_size: Vec2,
1026}
1027
1028impl Default for RectangleMeshBuilder {
1029    /// Returns the default [`RectangleMeshBuilder`] with a half-width and half-height of `0.5`.
1030    fn default() -> Self {
1031        Self {
1032            half_size: Vec2::splat(0.5),
1033        }
1034    }
1035}
1036
1037impl RectangleMeshBuilder {
1038    /// Creates a new [`RectangleMeshBuilder`] from a full width and height.
1039    ///
1040    /// # Panics
1041    ///
1042    /// Panics in debug mode if `width` or `height` is negative.
1043    pub const fn new(width: f32, height: f32) -> Self {
1044        debug_assert!(width >= 0.0, "rectangle has a negative width");
1045        debug_assert!(height >= 0.0, "rectangle has a negative height");
1046
1047        Self {
1048            half_size: Vec2::new(width / 2.0, height / 2.0),
1049        }
1050    }
1051}
1052
1053impl MeshBuilder for RectangleMeshBuilder {
1054    fn build(&self) -> Mesh {
1055        let [hw, hh] = [self.half_size.x, self.half_size.y];
1056        let positions = vec![
1057            [hw, hh, 0.0],
1058            [-hw, hh, 0.0],
1059            [-hw, -hh, 0.0],
1060            [hw, -hh, 0.0],
1061        ];
1062        let normals = vec![[0.0, 0.0, 1.0]; 4];
1063        let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
1064        let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]);
1065
1066        Mesh::new(
1067            PrimitiveTopology::TriangleList,
1068            RenderAssetUsages::default(),
1069        )
1070        .with_inserted_indices(indices)
1071        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
1072        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
1073        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
1074    }
1075}
1076
1077impl Extrudable for RectangleMeshBuilder {
1078    fn perimeter(&self) -> Vec<PerimeterSegment> {
1079        vec![PerimeterSegment::Flat {
1080            indices: vec![0, 1, 2, 3, 0],
1081        }]
1082    }
1083}
1084
1085impl Meshable for Rectangle {
1086    type Output = RectangleMeshBuilder;
1087
1088    fn mesh(&self) -> Self::Output {
1089        RectangleMeshBuilder {
1090            half_size: self.half_size,
1091        }
1092    }
1093}
1094
1095impl From<Rectangle> for Mesh {
1096    fn from(rectangle: Rectangle) -> Self {
1097        rectangle.mesh().build()
1098    }
1099}
1100
1101/// A builder used for creating a [`Mesh`] with a [`Capsule2d`] shape.
1102#[derive(Clone, Copy, Debug, Reflect)]
1103#[reflect(Default, Debug, Clone)]
1104pub struct Capsule2dMeshBuilder {
1105    /// The [`Capsule2d`] shape.
1106    pub capsule: Capsule2d,
1107    /// The number of vertices used for one hemicircle.
1108    /// The total number of vertices for the capsule mesh will be two times the resolution.
1109    ///
1110    /// The default is `16`.
1111    pub resolution: u32,
1112}
1113
1114impl Default for Capsule2dMeshBuilder {
1115    fn default() -> Self {
1116        Self {
1117            capsule: Capsule2d::default(),
1118            resolution: 16,
1119        }
1120    }
1121}
1122
1123impl Capsule2dMeshBuilder {
1124    /// Creates a new [`Capsule2dMeshBuilder`] from a given radius, length, and the number of vertices
1125    /// used for one hemicircle. The total number of vertices for the capsule mesh will be two times the resolution.
1126    #[inline]
1127    pub fn new(radius: f32, length: f32, resolution: u32) -> Self {
1128        Self {
1129            capsule: Capsule2d::new(radius, length),
1130            resolution,
1131        }
1132    }
1133
1134    /// Sets the number of vertices used for one hemicircle.
1135    /// The total number of vertices for the capsule mesh will be two times the resolution.
1136    #[inline]
1137    pub const fn resolution(mut self, resolution: u32) -> Self {
1138        self.resolution = resolution;
1139        self
1140    }
1141}
1142
1143impl MeshBuilder for Capsule2dMeshBuilder {
1144    fn build(&self) -> Mesh {
1145        // The resolution is the number of vertices for one semicircle
1146        let resolution = self.resolution;
1147        let vertex_count = 2 * resolution;
1148
1149        // Six extra indices for the two triangles between the semicircles
1150        let mut indices = Vec::with_capacity((resolution as usize - 2) * 2 * 3 + 6);
1151        let mut positions = Vec::with_capacity(vertex_count as usize);
1152        let normals = vec![[0.0, 0.0, 1.0]; vertex_count as usize];
1153        let mut uvs = Vec::with_capacity(vertex_count as usize);
1154
1155        let radius = self.capsule.radius;
1156        let step = core::f32::consts::TAU / vertex_count as f32;
1157
1158        // If the vertex count is even, offset starting angle of top semicircle by half a step
1159        // to position the vertices evenly.
1160        let start_angle = if vertex_count.is_multiple_of(2) {
1161            step / 2.0
1162        } else {
1163            0.0
1164        };
1165
1166        // How much the hemicircle radius is of the total half-height of the capsule.
1167        // This is used to prevent the UVs from stretching between the semicircles.
1168        let radius_frac = self.capsule.radius / (self.capsule.half_length + self.capsule.radius);
1169
1170        // Create top semicircle
1171        for i in 0..resolution {
1172            // Compute vertex position at angle theta
1173            let theta = start_angle + i as f32 * step;
1174            let (sin, cos) = ops::sin_cos(theta);
1175            let (x, y) = (cos * radius, sin * radius + self.capsule.half_length);
1176
1177            positions.push([x, y, 0.0]);
1178            uvs.push([0.5 * (cos + 1.0), radius_frac * (1.0 - 0.5 * (sin + 1.0))]);
1179        }
1180
1181        // Add top semicircle indices
1182        for i in 1..resolution - 1 {
1183            indices.extend_from_slice(&[0, i, i + 1]);
1184        }
1185
1186        // Add indices for top left triangle of the part between the semicircles
1187        indices.extend_from_slice(&[0, resolution - 1, resolution]);
1188
1189        // Create bottom semicircle
1190        for i in resolution..vertex_count {
1191            // Compute vertex position at angle theta
1192            let theta = start_angle + i as f32 * step;
1193            let (sin, cos) = ops::sin_cos(theta);
1194            let (x, y) = (cos * radius, sin * radius - self.capsule.half_length);
1195
1196            positions.push([x, y, 0.0]);
1197            uvs.push([0.5 * (cos + 1.0), 1.0 - radius_frac * 0.5 * (sin + 1.0)]);
1198        }
1199
1200        // Add bottom semicircle indices
1201        for i in 1..resolution - 1 {
1202            indices.extend_from_slice(&[resolution, resolution + i, resolution + i + 1]);
1203        }
1204
1205        // Add indices for bottom right triangle of the part between the semicircles
1206        indices.extend_from_slice(&[resolution, vertex_count - 1, 0]);
1207
1208        Mesh::new(
1209            PrimitiveTopology::TriangleList,
1210            RenderAssetUsages::default(),
1211        )
1212        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
1213        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
1214        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
1215        .with_inserted_indices(Indices::U32(indices))
1216    }
1217}
1218
1219impl Extrudable for Capsule2dMeshBuilder {
1220    fn perimeter(&self) -> Vec<PerimeterSegment> {
1221        let resolution = self.resolution;
1222        let top_semi_indices = (0..resolution).collect();
1223        let bottom_semi_indices = (resolution..(2 * resolution)).collect();
1224        vec![
1225            PerimeterSegment::Smooth {
1226                first_normal: Vec2::X,
1227                last_normal: Vec2::NEG_X,
1228                indices: top_semi_indices,
1229            }, // Top semi-circle
1230            PerimeterSegment::Flat {
1231                indices: vec![resolution - 1, resolution],
1232            }, // Left edge
1233            PerimeterSegment::Smooth {
1234                first_normal: Vec2::NEG_X,
1235                last_normal: Vec2::X,
1236                indices: bottom_semi_indices,
1237            }, // Bottom semi-circle
1238            PerimeterSegment::Flat {
1239                indices: vec![2 * resolution - 1, 0],
1240            }, // Right edge
1241        ]
1242    }
1243}
1244
1245impl Meshable for Capsule2d {
1246    type Output = Capsule2dMeshBuilder;
1247
1248    fn mesh(&self) -> Self::Output {
1249        Capsule2dMeshBuilder {
1250            capsule: *self,
1251            ..Default::default()
1252        }
1253    }
1254}
1255
1256impl From<Capsule2d> for Mesh {
1257    fn from(capsule: Capsule2d) -> Self {
1258        capsule.mesh().build()
1259    }
1260}
1261
1262#[cfg(test)]
1263mod tests {
1264    use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd};
1265    use bevy_platform::collections::HashSet;
1266
1267    use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};
1268
1269    fn count_distinct_positions(points: &[[f32; 3]]) -> usize {
1270        let mut map = <HashSet<_>>::default();
1271        for point in points {
1272            map.insert(point.map(FloatOrd));
1273        }
1274        map.len()
1275    }
1276
1277    #[test]
1278    fn test_annulus() {
1279        let mesh = Annulus::new(1.0, 1.2).mesh().resolution(16).build();
1280
1281        assert_eq!(
1282            32,
1283            count_distinct_positions(
1284                mesh.attribute(Mesh::ATTRIBUTE_POSITION)
1285                    .unwrap()
1286                    .as_float3()
1287                    .unwrap()
1288            )
1289        );
1290    }
1291
1292    /// Sin/cos and multiplication computations result in numbers like 0.4999999.
1293    /// Round these to numbers we expect like 0.5.
1294    fn fix_floats<const N: usize>(points: &mut [[f32; N]]) {
1295        for point in points.iter_mut() {
1296            for coord in point.iter_mut() {
1297                let round = (*coord * 2.).round() / 2.;
1298                if (*coord - round).abs() < 0.00001 {
1299                    *coord = round;
1300                }
1301            }
1302        }
1303    }
1304
1305    #[test]
1306    fn test_regular_polygon() {
1307        let mut mesh = Mesh::from(RegularPolygon::new(7.0, 4));
1308
1309        let Some(VertexAttributeValues::Float32x3(mut positions)) =
1310            mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)
1311        else {
1312            panic!("Expected positions f32x3");
1313        };
1314        let Some(VertexAttributeValues::Float32x2(mut uvs)) =
1315            mesh.remove_attribute(Mesh::ATTRIBUTE_UV_0)
1316        else {
1317            panic!("Expected uvs f32x2");
1318        };
1319        let Some(VertexAttributeValues::Float32x3(normals)) =
1320            mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)
1321        else {
1322            panic!("Expected normals f32x3");
1323        };
1324
1325        fix_floats(&mut positions);
1326        fix_floats(&mut uvs);
1327
1328        assert_eq!(
1329            [
1330                [0.0, 7.0, 0.0],
1331                [-7.0, 0.0, 0.0],
1332                [0.0, -7.0, 0.0],
1333                [7.0, 0.0, 0.0],
1334            ],
1335            &positions[..]
1336        );
1337
1338        // Note V coordinate increases in the opposite direction to the Y coordinate.
1339        assert_eq!([[0.5, 0.0], [0.0, 0.5], [0.5, 1.0], [1.0, 0.5],], &uvs[..]);
1340
1341        assert_eq!(&[[0.0, 0.0, 1.0]; 4], &normals[..]);
1342    }
1343}