bevy_render/mesh/primitives/dim3/
torus.rs

1use bevy_math::{primitives::Torus, Vec3};
2use std::ops::RangeInclusive;
3use wgpu::PrimitiveTopology;
4
5use crate::{
6    mesh::{Indices, Mesh, MeshBuilder, Meshable},
7    render_asset::RenderAssetUsages,
8};
9
10/// A builder used for creating a [`Mesh`] with a [`Torus`] shape.
11#[derive(Clone, Debug)]
12pub struct TorusMeshBuilder {
13    /// The [`Torus`] shape.
14    pub torus: Torus,
15    /// The number of vertices used for each circular segment
16    /// in the ring or tube of the torus.
17    ///
18    /// The default is `24`.
19    pub minor_resolution: usize,
20    /// The number of segments used for the main ring of the torus.
21    ///
22    /// A resolution of `4` would make the torus appear rectangular,
23    /// while a resolution of `32` resembles a circular ring.
24    ///
25    /// The default is `32`.
26    pub major_resolution: usize,
27    /// Optional angle range in radians, defaults to a full circle (0.0..=2 * PI)
28    pub angle_range: RangeInclusive<f32>,
29}
30
31impl Default for TorusMeshBuilder {
32    fn default() -> Self {
33        Self {
34            torus: Torus::default(),
35            minor_resolution: 24,
36            major_resolution: 32,
37            angle_range: (0.0..=2.0 * std::f32::consts::PI),
38        }
39    }
40}
41
42impl TorusMeshBuilder {
43    /// Creates a new [`TorusMeshBuilder`] from an inner and outer radius.
44    ///
45    /// The inner radius is the radius of the hole, and the outer radius
46    /// is the radius of the entire object.
47    #[inline]
48    pub fn new(inner_radius: f32, outer_radius: f32) -> Self {
49        Self {
50            torus: Torus::new(inner_radius, outer_radius),
51            ..Default::default()
52        }
53    }
54
55    /// Sets the number of vertices used for each circular segment
56    /// in the ring or tube of the torus.
57    #[inline]
58    pub const fn minor_resolution(mut self, resolution: usize) -> Self {
59        self.minor_resolution = resolution;
60        self
61    }
62
63    /// Sets the number of segments used for the main ring of the torus.
64    ///
65    /// A resolution of `4` would make the torus appear rectangular,
66    /// while a resolution of `32` resembles a circular ring.
67    #[inline]
68    pub const fn major_resolution(mut self, resolution: usize) -> Self {
69        self.major_resolution = resolution;
70        self
71    }
72
73    /// Sets a custom angle range in radians instead of a full circle
74    #[inline]
75    pub const fn angle_range(mut self, range: RangeInclusive<f32>) -> Self {
76        self.angle_range = range;
77        self
78    }
79}
80
81impl MeshBuilder for TorusMeshBuilder {
82    fn build(&self) -> Mesh {
83        // code adapted from http://apparat-engine.blogspot.com/2013/04/procedural-meshes-torus.html
84
85        let n_vertices = (self.major_resolution + 1) * (self.minor_resolution + 1);
86        let mut positions: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
87        let mut normals: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
88        let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(n_vertices);
89
90        let start_angle = self.angle_range.start();
91        let end_angle = self.angle_range.end();
92
93        let segment_stride = (end_angle - start_angle) / self.major_resolution as f32;
94        let side_stride = 2.0 * std::f32::consts::PI / self.minor_resolution as f32;
95
96        for segment in 0..=self.major_resolution {
97            let theta = start_angle + segment_stride * segment as f32;
98
99            for side in 0..=self.minor_resolution {
100                let phi = side_stride * side as f32;
101
102                let position = Vec3::new(
103                    theta.cos() * (self.torus.major_radius + self.torus.minor_radius * phi.cos()),
104                    self.torus.minor_radius * phi.sin(),
105                    theta.sin() * (self.torus.major_radius + self.torus.minor_radius * phi.cos()),
106                );
107
108                let center = Vec3::new(
109                    self.torus.major_radius * theta.cos(),
110                    0.,
111                    self.torus.major_radius * theta.sin(),
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}