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::{
8 ops,
9 primitives::{
10 Annulus, Capsule2d, Circle, CircularSector, CircularSegment, ConvexPolygon, Ellipse,
11 Rectangle, RegularPolygon, Rhombus, Triangle2d, Triangle3d, WindingOrder,
12 },
13 FloatExt, Vec2,
14};
15use bevy_reflect::prelude::*;
16use wgpu_types::PrimitiveTopology;
17
18#[derive(Clone, Copy, Debug, Reflect)]
20#[reflect(Default, Debug, Clone)]
21pub struct CircleMeshBuilder {
22 pub circle: Circle,
24 #[doc(alias = "vertices")]
27 pub resolution: u32,
28}
29
30impl Default for CircleMeshBuilder {
31 fn default() -> Self {
32 Self {
33 circle: Circle::default(),
34 resolution: 32,
35 }
36 }
37}
38
39impl CircleMeshBuilder {
40 #[inline]
42 pub const fn new(radius: f32, resolution: u32) -> Self {
43 Self {
44 circle: Circle { radius },
45 resolution,
46 }
47 }
48
49 #[inline]
51 #[doc(alias = "vertices")]
52 pub const fn resolution(mut self, resolution: u32) -> Self {
53 self.resolution = resolution;
54 self
55 }
56}
57
58impl MeshBuilder for CircleMeshBuilder {
59 fn build(&self) -> Mesh {
60 Ellipse::new(self.circle.radius, self.circle.radius)
61 .mesh()
62 .resolution(self.resolution)
63 .build()
64 }
65}
66
67impl Extrudable for CircleMeshBuilder {
68 fn perimeter(&self) -> Vec<PerimeterSegment> {
69 vec![PerimeterSegment::Smooth {
70 first_normal: Vec2::Y,
71 last_normal: Vec2::Y,
72 indices: (0..self.resolution).chain([0]).collect(),
73 }]
74 }
75}
76
77impl Meshable for Circle {
78 type Output = CircleMeshBuilder;
79
80 fn mesh(&self) -> Self::Output {
81 CircleMeshBuilder {
82 circle: *self,
83 ..Default::default()
84 }
85 }
86}
87
88impl From<Circle> for Mesh {
89 fn from(circle: Circle) -> Self {
90 circle.mesh().build()
91 }
92}
93
94#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
104#[reflect(Default, Debug, Clone)]
105#[non_exhaustive]
106pub enum CircularMeshUvMode {
107 Mask {
110 angle: f32,
112 },
113}
114
115impl Default for CircularMeshUvMode {
116 fn default() -> Self {
117 CircularMeshUvMode::Mask { angle: 0.0 }
118 }
119}
120
121#[derive(Clone, Debug, Reflect)]
126#[reflect(Default, Debug, Clone)]
127pub struct CircularSectorMeshBuilder {
128 pub sector: CircularSector,
130 #[doc(alias = "vertices")]
133 pub resolution: u32,
134 pub uv_mode: CircularMeshUvMode,
136}
137
138impl Default for CircularSectorMeshBuilder {
139 fn default() -> Self {
140 Self {
141 sector: CircularSector::default(),
142 resolution: 32,
143 uv_mode: CircularMeshUvMode::default(),
144 }
145 }
146}
147
148impl CircularSectorMeshBuilder {
149 #[inline]
151 pub fn new(sector: CircularSector) -> Self {
152 Self {
153 sector,
154 ..Self::default()
155 }
156 }
157
158 #[inline]
160 #[doc(alias = "vertices")]
161 pub const fn resolution(mut self, resolution: u32) -> Self {
162 self.resolution = resolution;
163 self
164 }
165
166 #[inline]
168 pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {
169 self.uv_mode = uv_mode;
170 self
171 }
172}
173
174impl MeshBuilder for CircularSectorMeshBuilder {
175 fn build(&self) -> Mesh {
176 let resolution = self.resolution as usize;
177 let mut indices = Vec::with_capacity((resolution - 1) * 3);
178 let mut positions = Vec::with_capacity(resolution + 1);
179 let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];
180 let mut uvs = Vec::with_capacity(resolution + 1);
181
182 let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;
183
184 positions.push([0.0; 3]);
186 uvs.push([0.5; 2]);
187
188 let first_angle = FRAC_PI_2 - self.sector.half_angle();
189 let last_angle = FRAC_PI_2 + self.sector.half_angle();
190 let last_i = (self.resolution - 1) as f32;
191 for i in 0..self.resolution {
192 let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);
193
194 let vertex = self.sector.radius() * Vec2::from_angle(angle);
196 let uv =
199 Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));
200
201 positions.push([vertex.x, vertex.y, 0.0]);
202 uvs.push([uv.x, uv.y]);
203 }
204
205 for i in 1..self.resolution {
206 indices.extend_from_slice(&[0, i, i + 1]);
208 }
209
210 Mesh::new(
211 PrimitiveTopology::TriangleList,
212 RenderAssetUsages::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}
220
221impl Extrudable for CircularSectorMeshBuilder {
222 fn perimeter(&self) -> Vec<PerimeterSegment> {
223 let (sin, cos) = ops::sin_cos(self.sector.arc.half_angle);
224 let first_normal = Vec2::new(sin, cos);
225 let last_normal = Vec2::new(-sin, cos);
226 vec![
227 PerimeterSegment::Flat {
228 indices: vec![self.resolution, 0, 1],
229 },
230 PerimeterSegment::Smooth {
231 first_normal,
232 last_normal,
233 indices: (1..=self.resolution).collect(),
234 },
235 ]
236 }
237}
238
239impl Meshable for CircularSector {
240 type Output = CircularSectorMeshBuilder;
241
242 fn mesh(&self) -> Self::Output {
243 CircularSectorMeshBuilder {
244 sector: *self,
245 ..Default::default()
246 }
247 }
248}
249
250impl From<CircularSector> for Mesh {
251 fn from(sector: CircularSector) -> Self {
255 sector.mesh().build()
256 }
257}
258
259#[derive(Clone, Copy, Debug, Reflect)]
264#[reflect(Default, Debug, Clone)]
265pub struct CircularSegmentMeshBuilder {
266 pub segment: CircularSegment,
268 #[doc(alias = "vertices")]
271 pub resolution: u32,
272 pub uv_mode: CircularMeshUvMode,
274}
275
276impl Default for CircularSegmentMeshBuilder {
277 fn default() -> Self {
278 Self {
279 segment: CircularSegment::default(),
280 resolution: 32,
281 uv_mode: CircularMeshUvMode::default(),
282 }
283 }
284}
285
286impl CircularSegmentMeshBuilder {
287 #[inline]
289 pub fn new(segment: CircularSegment) -> Self {
290 Self {
291 segment,
292 ..Self::default()
293 }
294 }
295
296 #[inline]
298 #[doc(alias = "vertices")]
299 pub const fn resolution(mut self, resolution: u32) -> Self {
300 self.resolution = resolution;
301 self
302 }
303
304 #[inline]
306 pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {
307 self.uv_mode = uv_mode;
308 self
309 }
310}
311
312impl MeshBuilder for CircularSegmentMeshBuilder {
313 fn build(&self) -> Mesh {
314 let resolution = self.resolution as usize;
315 let mut indices = Vec::with_capacity((resolution - 1) * 3);
316 let mut positions = Vec::with_capacity(resolution + 1);
317 let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];
318 let mut uvs = Vec::with_capacity(resolution + 1);
319
320 let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;
321
322 let midpoint_vertex = self.segment.chord_midpoint();
324 positions.push([midpoint_vertex.x, midpoint_vertex.y, 0.0]);
325 let midpoint_uv = Vec2::from_angle(-uv_angle - FRAC_PI_2).mul_add(
330 Vec2::splat(0.5 * (self.segment.apothem() / self.segment.radius())),
331 Vec2::splat(0.5),
332 );
333 uvs.push([midpoint_uv.x, midpoint_uv.y]);
334
335 let first_angle = FRAC_PI_2 - self.segment.half_angle();
336 let last_angle = FRAC_PI_2 + self.segment.half_angle();
337 let last_i = (self.resolution - 1) as f32;
338 for i in 0..self.resolution {
339 let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);
340
341 let vertex = self.segment.radius() * Vec2::from_angle(angle);
343 let uv =
346 Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));
347
348 positions.push([vertex.x, vertex.y, 0.0]);
349 uvs.push([uv.x, uv.y]);
350 }
351
352 for i in 1..self.resolution {
353 indices.extend_from_slice(&[0, i, i + 1]);
355 }
356
357 Mesh::new(
358 PrimitiveTopology::TriangleList,
359 RenderAssetUsages::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}
367
368impl Extrudable for CircularSegmentMeshBuilder {
369 fn perimeter(&self) -> Vec<PerimeterSegment> {
370 let (sin, cos) = ops::sin_cos(self.segment.arc.half_angle);
371 let first_normal = Vec2::new(sin, cos);
372 let last_normal = Vec2::new(-sin, cos);
373 vec![
374 PerimeterSegment::Flat {
375 indices: vec![self.resolution, 0, 1],
376 },
377 PerimeterSegment::Smooth {
378 first_normal,
379 last_normal,
380 indices: (1..=self.resolution).collect(),
381 },
382 ]
383 }
384}
385
386impl Meshable for CircularSegment {
387 type Output = CircularSegmentMeshBuilder;
388
389 fn mesh(&self) -> Self::Output {
390 CircularSegmentMeshBuilder {
391 segment: *self,
392 ..Default::default()
393 }
394 }
395}
396
397impl From<CircularSegment> for Mesh {
398 fn from(segment: CircularSegment) -> Self {
402 segment.mesh().build()
403 }
404}
405
406#[derive(Clone, Copy, Debug, Reflect)]
411#[reflect(Debug, Clone)]
412pub struct ConvexPolygonMeshBuilder<const N: usize> {
413 pub vertices: [Vec2; N],
414}
415
416impl<const N: usize> Meshable for ConvexPolygon<N> {
417 type Output = ConvexPolygonMeshBuilder<N>;
418
419 fn mesh(&self) -> Self::Output {
420 Self::Output {
421 vertices: *self.vertices(),
422 }
423 }
424}
425
426impl<const N: usize> MeshBuilder for ConvexPolygonMeshBuilder<N> {
427 fn build(&self) -> Mesh {
428 let mut indices = Vec::with_capacity((N - 2) * 3);
429 let mut positions = Vec::with_capacity(N);
430
431 for vertex in self.vertices {
432 positions.push([vertex.x, vertex.y, 0.0]);
433 }
434 for i in 2..N as u32 {
435 indices.extend_from_slice(&[0, i - 1, i]);
436 }
437 Mesh::new(
438 PrimitiveTopology::TriangleList,
439 RenderAssetUsages::default(),
440 )
441 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
442 .with_inserted_indices(Indices::U32(indices))
443 }
444}
445
446impl<const N: usize> Extrudable for ConvexPolygonMeshBuilder<N> {
447 fn perimeter(&self) -> Vec<PerimeterSegment> {
448 vec![PerimeterSegment::Flat {
449 indices: (0..N as u32).chain([0]).collect(),
450 }]
451 }
452}
453
454impl<const N: usize> From<ConvexPolygon<N>> for Mesh {
455 fn from(polygon: ConvexPolygon<N>) -> Self {
456 polygon.mesh().build()
457 }
458}
459
460#[derive(Clone, Copy, Debug, Reflect)]
462#[reflect(Default, Debug, Clone)]
463pub struct RegularPolygonMeshBuilder {
464 circumradius: f32,
465 sides: u32,
466}
467
468impl Default for RegularPolygonMeshBuilder {
469 fn default() -> Self {
471 Self {
472 circumradius: 0.5,
473 sides: 6,
474 }
475 }
476}
477
478impl RegularPolygonMeshBuilder {
479 pub const fn new(circumradius: f32, sides: u32) -> Self {
486 debug_assert!(
487 circumradius.is_sign_positive(),
488 "polygon has a negative radius"
489 );
490 debug_assert!(sides > 2, "polygon has less than 3 sides");
491
492 Self {
493 circumradius,
494 sides,
495 }
496 }
497}
498
499impl Meshable for RegularPolygon {
500 type Output = RegularPolygonMeshBuilder;
501
502 fn mesh(&self) -> Self::Output {
503 Self::Output {
504 circumradius: self.circumcircle.radius,
505 sides: self.sides,
506 }
507 }
508}
509
510impl MeshBuilder for RegularPolygonMeshBuilder {
511 fn build(&self) -> Mesh {
512 Ellipse::new(self.circumradius, self.circumradius)
514 .mesh()
515 .resolution(self.sides)
516 .build()
517 }
518}
519
520impl Extrudable for RegularPolygonMeshBuilder {
521 fn perimeter(&self) -> Vec<PerimeterSegment> {
522 vec![PerimeterSegment::Flat {
523 indices: (0..self.sides).chain([0]).collect(),
524 }]
525 }
526}
527
528impl From<RegularPolygon> for Mesh {
529 fn from(polygon: RegularPolygon) -> Self {
530 polygon.mesh().build()
531 }
532}
533
534#[derive(Clone, Copy, Debug, Reflect)]
536#[reflect(Default, Debug, Clone)]
537pub struct EllipseMeshBuilder {
538 pub ellipse: Ellipse,
540 #[doc(alias = "vertices")]
543 pub resolution: u32,
544}
545
546impl Default for EllipseMeshBuilder {
547 fn default() -> Self {
548 Self {
549 ellipse: Ellipse::default(),
550 resolution: 32,
551 }
552 }
553}
554
555impl EllipseMeshBuilder {
556 #[inline]
558 pub const fn new(half_width: f32, half_height: f32, resolution: u32) -> Self {
559 Self {
560 ellipse: Ellipse::new(half_width, half_height),
561 resolution,
562 }
563 }
564
565 #[inline]
567 #[doc(alias = "vertices")]
568 pub const fn resolution(mut self, resolution: u32) -> Self {
569 self.resolution = resolution;
570 self
571 }
572}
573
574impl MeshBuilder for EllipseMeshBuilder {
575 fn build(&self) -> Mesh {
576 let resolution = self.resolution as usize;
577 let mut indices = Vec::with_capacity((resolution - 2) * 3);
578 let mut positions = Vec::with_capacity(resolution);
579 let normals = vec![[0.0, 0.0, 1.0]; resolution];
580 let mut uvs = Vec::with_capacity(resolution);
581
582 let start_angle = FRAC_PI_2;
584 let step = core::f32::consts::TAU / self.resolution as f32;
585
586 for i in 0..self.resolution {
587 let theta = start_angle + i as f32 * step;
589 let (sin, cos) = ops::sin_cos(theta);
590 let x = cos * self.ellipse.half_size.x;
591 let y = sin * self.ellipse.half_size.y;
592
593 positions.push([x, y, 0.0]);
594 uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
595 }
596
597 for i in 1..(self.resolution - 1) {
598 indices.extend_from_slice(&[0, i, i + 1]);
599 }
600
601 Mesh::new(
602 PrimitiveTopology::TriangleList,
603 RenderAssetUsages::default(),
604 )
605 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
606 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
607 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
608 .with_inserted_indices(Indices::U32(indices))
609 }
610}
611
612impl Extrudable for EllipseMeshBuilder {
613 fn perimeter(&self) -> Vec<PerimeterSegment> {
614 vec![PerimeterSegment::Smooth {
615 first_normal: Vec2::Y,
616 last_normal: Vec2::Y,
617 indices: (0..self.resolution).chain([0]).collect(),
618 }]
619 }
620}
621
622impl Meshable for Ellipse {
623 type Output = EllipseMeshBuilder;
624
625 fn mesh(&self) -> Self::Output {
626 EllipseMeshBuilder {
627 ellipse: *self,
628 ..Default::default()
629 }
630 }
631}
632
633impl From<Ellipse> for Mesh {
634 fn from(ellipse: Ellipse) -> Self {
635 ellipse.mesh().build()
636 }
637}
638
639#[derive(Clone, Copy, Debug, Reflect)]
641#[reflect(Default, Debug, Clone)]
642pub struct AnnulusMeshBuilder {
643 pub annulus: Annulus,
645
646 pub resolution: u32,
649}
650
651impl Default for AnnulusMeshBuilder {
652 fn default() -> Self {
653 Self {
654 annulus: Annulus::default(),
655 resolution: 32,
656 }
657 }
658}
659
660impl AnnulusMeshBuilder {
661 #[inline]
663 pub fn new(inner_radius: f32, outer_radius: f32, resolution: u32) -> Self {
664 Self {
665 annulus: Annulus::new(inner_radius, outer_radius),
666 resolution,
667 }
668 }
669
670 #[inline]
672 pub fn resolution(mut self, resolution: u32) -> Self {
673 self.resolution = resolution;
674 self
675 }
676}
677
678impl MeshBuilder for AnnulusMeshBuilder {
679 fn build(&self) -> Mesh {
680 let inner_radius = self.annulus.inner_circle.radius;
681 let outer_radius = self.annulus.outer_circle.radius;
682
683 let num_vertices = (self.resolution as usize + 1) * 2;
684 let mut indices = Vec::with_capacity(self.resolution as usize * 6);
685 let mut positions = Vec::with_capacity(num_vertices);
686 let mut uvs = Vec::with_capacity(num_vertices);
687 let normals = vec![[0.0, 0.0, 1.0]; num_vertices];
688
689 let start_angle = FRAC_PI_2;
694 let step = core::f32::consts::TAU / self.resolution as f32;
695 for i in 0..=self.resolution {
696 let theta = start_angle + (i % self.resolution) as f32 * step;
697 let (sin, cos) = ops::sin_cos(theta);
698 let inner_pos = [cos * inner_radius, sin * inner_radius, 0.];
699 let outer_pos = [cos * outer_radius, sin * outer_radius, 0.];
700 positions.push(inner_pos);
701 positions.push(outer_pos);
702
703 let inner_uv = [0., i as f32 / self.resolution as f32];
708 let outer_uv = [1., i as f32 / self.resolution as f32];
709 uvs.push(inner_uv);
710 uvs.push(outer_uv);
711 }
712
713 for i in 0..self.resolution {
718 let inner_vertex = 2 * i;
719 let outer_vertex = 2 * i + 1;
720 let next_inner = inner_vertex + 2;
721 let next_outer = outer_vertex + 2;
722 indices.extend_from_slice(&[inner_vertex, outer_vertex, next_outer]);
723 indices.extend_from_slice(&[next_outer, next_inner, inner_vertex]);
724 }
725
726 Mesh::new(
727 PrimitiveTopology::TriangleList,
728 RenderAssetUsages::default(),
729 )
730 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
731 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
732 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
733 .with_inserted_indices(Indices::U32(indices))
734 }
735}
736
737impl Extrudable for AnnulusMeshBuilder {
738 fn perimeter(&self) -> Vec<PerimeterSegment> {
739 let vert_count = 2 * self.resolution;
740 vec![
741 PerimeterSegment::Smooth {
742 first_normal: Vec2::NEG_Y,
743 last_normal: Vec2::NEG_Y,
744 indices: (0..vert_count).step_by(2).chain([0]).rev().collect(), },
746 PerimeterSegment::Smooth {
747 first_normal: Vec2::Y,
748 last_normal: Vec2::Y,
749 indices: (1..vert_count).step_by(2).chain([1]).collect(), },
751 ]
752 }
753}
754
755impl Meshable for Annulus {
756 type Output = AnnulusMeshBuilder;
757
758 fn mesh(&self) -> Self::Output {
759 AnnulusMeshBuilder {
760 annulus: *self,
761 ..Default::default()
762 }
763 }
764}
765
766impl From<Annulus> for Mesh {
767 fn from(annulus: Annulus) -> Self {
768 annulus.mesh().build()
769 }
770}
771
772#[derive(Clone, Copy, Debug, Reflect)]
774#[reflect(Default, Debug, Clone)]
775pub struct RhombusMeshBuilder {
776 half_diagonals: Vec2,
777}
778
779impl Default for RhombusMeshBuilder {
780 fn default() -> Self {
782 Self {
783 half_diagonals: Vec2::splat(0.5),
784 }
785 }
786}
787
788impl RhombusMeshBuilder {
789 pub const fn new(horizontal_diagonal: f32, vertical_diagonal: f32) -> Self {
795 debug_assert!(
796 horizontal_diagonal >= 0.0,
797 "rhombus has a negative horizontal size",
798 );
799 debug_assert!(
800 vertical_diagonal >= 0.0,
801 "rhombus has a negative vertical size"
802 );
803
804 Self {
805 half_diagonals: Vec2::new(horizontal_diagonal / 2.0, vertical_diagonal / 2.0),
806 }
807 }
808}
809
810impl MeshBuilder for RhombusMeshBuilder {
811 fn build(&self) -> Mesh {
812 let [hhd, vhd] = [self.half_diagonals.x, self.half_diagonals.y];
813 let positions = vec![
814 [hhd, 0.0, 0.0],
815 [-hhd, 0.0, 0.0],
816 [0.0, vhd, 0.0],
817 [0.0, -vhd, 0.0],
818 ];
819 let normals = vec![[0.0, 0.0, 1.0]; 4];
820 let uvs = vec![[1.0, 0.5], [0.0, 0.5], [0.5, 0.0], [0.5, 1.0]];
821 let indices = Indices::U32(vec![1, 0, 2, 1, 3, 0]);
822
823 Mesh::new(
824 PrimitiveTopology::TriangleList,
825 RenderAssetUsages::default(),
826 )
827 .with_inserted_indices(indices)
828 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
829 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
830 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
831 }
832}
833
834impl Extrudable for RhombusMeshBuilder {
835 fn perimeter(&self) -> Vec<PerimeterSegment> {
836 vec![PerimeterSegment::Flat {
837 indices: vec![0, 2, 1, 3, 0],
838 }]
839 }
840}
841
842impl Meshable for Rhombus {
843 type Output = RhombusMeshBuilder;
844
845 fn mesh(&self) -> Self::Output {
846 Self::Output {
847 half_diagonals: self.half_diagonals,
848 }
849 }
850}
851
852impl From<Rhombus> for Mesh {
853 fn from(rhombus: Rhombus) -> Self {
854 rhombus.mesh().build()
855 }
856}
857
858#[derive(Clone, Copy, Debug, Default, Reflect)]
860#[reflect(Default, Debug, Clone)]
861pub struct Triangle2dMeshBuilder {
862 triangle: Triangle2d,
863}
864
865impl Triangle2dMeshBuilder {
866 pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {
868 Self {
869 triangle: Triangle2d::new(a, b, c),
870 }
871 }
872}
873
874impl Meshable for Triangle2d {
875 type Output = Triangle2dMeshBuilder;
876
877 fn mesh(&self) -> Self::Output {
878 Self::Output { triangle: *self }
879 }
880}
881
882impl MeshBuilder for Triangle2dMeshBuilder {
883 fn build(&self) -> Mesh {
884 let vertices_3d = self.triangle.vertices.map(|v| v.extend(0.));
885
886 let positions: Vec<_> = vertices_3d.into();
887 let normals = vec![[0.0, 0.0, 1.0]; 3];
888
889 let uvs: Vec<_> = triangle3d::uv_coords(&Triangle3d::new(
890 vertices_3d[0],
891 vertices_3d[1],
892 vertices_3d[2],
893 ))
894 .into();
895
896 let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
897 let indices = if is_ccw {
898 Indices::U32(vec![0, 1, 2])
899 } else {
900 Indices::U32(vec![2, 1, 0])
901 };
902
903 Mesh::new(
904 PrimitiveTopology::TriangleList,
905 RenderAssetUsages::default(),
906 )
907 .with_inserted_indices(indices)
908 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
909 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
910 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
911 }
912}
913
914impl Extrudable for Triangle2dMeshBuilder {
915 fn perimeter(&self) -> Vec<PerimeterSegment> {
916 let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
917 if is_ccw {
918 vec![PerimeterSegment::Flat {
919 indices: vec![0, 1, 2, 0],
920 }]
921 } else {
922 vec![PerimeterSegment::Flat {
923 indices: vec![2, 1, 0, 2],
924 }]
925 }
926 }
927}
928
929impl From<Triangle2d> for Mesh {
930 fn from(triangle: Triangle2d) -> Self {
931 triangle.mesh().build()
932 }
933}
934
935#[derive(Clone, Copy, Debug, Reflect)]
937#[reflect(Default, Debug, Clone)]
938pub struct RectangleMeshBuilder {
939 half_size: Vec2,
940}
941
942impl Default for RectangleMeshBuilder {
943 fn default() -> Self {
945 Self {
946 half_size: Vec2::splat(0.5),
947 }
948 }
949}
950
951impl RectangleMeshBuilder {
952 pub const fn new(width: f32, height: f32) -> Self {
958 debug_assert!(width >= 0.0, "rectangle has a negative width");
959 debug_assert!(height >= 0.0, "rectangle has a negative height");
960
961 Self {
962 half_size: Vec2::new(width / 2.0, height / 2.0),
963 }
964 }
965}
966
967impl MeshBuilder for RectangleMeshBuilder {
968 fn build(&self) -> Mesh {
969 let [hw, hh] = [self.half_size.x, self.half_size.y];
970 let positions = vec![
971 [hw, hh, 0.0],
972 [-hw, hh, 0.0],
973 [-hw, -hh, 0.0],
974 [hw, -hh, 0.0],
975 ];
976 let normals = vec![[0.0, 0.0, 1.0]; 4];
977 let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
978 let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]);
979
980 Mesh::new(
981 PrimitiveTopology::TriangleList,
982 RenderAssetUsages::default(),
983 )
984 .with_inserted_indices(indices)
985 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
986 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
987 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
988 }
989}
990
991impl Extrudable for RectangleMeshBuilder {
992 fn perimeter(&self) -> Vec<PerimeterSegment> {
993 vec![PerimeterSegment::Flat {
994 indices: vec![0, 1, 2, 3, 0],
995 }]
996 }
997}
998
999impl Meshable for Rectangle {
1000 type Output = RectangleMeshBuilder;
1001
1002 fn mesh(&self) -> Self::Output {
1003 RectangleMeshBuilder {
1004 half_size: self.half_size,
1005 }
1006 }
1007}
1008
1009impl From<Rectangle> for Mesh {
1010 fn from(rectangle: Rectangle) -> Self {
1011 rectangle.mesh().build()
1012 }
1013}
1014
1015#[derive(Clone, Copy, Debug, Reflect)]
1017#[reflect(Default, Debug, Clone)]
1018pub struct Capsule2dMeshBuilder {
1019 pub capsule: Capsule2d,
1021 pub resolution: u32,
1026}
1027
1028impl Default for Capsule2dMeshBuilder {
1029 fn default() -> Self {
1030 Self {
1031 capsule: Capsule2d::default(),
1032 resolution: 16,
1033 }
1034 }
1035}
1036
1037impl Capsule2dMeshBuilder {
1038 #[inline]
1041 pub fn new(radius: f32, length: f32, resolution: u32) -> Self {
1042 Self {
1043 capsule: Capsule2d::new(radius, length),
1044 resolution,
1045 }
1046 }
1047
1048 #[inline]
1051 pub const fn resolution(mut self, resolution: u32) -> Self {
1052 self.resolution = resolution;
1053 self
1054 }
1055}
1056
1057impl MeshBuilder for Capsule2dMeshBuilder {
1058 fn build(&self) -> Mesh {
1059 let resolution = self.resolution;
1061 let vertex_count = 2 * resolution;
1062
1063 let mut indices = Vec::with_capacity((resolution as usize - 2) * 2 * 3 + 6);
1065 let mut positions = Vec::with_capacity(vertex_count as usize);
1066 let normals = vec![[0.0, 0.0, 1.0]; vertex_count as usize];
1067 let mut uvs = Vec::with_capacity(vertex_count as usize);
1068
1069 let radius = self.capsule.radius;
1070 let step = core::f32::consts::TAU / vertex_count as f32;
1071
1072 let start_angle = if vertex_count % 2 == 0 {
1075 step / 2.0
1076 } else {
1077 0.0
1078 };
1079
1080 let radius_frac = self.capsule.radius / (self.capsule.half_length + self.capsule.radius);
1083
1084 for i in 0..resolution {
1086 let theta = start_angle + i as f32 * step;
1088 let (sin, cos) = ops::sin_cos(theta);
1089 let (x, y) = (cos * radius, sin * radius + self.capsule.half_length);
1090
1091 positions.push([x, y, 0.0]);
1092 uvs.push([0.5 * (cos + 1.0), radius_frac * (1.0 - 0.5 * (sin + 1.0))]);
1093 }
1094
1095 for i in 1..resolution - 1 {
1097 indices.extend_from_slice(&[0, i, i + 1]);
1098 }
1099
1100 indices.extend_from_slice(&[0, resolution - 1, resolution]);
1102
1103 for i in resolution..vertex_count {
1105 let theta = start_angle + i as f32 * step;
1107 let (sin, cos) = ops::sin_cos(theta);
1108 let (x, y) = (cos * radius, sin * radius - self.capsule.half_length);
1109
1110 positions.push([x, y, 0.0]);
1111 uvs.push([0.5 * (cos + 1.0), 1.0 - radius_frac * 0.5 * (sin + 1.0)]);
1112 }
1113
1114 for i in 1..resolution - 1 {
1116 indices.extend_from_slice(&[resolution, resolution + i, resolution + i + 1]);
1117 }
1118
1119 indices.extend_from_slice(&[resolution, vertex_count - 1, 0]);
1121
1122 Mesh::new(
1123 PrimitiveTopology::TriangleList,
1124 RenderAssetUsages::default(),
1125 )
1126 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
1127 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
1128 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
1129 .with_inserted_indices(Indices::U32(indices))
1130 }
1131}
1132
1133impl Extrudable for Capsule2dMeshBuilder {
1134 fn perimeter(&self) -> Vec<PerimeterSegment> {
1135 let resolution = self.resolution;
1136 let top_semi_indices = (0..resolution).collect();
1137 let bottom_semi_indices = (resolution..(2 * resolution)).collect();
1138 vec![
1139 PerimeterSegment::Smooth {
1140 first_normal: Vec2::X,
1141 last_normal: Vec2::NEG_X,
1142 indices: top_semi_indices,
1143 }, PerimeterSegment::Flat {
1145 indices: vec![resolution - 1, resolution],
1146 }, PerimeterSegment::Smooth {
1148 first_normal: Vec2::NEG_X,
1149 last_normal: Vec2::X,
1150 indices: bottom_semi_indices,
1151 }, PerimeterSegment::Flat {
1153 indices: vec![2 * resolution - 1, 0],
1154 }, ]
1156 }
1157}
1158
1159impl Meshable for Capsule2d {
1160 type Output = Capsule2dMeshBuilder;
1161
1162 fn mesh(&self) -> Self::Output {
1163 Capsule2dMeshBuilder {
1164 capsule: *self,
1165 ..Default::default()
1166 }
1167 }
1168}
1169
1170impl From<Capsule2d> for Mesh {
1171 fn from(capsule: Capsule2d) -> Self {
1172 capsule.mesh().build()
1173 }
1174}
1175
1176#[cfg(test)]
1177mod tests {
1178 use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd};
1179 use bevy_platform::collections::HashSet;
1180
1181 use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};
1182
1183 fn count_distinct_positions(points: &[[f32; 3]]) -> usize {
1184 let mut map = <HashSet<_>>::default();
1185 for point in points {
1186 map.insert(point.map(FloatOrd));
1187 }
1188 map.len()
1189 }
1190
1191 #[test]
1192 fn test_annulus() {
1193 let mesh = Annulus::new(1.0, 1.2).mesh().resolution(16).build();
1194
1195 assert_eq!(
1196 32,
1197 count_distinct_positions(
1198 mesh.attribute(Mesh::ATTRIBUTE_POSITION)
1199 .unwrap()
1200 .as_float3()
1201 .unwrap()
1202 )
1203 );
1204 }
1205
1206 fn fix_floats<const N: usize>(points: &mut [[f32; N]]) {
1209 for point in points.iter_mut() {
1210 for coord in point.iter_mut() {
1211 let round = (*coord * 2.).round() / 2.;
1212 if (*coord - round).abs() < 0.00001 {
1213 *coord = round;
1214 }
1215 }
1216 }
1217 }
1218
1219 #[test]
1220 fn test_regular_polygon() {
1221 let mut mesh = Mesh::from(RegularPolygon::new(7.0, 4));
1222
1223 let Some(VertexAttributeValues::Float32x3(mut positions)) =
1224 mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)
1225 else {
1226 panic!("Expected positions f32x3");
1227 };
1228 let Some(VertexAttributeValues::Float32x2(mut uvs)) =
1229 mesh.remove_attribute(Mesh::ATTRIBUTE_UV_0)
1230 else {
1231 panic!("Expected uvs f32x2");
1232 };
1233 let Some(VertexAttributeValues::Float32x3(normals)) =
1234 mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)
1235 else {
1236 panic!("Expected normals f32x3");
1237 };
1238
1239 fix_floats(&mut positions);
1240 fix_floats(&mut uvs);
1241
1242 assert_eq!(
1243 [
1244 [0.0, 7.0, 0.0],
1245 [-7.0, 0.0, 0.0],
1246 [0.0, -7.0, 0.0],
1247 [7.0, 0.0, 0.0],
1248 ],
1249 &positions[..]
1250 );
1251
1252 assert_eq!([[0.5, 0.0], [0.0, 0.5], [0.5, 1.0], [1.0, 0.5],], &uvs[..]);
1254
1255 assert_eq!(&[[0.0, 0.0, 1.0]; 4], &normals[..]);
1256 }
1257}