bevy_mesh/primitives/dim3/
torus.rs

1use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
2use bevy_asset::RenderAssetUsages;
3use bevy_math::{ops, primitives::Torus, Vec3};
4use bevy_reflect::prelude::*;
5use core::ops::RangeInclusive;
6
7/// A builder used for creating a [`Mesh`] with a [`Torus`] shape.
8#[derive(Clone, Debug, Reflect)]
9#[reflect(Default, Debug, Clone)]
10pub struct TorusMeshBuilder {
11    /// The [`Torus`] shape.
12    pub torus: Torus,
13    /// The number of vertices used for each circular segment
14    /// in the ring or tube of the torus.
15    ///
16    /// The default is `24`.
17    pub minor_resolution: usize,
18    /// The number of segments used for the main ring of the torus.
19    ///
20    /// A resolution of `4` would make the torus appear rectangular,
21    /// while a resolution of `32` resembles a circular ring.
22    ///
23    /// The default is `32`.
24    pub major_resolution: usize,
25    /// Optional angle range in radians, defaults to a full circle (0.0..=2 * PI)
26    pub angle_range: RangeInclusive<f32>,
27}
28
29impl Default for TorusMeshBuilder {
30    fn default() -> Self {
31        Self {
32            torus: Torus::default(),
33            minor_resolution: 24,
34            major_resolution: 32,
35            angle_range: (0.0..=2.0 * core::f32::consts::PI),
36        }
37    }
38}
39
40impl TorusMeshBuilder {
41    /// Creates a new [`TorusMeshBuilder`] from an inner and outer radius.
42    ///
43    /// The inner radius is the radius of the hole, and the outer radius
44    /// is the radius of the entire object.
45    #[inline]
46    pub fn new(inner_radius: f32, outer_radius: f32) -> Self {
47        Self {
48            torus: Torus::new(inner_radius, outer_radius),
49            ..Default::default()
50        }
51    }
52
53    /// Sets the number of vertices used for each circular segment
54    /// in the ring or tube of the torus.
55    #[inline]
56    pub const fn minor_resolution(mut self, resolution: usize) -> Self {
57        self.minor_resolution = resolution;
58        self
59    }
60
61    /// Sets the number of segments used for the main ring of the torus.
62    ///
63    /// A resolution of `4` would make the torus appear rectangular,
64    /// while a resolution of `32` resembles a circular ring.
65    #[inline]
66    pub const fn major_resolution(mut self, resolution: usize) -> Self {
67        self.major_resolution = resolution;
68        self
69    }
70
71    /// Sets a custom angle range in radians instead of a full circle
72    #[inline]
73    pub const fn angle_range(mut self, range: RangeInclusive<f32>) -> Self {
74        self.angle_range = range;
75        self
76    }
77}
78
79impl MeshBuilder for TorusMeshBuilder {
80    fn build(&self) -> Mesh {
81        // code adapted from http://apparat-engine.blogspot.com/2013/04/procedural-meshes-torus.html
82
83        let n_vertices = (self.major_resolution + 1) * (self.minor_resolution + 1);
84        let mut positions: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
85        let mut normals: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
86        let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(n_vertices);
87
88        let start_angle = self.angle_range.start();
89        let end_angle = self.angle_range.end();
90
91        let segment_stride = (end_angle - start_angle) / self.major_resolution as f32;
92        let side_stride = 2.0 * core::f32::consts::PI / self.minor_resolution as f32;
93
94        for segment in 0..=self.major_resolution {
95            let theta = start_angle + segment_stride * segment as f32;
96
97            for side in 0..=self.minor_resolution {
98                let phi = side_stride * side as f32;
99                let (sin_theta, cos_theta) = ops::sin_cos(theta);
100                let (sin_phi, cos_phi) = ops::sin_cos(phi);
101                let radius = self.torus.major_radius + self.torus.minor_radius * cos_phi;
102
103                let position = Vec3::new(
104                    cos_theta * radius,
105                    self.torus.minor_radius * sin_phi,
106                    sin_theta * radius,
107                );
108
109                let center = Vec3::new(
110                    self.torus.major_radius * cos_theta,
111                    0.,
112                    self.torus.major_radius * sin_theta,
113                );
114                let normal = (position - center).normalize();
115
116                positions.push(position.into());
117                normals.push(normal.into());
118                uvs.push([
119                    segment as f32 / self.major_resolution as f32,
120                    side as f32 / self.minor_resolution as f32,
121                ]);
122            }
123        }
124
125        let n_faces = (self.major_resolution) * (self.minor_resolution);
126        let n_triangles = n_faces * 2;
127        let n_indices = n_triangles * 3;
128
129        let mut indices: Vec<u32> = Vec::with_capacity(n_indices);
130
131        let n_vertices_per_row = self.minor_resolution + 1;
132        for segment in 0..self.major_resolution {
133            for side in 0..self.minor_resolution {
134                let lt = side + segment * n_vertices_per_row;
135                let rt = (side + 1) + segment * n_vertices_per_row;
136
137                let lb = side + (segment + 1) * n_vertices_per_row;
138                let rb = (side + 1) + (segment + 1) * n_vertices_per_row;
139
140                indices.push(lt as u32);
141                indices.push(rt as u32);
142                indices.push(lb as u32);
143
144                indices.push(rt as u32);
145                indices.push(rb as u32);
146                indices.push(lb as u32);
147            }
148        }
149
150        Mesh::new(
151            PrimitiveTopology::TriangleList,
152            RenderAssetUsages::default(),
153        )
154        .with_inserted_indices(Indices::U32(indices))
155        .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
156        .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
157        .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
158    }
159}
160
161impl Meshable for Torus {
162    type Output = TorusMeshBuilder;
163
164    fn mesh(&self) -> Self::Output {
165        TorusMeshBuilder {
166            torus: *self,
167            ..Default::default()
168        }
169    }
170}
171
172impl From<Torus> for Mesh {
173    fn from(torus: Torus) -> Self {
174        torus.mesh().build()
175    }
176}