use std::collections::BTreeMap;
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiPrimaryContextPass};
use ordered_float::OrderedFloat;
use ron::ser::PrettyConfig;
use crate::particles::{LifetimeCurve, ParticleEffect, RandF32, RandUsize, RandVec2};
pub fn particle_editor_plugin(app: &mut App) {
app.add_plugins(DefaultPlugins);
app.add_plugins(EguiPlugin::default());
app.add_systems(Startup, setup_camera_system);
app.add_systems(EguiPrimaryContextPass, editor_ui);
app.add_systems(Startup, setup_editor_effect);
app.insert_resource(EditorResource {
ser_field: String::new(),
status: "Ready".to_string(),
add_scale_t: 0.0,
add_scale_v: 0.0,
add_color_t: 0.0,
add_color_v: [0u8; 4],
scale_curve: LifetimeCurve::new(&[
(0.0f32, 5.0),
(2.0, 0.0)
]).0.iter().map(|u| (u.0.clone(), u.1.clone())).collect::<Vec<_>>(),
color_curve: vec![(OrderedFloat(0.0f32), [255, 0, 0, 255])]
});
}
fn setup_editor_effect(mut commands: Commands) {
commands.spawn((
ParticleEffect {
lifetime_seconds: RandF32 {
value: 2.0,
randomness: 0.1,
},
batch_spawn_delay_seconds: RandF32 { value: 0.1, randomness: 0.05 },
particles_in_batch: RandUsize {
value: 1,
randomness: 1
},
initial_linear_velocity: RandVec2 {
x: RandF32 { value: 0.0, randomness: 0.5 },
y: RandF32 { value: 10.0, randomness: 1.0 }
},
initial_angular_velocity: RandF32 { value: 1.0, randomness: 0.5 },
scale: LifetimeCurve::new(&[
(0.0f32, 5.0),
(2.0, 0.0)
]),
color: LifetimeCurve::new(&[
(0.0f32, Srgba::new(1.0, 0.0, 0.0, 1.0).into()),
]),
}
));
}
fn setup_camera_system(mut commands: Commands) {
commands.spawn(Camera2d);
}
#[derive(Resource)]
struct EditorResource {
ser_field: String,
status: String,
add_scale_t: f32,
add_scale_v: f32,
add_color_t: f32,
add_color_v: [u8; 4],
scale_curve: Vec<(OrderedFloat<f32>, f32)>,
color_curve: Vec<(OrderedFloat<f32>, [u8; 4])>
}
fn editor_ui(mut contexts: EguiContexts, effect: Single<&mut ParticleEffect>, mut editor_resource: ResMut<EditorResource>) -> Result {
let mut effect = effect.into_inner();
egui::Window::new("Particle Effect").resizable(false).show(contexts.ctx_mut()?, |ui| {
egui::Grid::new("effect").striped(true).show(ui, |ui| {
draw_rand_f32(&mut effect.lifetime_seconds, "Lifetime (seconds): ", ui);
draw_rand_f32(&mut effect.batch_spawn_delay_seconds, "Delay in between batches (seconds): ", ui);
draw_rand_usize(&mut effect.particles_in_batch, "Number of particles in batch: ", ui);
draw_rand_f32(&mut effect.initial_linear_velocity.x, "Linear velocity (x-axis, m/s): ", ui);
draw_rand_f32(&mut effect.initial_linear_velocity.y, "Linear velocity (y-axis, m/s): ", ui);
draw_rand_f32(&mut effect.initial_angular_velocity, "Angular velocity (radians/second): ", ui);
ui.separator();
ui.label("Scale curve");
if ui.button("sort").clicked() {
editor_resource.scale_curve.sort_by_key(|u|u.0);
}
ui.end_row();
editor_resource.scale_curve.retain_mut(|(k, v)| {
ui.label("scale t=");
ui.add(egui::DragValue::new(k.as_mut()).speed(0.01).range(0.0f32..=effect.lifetime_seconds.value));
ui.label("v=");
ui.add(egui::DragValue::new(v).speed(0.01));
let r = ui.button("-");
ui.end_row();
!r.clicked()
});
ui.separator();
ui.end_row();
ui.label("new scale: t=");
ui.add(egui::DragValue::new(&mut editor_resource.add_scale_t).speed(0.01).range(0.0f32..=effect.lifetime_seconds.value));
ui.label("v=");
ui.add(egui::DragValue::new(&mut editor_resource.add_scale_v).speed(0.01));
if ui.button("+").clicked() {
let new_v = (OrderedFloat(editor_resource.add_scale_t), editor_resource.add_scale_v).clone();
editor_resource.scale_curve.push(new_v);
}
ui.end_row();
effect.scale = LifetimeCurve(BTreeMap::from_iter(editor_resource.scale_curve.iter().copied()));
ui.separator();
ui.end_row();
ui.separator();
ui.label("Color curve");
if ui.button("sort").clicked() {
editor_resource.color_curve.sort_by_key(|u|u.0);
}
ui.end_row();
editor_resource.color_curve.retain_mut(|(k, v)| {
ui.label("color t=");
ui.add(egui::DragValue::new(k.as_mut()).speed(0.01).range(0.0f32..=effect.lifetime_seconds.value));
ui.label("v=");
ui.color_edit_button_srgba_unmultiplied(v);
let r = ui.button("-");
ui.end_row();
!r.clicked()
});
ui.separator();
ui.end_row();
ui.label("new color: t=");
ui.add(egui::DragValue::new(&mut editor_resource.add_color_t).speed(0.01).range(0.0f32..=effect.lifetime_seconds.value));
ui.label("v=");
ui.color_edit_button_srgba_unmultiplied(&mut editor_resource.add_color_v);
if ui.button("+").clicked() {
let new_v = (OrderedFloat(editor_resource.add_color_t), editor_resource.add_color_v).clone();
editor_resource.color_curve.push(new_v);
}
ui.end_row();
let curve_copied = editor_resource.color_curve.clone();
effect.color = LifetimeCurve(BTreeMap::from_iter(curve_copied.iter().map(
|(u, v)| (
*u,
Color::Srgba(Srgba::new(
v[0] as f32 / 256.0,
v[1] as f32 / 256.0,
v[2] as f32 / 256.0,
v[3] as f32 / 256.0
))
)
)));
ui.separator();
ui.end_row();
});
ui.horizontal(|ui| {
if ui.button("Generate").clicked() {
effect.scale = LifetimeCurve(BTreeMap::from_iter(editor_resource.scale_curve.iter().copied()));
let curve_copied = editor_resource.color_curve.clone();
effect.color = LifetimeCurve(BTreeMap::from_iter(curve_copied.iter().map(
|(u, v)| (
*u,
Color::Srgba(Srgba::new(
v[0] as f32 / 256.0,
v[1] as f32 / 256.0,
v[2] as f32 / 256.0,
v[3] as f32 / 256.0
))
)
)));
editor_resource.ser_field = ron::ser::to_string(effect.as_ref()).unwrap();
editor_resource.status = "Ready; Generated OK".to_string();
}
if ui.button("Load").clicked() {
match ron::from_str(&editor_resource.ser_field) {
Ok(e) => {
*effect = e;
editor_resource.scale_curve = effect.scale.0.iter().map(|u| (u.0.clone(), u.1.clone())).collect::<Vec<_>>();
editor_resource.color_curve = effect.color.0.iter().map(|u| (
u.0.clone(),
{
let mut r = [0u8; 4];
let srgba: Srgba = (*u.1).into();
r[0] = (srgba.red * 256.0).floor() as u8;
r[1] = (srgba.green * 256.0).floor() as u8;
r[2] = (srgba.blue * 256.0).floor() as u8;
r[3] = (srgba.alpha * 256.0).floor() as u8;
r
}
)).collect::<Vec<_>>();
},
Err(e) => {
editor_resource.status = e.to_string();
}
};
}
});
ui.text_edit_multiline(&mut editor_resource.ser_field);
ui.text_edit_multiline(&mut editor_resource.status.as_str());
});
Ok(())
}
fn draw_rand_f32(v: &mut RandF32, l: &str, ui: &mut egui::Ui) {
ui.label(l);
ui.add(egui::DragValue::new(&mut v.value).speed(0.01));
ui.label("variance:");
ui.add(egui::DragValue::new(&mut v.randomness).speed(0.01));
ui.end_row();
}
fn draw_rand_usize(v: &mut RandUsize, l: &str, ui: &mut egui::Ui) {
ui.label(l);
ui.add(egui::DragValue::new(&mut v.value).speed(0.1));
ui.label("variance:");
ui.add(egui::DragValue::new(&mut v.randomness).speed(0.1));
ui.end_row();
}