@@ 818,7 818,7 @@ dependencies = [
"futures-lite",
"js-sys",
"parking_lot",
- "ron",
+ "ron 0.8.1",
"serde",
"thiserror 1.0.69",
"wasm-bindgen",
@@ 856,7 856,7 @@ dependencies = [
"js-sys",
"notify-debouncer-full",
"parking_lot",
- "ron",
+ "ron 0.8.1",
"serde",
"stackfuture",
"thiserror 2.0.12",
@@ 1217,7 1217,7 @@ checksum = "740289c63cb752adbad3823c49c01d479d853a10d76ccfe5ce3d1f3ea8e14ac2"
dependencies = [
"bevy 0.16.1",
"rand 0.8.5",
- "ron",
+ "ron 0.8.1",
"serde",
]
@@ 7209,6 7209,19 @@ dependencies = [
]
[[package]]
+name = "ron"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f"
+dependencies = [
+ "base64 0.22.1",
+ "bitflags 2.9.1",
+ "serde",
+ "serde_derive",
+ "unicode-ident",
+]
+
+[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 7765,6 7778,7 @@ dependencies = [
"getrandom 0.3.3",
"ordered-float 5.0.0",
"rand 0.9.1",
+ "ron 0.10.1",
"serde",
"tracing-subscriber",
"tracing-wasm",
@@ 1,12 1,29 @@
+use std::collections::BTreeMap;
use bevy::prelude::*;
-use bevy_egui::{EguiPlugin, EguiPrimaryContextPass};
+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) {
@@ 32,13 49,184 @@ fn setup_editor_effect(mut commands: Commands) {
]),
color: LifetimeCurve::new(&[
(0.0f32, Srgba::new(1.0, 0.0, 0.0, 1.0).into()),
- (0.5, Srgba::new(0.0, 1.0, 0.0, 1.0).into()),
- (1.0, Srgba::new(0.0, 0.0, 1.0, 1.0).into()),
- (1.5, Srgba::new(1.0, 0.0, 1.0, 1.0).into()),
- (2.0, Srgba::new(1.0, 1.0, 1.0, 1.0).into())
]),
}
));
}
-fn editor_ui() {}>
\ No newline at end of file
+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));
+ 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));
+ 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();
+}<
\ No newline at end of file