1use glam::Vec3;
4
5#[derive(Clone, Debug, PartialEq)]
9pub enum SurfaceType {
10 Sphere,
11 Torus,
12 DoubleTorus,
13 TripleTorus,
14 HigherGenusTorus(i32),
15 KleinBottle,
16 ProjectivePlane,
17 NonOrientable(i32), }
19
20pub fn euler_characteristic(vertices: usize, edges: usize, faces: usize) -> i32 {
22 vertices as i32 - edges as i32 + faces as i32
23}
24
25pub fn genus_from_mesh(vertices: usize, edges: usize, faces: usize) -> i32 {
28 let chi = euler_characteristic(vertices, edges, faces);
29 (2 - chi) / 2
30}
31
32pub fn classify_surface(chi: i32, orientable: bool) -> SurfaceType {
34 if orientable {
35 let genus = (2 - chi) / 2;
36 match genus {
37 0 => SurfaceType::Sphere,
38 1 => SurfaceType::Torus,
39 2 => SurfaceType::DoubleTorus,
40 3 => SurfaceType::TripleTorus,
41 g => SurfaceType::HigherGenusTorus(g),
42 }
43 } else {
44 let k = 2 - chi;
46 match k {
47 1 => SurfaceType::ProjectivePlane,
48 2 => SurfaceType::KleinBottle,
49 n => SurfaceType::NonOrientable(n),
50 }
51 }
52}
53
54pub fn connected_sum(a: &SurfaceType, b: &SurfaceType) -> SurfaceType {
59 let (g_a, orient_a) = surface_params(a);
60 let (g_b, orient_b) = surface_params(b);
61
62 if orient_a && orient_b {
63 let total_genus = g_a + g_b;
65 classify_surface(2 - 2 * total_genus, true)
66 } else {
67 let k_a = if orient_a { 2 * g_a } else { g_a };
70 let k_b = if orient_b { 2 * g_b } else { g_b };
71 let total_k = k_a + k_b;
72 classify_surface(2 - total_k, false)
73 }
74}
75
76fn surface_params(s: &SurfaceType) -> (i32, bool) {
78 match s {
79 SurfaceType::Sphere => (0, true),
80 SurfaceType::Torus => (1, true),
81 SurfaceType::DoubleTorus => (2, true),
82 SurfaceType::TripleTorus => (3, true),
83 SurfaceType::HigherGenusTorus(g) => (*g, true),
84 SurfaceType::ProjectivePlane => (1, false),
85 SurfaceType::KleinBottle => (2, false),
86 SurfaceType::NonOrientable(k) => (*k, false),
87 }
88}
89
90pub struct GenusRenderer;
94
95impl GenusRenderer {
96 pub fn generate_surface(genus: i32, resolution: usize) -> Vec<Vec3> {
99 if genus <= 0 {
100 return Self::generate_sphere(resolution);
101 }
102
103 let mut vertices = Vec::new();
104
105 let spacing = 3.0;
107 let major_r = 1.0;
108 let minor_r = 0.4;
109
110 for hole in 0..genus {
111 let offset_x = hole as f32 * spacing - (genus as f32 - 1.0) * spacing / 2.0;
112
113 for i in 0..resolution {
114 let u = std::f32::consts::PI * 2.0 * i as f32 / resolution as f32;
115 for j in 0..resolution {
116 let v = std::f32::consts::PI * 2.0 * j as f32 / resolution as f32;
117 let x = (major_r + minor_r * v.cos()) * u.cos() + offset_x;
118 let y = (major_r + minor_r * v.cos()) * u.sin();
119 let z = minor_r * v.sin();
120 vertices.push(Vec3::new(x, y, z));
121 }
122 }
123
124 if hole < genus - 1 {
126 let tube_start = offset_x + major_r + minor_r;
127 let tube_end = offset_x + spacing - major_r - minor_r;
128 for i in 0..resolution {
129 let angle = std::f32::consts::PI * 2.0 * i as f32 / resolution as f32;
130 for j in 0..resolution / 2 {
131 let t = j as f32 / (resolution / 2) as f32;
132 let x = tube_start + t * (tube_end - tube_start);
133 let y = minor_r * angle.cos();
134 let z = minor_r * angle.sin();
135 vertices.push(Vec3::new(x, y, z));
136 }
137 }
138 }
139 }
140
141 vertices
142 }
143
144 fn generate_sphere(resolution: usize) -> Vec<Vec3> {
146 let mut vertices = Vec::new();
147 for i in 0..=resolution {
148 let theta = std::f32::consts::PI * i as f32 / resolution as f32;
149 for j in 0..resolution {
150 let phi = 2.0 * std::f32::consts::PI * j as f32 / resolution as f32;
151 vertices.push(Vec3::new(
152 theta.sin() * phi.cos(),
153 theta.sin() * phi.sin(),
154 theta.cos(),
155 ));
156 }
157 }
158 vertices
159 }
160}
161
162#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_euler_characteristic_tetrahedron() {
170 assert_eq!(euler_characteristic(4, 6, 4), 2);
172 }
173
174 #[test]
175 fn test_euler_characteristic_cube() {
176 assert_eq!(euler_characteristic(8, 12, 6), 2);
178 }
179
180 #[test]
181 fn test_genus_sphere() {
182 assert_eq!(genus_from_mesh(4, 6, 4), 0);
184 }
185
186 #[test]
187 fn test_genus_torus() {
188 assert_eq!(euler_characteristic(9, 27, 18), 0);
190 assert_eq!(genus_from_mesh(9, 27, 18), 1);
191 }
192
193 #[test]
194 fn test_genus_double_torus() {
195 let chi = euler_characteristic(10, 30, 18);
198 assert_eq!(chi, -2);
199 assert_eq!(genus_from_mesh(10, 30, 18), 2);
200 }
201
202 #[test]
203 fn test_classify_sphere() {
204 assert_eq!(classify_surface(2, true), SurfaceType::Sphere);
205 }
206
207 #[test]
208 fn test_classify_torus() {
209 assert_eq!(classify_surface(0, true), SurfaceType::Torus);
210 }
211
212 #[test]
213 fn test_classify_klein() {
214 assert_eq!(classify_surface(0, false), SurfaceType::KleinBottle);
215 }
216
217 #[test]
218 fn test_classify_projective_plane() {
219 assert_eq!(classify_surface(1, false), SurfaceType::ProjectivePlane);
220 }
221
222 #[test]
223 fn test_connected_sum_torus_torus() {
224 let result = connected_sum(&SurfaceType::Torus, &SurfaceType::Torus);
225 assert_eq!(result, SurfaceType::DoubleTorus);
226 }
227
228 #[test]
229 fn test_connected_sum_sphere_anything() {
230 let result = connected_sum(&SurfaceType::Sphere, &SurfaceType::Torus);
232 assert_eq!(result, SurfaceType::Torus);
233 }
234
235 #[test]
236 fn test_connected_sum_klein_projective() {
237 let result = connected_sum(&SurfaceType::ProjectivePlane, &SurfaceType::KleinBottle);
239 assert_eq!(result, SurfaceType::NonOrientable(3));
240 }
241
242 #[test]
243 fn test_genus_renderer_sphere() {
244 let verts = GenusRenderer::generate_surface(0, 10);
245 assert!(!verts.is_empty());
246 }
247
248 #[test]
249 fn test_genus_renderer_torus() {
250 let verts = GenusRenderer::generate_surface(1, 10);
251 assert!(!verts.is_empty());
252 }
253
254 #[test]
255 fn test_genus_renderer_double_torus() {
256 let verts = GenusRenderer::generate_surface(2, 10);
257 assert!(verts.len() > GenusRenderer::generate_surface(1, 10).len());
258 }
259}