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 wgpu::PrimitiveTopology;
16
17#[derive(Clone, Copy, Debug)]
19pub struct CircleMeshBuilder {
20 pub circle: Circle,
22 #[doc(alias = "vertices")]
25 pub resolution: u32,
26}
27
28impl Default for CircleMeshBuilder {
29 fn default() -> Self {
30 Self {
31 circle: Circle::default(),
32 resolution: 32,
33 }
34 }
35}
36
37impl CircleMeshBuilder {
38 #[inline]
40 pub const fn new(radius: f32, resolution: u32) -> Self {
41 Self {
42 circle: Circle { radius },
43 resolution,
44 }
45 }
46
47 #[inline]
49 #[doc(alias = "vertices")]
50 pub const fn resolution(mut self, resolution: u32) -> Self {
51 self.resolution = resolution;
52 self
53 }
54}
55
56impl MeshBuilder for CircleMeshBuilder {
57 fn build(&self) -> Mesh {
58 Ellipse::new(self.circle.radius, self.circle.radius)
59 .mesh()
60 .resolution(self.resolution)
61 .build()
62 }
63}
64
65impl Extrudable for CircleMeshBuilder {
66 fn perimeter(&self) -> Vec<PerimeterSegment> {
67 vec![PerimeterSegment::Smooth {
68 first_normal: Vec2::Y,
69 last_normal: Vec2::Y,
70 indices: (0..self.resolution).chain([0]).collect(),
71 }]
72 }
73}
74
75impl Meshable for Circle {
76 type Output = CircleMeshBuilder;
77
78 fn mesh(&self) -> Self::Output {
79 CircleMeshBuilder {
80 circle: *self,
81 ..Default::default()
82 }
83 }
84}
85
86impl From<Circle> for Mesh {
87 fn from(circle: Circle) -> Self {
88 circle.mesh().build()
89 }
90}
91
92#[derive(Copy, Clone, Debug, PartialEq)]
102#[non_exhaustive]
103pub enum CircularMeshUvMode {
104 Mask {
107 angle: f32,
109 },
110}
111
112impl Default for CircularMeshUvMode {
113 fn default() -> Self {
114 CircularMeshUvMode::Mask { angle: 0.0 }
115 }
116}
117
118#[derive(Clone, Debug)]
123pub struct CircularSectorMeshBuilder {
124 pub sector: CircularSector,
126 #[doc(alias = "vertices")]
129 pub resolution: u32,
130 pub uv_mode: CircularMeshUvMode,
132}
133
134impl Default for CircularSectorMeshBuilder {
135 fn default() -> Self {
136 Self {
137 sector: CircularSector::default(),
138 resolution: 32,
139 uv_mode: CircularMeshUvMode::default(),
140 }
141 }
142}
143
144impl CircularSectorMeshBuilder {
145 #[inline]
147 pub fn new(sector: CircularSector) -> Self {
148 Self {
149 sector,
150 ..Self::default()
151 }
152 }
153
154 #[inline]
156 #[doc(alias = "vertices")]
157 pub const fn resolution(mut self, resolution: u32) -> Self {
158 self.resolution = resolution;
159 self
160 }
161
162 #[inline]
164 pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {
165 self.uv_mode = uv_mode;
166 self
167 }
168}
169
170impl MeshBuilder for CircularSectorMeshBuilder {
171 fn build(&self) -> Mesh {
172 let resolution = self.resolution as usize;
173 let mut indices = Vec::with_capacity((resolution - 1) * 3);
174 let mut positions = Vec::with_capacity(resolution + 1);
175 let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];
176 let mut uvs = Vec::with_capacity(resolution + 1);
177
178 let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;
179
180 positions.push([0.0; 3]);
182 uvs.push([0.5; 2]);
183
184 let first_angle = FRAC_PI_2 - self.sector.half_angle();
185 let last_angle = FRAC_PI_2 + self.sector.half_angle();
186 let last_i = (self.resolution - 1) as f32;
187 for i in 0..self.resolution {
188 let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);
189
190 let vertex = self.sector.radius() * Vec2::from_angle(angle);
192 let uv =
195 Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));
196
197 positions.push([vertex.x, vertex.y, 0.0]);
198 uvs.push([uv.x, uv.y]);
199 }
200
201 for i in 1..self.resolution {
202 indices.extend_from_slice(&[0, i, i + 1]);
204 }
205
206 Mesh::new(
207 PrimitiveTopology::TriangleList,
208 RenderAssetUsages::default(),
209 )
210 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
211 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
212 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
213 .with_inserted_indices(Indices::U32(indices))
214 }
215}
216
217impl Extrudable for CircularSectorMeshBuilder {
218 fn perimeter(&self) -> Vec<PerimeterSegment> {
219 let (sin, cos) = ops::sin_cos(self.sector.arc.half_angle);
220 let first_normal = Vec2::new(sin, cos);
221 let last_normal = Vec2::new(-sin, cos);
222 vec![
223 PerimeterSegment::Flat {
224 indices: vec![self.resolution, 0, 1],
225 },
226 PerimeterSegment::Smooth {
227 first_normal,
228 last_normal,
229 indices: (1..=self.resolution).collect(),
230 },
231 ]
232 }
233}
234
235impl Meshable for CircularSector {
236 type Output = CircularSectorMeshBuilder;
237
238 fn mesh(&self) -> Self::Output {
239 CircularSectorMeshBuilder {
240 sector: *self,
241 ..Default::default()
242 }
243 }
244}
245
246impl From<CircularSector> for Mesh {
247 fn from(sector: CircularSector) -> Self {
251 sector.mesh().build()
252 }
253}
254
255#[derive(Clone, Copy, Debug)]
260pub struct CircularSegmentMeshBuilder {
261 pub segment: CircularSegment,
263 #[doc(alias = "vertices")]
266 pub resolution: u32,
267 pub uv_mode: CircularMeshUvMode,
269}
270
271impl Default for CircularSegmentMeshBuilder {
272 fn default() -> Self {
273 Self {
274 segment: CircularSegment::default(),
275 resolution: 32,
276 uv_mode: CircularMeshUvMode::default(),
277 }
278 }
279}
280
281impl CircularSegmentMeshBuilder {
282 #[inline]
284 pub fn new(segment: CircularSegment) -> Self {
285 Self {
286 segment,
287 ..Self::default()
288 }
289 }
290
291 #[inline]
293 #[doc(alias = "vertices")]
294 pub const fn resolution(mut self, resolution: u32) -> Self {
295 self.resolution = resolution;
296 self
297 }
298
299 #[inline]
301 pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {
302 self.uv_mode = uv_mode;
303 self
304 }
305}
306
307impl MeshBuilder for CircularSegmentMeshBuilder {
308 fn build(&self) -> Mesh {
309 let resolution = self.resolution as usize;
310 let mut indices = Vec::with_capacity((resolution - 1) * 3);
311 let mut positions = Vec::with_capacity(resolution + 1);
312 let normals = vec![[0.0, 0.0, 1.0]; resolution + 1];
313 let mut uvs = Vec::with_capacity(resolution + 1);
314
315 let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;
316
317 let midpoint_vertex = self.segment.chord_midpoint();
319 positions.push([midpoint_vertex.x, midpoint_vertex.y, 0.0]);
320 let midpoint_uv = Vec2::from_angle(-uv_angle - FRAC_PI_2).mul_add(
325 Vec2::splat(0.5 * (self.segment.apothem() / self.segment.radius())),
326 Vec2::splat(0.5),
327 );
328 uvs.push([midpoint_uv.x, midpoint_uv.y]);
329
330 let first_angle = FRAC_PI_2 - self.segment.half_angle();
331 let last_angle = FRAC_PI_2 + self.segment.half_angle();
332 let last_i = (self.resolution - 1) as f32;
333 for i in 0..self.resolution {
334 let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);
335
336 let vertex = self.segment.radius() * Vec2::from_angle(angle);
338 let uv =
341 Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));
342
343 positions.push([vertex.x, vertex.y, 0.0]);
344 uvs.push([uv.x, uv.y]);
345 }
346
347 for i in 1..self.resolution {
348 indices.extend_from_slice(&[0, i, i + 1]);
350 }
351
352 Mesh::new(
353 PrimitiveTopology::TriangleList,
354 RenderAssetUsages::default(),
355 )
356 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
357 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
358 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
359 .with_inserted_indices(Indices::U32(indices))
360 }
361}
362
363impl Extrudable for CircularSegmentMeshBuilder {
364 fn perimeter(&self) -> Vec<PerimeterSegment> {
365 let (sin, cos) = ops::sin_cos(self.segment.arc.half_angle);
366 let first_normal = Vec2::new(sin, cos);
367 let last_normal = Vec2::new(-sin, cos);
368 vec![
369 PerimeterSegment::Flat {
370 indices: vec![self.resolution, 0, 1],
371 },
372 PerimeterSegment::Smooth {
373 first_normal,
374 last_normal,
375 indices: (1..=self.resolution).collect(),
376 },
377 ]
378 }
379}
380
381impl Meshable for CircularSegment {
382 type Output = CircularSegmentMeshBuilder;
383
384 fn mesh(&self) -> Self::Output {
385 CircularSegmentMeshBuilder {
386 segment: *self,
387 ..Default::default()
388 }
389 }
390}
391
392impl From<CircularSegment> for Mesh {
393 fn from(segment: CircularSegment) -> Self {
397 segment.mesh().build()
398 }
399}
400
401pub struct ConvexPolygonMeshBuilder<const N: usize> {
403 pub vertices: [Vec2; N],
404}
405
406impl<const N: usize> Meshable for ConvexPolygon<N> {
407 type Output = ConvexPolygonMeshBuilder<N>;
408
409 fn mesh(&self) -> Self::Output {
410 Self::Output {
411 vertices: *self.vertices(),
412 }
413 }
414}
415
416impl<const N: usize> MeshBuilder for ConvexPolygonMeshBuilder<N> {
417 fn build(&self) -> Mesh {
418 let mut indices = Vec::with_capacity((N - 2) * 3);
419 let mut positions = Vec::with_capacity(N);
420
421 for vertex in self.vertices {
422 positions.push([vertex.x, vertex.y, 0.0]);
423 }
424 for i in 2..N as u32 {
425 indices.extend_from_slice(&[0, i - 1, i]);
426 }
427 Mesh::new(
428 PrimitiveTopology::TriangleList,
429 RenderAssetUsages::default(),
430 )
431 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
432 .with_inserted_indices(Indices::U32(indices))
433 }
434}
435
436impl<const N: usize> Extrudable for ConvexPolygonMeshBuilder<N> {
437 fn perimeter(&self) -> Vec<PerimeterSegment> {
438 vec![PerimeterSegment::Flat {
439 indices: (0..N as u32).chain([0]).collect(),
440 }]
441 }
442}
443
444impl<const N: usize> From<ConvexPolygon<N>> for Mesh {
445 fn from(polygon: ConvexPolygon<N>) -> Self {
446 polygon.mesh().build()
447 }
448}
449
450pub struct RegularPolygonMeshBuilder {
452 circumradius: f32,
453 sides: u32,
454}
455impl Meshable for RegularPolygon {
456 type Output = RegularPolygonMeshBuilder;
457
458 fn mesh(&self) -> Self::Output {
459 Self::Output {
460 circumradius: self.circumcircle.radius,
461 sides: self.sides,
462 }
463 }
464}
465
466impl MeshBuilder for RegularPolygonMeshBuilder {
467 fn build(&self) -> Mesh {
468 Ellipse::new(self.circumradius, self.circumradius)
470 .mesh()
471 .resolution(self.sides)
472 .build()
473 }
474}
475
476impl Extrudable for RegularPolygonMeshBuilder {
477 fn perimeter(&self) -> Vec<PerimeterSegment> {
478 vec![PerimeterSegment::Flat {
479 indices: (0..self.sides).chain([0]).collect(),
480 }]
481 }
482}
483
484impl From<RegularPolygon> for Mesh {
485 fn from(polygon: RegularPolygon) -> Self {
486 polygon.mesh().build()
487 }
488}
489
490#[derive(Clone, Copy, Debug)]
492pub struct EllipseMeshBuilder {
493 pub ellipse: Ellipse,
495 #[doc(alias = "vertices")]
498 pub resolution: u32,
499}
500
501impl Default for EllipseMeshBuilder {
502 fn default() -> Self {
503 Self {
504 ellipse: Ellipse::default(),
505 resolution: 32,
506 }
507 }
508}
509
510impl EllipseMeshBuilder {
511 #[inline]
513 pub const fn new(half_width: f32, half_height: f32, resolution: u32) -> Self {
514 Self {
515 ellipse: Ellipse::new(half_width, half_height),
516 resolution,
517 }
518 }
519
520 #[inline]
522 #[doc(alias = "vertices")]
523 pub const fn resolution(mut self, resolution: u32) -> Self {
524 self.resolution = resolution;
525 self
526 }
527}
528
529impl MeshBuilder for EllipseMeshBuilder {
530 fn build(&self) -> Mesh {
531 let resolution = self.resolution as usize;
532 let mut indices = Vec::with_capacity((resolution - 2) * 3);
533 let mut positions = Vec::with_capacity(resolution);
534 let normals = vec![[0.0, 0.0, 1.0]; resolution];
535 let mut uvs = Vec::with_capacity(resolution);
536
537 let start_angle = FRAC_PI_2;
539 let step = core::f32::consts::TAU / self.resolution as f32;
540
541 for i in 0..self.resolution {
542 let theta = start_angle + i as f32 * step;
544 let (sin, cos) = ops::sin_cos(theta);
545 let x = cos * self.ellipse.half_size.x;
546 let y = sin * self.ellipse.half_size.y;
547
548 positions.push([x, y, 0.0]);
549 uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
550 }
551
552 for i in 1..(self.resolution - 1) {
553 indices.extend_from_slice(&[0, i, i + 1]);
554 }
555
556 Mesh::new(
557 PrimitiveTopology::TriangleList,
558 RenderAssetUsages::default(),
559 )
560 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
561 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
562 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
563 .with_inserted_indices(Indices::U32(indices))
564 }
565}
566
567impl Extrudable for EllipseMeshBuilder {
568 fn perimeter(&self) -> Vec<PerimeterSegment> {
569 vec![PerimeterSegment::Smooth {
570 first_normal: Vec2::Y,
571 last_normal: Vec2::Y,
572 indices: (0..self.resolution).chain([0]).collect(),
573 }]
574 }
575}
576
577impl Meshable for Ellipse {
578 type Output = EllipseMeshBuilder;
579
580 fn mesh(&self) -> Self::Output {
581 EllipseMeshBuilder {
582 ellipse: *self,
583 ..Default::default()
584 }
585 }
586}
587
588impl From<Ellipse> for Mesh {
589 fn from(ellipse: Ellipse) -> Self {
590 ellipse.mesh().build()
591 }
592}
593
594pub struct AnnulusMeshBuilder {
596 pub annulus: Annulus,
598
599 pub resolution: u32,
602}
603
604impl Default for AnnulusMeshBuilder {
605 fn default() -> Self {
606 Self {
607 annulus: Annulus::default(),
608 resolution: 32,
609 }
610 }
611}
612
613impl AnnulusMeshBuilder {
614 #[inline]
616 pub fn new(inner_radius: f32, outer_radius: f32, resolution: u32) -> Self {
617 Self {
618 annulus: Annulus::new(inner_radius, outer_radius),
619 resolution,
620 }
621 }
622
623 #[inline]
625 pub fn resolution(mut self, resolution: u32) -> Self {
626 self.resolution = resolution;
627 self
628 }
629}
630
631impl MeshBuilder for AnnulusMeshBuilder {
632 fn build(&self) -> Mesh {
633 let inner_radius = self.annulus.inner_circle.radius;
634 let outer_radius = self.annulus.outer_circle.radius;
635
636 let num_vertices = (self.resolution as usize + 1) * 2;
637 let mut indices = Vec::with_capacity(self.resolution as usize * 6);
638 let mut positions = Vec::with_capacity(num_vertices);
639 let mut uvs = Vec::with_capacity(num_vertices);
640 let normals = vec![[0.0, 0.0, 1.0]; num_vertices];
641
642 let start_angle = FRAC_PI_2;
647 let step = core::f32::consts::TAU / self.resolution as f32;
648 for i in 0..=self.resolution {
649 let theta = start_angle + (i % self.resolution) as f32 * step;
650 let (sin, cos) = ops::sin_cos(theta);
651 let inner_pos = [cos * inner_radius, sin * inner_radius, 0.];
652 let outer_pos = [cos * outer_radius, sin * outer_radius, 0.];
653 positions.push(inner_pos);
654 positions.push(outer_pos);
655
656 let inner_uv = [0., i as f32 / self.resolution as f32];
661 let outer_uv = [1., i as f32 / self.resolution as f32];
662 uvs.push(inner_uv);
663 uvs.push(outer_uv);
664 }
665
666 for i in 0..self.resolution {
671 let inner_vertex = 2 * i;
672 let outer_vertex = 2 * i + 1;
673 let next_inner = inner_vertex + 2;
674 let next_outer = outer_vertex + 2;
675 indices.extend_from_slice(&[inner_vertex, outer_vertex, next_outer]);
676 indices.extend_from_slice(&[next_outer, next_inner, inner_vertex]);
677 }
678
679 Mesh::new(
680 PrimitiveTopology::TriangleList,
681 RenderAssetUsages::default(),
682 )
683 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
684 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
685 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
686 .with_inserted_indices(Indices::U32(indices))
687 }
688}
689
690impl Extrudable for AnnulusMeshBuilder {
691 fn perimeter(&self) -> Vec<PerimeterSegment> {
692 let vert_count = 2 * self.resolution;
693 vec![
694 PerimeterSegment::Smooth {
695 first_normal: Vec2::NEG_Y,
696 last_normal: Vec2::NEG_Y,
697 indices: (0..vert_count).step_by(2).chain([0]).rev().collect(), },
699 PerimeterSegment::Smooth {
700 first_normal: Vec2::Y,
701 last_normal: Vec2::Y,
702 indices: (1..vert_count).step_by(2).chain([1]).collect(), },
704 ]
705 }
706}
707
708impl Meshable for Annulus {
709 type Output = AnnulusMeshBuilder;
710
711 fn mesh(&self) -> Self::Output {
712 AnnulusMeshBuilder {
713 annulus: *self,
714 ..Default::default()
715 }
716 }
717}
718
719impl From<Annulus> for Mesh {
720 fn from(annulus: Annulus) -> Self {
721 annulus.mesh().build()
722 }
723}
724
725pub struct RhombusMeshBuilder {
726 half_diagonals: Vec2,
727}
728
729impl MeshBuilder for RhombusMeshBuilder {
730 fn build(&self) -> Mesh {
731 let [hhd, vhd] = [self.half_diagonals.x, self.half_diagonals.y];
732 let positions = vec![
733 [hhd, 0.0, 0.0],
734 [-hhd, 0.0, 0.0],
735 [0.0, vhd, 0.0],
736 [0.0, -vhd, 0.0],
737 ];
738 let normals = vec![[0.0, 0.0, 1.0]; 4];
739 let uvs = vec![[1.0, 0.5], [0.0, 0.5], [0.5, 0.0], [0.5, 1.0]];
740 let indices = Indices::U32(vec![1, 0, 2, 1, 3, 0]);
741
742 Mesh::new(
743 PrimitiveTopology::TriangleList,
744 RenderAssetUsages::default(),
745 )
746 .with_inserted_indices(indices)
747 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
748 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
749 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
750 }
751}
752
753impl Extrudable for RhombusMeshBuilder {
754 fn perimeter(&self) -> Vec<PerimeterSegment> {
755 vec![PerimeterSegment::Flat {
756 indices: vec![0, 2, 1, 3, 0],
757 }]
758 }
759}
760
761impl Meshable for Rhombus {
762 type Output = RhombusMeshBuilder;
763
764 fn mesh(&self) -> Self::Output {
765 Self::Output {
766 half_diagonals: self.half_diagonals,
767 }
768 }
769}
770
771impl From<Rhombus> for Mesh {
772 fn from(rhombus: Rhombus) -> Self {
773 rhombus.mesh().build()
774 }
775}
776
777pub struct Triangle2dMeshBuilder {
779 triangle: Triangle2d,
780}
781impl Meshable for Triangle2d {
782 type Output = Triangle2dMeshBuilder;
783
784 fn mesh(&self) -> Self::Output {
785 Self::Output { triangle: *self }
786 }
787}
788impl MeshBuilder for Triangle2dMeshBuilder {
789 fn build(&self) -> Mesh {
790 let vertices_3d = self.triangle.vertices.map(|v| v.extend(0.));
791
792 let positions: Vec<_> = vertices_3d.into();
793 let normals = vec![[0.0, 0.0, 1.0]; 3];
794
795 let uvs: Vec<_> = triangle3d::uv_coords(&Triangle3d::new(
796 vertices_3d[0],
797 vertices_3d[1],
798 vertices_3d[2],
799 ))
800 .into();
801
802 let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
803 let indices = if is_ccw {
804 Indices::U32(vec![0, 1, 2])
805 } else {
806 Indices::U32(vec![2, 1, 0])
807 };
808
809 Mesh::new(
810 PrimitiveTopology::TriangleList,
811 RenderAssetUsages::default(),
812 )
813 .with_inserted_indices(indices)
814 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
815 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
816 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
817 }
818}
819
820impl Extrudable for Triangle2dMeshBuilder {
821 fn perimeter(&self) -> Vec<PerimeterSegment> {
822 let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
823 if is_ccw {
824 vec![PerimeterSegment::Flat {
825 indices: vec![0, 1, 2, 0],
826 }]
827 } else {
828 vec![PerimeterSegment::Flat {
829 indices: vec![2, 1, 0, 2],
830 }]
831 }
832 }
833}
834
835impl From<Triangle2d> for Mesh {
836 fn from(triangle: Triangle2d) -> Self {
837 triangle.mesh().build()
838 }
839}
840
841pub struct RectangleMeshBuilder {
843 half_size: Vec2,
844}
845
846impl MeshBuilder for RectangleMeshBuilder {
847 fn build(&self) -> Mesh {
848 let [hw, hh] = [self.half_size.x, self.half_size.y];
849 let positions = vec![
850 [hw, hh, 0.0],
851 [-hw, hh, 0.0],
852 [-hw, -hh, 0.0],
853 [hw, -hh, 0.0],
854 ];
855 let normals = vec![[0.0, 0.0, 1.0]; 4];
856 let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
857 let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]);
858
859 Mesh::new(
860 PrimitiveTopology::TriangleList,
861 RenderAssetUsages::default(),
862 )
863 .with_inserted_indices(indices)
864 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
865 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
866 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
867 }
868}
869
870impl Extrudable for RectangleMeshBuilder {
871 fn perimeter(&self) -> Vec<PerimeterSegment> {
872 vec![PerimeterSegment::Flat {
873 indices: vec![0, 1, 2, 3, 0],
874 }]
875 }
876}
877
878impl Meshable for Rectangle {
879 type Output = RectangleMeshBuilder;
880
881 fn mesh(&self) -> Self::Output {
882 RectangleMeshBuilder {
883 half_size: self.half_size,
884 }
885 }
886}
887
888impl From<Rectangle> for Mesh {
889 fn from(rectangle: Rectangle) -> Self {
890 rectangle.mesh().build()
891 }
892}
893
894#[derive(Clone, Copy, Debug)]
896pub struct Capsule2dMeshBuilder {
897 pub capsule: Capsule2d,
899 pub resolution: u32,
904}
905
906impl Default for Capsule2dMeshBuilder {
907 fn default() -> Self {
908 Self {
909 capsule: Capsule2d::default(),
910 resolution: 16,
911 }
912 }
913}
914
915impl Capsule2dMeshBuilder {
916 #[inline]
919 pub fn new(radius: f32, length: f32, resolution: u32) -> Self {
920 Self {
921 capsule: Capsule2d::new(radius, length),
922 resolution,
923 }
924 }
925
926 #[inline]
929 pub const fn resolution(mut self, resolution: u32) -> Self {
930 self.resolution = resolution;
931 self
932 }
933}
934
935impl MeshBuilder for Capsule2dMeshBuilder {
936 fn build(&self) -> Mesh {
937 let resolution = self.resolution;
939 let vertex_count = 2 * resolution;
940
941 let mut indices = Vec::with_capacity((resolution as usize - 2) * 2 * 3 + 6);
943 let mut positions = Vec::with_capacity(vertex_count as usize);
944 let normals = vec![[0.0, 0.0, 1.0]; vertex_count as usize];
945 let mut uvs = Vec::with_capacity(vertex_count as usize);
946
947 let radius = self.capsule.radius;
948 let step = core::f32::consts::TAU / vertex_count as f32;
949
950 let start_angle = if vertex_count % 2 == 0 {
953 step / 2.0
954 } else {
955 0.0
956 };
957
958 let radius_frac = self.capsule.radius / (self.capsule.half_length + self.capsule.radius);
961
962 for i in 0..resolution {
964 let theta = start_angle + i as f32 * step;
966 let (sin, cos) = ops::sin_cos(theta);
967 let (x, y) = (cos * radius, sin * radius + self.capsule.half_length);
968
969 positions.push([x, y, 0.0]);
970 uvs.push([0.5 * (cos + 1.0), radius_frac * (1.0 - 0.5 * (sin + 1.0))]);
971 }
972
973 for i in 1..resolution - 1 {
975 indices.extend_from_slice(&[0, i, i + 1]);
976 }
977
978 indices.extend_from_slice(&[0, resolution - 1, resolution]);
980
981 for i in resolution..vertex_count {
983 let theta = start_angle + i as f32 * step;
985 let (sin, cos) = ops::sin_cos(theta);
986 let (x, y) = (cos * radius, sin * radius - self.capsule.half_length);
987
988 positions.push([x, y, 0.0]);
989 uvs.push([0.5 * (cos + 1.0), 1.0 - radius_frac * 0.5 * (sin + 1.0)]);
990 }
991
992 for i in 1..resolution - 1 {
994 indices.extend_from_slice(&[resolution, resolution + i, resolution + i + 1]);
995 }
996
997 indices.extend_from_slice(&[resolution, vertex_count - 1, 0]);
999
1000 Mesh::new(
1001 PrimitiveTopology::TriangleList,
1002 RenderAssetUsages::default(),
1003 )
1004 .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
1005 .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
1006 .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
1007 .with_inserted_indices(Indices::U32(indices))
1008 }
1009}
1010
1011impl Extrudable for Capsule2dMeshBuilder {
1012 fn perimeter(&self) -> Vec<PerimeterSegment> {
1013 let resolution = self.resolution;
1014 let top_semi_indices = (0..resolution).collect();
1015 let bottom_semi_indices = (resolution..(2 * resolution)).collect();
1016 vec![
1017 PerimeterSegment::Smooth {
1018 first_normal: Vec2::X,
1019 last_normal: Vec2::NEG_X,
1020 indices: top_semi_indices,
1021 }, PerimeterSegment::Flat {
1023 indices: vec![resolution - 1, resolution],
1024 }, PerimeterSegment::Smooth {
1026 first_normal: Vec2::NEG_X,
1027 last_normal: Vec2::X,
1028 indices: bottom_semi_indices,
1029 }, PerimeterSegment::Flat {
1031 indices: vec![2 * resolution - 1, 0],
1032 }, ]
1034 }
1035}
1036
1037impl Meshable for Capsule2d {
1038 type Output = Capsule2dMeshBuilder;
1039
1040 fn mesh(&self) -> Self::Output {
1041 Capsule2dMeshBuilder {
1042 capsule: *self,
1043 ..Default::default()
1044 }
1045 }
1046}
1047
1048impl From<Capsule2d> for Mesh {
1049 fn from(capsule: Capsule2d) -> Self {
1050 capsule.mesh().build()
1051 }
1052}
1053
1054#[cfg(test)]
1055mod tests {
1056 use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd};
1057 use bevy_utils::HashSet;
1058
1059 use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};
1060
1061 fn count_distinct_positions(points: &[[f32; 3]]) -> usize {
1062 let mut map = HashSet::new();
1063 for point in points {
1064 map.insert(point.map(FloatOrd));
1065 }
1066 map.len()
1067 }
1068
1069 #[test]
1070 fn test_annulus() {
1071 let mesh = Annulus::new(1.0, 1.2).mesh().resolution(16).build();
1072
1073 assert_eq!(
1074 32,
1075 count_distinct_positions(
1076 mesh.attribute(Mesh::ATTRIBUTE_POSITION)
1077 .unwrap()
1078 .as_float3()
1079 .unwrap()
1080 )
1081 );
1082 }
1083
1084 fn fix_floats<const N: usize>(points: &mut [[f32; N]]) {
1087 for point in points.iter_mut() {
1088 for coord in point.iter_mut() {
1089 let round = (*coord * 2.).round() / 2.;
1090 if (*coord - round).abs() < 0.00001 {
1091 *coord = round;
1092 }
1093 }
1094 }
1095 }
1096
1097 #[test]
1098 fn test_regular_polygon() {
1099 let mut mesh = Mesh::from(RegularPolygon::new(7.0, 4));
1100
1101 let Some(VertexAttributeValues::Float32x3(mut positions)) =
1102 mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)
1103 else {
1104 panic!("Expected positions f32x3");
1105 };
1106 let Some(VertexAttributeValues::Float32x2(mut uvs)) =
1107 mesh.remove_attribute(Mesh::ATTRIBUTE_UV_0)
1108 else {
1109 panic!("Expected uvs f32x2");
1110 };
1111 let Some(VertexAttributeValues::Float32x3(normals)) =
1112 mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)
1113 else {
1114 panic!("Expected normals f32x3");
1115 };
1116
1117 fix_floats(&mut positions);
1118 fix_floats(&mut uvs);
1119
1120 assert_eq!(
1121 [
1122 [0.0, 7.0, 0.0],
1123 [-7.0, 0.0, 0.0],
1124 [0.0, -7.0, 0.0],
1125 [7.0, 0.0, 0.0],
1126 ],
1127 &positions[..]
1128 );
1129
1130 assert_eq!([[0.5, 0.0], [0.0, 0.5], [0.5, 1.0], [1.0, 0.5],], &uvs[..]);
1132
1133 assert_eq!(&[[0.0, 0.0, 1.0]; 4], &normals[..]);
1134 }
1135}