1use glam::{Vec2, Vec3};
4use std::f32::consts::PI;
5
6pub struct KleinBottle {
11 pub scale: f32,
12}
13
14impl KleinBottle {
15 pub fn new(scale: f32) -> Self {
16 Self { scale }
17 }
18
19 pub fn parametric(&self, u: f32, v: f32) -> Vec3 {
23 let s = self.scale;
24 let cos_u = u.cos();
25 let sin_u = u.sin();
26 let cos_v = v.cos();
27 let sin_v = v.sin();
28
29 let r = 1.0;
31 let x = s * (r + cos_u * (v / 2.0).cos() - sin_u * (v / 2.0).sin().powi(2)) * cos_v;
32 let a = 2.0;
34 let x = s * ((a + cos_u * 0.5 * cos_v.cos()) * v.cos());
35 let (x, y, z) = klein_bottle_immersion(u, v);
37 Vec3::new(x * s, y * s, z * s)
38 }
39
40 pub fn generate_mesh(&self, u_steps: usize, v_steps: usize) -> Vec<Vec3> {
42 let mut points = Vec::with_capacity(u_steps * v_steps);
43 for i in 0..u_steps {
44 let u = 2.0 * PI * i as f32 / u_steps as f32;
45 for j in 0..v_steps {
46 let v = 2.0 * PI * j as f32 / v_steps as f32;
47 points.push(self.parametric(u, v));
48 }
49 }
50 points
51 }
52}
53
54fn klein_bottle_immersion(u: f32, v: f32) -> (f32, f32, f32) {
57 let a = 3.0;
59 let cos_u = u.cos();
60 let sin_u = u.sin();
61 let cos_v = v.cos();
62 let sin_v = v.sin();
63 let half_v = v / 2.0;
64 let cos_hv = half_v.cos();
65 let sin_hv = half_v.sin();
66
67 let r = a + cos_hv * sin_u - sin_hv * (2.0 * u).sin();
68 let x = r * cos_v;
69 let y = r * sin_v;
70 let z = sin_hv * sin_u + cos_hv * (2.0 * u).sin();
71
72 (x, y, z)
73}
74
75pub fn klein_wrap(pos: Vec2, width: f32, height: f32) -> (Vec2, bool) {
82 let mut x = ((pos.x % width) + width) % width;
84 let mut y = pos.y;
85 let mut flipped = false;
86
87 let crossings = (pos.y / height).floor() as i32;
89 y = ((y % height) + height) % height;
90
91 if crossings.abs() % 2 != 0 {
93 x = width - x;
94 flipped = true;
95 }
96
97 (Vec2::new(x, y), flipped)
98}
99
100pub struct KleinNavigation {
105 pub width: f32,
106 pub height: f32,
107 pub position: Vec2,
108 pub flipped: bool,
109}
110
111impl KleinNavigation {
112 pub fn new(width: f32, height: f32, start: Vec2) -> Self {
113 let (pos, flipped) = klein_wrap(start, width, height);
114 Self {
115 width,
116 height,
117 position: pos,
118 flipped,
119 }
120 }
121
122 pub fn move_by(&mut self, delta: Vec2) {
124 let effective_dx = if self.flipped { -delta.x } else { delta.x };
125 let raw = Vec2::new(self.position.x + effective_dx, self.position.y + delta.y);
126 let (pos, flipped) = klein_wrap(raw, self.width, self.height);
127
128 let y_crossings = ((self.position.y + delta.y) / self.height).floor() as i32
130 - (self.position.y / self.height).floor() as i32;
131 if y_crossings.abs() % 2 != 0 {
132 self.flipped = !self.flipped;
133 }
134 self.position = pos;
135 }
136
137 pub fn orientation(&self) -> f32 {
139 if self.flipped { -1.0 } else { 1.0 }
140 }
141}
142
143pub fn orientation_at(pos: Vec2, height: f32) -> f32 {
146 let crossings = (pos.y / height).floor() as i32;
147 if crossings.abs() % 2 != 0 {
148 -1.0
149 } else {
150 1.0
151 }
152}
153
154pub struct KleinRenderer {
158 pub width: f32,
159 pub height: f32,
160}
161
162impl KleinRenderer {
163 pub fn new(width: f32, height: f32) -> Self {
164 Self { width, height }
165 }
166
167 pub fn boundary_indicators(&self) -> Vec<(Vec2, Vec2, bool)> {
170 vec![
171 (Vec2::new(0.0, 0.0), Vec2::new(0.0, self.height), false),
173 (Vec2::new(self.width, 0.0), Vec2::new(self.width, self.height), false),
174 (Vec2::new(0.0, 0.0), Vec2::new(self.width, 0.0), true),
176 (Vec2::new(0.0, self.height), Vec2::new(self.width, self.height), true),
177 ]
178 }
179
180 pub fn ghost_positions(&self, positions: &[Vec2], margin: f32) -> Vec<(Vec2, bool)> {
183 let mut ghosts = Vec::new();
184
185 for &pos in positions {
186 if pos.y < margin {
188 let ghost = Vec2::new(self.width - pos.x, self.height + pos.y);
190 ghosts.push((ghost, true));
191 }
192 if pos.y > self.height - margin {
194 let ghost = Vec2::new(self.width - pos.x, pos.y - self.height);
195 ghosts.push((ghost, true));
196 }
197 if pos.x < margin {
199 ghosts.push((Vec2::new(pos.x + self.width, pos.y), false));
200 }
201 if pos.x > self.width - margin {
202 ghosts.push((Vec2::new(pos.x - self.width, pos.y), false));
203 }
204 }
205
206 ghosts
207 }
208}
209
210#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_klein_wrap_no_crossing() {
218 let (pos, flipped) = klein_wrap(Vec2::new(5.0, 5.0), 10.0, 10.0);
219 assert!((pos.x - 5.0).abs() < 1e-4);
220 assert!((pos.y - 5.0).abs() < 1e-4);
221 assert!(!flipped);
222 }
223
224 #[test]
225 fn test_klein_wrap_x_only() {
226 let (pos, flipped) = klein_wrap(Vec2::new(15.0, 5.0), 10.0, 10.0);
227 assert!((pos.x - 5.0).abs() < 1e-4);
228 assert!(!flipped); }
230
231 #[test]
232 fn test_klein_wrap_y_flip() {
233 let (pos, flipped) = klein_wrap(Vec2::new(3.0, 15.0), 10.0, 10.0);
234 assert!((pos.x - 7.0).abs() < 1e-4, "x should flip: got {}", pos.x);
235 assert!((pos.y - 5.0).abs() < 1e-4);
236 assert!(flipped);
237 }
238
239 #[test]
240 fn test_klein_wrap_double_y_crossing() {
241 let (pos, flipped) = klein_wrap(Vec2::new(3.0, 25.0), 10.0, 10.0);
243 assert!((pos.x - 3.0).abs() < 1e-4);
244 assert!(!flipped);
245 }
246
247 #[test]
248 fn test_klein_navigation_simple_move() {
249 let mut nav = KleinNavigation::new(10.0, 10.0, Vec2::new(5.0, 5.0));
250 nav.move_by(Vec2::new(1.0, 0.0));
251 assert!((nav.position.x - 6.0).abs() < 1e-4);
252 assert!(!nav.flipped);
253 }
254
255 #[test]
256 fn test_klein_navigation_y_crossing() {
257 let mut nav = KleinNavigation::new(10.0, 10.0, Vec2::new(3.0, 9.0));
258 nav.move_by(Vec2::new(0.0, 2.0)); assert!(nav.flipped, "Should be flipped after crossing y boundary");
260 }
261
262 #[test]
263 fn test_orientation_at() {
264 assert_eq!(orientation_at(Vec2::new(0.0, 5.0), 10.0), 1.0);
265 assert_eq!(orientation_at(Vec2::new(0.0, 15.0), 10.0), -1.0);
266 assert_eq!(orientation_at(Vec2::new(0.0, 25.0), 10.0), 1.0);
267 }
268
269 #[test]
270 fn test_klein_bottle_mesh() {
271 let kb = KleinBottle::new(1.0);
272 let mesh = kb.generate_mesh(10, 10);
273 assert_eq!(mesh.len(), 100);
274 }
275
276 #[test]
277 fn test_klein_renderer_boundaries() {
278 let renderer = KleinRenderer::new(10.0, 10.0);
279 let indicators = renderer.boundary_indicators();
280 assert_eq!(indicators.len(), 4);
281 assert!(indicators[2].2);
283 assert!(indicators[3].2);
284 assert!(!indicators[0].2);
286 assert!(!indicators[1].2);
287 }
288
289 #[test]
290 fn test_klein_renderer_ghosts() {
291 let renderer = KleinRenderer::new(10.0, 10.0);
292 let positions = vec![Vec2::new(5.0, 1.0)]; let ghosts = renderer.ghost_positions(&positions, 2.0);
294 assert!(!ghosts.is_empty());
295 let (ghost_pos, is_flipped) = ghosts[0];
297 assert!(is_flipped);
298 assert!((ghost_pos.x - 5.0).abs() < 1e-4);
299 }
300}