bevy_mesh/primitives/dim3/
torus.rs

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