1use macroquad::prelude::*;
2
3use crate::scene::FrameContext;
4
5#[derive(Debug, Clone, Copy)]
6pub struct GlowStyle {
7 pub rings: usize,
8 pub spread: f32,
9 pub alpha: f32,
10}
11
12impl GlowStyle {
13 pub fn soft() -> Self {
14 Self {
15 rings: 5,
16 spread: 18.0,
17 alpha: 0.18,
18 }
19 }
20}
21
22#[derive(Debug, Clone, Copy)]
23pub struct Particle {
24 pub orbit_radius: f32,
25 pub speed: f32,
26 pub phase: f32,
27 pub size: f32,
28 pub height_bias: f32,
29}
30
31#[derive(Debug, Clone)]
32pub struct ParticleField {
33 particles: Vec<Particle>,
34}
35
36impl ParticleField {
37 pub fn orbiting(count: usize, radius_start: f32, radius_end: f32) -> Self {
38 let count = count.max(1);
39 let mut particles = Vec::with_capacity(count);
40
41 for index in 0..count {
42 let t = index as f32 / count as f32;
43 particles.push(Particle {
44 orbit_radius: radius_start + (radius_end - radius_start) * t,
45 speed: 0.18 + t * 0.95,
46 phase: t * std::f32::consts::TAU * 4.0,
47 size: 1.2 + (index % 6) as f32 * 0.45,
48 height_bias: -120.0 + ((index * 53) % 240) as f32,
49 });
50 }
51
52 Self { particles }
53 }
54
55 pub fn particles(&self) -> &[Particle] {
56 &self.particles
57 }
58}
59
60pub fn mix_color(a: Color, b: Color, t: f32) -> Color {
61 let t = t.clamp(0.0, 1.0);
62 Color::new(
63 a.r + (b.r - a.r) * t,
64 a.g + (b.g - a.g) * t,
65 a.b + (b.b - a.b) * t,
66 a.a + (b.a - a.a) * t,
67 )
68}
69
70pub fn draw_gradient_background(ctx: &FrameContext, top: Color, bottom: Color, steps: usize) {
71 let steps = steps.max(2);
72
73 for i in 0..steps {
74 let t = i as f32 / (steps - 1) as f32;
75 let color = mix_color(top, bottom, t);
76 draw_rectangle(
77 0.0,
78 ctx.height * t,
79 ctx.width,
80 ctx.height / steps as f32 + 1.0,
81 color,
82 );
83 }
84}
85
86pub fn draw_glow_circle(center: Vec2, radius: f32, color: Color, style: GlowStyle) {
87 for index in 0..style.rings {
88 let spread = radius + index as f32 * style.spread;
89 let alpha = color.a * (style.alpha - index as f32 * 0.025).max(0.02);
90 draw_circle(center.x, center.y, spread, Color::new(color.r, color.g, color.b, alpha));
91 }
92}
93
94pub fn draw_polyline(points: &[Vec2], thickness: f32, color: Color) {
95 for pair in points.windows(2) {
96 let start = pair[0];
97 let end = pair[1];
98 draw_line(start.x, start.y, end.x, end.y, thickness, color);
99 }
100}
101
102pub fn draw_particle_field<F>(
103 ctx: &FrameContext,
104 origin: Vec2,
105 field: &ParticleField,
106 mut color_at: F,
107)
108where
109 F: FnMut(&Particle, Vec2, f32) -> Color,
110{
111 for particle in field.particles() {
112 let angle = ctx.time * particle.speed + particle.phase;
113 let local = vec2(
114 angle.cos() * particle.orbit_radius,
115 particle.height_bias + angle.sin() * (36.0 + particle.orbit_radius * 0.08),
116 );
117 let position = origin + local;
118 let shimmer = 0.2 + 0.8 * (ctx.time * 1.6 + particle.phase).sin().abs();
119 let color = color_at(particle, position, shimmer);
120 draw_circle(
121 position.x,
122 position.y,
123 particle.size + shimmer * 1.6,
124 color,
125 );
126 }
127}