wde_renderer/assets/meshes/
capsule.rs

1use bevy::math::Vec3;
2use wde_wgpu::vertex::Vertex;
3
4use crate::assets::{Mesh, MeshBbox};
5
6pub struct CapsuleMeshConfig {
7    pub radius: f32,
8    pub height: f32,
9    pub segments: u32,
10    pub rings: u32
11}
12impl Default for CapsuleMeshConfig {
13    fn default() -> Self {
14        Self {
15            radius: 0.5,
16            height: 2.0,
17            segments: 16,
18            rings: 8
19        }
20    }
21}
22
23pub struct CapsuleMesh;
24impl CapsuleMesh {
25    /// Create a new capsule mesh.
26    /// The capsule is centered at the origin and extends along the Y axis.
27    ///
28    /// # Arguments
29    ///
30    /// - `label`: A label for the mesh asset.
31    /// - `config`: Configuration parameters for the capsule mesh.
32    ///
33    /// # Returns
34    ///
35    /// The capsule mesh.
36    pub fn from(label: &str, config: CapsuleMeshConfig) -> Mesh {
37        let (radius, height, segments, rings) =
38            (config.radius, config.height, config.segments, config.rings);
39
40        let segments = segments.max(3);
41        let rings = rings.max(1);
42
43        // Calculate cylinder height (total height minus the two hemisphere radii)
44        let cylinder_height = (height - 2.0 * radius).max(0.0);
45        let half_cylinder_height = cylinder_height / 2.0;
46
47        let mut vertices = Vec::new();
48        let mut indices = Vec::new();
49
50        // Generate top hemisphere
51        for ring in 0..=rings {
52            let phi = std::f32::consts::PI * 0.5 * (ring as f32) / (rings as f32);
53            let y = radius * phi.cos() + half_cylinder_height;
54            let ring_radius = radius * phi.sin();
55
56            for segment in 0..=segments {
57                let theta = 2.0 * std::f32::consts::PI * (segment as f32) / (segments as f32);
58                let x = ring_radius * theta.cos();
59                let z = ring_radius * theta.sin();
60
61                // Normal for sphere is just the normalized position vector
62                let normal_x = x;
63                let normal_y = radius * phi.cos();
64                let normal_z = z;
65                let normal_length =
66                    (normal_x * normal_x + normal_y * normal_y + normal_z * normal_z).sqrt();
67
68                // UV coordinates
69                let u = (segment as f32) / (segments as f32);
70                let v = 1.0 - (ring as f32) / (rings as f32) * 0.25; // Top hemisphere uses top 25% of texture
71
72                vertices.push(Vertex {
73                    position: [x, y, z],
74                    normal: [
75                        normal_x / normal_length,
76                        normal_y / normal_length,
77                        normal_z / normal_length
78                    ],
79                    uv: [u, v],
80                    tangent: [0.0, 0.0, 0.0, 0.0]
81                });
82            }
83        }
84
85        // Generate cylinder body
86        for ring in 0..=1 {
87            let y = if ring == 0 {
88                half_cylinder_height
89            } else {
90                -half_cylinder_height
91            };
92
93            for segment in 0..=segments {
94                let theta = 2.0 * std::f32::consts::PI * (segment as f32) / (segments as f32);
95                let x = radius * theta.cos();
96                let z = radius * theta.sin();
97
98                // Normal points outward horizontally
99                let normal_x = theta.cos();
100                let normal_z = theta.sin();
101
102                // UV coordinates
103                let u = (segment as f32) / (segments as f32);
104                let v = 0.75 - (ring as f32) * 0.5; // Cylinder uses middle 50% of texture
105
106                vertices.push(Vertex {
107                    position: [x, y, z],
108                    normal: [normal_x, 0.0, normal_z],
109                    uv: [u, v],
110                    tangent: [0.0, 0.0, 0.0, 0.0]
111                });
112            }
113        }
114
115        // Generate bottom hemisphere
116        for ring in 0..=rings {
117            let phi = std::f32::consts::PI * 0.5 * (ring as f32) / (rings as f32);
118            let y = -radius * phi.cos() - half_cylinder_height;
119            let ring_radius = radius * phi.sin();
120
121            for segment in 0..=segments {
122                let theta = 2.0 * std::f32::consts::PI * (segment as f32) / (segments as f32);
123                let x = ring_radius * theta.cos();
124                let z = ring_radius * theta.sin();
125
126                // Normal for sphere is just the normalized position vector
127                let normal_x = x;
128                let normal_y = -radius * phi.cos();
129                let normal_z = z;
130                let normal_length =
131                    (normal_x * normal_x + normal_y * normal_y + normal_z * normal_z).sqrt();
132
133                // UV coordinates
134                let u = (segment as f32) / (segments as f32);
135                let v = 0.25 - (ring as f32) / (rings as f32) * 0.25; // Bottom hemisphere uses bottom 25% of texture
136
137                vertices.push(Vertex {
138                    position: [x, y, z],
139                    normal: [
140                        normal_x / normal_length,
141                        normal_y / normal_length,
142                        normal_z / normal_length
143                    ],
144                    uv: [u, v],
145                    tangent: [0.0, 0.0, 0.0, 0.0]
146                });
147            }
148        }
149
150        // Generate indices for top hemisphere
151        let mut vertex_offset = 0u32;
152        for ring in 0..rings {
153            for segment in 0..segments {
154                let current = vertex_offset + ring * (segments + 1) + segment;
155                let next = current + segments + 1;
156
157                indices.push(current);
158                indices.push(next);
159                indices.push(current + 1);
160
161                indices.push(current + 1);
162                indices.push(next);
163                indices.push(next + 1);
164            }
165        }
166
167        // Generate indices for cylinder
168        vertex_offset += (rings + 1) * (segments + 1);
169        for segment in 0..segments {
170            let current = vertex_offset + segment;
171            let next = current + segments + 1;
172
173            indices.push(current);
174            indices.push(next);
175            indices.push(current + 1);
176
177            indices.push(current + 1);
178            indices.push(next);
179            indices.push(next + 1);
180        }
181
182        // Generate indices for bottom hemisphere
183        vertex_offset += 2 * (segments + 1);
184        for ring in 0..rings {
185            for segment in 0..segments {
186                let current = vertex_offset + ring * (segments + 1) + segment;
187                let next = current + segments + 1;
188
189                indices.push(current);
190                indices.push(next);
191                indices.push(current + 1);
192
193                indices.push(current + 1);
194                indices.push(next);
195                indices.push(next + 1);
196            }
197        }
198
199        // Create bounding box
200        let half_height = height / 2.0;
201        let bounding_box = MeshBbox {
202            min: Vec3::new(-radius, -half_height, -radius),
203            max: Vec3::new(radius, half_height, radius)
204        };
205
206        Mesh {
207            label: label.to_string(),
208            vertices,
209            indices,
210            bbox: bounding_box,
211            use_ssbo: false
212        }
213    }
214}