Skip to main content

bevy_mesh/primitives/dim3/
torus.rs

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