1use glam::Vec3;
8use std::f32::consts::{TAU, PI};
9
10#[derive(Clone, Debug)]
12pub struct Formation {
13 pub positions: Vec<Vec3>,
14 pub chars: Vec<char>,
15}
16
17impl Formation {
20 pub fn single(ch: char) -> Self {
22 Self { positions: vec![Vec3::ZERO], chars: vec![ch] }
23 }
24
25 pub fn grid_3x3() -> Self {
27 Self::grid(3, 3, 1.0)
28 }
29
30 pub fn grid(cols: i32, rows: i32, spacing: f32) -> Self {
32 let mut positions = Vec::new();
33 let mut chars = Vec::new();
34 let cx = (cols - 1) as f32 * spacing * 0.5;
35 let cy = (rows - 1) as f32 * spacing * 0.5;
36 for row in 0..rows {
37 for col in 0..cols {
38 let x = col as f32 * spacing - cx;
39 let y = row as f32 * spacing - cy;
40 positions.push(Vec3::new(x, y, 0.0));
41 chars.push(if col == cols / 2 && row == rows / 2 { '@' } else { '#' });
42 }
43 }
44 Self { positions, chars }
45 }
46
47 pub fn circle(n: usize, radius: f32) -> Self {
49 let mut positions = Vec::new();
50 let mut chars = Vec::new();
51 for i in 0..n {
52 let angle = i as f32 / n as f32 * TAU;
53 positions.push(Vec3::new(angle.cos() * radius, angle.sin() * radius, 0.0));
54 chars.push('◆');
55 }
56 Self { positions, chars }
57 }
58
59 pub fn rings(spec: &[(usize, f32)]) -> Self {
61 let ring_chars = ['◆', '◇', '◈', '◉', '·', '·'];
62 let mut positions = Vec::new();
63 let mut chars = Vec::new();
64 positions.push(Vec3::ZERO);
66 chars.push('◈');
67 for (ri, &(n, radius)) in spec.iter().enumerate() {
68 let ch = ring_chars[ri.min(ring_chars.len() - 1)];
69 for i in 0..n {
70 let angle = i as f32 / n as f32 * TAU;
71 positions.push(Vec3::new(angle.cos() * radius, angle.sin() * radius, 0.0));
72 chars.push(ch);
73 }
74 }
75 Self { positions, chars }
76 }
77
78 pub fn diamond(size: i32) -> Self {
80 let mut positions = Vec::new();
81 let mut chars = Vec::new();
82 for y in -size..=size {
83 for x in -size..=size {
84 if x.abs() + y.abs() <= size {
85 positions.push(Vec3::new(x as f32, y as f32, 0.0));
86 chars.push(if x == 0 && y == 0 { '◈' } else { '◇' });
87 }
88 }
89 }
90 Self { positions, chars }
91 }
92
93 pub fn cross(arm_length: i32) -> Self {
95 let mut positions = Vec::new();
96 let mut chars = Vec::new();
97 positions.push(Vec3::ZERO);
98 chars.push('╬');
99 for i in 1..=arm_length {
100 for &(dx, dy, ch) in &[(1i32,0i32,'═'),(-1,0,'═'),(0,1,'║'),(0,-1,'║')] {
101 positions.push(Vec3::new(dx as f32 * i as f32, dy as f32 * i as f32, 0.0));
102 chars.push(ch);
103 }
104 }
105 Self { positions, chars }
106 }
107
108 pub fn star(points: usize, inner_r: f32, outer_r: f32) -> Self {
110 let mut positions = Vec::new();
111 let mut chars = Vec::new();
112 let n = points * 2;
113 for i in 0..n {
114 let angle = i as f32 / n as f32 * TAU - PI / 2.0;
115 let r = if i % 2 == 0 { outer_r } else { inner_r };
116 positions.push(Vec3::new(angle.cos() * r, angle.sin() * r, 0.0));
117 chars.push(if i % 2 == 0 { '★' } else { '·' });
118 }
119 Self { positions, chars }
120 }
121
122 pub fn hex_cluster(radius: f32) -> Self {
124 let mut positions = Vec::new();
125 let mut chars = Vec::new();
126 let spacing = 1.0f32;
127 let h = spacing * 3.0f32.sqrt() * 0.5;
128 let imax = (radius / spacing) as i32 + 2;
129 for row in -imax..=imax {
130 for col in -imax..=imax {
131 let x = col as f32 * spacing + (row % 2) as f32 * spacing * 0.5;
132 let y = row as f32 * h;
133 if (x * x + y * y).sqrt() <= radius {
134 positions.push(Vec3::new(x, y, 0.0));
135 chars.push(if x.abs() < 0.01 && y.abs() < 0.01 { '⊕' } else { '◆' });
136 }
137 }
138 }
139 Self { positions, chars }
140 }
141
142 pub fn spiral(turns: f32, density: usize) -> Self {
144 let mut positions = Vec::new();
145 let mut chars = Vec::new();
146 let n = (turns * density as f32) as usize;
147 let spiral_chars = ['·', '·', '✦', '·', '·', '+'];
148 for i in 0..n {
149 let t = i as f32 / density as f32;
150 let angle = t * TAU;
151 let r = t * 0.6;
152 positions.push(Vec3::new(angle.cos() * r, angle.sin() * r, 0.0));
153 chars.push(spiral_chars[i % spiral_chars.len()]);
154 }
155 Self { positions, chars }
156 }
157
158 pub fn fibonacci_spiral(n: usize, scale: f32) -> Self {
160 let phi = (1.0 + 5.0f32.sqrt()) * 0.5;
161 let golden_angle = TAU / (phi * phi);
162 let mut positions = Vec::new();
163 let mut chars = Vec::new();
164 let fib_chars = ['·', '+', '✦', '·', '*', '◆'];
165 for i in 0..n {
166 let r = (i as f32).sqrt() * scale;
167 let angle = i as f32 * golden_angle;
168 positions.push(Vec3::new(angle.cos() * r, angle.sin() * r, 0.0));
169 chars.push(fib_chars[i % fib_chars.len()]);
170 }
171 Self { positions, chars }
172 }
173
174 pub fn dna_helix(height: f32, turns: f32, n: usize) -> Self {
176 let mut positions = Vec::new();
177 let mut chars = Vec::new();
178 let radius = 1.5f32;
179 for i in 0..n {
180 let t = i as f32 / n as f32;
181 let angle = t * turns * TAU;
182 let y = (t - 0.5) * height;
183 positions.push(Vec3::new(angle.cos() * radius, y, angle.sin() * radius));
185 chars.push('◆');
186 let angle_b = angle + PI;
188 positions.push(Vec3::new(angle_b.cos() * radius, y, angle_b.sin() * radius));
189 chars.push('◇');
190 if i % 4 == 0 {
192 let mid_x = (angle.cos() + angle_b.cos()) * radius * 0.5;
193 let mid_z = (angle.sin() + angle_b.sin()) * radius * 0.5;
194 positions.push(Vec3::new(mid_x, y, mid_z));
195 chars.push('═');
196 }
197 }
198 Self { positions, chars }
199 }
200
201 pub fn triangle(size: f32) -> Self {
203 let mut positions = Vec::new();
204 let mut chars = Vec::new();
205 let n = (size as usize).max(2);
206 let verts = [
208 Vec3::new(0.0, size * 0.577, 0.0),
209 Vec3::new(-size * 0.5, -size * 0.289, 0.0),
210 Vec3::new( size * 0.5, -size * 0.289, 0.0),
211 ];
212 for side in 0..3 {
213 let a = verts[side];
214 let b = verts[(side + 1) % 3];
215 for i in 0..n {
216 let t = i as f32 / n as f32;
217 positions.push(a + (b - a) * t);
218 chars.push(if i == 0 { '▲' } else { '△' });
219 }
220 }
221 Self { positions, chars }
222 }
223
224 pub fn lissajous(n: usize, a: f32, b: f32, delta: f32, scale: f32) -> Self {
226 let mut positions = Vec::new();
227 let mut chars = Vec::new();
228 let liss_chars = ['·', '✦', '+', '·', '*'];
229 for i in 0..n {
230 let t = i as f32 / n as f32 * TAU;
231 let x = (a * t + delta).sin() * scale;
232 let y = (b * t).sin() * scale;
233 positions.push(Vec3::new(x, y, 0.0));
234 chars.push(liss_chars[i % liss_chars.len()]);
235 }
236 Self { positions, chars }
237 }
238
239 pub fn lorenz_trace(n: usize, scale: f32) -> Self {
241 use crate::math::attractors::{step, initial_state, AttractorType};
242 let mut state = initial_state(AttractorType::Lorenz);
243 let mut positions = Vec::new();
244 let mut chars = Vec::new();
245 let trace_chars = ['·', '·', '✦', '·', '·', '*', '·'];
246 for _ in 0..500 {
248 let (next, _) = step(AttractorType::Lorenz, state, 0.01);
249 state = next;
250 }
251 for i in 0..n {
252 let (next, _) = step(AttractorType::Lorenz, state, 0.01);
253 state = next;
254 positions.push(state * scale);
255 chars.push(trace_chars[i % trace_chars.len()]);
256 }
257 Self { positions, chars }
258 }
259
260 pub fn arrow(length: usize, direction: Vec3) -> Self {
262 let dir = direction.normalize_or_zero();
263 let right = if dir.abs().dot(Vec3::X) < 0.9 {
264 dir.cross(Vec3::X).normalize()
265 } else {
266 dir.cross(Vec3::Y).normalize()
267 };
268 let mut positions = Vec::new();
269 let mut chars = Vec::new();
270 for i in 0..length {
271 positions.push(dir * i as f32);
272 chars.push('·');
273 }
274 let tip = dir * length as f32;
276 positions.push(tip);
277 chars.push('►');
278 positions.push(tip - dir * 0.8 + right * 0.5);
279 chars.push('╱');
280 positions.push(tip - dir * 0.8 - right * 0.5);
281 chars.push('╲');
282 Self { positions, chars }
283 }
284
285 pub fn scatter(n: usize, radius: f32, seed: u64) -> Self {
287 let mut positions = Vec::new();
288 let mut chars = Vec::new();
289 let scatter_chars = ['·', '+', '✦', '*', '◆', '◇'];
290 let mut rng = seed;
291 for i in 0..n {
292 rng = rng.wrapping_mul(0x6c62272e07bb0142).wrapping_add(0x62b821756295c58d);
293 let x = ((rng >> 32) as f32 / u32::MAX as f32) * 2.0 - 1.0;
294 rng = rng.wrapping_mul(0x6c62272e07bb0142).wrapping_add(0x62b821756295c58d);
295 let y = ((rng >> 32) as f32 / u32::MAX as f32) * 2.0 - 1.0;
296 rng = rng.wrapping_mul(0x6c62272e07bb0142).wrapping_add(0x62b821756295c58d);
297 let z = ((rng >> 32) as f32 / u32::MAX as f32) * 2.0 - 1.0;
298 let v = Vec3::new(x, y, z).normalize_or_zero();
299 rng = rng.wrapping_mul(0x6c62272e07bb0142).wrapping_add(0x62b821756295c58d);
300 let r = ((rng >> 32) as f32 / u32::MAX as f32).sqrt() * radius;
301 positions.push(v * r);
302 chars.push(scatter_chars[i % scatter_chars.len()]);
303 }
304 Self { positions, chars }
305 }
306
307 pub fn rune_sigma() -> Self {
311 let pts: &[(f32, f32, char)] = &[
312 (1.5, 1.0, '╗'), (0.5, 1.0, '═'), (-0.5, 1.0, '═'), (-1.5, 1.0, '╔'),
313 (1.5, 0.0, '·'), (-1.5, 0.0, '·'),
314 (1.5, -1.0, '╝'), (0.5, -1.0, '═'), (-0.5, -1.0, '═'), (-1.5, -1.0, '╚'),
315 (0.0, 0.5, '╲'), (0.0, -0.5, '╲'),
316 ];
317 let positions = pts.iter().map(|&(x, y, _)| Vec3::new(x, y, 0.0)).collect();
318 let chars = pts.iter().map(|&(_, _, c)| c).collect();
319 Self { positions, chars }
320 }
321
322 pub fn rune_infinity() -> Self {
324 let n = 32usize;
325 let mut positions = Vec::new();
326 let mut chars = Vec::new();
327 let inf_chars = ['·', '◆', '·', '✦'];
328 for i in 0..n {
329 let t = i as f32 / n as f32 * TAU;
330 let denom = 1.0 + (t.sin()).powi(2);
332 let x = 2.5 * t.cos() / denom;
333 let y = 2.5 * t.cos() * t.sin() / denom;
334 positions.push(Vec3::new(x, y, 0.0));
335 chars.push(inf_chars[i % inf_chars.len()]);
336 }
337 Self { positions, chars }
338 }
339
340 pub fn rune_chaos() -> Self {
342 let mut positions = Vec::new();
343 let mut chars = Vec::new();
344 let arms = 3;
345 let arm_len = 8;
346 for arm in 0..arms {
347 let base_angle = arm as f32 / arms as f32 * TAU;
348 for j in 0..arm_len {
349 let t = j as f32 / arm_len as f32;
350 let angle = base_angle + t * PI * 0.5;
351 let r = t * 2.5;
352 positions.push(Vec3::new(angle.cos() * r, angle.sin() * r, 0.0));
353 chars.push(if j == arm_len - 1 { '▲' } else { '·' });
354 }
355 }
356 positions.push(Vec3::ZERO);
358 chars.push('⊕');
359 Self { positions, chars }
360 }
361
362 pub fn sierpinski(depth: u32, size: f32) -> Self {
366 let mut positions = Vec::new();
367 let mut chars = Vec::new();
368 sierpinski_recurse(&mut positions, &mut chars,
369 Vec3::new(0.0, size * 0.577, 0.0),
370 Vec3::new(-size * 0.5, -size * 0.289, 0.0),
371 Vec3::new( size * 0.5, -size * 0.289, 0.0),
372 depth);
373 Self { positions, chars }
374 }
375
376 pub fn mandala(layers: usize, base_n: usize, base_r: f32) -> Self {
378 let mandala_chars = ['◆', '◇', '◈', '✦', '★', '·'];
379 let mut positions = Vec::new();
380 let mut chars = Vec::new();
381 positions.push(Vec3::ZERO);
382 chars.push('⊙');
383 for layer in 0..layers {
384 let n = base_n + layer * base_n;
385 let r = base_r * (layer + 1) as f32;
386 let phase_offset = if layer % 2 == 0 { 0.0 } else { PI / n as f32 };
387 let ch = mandala_chars[layer.min(mandala_chars.len() - 1)];
388 for i in 0..n {
389 let angle = i as f32 / n as f32 * TAU + phase_offset;
390 positions.push(Vec3::new(angle.cos() * r, angle.sin() * r, 0.0));
391 chars.push(ch);
392 }
393 }
394 Self { positions, chars }
395 }
396
397 pub fn scaled(mut self, s: f32) -> Self {
401 for p in &mut self.positions { *p *= s; }
402 self
403 }
404
405 pub fn rotated_z(mut self, angle: f32) -> Self {
407 let (s, c) = angle.sin_cos();
408 for p in &mut self.positions {
409 let x = p.x * c - p.y * s;
410 let y = p.x * s + p.y * c;
411 p.x = x;
412 p.y = y;
413 }
414 self
415 }
416
417 pub fn translated(mut self, offset: Vec3) -> Self {
419 for p in &mut self.positions { *p += offset; }
420 self
421 }
422
423 pub fn mirrored_x(mut self) -> Self {
425 for p in &mut self.positions { p.x = -p.x; }
426 self
427 }
428
429 pub fn join(mut self, other: Formation) -> Self {
431 self.positions.extend(other.positions);
432 self.chars.extend(other.chars);
433 self
434 }
435
436 pub fn with_char(mut self, ch: char) -> Self {
438 self.chars.iter_mut().for_each(|c| *c = ch);
439 self
440 }
441
442 pub fn len(&self) -> usize { self.positions.len() }
444
445 pub fn is_empty(&self) -> bool { self.positions.is_empty() }
447
448 pub fn centroid(&self) -> Vec3 {
450 if self.positions.is_empty() { return Vec3::ZERO; }
451 self.positions.iter().copied().sum::<Vec3>() / self.positions.len() as f32
452 }
453
454 pub fn bounding_radius(&self) -> f32 {
456 let c = self.centroid();
457 self.positions.iter().map(|p| (*p - c).length()).fold(0.0f32, f32::max)
458 }
459
460 pub fn normalized(self) -> Self {
462 let r = self.bounding_radius().max(0.001);
463 self.scaled(1.0 / r)
464 }
465}
466
467fn sierpinski_recurse(
470 positions: &mut Vec<Vec3>,
471 chars: &mut Vec<char>,
472 a: Vec3, b: Vec3, c: Vec3,
473 depth: u32,
474) {
475 if depth == 0 {
476 positions.push(a);
477 positions.push(b);
478 positions.push(c);
479 chars.push('▲');
480 chars.push('▲');
481 chars.push('▲');
482 return;
483 }
484 let ab = (a + b) * 0.5;
485 let bc = (b + c) * 0.5;
486 let ca = (c + a) * 0.5;
487 sierpinski_recurse(positions, chars, a, ab, ca, depth - 1);
488 sierpinski_recurse(positions, chars, ab, b, bc, depth - 1);
489 sierpinski_recurse(positions, chars, ca, bc, c, depth - 1);
490}
491
492impl Formation {
495 #[allow(dead_code)]
497 fn cross_inner(arm_length: i32) -> Vec<(i32, i32, char)> {
498 let mut pts = vec![(0, 0, '╬')];
499 for i in 1..=arm_length {
500 pts.push(( i, 0, '═'));
501 pts.push((-i, 0, '═'));
502 pts.push(( 0, i, '║'));
503 pts.push(( 0, -i, '║'));
504 }
505 pts
506 }
507}