1use serde_json::{json, Value};
15
16pub fn neon_glow(cx: f64, cy: f64, base_radius: f64, glow_layers: usize,
34 glow_spread: f64, intensity: f64, base_color: &str, t: f64) -> Vec<Value> {
35 let glow_layers = glow_layers.clamp(3, 12);
36 let glow_spread = glow_spread.clamp(1.2, 4.0);
37 let intensity = intensity.clamp(0.1, 1.0);
38 let pulse = 1.0 + 0.1 * (t * 3.0).sin(); let mut result = Vec::new();
41
42 for i in (1..=glow_layers).rev() {
44 let radius = base_radius * glow_spread.powi(i as i32) * pulse;
45 let alpha = (intensity / (i as f64)).min(1.0) * 0.4;
46
47 result.push(json!({
48 "type": "glow_ring",
49 "x": cx, "y": cy,
50 "radius": radius,
51 "color": base_color,
52 "alpha": alpha
53 }));
54 }
55
56 result.push(json!({
58 "type": "core",
59 "x": cx, "y": cy,
60 "radius": base_radius * pulse,
61 "color": "#FFFFFF",
62 "alpha": 1.0
63 }));
64
65 result
66}
67
68pub fn motion_blur(prev_positions: &[(f64, f64)], current_pos: (f64, f64),
83 blur_intensity: f64, fade_rate: f64) -> Vec<Value> {
84 let blur_intensity = blur_intensity.clamp(0.1, 1.0);
85 let fade_rate = fade_rate.clamp(0.3, 0.98);
86 let max_blurs = (blur_intensity * 20.0) as usize;
87
88 let mut result = Vec::new();
89
90 result.push(json!({
92 "type": "sharp",
93 "x": current_pos.0,
94 "y": current_pos.1,
95 "alpha": 1.0,
96 "scale": 1.0
97 }));
98
99 let blurs = prev_positions.len().min(max_blurs);
101 for i in 0..blurs {
102 let (px, py) = prev_positions[prev_positions.len() - 1 - i];
103 let alpha = fade_rate.powi((i + 1) as i32);
104 let scale = 1.0 + 0.05 * (i as f64); result.push(json!({
107 "type": "blur_copy",
108 "x": px,
109 "y": py,
110 "alpha": alpha,
111 "scale": scale
112 }));
113 }
114
115 result
116}
117
118pub fn chromatic_aberration(cx: f64, cy: f64, radius: f64, separation: f64,
134 t: f64, shape_type: &str) -> Vec<Value> {
135 let sep = separation.clamp(0.0, 30.0);
136 let angle = t * 0.5;
138 let dx = angle.cos() * sep;
139 let dy = angle.sin() * sep;
140
141 let mut result = Vec::new();
142
143 result.push(json!({
145 "type": shape_type,
146 "channel": "red",
147 "x": cx - dx, "y": cy - dy,
148 "radius": radius,
149 "color": "#FF0000"
150 }));
151
152 result.push(json!({
154 "type": shape_type,
155 "channel": "green",
156 "x": cx, "y": cy,
157 "radius": radius,
158 "color": "#00FF00"
159 }));
160
161 result.push(json!({
163 "type": shape_type,
164 "channel": "blue",
165 "x": cx + dx, "y": cy + dy,
166 "radius": radius,
167 "color": "#0000FF"
168 }));
169
170 result
171}
172
173pub fn bloom_effect(sources: &[(f64, f64, f64, f64)], bloom_radius: f64,
188 bloom_intensity: f64, t: f64) -> Vec<Value> {
189 let bloom_radius = bloom_radius.max(5.0);
190 let bloom_intensity = bloom_intensity.clamp(0.1, 1.0);
191
192 let mut result = Vec::new();
193
194 for (i, (x, y, intensity, radius)) in sources.iter().enumerate() {
195 let flicker = 1.0 + 0.1 * ((t * 4.0 + i as f64) as f64).sin();
196 let effective_intensity = intensity * bloom_intensity * flicker;
197 let effective_radius = radius + bloom_radius * effective_intensity;
198
199 result.push(json!({
201 "type": "bloom_halo",
202 "x": x, "y": y,
203 "radius": effective_radius,
204 "alpha": effective_intensity * 0.5,
205 "color": "#FFFFAA"
206 }));
207
208 result.push(json!({
210 "type": "bloom_core",
211 "x": x, "y": y,
212 "radius": radius,
213 "alpha": intensity,
214 "color": "#FFFFFF"
215 }));
216 }
217
218 result
219}
220
221pub fn particle_trails(positions: &[(f64, f64, f64, f64)], trail_length: usize,
236 trail_fade: f64, trail_color: &str) -> Vec<Value> {
237 let trail_length = trail_length.clamp(3, 50);
238 let trail_fade = trail_fade.clamp(0.5, 0.99);
239
240 let mut result = Vec::new();
241
242 for (px, py, vx, vy) in positions {
243 let speed = (vx * vx + vy * vy).sqrt();
244
245 for i in 0..trail_length {
246 let trail_x = px - vx * (i as f64) * 0.3;
247 let trail_y = py - vy * (i as f64) * 0.3;
248 let alpha = trail_fade.powi((i + 1) as i32);
249 let size = (3.0 - i as f64 * 0.1).max(0.5);
250
251 result.push(json!({
252 "type": "trail_dot",
253 "x": trail_x,
254 "y": trail_y,
255 "size": size * (speed / 5.0).min(2.0),
256 "alpha": alpha,
257 "color": trail_color
258 }));
259 }
260
261 result.push(json!({
263 "type": "particle",
264 "x": px, "y": py,
265 "size": 4.0,
266 "alpha": 1.0,
267 "color": "#FFFFFF"
268 }));
269 }
270
271 result
272}
273
274pub fn morph_shapes(shape_a: &[(f64, f64)], shape_b: &[(f64, f64)],
289 t: f64, easing: &str) -> Vec<Value> {
290 let t = t.clamp(0.0, 1.0);
291
292 let et = match easing {
294 "ease_in" => t * t,
295 "ease_out" => t * (2.0 - t),
296 "ease_in_out" => if t < 0.5 { 2.0 * t * t } else { 1.0 - 2.0 * (1.0 - t) * (1.0 - t) },
297 _ => t, };
299
300 let len = shape_a.len().max(shape_b.len());
301 let mut result = Vec::new();
302
303 for i in 0..len {
304 let ax = shape_a.get(i % shape_a.len()).map(|p| p.0).unwrap_or(0.0);
305 let ay = shape_a.get(i % shape_a.len()).map(|p| p.1).unwrap_or(0.0);
306 let bx = shape_b.get(i % shape_b.len()).map(|p| p.0).unwrap_or(0.0);
307 let by = shape_b.get(i % shape_b.len()).map(|p| p.1).unwrap_or(0.0);
308
309 let x = ax + (bx - ax) * et;
310 let y = ay + (by - ay) * et;
311
312 result.push(json!({ "x": x, "y": y }));
313 }
314
315 result
316}
317
318#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn test_neon_glow() {
328 let result = neon_glow(400.0, 300.0, 20.0, 5, 2.0, 0.8, "#FF00FF", 0.5);
329 assert_eq!(result.len(), 6); assert!(result[0].get("radius").is_some());
331 }
332
333 #[test]
334 fn test_motion_blur() {
335 let prev = vec![(0.0, 0.0), (10.0, 0.0), (20.0, 0.0)];
336 let result = motion_blur(&prev, (30.0, 0.0), 0.8, 0.8);
337 assert!(result.len() > 1); }
339
340 #[test]
341 fn test_chromatic_aberration() {
342 let result = chromatic_aberration(400.0, 300.0, 30.0, 10.0, 0.5, "circle");
343 assert_eq!(result.len(), 3); let red = &result[0];
345 let blue = &result[2];
346 assert!(red.get("x").unwrap().as_f64().unwrap() < blue.get("x").unwrap().as_f64().unwrap());
347 }
348
349 #[test]
350 fn test_bloom_effect() {
351 let sources = vec![(400.0, 300.0, 1.0, 10.0)];
352 let result = bloom_effect(&sources, 50.0, 0.8, 0.5);
353 assert!(result.len() >= 2); }
355
356 #[test]
357 fn test_particle_trails() {
358 let positions = vec![(100.0, 100.0, 5.0, 0.0)];
359 let result = particle_trails(&positions, 10, 0.85, "#FFAA00");
360 assert!(result.len() > 10); }
362
363 #[test]
364 fn test_morph_shapes() {
365 let shape_a = vec![(0.0, 0.0), (100.0, 0.0), (50.0, 100.0)]; let shape_b = vec![(0.0, 0.0), (100.0, 0.0), (100.0, 100.0), (0.0, 100.0)]; let result = morph_shapes(&shape_a, &shape_b, 0.5, "linear");
368 assert_eq!(result.len(), 4); }
370
371 #[test]
372 fn test_morph_easing() {
373 let a = vec![(0.0, 0.0)];
374 let b = vec![(100.0, 0.0)];
375
376 let linear = morph_shapes(&a, &b, 0.5, "linear");
377 let ease_in = morph_shapes(&a, &b, 0.5, "ease_in");
378
379 assert!(linear[0].get("x").unwrap().as_f64().unwrap() > ease_in[0].get("x").unwrap().as_f64().unwrap());
381 }
382}