use crate::gui::effect::PresetEffectOptions;
use crate::gui::effect::particle::Particle;
use std::f32::consts::PI;
const SEGMENTS_PER_RIBBON: usize = 120;
fn lerp_color(c1: [f32; 4], c2: [f32; 4], t: f32) -> [f32; 4] {
[
c1[0] + (c2[0] - c1[0]) * t,
c1[1] + (c2[1] - c1[1]) * t,
c1[2] + (c2[2] - c1[2]) * t,
c1[3] + (c2[3] - c1[3]) * t,
]
}
fn get_gradient_color(position: f32, colors: &[[f32; 4]]) -> [f32; 4] {
if colors.is_empty() {
return [0.4, 0.8, 1.0, 1.0]; }
if colors.len() == 1 {
return colors[0];
}
let total_segments = colors.len();
let scaled_pos = position * total_segments as f32;
let idx = (scaled_pos.floor() as usize) % total_segments;
let next_idx = (idx + 1) % total_segments;
let t = scaled_pos.fract();
lerp_color(colors[idx], colors[next_idx], t)
}
pub fn spawn(pos: f32, options: &PresetEffectOptions, width: f32, height: f32) -> Particle {
let cx = width / 2.0;
let cy = height / 2.0;
let radius = width.min(height) / 2.0;
let ribbon_count = options.ribbon_count.max(1);
let total_segments = SEGMENTS_PER_RIBBON * ribbon_count;
let total_index = (pos * total_segments as f32) as usize;
let ribbon_id = total_index / SEGMENTS_PER_RIBBON;
let segment_index = total_index % SEGMENTS_PER_RIBBON;
let angle = (segment_index as f32 / SEGMENTS_PER_RIBBON as f32) * 2.0 * PI;
let gap = 5.0;
let x = cx + angle.cos() * (radius + gap);
let y = cy + angle.sin() * (radius + gap);
let position = segment_index as f32 / SEGMENTS_PER_RIBBON as f32;
let color = get_gradient_color(position, &options.particle_colors);
let size = 1.5;
let mut particle = Particle::new(x, y)
.with_size(size)
.with_color(color)
.with_velocity(0.0, 0.0)
.with_lifetime(f32::MAX)
.as_line();
particle.custom = angle;
particle.custom2 = (ribbon_id * 1000 + segment_index) as f32;
particle.prev_position = (x, y);
particle
}
#[allow(clippy::too_many_arguments)]
fn calc_ribbon_position(
angle: f32,
time: f32,
ribbon_id: usize,
ribbon_count: usize,
options: &PresetEffectOptions,
cx: f32,
cy: f32,
radius: f32,
) -> (f32, f32) {
let wave_speed = 0.5 * options.speed;
let t = time * wave_speed;
let petal_count = 5.0;
let petal_amp = options.petal_amplitude * options.intensity;
let petals_per_ribbon = petal_count / ribbon_count as f32;
let phase_offset = ribbon_id as f32 * petals_per_ribbon * (2.0 * PI / petal_count);
let wave = (1.0 + (angle * petal_count + t + phase_offset).sin()) * 0.5 * petal_amp;
let gap = 5.0;
let dist = radius + gap + wave;
let x = cx + angle.cos() * dist;
let y = cy + angle.sin() * dist;
(x, y)
}
pub fn update(
particle: &mut Particle,
_dt: f32,
time: f32,
options: &PresetEffectOptions,
width: f32,
height: f32,
) {
let cx = width / 2.0;
let cy = height / 2.0;
let radius = width.min(height) / 2.0;
let ribbon_count = options.ribbon_count.max(1);
let encoded = particle.custom2 as usize;
let ribbon_id = encoded / 1000;
let segment_index = encoded % 1000;
let current_angle = particle.custom;
let next_segment = (segment_index + 1) % SEGMENTS_PER_RIBBON;
let next_angle = (next_segment as f32 / SEGMENTS_PER_RIBBON as f32) * 2.0 * PI;
let (curr_x, curr_y) =
calc_ribbon_position(current_angle, time, ribbon_id, ribbon_count, options, cx, cy, radius);
let (next_x, next_y) =
calc_ribbon_position(next_angle, time, ribbon_id, ribbon_count, options, cx, cy, radius);
particle.position = (curr_x, curr_y);
particle.prev_position = (next_x, next_y);
let position = segment_index as f32 / SEGMENTS_PER_RIBBON as f32;
let color = get_gradient_color(position, &options.particle_colors);
particle.color = color;
particle.alpha = 1.0;
}