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#[derive(Clone, Copy, Debug, Reflect)]
21#[reflect(Default, Debug, Clone)]
22pub struct CircleMeshBuilder {
23 pub circle: Circle,
25 #[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 #[inline]
43 pub const fn new(radius: f32, resolution: u32) -> Self {
44 Self {
45 circle: Circle { radius },
46 resolution,
47 }
48 }
49
50 #[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#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
105#[reflect(Default, Debug, Clone)]
106#[non_exhaustive]
107pub enum CircularMeshUvMode {
108 Mask {
111 angle: f32,
113 },
114}
115
116impl Default for CircularMeshUvMode {
117 fn default() -> Self {
118 CircularMeshUvMode::Mask { angle: 0.0 }
119 }
120}
121
122#[derive(Clone, Debug, Reflect)]
127#[reflect(Default, Debug, Clone)]
128pub struct CircularSectorMeshBuilder {
129 pub sector: CircularSector,
131 #[doc(alias = "vertices")]
134 pub resolution: u32,
135 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 #[inline]
152 pub fn new(sector: CircularSector) -> Self {
153 Self {
154 sector,
155 ..Self::default()
156 }
157 }
158
159 #[inline]
161 #[doc(alias = "vertices")]
162 pub const fn resolution(mut self, resolution: u32) -> Self {
163 self.resolution = resolution;
164 self
165 }
166
167 #[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 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 let vertex = self.sector.radius() * Vec2::from_angle(angle);
197 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 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 fn from(sector: CircularSector) -> Self {
256 sector.mesh().build()
257 }
258}
259
260#[derive(Clone, Copy, Debug, Reflect)]
265#[reflect(Default, Debug, Clone)]
266pub struct CircularSegmentMeshBuilder {
267 pub segment: CircularSegment,
269 #[doc(alias = "vertices")]
272 pub resolution: u32,
273 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 #[inline]
290 pub fn new(segment: CircularSegment) -> Self {
291 Self {
292 segment,
293 ..Self::default()
294 }
295 }
296
297 #[inline]
299 #[doc(alias = "vertices")]
300 pub const fn resolution(mut self, resolution: u32) -> Self {
301 self.resolution = resolution;
302 self
303 }
304
305 #[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 let midpoint_vertex = self.segment.chord_midpoint();
325 positions.push([midpoint_vertex.x, midpoint_vertex.y, 0.0]);
326 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 let vertex = self.segment.radius() * Vec2::from_angle(angle);
344 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 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 fn from(segment: CircularSegment) -> Self {
403 segment.mesh().build()
404 }
405}
406
407#[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#[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 fn default() -> Self {
473 Self {
474 circumradius: 0.5,
475 sides: 6,
476 }
477 }
478}
479
480impl RegularPolygonMeshBuilder {
481 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 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#[derive(Clone, Copy, Debug, Reflect)]
538#[reflect(Default, Debug, Clone)]
539pub struct EllipseMeshBuilder {
540 pub ellipse: Ellipse,
542 #[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 #[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 #[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 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 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
641pub struct Segment2dMeshBuilder {
643 pub segment: Segment2d,
645}
646
647impl Segment2dMeshBuilder {
648 #[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 fn from(segment: Segment2d) -> Self {
677 segment.mesh().build()
678 }
679}
680
681#[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#[derive(Clone, Copy, Debug, Reflect)]
727#[reflect(Default, Debug, Clone)]
728pub struct AnnulusMeshBuilder {
729 pub annulus: Annulus,
731
732 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 #[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 #[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 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 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 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(), },
832 PerimeterSegment::Smooth {
833 first_normal: Vec2::Y,
834 last_normal: Vec2::Y,
835 indices: (1..vert_count).step_by(2).chain([1]).collect(), },
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#[derive(Clone, Copy, Debug, Reflect)]
860#[reflect(Default, Debug, Clone)]
861pub struct RhombusMeshBuilder {
862 half_diagonals: Vec2,
863}
864
865impl Default for RhombusMeshBuilder {
866 fn default() -> Self {
868 Self {
869 half_diagonals: Vec2::splat(0.5),
870 }
871 }
872}
873
874impl RhombusMeshBuilder {
875 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#[derive(Clone, Copy, Debug, Default, Reflect)]
946#[reflect(Default, Debug, Clone)]
947pub struct Triangle2dMeshBuilder {
948 triangle: Triangle2d,
949}
950
951impl Triangle2dMeshBuilder {
952 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#[derive(Clone, Copy, Debug, Reflect)]
1023#[reflect(Default, Debug, Clone)]
1024pub struct RectangleMeshBuilder {
1025 half_size: Vec2,
1026}
1027
1028impl Default for RectangleMeshBuilder {
1029 fn default() -> Self {
1031 Self {
1032 half_size: Vec2::splat(0.5),
1033 }
1034 }
1035}
1036
1037impl RectangleMeshBuilder {
1038 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#[derive(Clone, Copy, Debug, Reflect)]
1103#[reflect(Default, Debug, Clone)]
1104pub struct Capsule2dMeshBuilder {
1105 pub capsule: Capsule2d,
1107 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 #[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 #[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 let resolution = self.resolution;
1147 let vertex_count = 2 * resolution;
1148
1149 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 let start_angle = if vertex_count.is_multiple_of(2) {
1161 step / 2.0
1162 } else {
1163 0.0
1164 };
1165
1166 let radius_frac = self.capsule.radius / (self.capsule.half_length + self.capsule.radius);
1169
1170 for i in 0..resolution {
1172 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 for i in 1..resolution - 1 {
1183 indices.extend_from_slice(&[0, i, i + 1]);
1184 }
1185
1186 indices.extend_from_slice(&[0, resolution - 1, resolution]);
1188
1189 for i in resolution..vertex_count {
1191 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 for i in 1..resolution - 1 {
1202 indices.extend_from_slice(&[resolution, resolution + i, resolution + i + 1]);
1203 }
1204
1205 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 }, PerimeterSegment::Flat {
1231 indices: vec![resolution - 1, resolution],
1232 }, PerimeterSegment::Smooth {
1234 first_normal: Vec2::NEG_X,
1235 last_normal: Vec2::X,
1236 indices: bottom_semi_indices,
1237 }, PerimeterSegment::Flat {
1239 indices: vec![2 * resolution - 1, 0],
1240 }, ]
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 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 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}