From 321ab93273805e5822bf7d843f129a7efe36ade4 Mon Sep 17 00:00:00 2001 From: core Date: Sun, 6 Jul 2025 20:34:18 -0400 Subject: [PATCH] fix: new LifetimeCurve for peffect --- Cargo.lock | 107 ++++++++++++++++++++++ crates/unified/Cargo.toml | 7 +- crates/unified/src/lib.rs | 4 +- crates/unified/src/main.rs | 10 +- crates/unified/src/particle_editor/mod.rs | 46 ++++++++++ crates/unified/src/particles.rs | 77 +++++++++++++--- 6 files changed, 232 insertions(+), 19 deletions(-) create mode 100644 crates/unified/src/particle_editor/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 0a44d1deb7f206bed193e5f44be6c35a59d29e61..4d6360425b355a76b9958d17dfef372988838742 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -439,6 +439,26 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arboard" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55f533f8e0af236ffe5eb979b99381df3258853f00ba2e44b6e1955292c75227" +dependencies = [ + "clipboard-win", + "image 0.25.6", + "log", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "x11rb", +] + [[package]] name = "arg_enum_proc_macro" version = "0.3.4" @@ -1130,6 +1150,45 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "bevy_egui" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67c9ddb60d10926899c3eaa022aee43fb92ba507191c62e1ff935b323c5a98" +dependencies = [ + "arboard", + "bevy_app 0.16.1", + "bevy_asset 0.16.1", + "bevy_core_pipeline 0.16.1", + "bevy_derive 0.16.1", + "bevy_ecs 0.16.1", + "bevy_image", + "bevy_input 0.16.1", + "bevy_log 0.16.1", + "bevy_math 0.16.1", + "bevy_picking", + "bevy_platform", + "bevy_reflect 0.16.1", + "bevy_render 0.16.1", + "bevy_time 0.16.1", + "bevy_transform 0.16.1", + "bevy_window 0.16.1", + "bevy_winit 0.16.1", + "bytemuck", + "crossbeam-channel", + "egui", + "encase 0.10.0", + "image 0.25.6", + "js-sys", + "thread_local", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webbrowser", + "wgpu-types 24.0.0", + "winit 0.30.11", +] + [[package]] name = "bevy_encase_derive" version = "0.13.2" @@ -1587,6 +1646,7 @@ dependencies = [ "bevy_ecs 0.16.1", "bevy_input 0.16.1", "bevy_math 0.16.1", + "bevy_mesh", "bevy_platform", "bevy_reflect 0.16.1", "bevy_render 0.16.1", @@ -1594,6 +1654,7 @@ dependencies = [ "bevy_transform 0.16.1", "bevy_utils 0.16.1", "bevy_window 0.16.1", + "crossbeam-channel", "tracing", "uuid", ] @@ -2923,6 +2984,15 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + [[package]] name = "cmake" version = "0.1.54" @@ -3835,6 +3905,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "euclid" version = "0.22.11" @@ -5913,6 +5989,7 @@ dependencies = [ "bitflags 2.9.1", "objc2 0.6.1", "objc2-core-foundation", + "objc2-core-graphics", "objc2-foundation 0.3.1", ] @@ -5963,6 +6040,19 @@ dependencies = [ "objc2 0.6.1", ] +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" @@ -6032,6 +6122,17 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + [[package]] name = "objc2-link-presentation" version = "0.2.2" @@ -6210,6 +6311,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" dependencies = [ "num-traits", + "rand 0.8.5", + "serde", ] [[package]] @@ -6695,6 +6798,7 @@ dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", + "serde", ] [[package]] @@ -6734,6 +6838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.16", + "serde", ] [[package]] @@ -7651,12 +7756,14 @@ dependencies = [ "aeronet_websocket", "bevy 0.16.1", "bevy_common_assets", + "bevy_egui", "bevy_enoki", "bevy_rapier2d 0.30.0", "bevy_replicon", "clap", "console_error_panic_hook", "getrandom 0.3.3", + "ordered-float 5.0.0", "rand 0.9.1", "serde", "tracing-subscriber", diff --git a/crates/unified/Cargo.toml b/crates/unified/Cargo.toml index 1f2130a7224a9c99ba4df64202350346aafc4978..d170fdce8343fafdf0a3c03a414c24930f01d558 100644 --- a/crates/unified/Cargo.toml +++ b/crates/unified/Cargo.toml @@ -46,6 +46,10 @@ aeronet_replicon = { version = "0.15", features = ["client"] } aeronet_websocket = { version = "0.14", features = ["client"] } aeronet_transport = "0.14" +bevy_egui = "0.35" + +ordered-float = { version = "5", features = ["serde"] } + bevy_enoki = "0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -62,4 +66,5 @@ native = [ "aeronet_websocket/server", "aeronet_replicon/server" ] -wasm = ["getrandom/wasm_js"] \ No newline at end of file +wasm = ["getrandom/wasm_js"] +particle_editor = [] \ No newline at end of file diff --git a/crates/unified/src/lib.rs b/crates/unified/src/lib.rs index f37a302e6030af9fa53895de3932b9977ed6f746..f190f7f618714819cff44ca43aadef7fa0733e96 100644 --- a/crates/unified/src/lib.rs +++ b/crates/unified/src/lib.rs @@ -24,4 +24,6 @@ pub mod ecs; pub mod server; #[cfg(all(not(target_arch = "wasm32"), feature = "native"))] pub mod server_plugins; -pub mod shared_plugins; \ No newline at end of file +pub mod shared_plugins; +#[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))] +pub mod particle_editor; \ No newline at end of file diff --git a/crates/unified/src/main.rs b/crates/unified/src/main.rs index 1b94aaed4afa7d43ef0704a68bb1305400daf173..45aaacb0fb4f41cf0dd5e4666cc1e542c85ce6e5 100644 --- a/crates/unified/src/main.rs +++ b/crates/unified/src/main.rs @@ -26,6 +26,8 @@ enum Cli { #[arg(short = 'C', long)] max_clients: usize, }, + #[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))] + ParticleEditor {} } fn main() -> AppExit { @@ -44,6 +46,7 @@ fn main() -> AppExit { match cli { Cli::Client { server } => { app.add_plugins(ClientPluginGroup { server }); + app.add_plugins(SharedPluginGroup); } #[cfg(not(target_arch = "wasm32"))] Cli::Server { @@ -61,10 +64,13 @@ fn main() -> AppExit { tick_rate, max_clients, }); + app.add_plugins(SharedPluginGroup); + }, + #[cfg(all(not(target_arch = "wasm32"), feature = "particle_editor"))] + Cli::ParticleEditor {} => { + app.add_plugins(starkingdoms::particle_editor::particle_editor_plugin); } } - app.add_plugins(SharedPluginGroup); - app.run() } diff --git a/crates/unified/src/particle_editor/mod.rs b/crates/unified/src/particle_editor/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..2e33fd8c3ec942cc24accd9aba64af1e41a4774f --- /dev/null +++ b/crates/unified/src/particle_editor/mod.rs @@ -0,0 +1,46 @@ +use bevy::prelude::*; +use bevy_egui::{EguiPlugin, EguiPrimaryContextPass}; +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(EguiPrimaryContextPass, editor_ui); + app.add_systems(Startup, setup_editor_effect); +} + +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.0, 5.0), + (2.0, 0.0) + ]), + color: LifetimeCurve::new(&[ + (0.0, Srgba::new(1.0, 0.0, 0.0, 1.0)), + (0.5, Srgba::new(0.0, 1.0, 0.0, 1.0)), + (1.0, Srgba::new(0.0, 0.0, 1.0, 1.0)), + (1.5, Srgba::new(1.0, 0.0, 1.0, 1.0)), + (2.0, Srgba::new(1.0, 1.0, 1.0, 1.0)) + ]), + } + )); +} + +fn editor_ui(app: &mut App) { + +} \ No newline at end of file diff --git a/crates/unified/src/particles.rs b/crates/unified/src/particles.rs index e62dfcfcff566cdb841a7a403a93a3c4c015a04a..ff6cb2a9c0053fd3fface38024cfa78cc0bcd312 100644 --- a/crates/unified/src/particles.rs +++ b/crates/unified/src/particles.rs @@ -1,10 +1,14 @@ -use bevy::color::LinearRgba; +use std::collections::BTreeMap; +use std::ops::Bound::{Included, Unbounded}; +use bevy::color::{Color, LinearRgba}; use bevy::math::cubic_splines::LinearSpline; use bevy::math::Vec2; +use bevy::prelude::Component; +use ordered_float::{FloatCore, OrderedFloat}; use rand::Rng; use serde::{Deserialize, Serialize}; -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Component)] pub struct ParticleEffect { // -- lifetime / spawning -- // @@ -13,7 +17,7 @@ pub struct ParticleEffect { /// Delay inbetween each batch of particles spawned pub batch_spawn_delay_seconds: RandF32, /// Number of distinct particles spawned per batch - pub particles_in_batch: RandF32, + pub particles_in_batch: RandUsize, // -- velocity -- // @@ -25,26 +29,56 @@ pub struct ParticleEffect { // -- scale -- // // Scale curve over the lifetime of the particle - pub scale: ScaleSpline, + pub scale: LifetimeCurve, // -- color -- // // Color curve over the lifetime of the particle - pub color: ColorSpline, + pub color: LifetimeCurve, } -#[derive(Deserialize, Serialize)] -pub struct ScaleSpline(Vec); -impl From for LinearSpline { - fn from(value: ScaleSpline) -> Self { - Self::new(value.0) +#[derive(Serialize, Deserialize)] +pub struct LifetimeCurve(BTreeMap, P>); +impl LifetimeCurve

{ + pub fn new(points: impl IntoIterator) -> Self { + Self(BTreeMap::from_iter(points.into_iter().map(|u| (OrderedFloat(u.0), u.1)))) + } + + /// Sample for the value at T. Returns None if the curve has no points, or if T is outside + /// the domain of the points specified. + pub fn sample(&self, at: f32) -> Option

{ + if self.0.is_empty() { return None; } + + // Find A, the point "to the left" of `at` + let mut r1 = self.0.range((Included(OrderedFloat(at)), Unbounded)); + let (a_key, a_val) = r1.next()?; + + // Find B, the point "to the right" of `at` + let mut r2 = self.0.range((Unbounded, Included(OrderedFloat(at)))); + let (b_key, b_val) = r2.next()?; + + // Calculate `t` (value from 0 - 1 indicating our progress from A to B) + let t = at - *(a_key / (b_key - a_key)); + + // Lerp + Some(a_val.lerp(b_val, t)) } } -#[derive(Deserialize, Serialize)] -pub struct ColorSpline(Vec); -impl From for LinearSpline { - fn from(value: ColorSpline) -> Self { - Self::new(value.0) + + +pub trait Lerp { + fn lerp(&self, other: &Self, t: f32) -> Self; +} +impl Lerp for f32 { + fn lerp(&self, other: &Self, t: f32) -> Self { + (1.0 - t) * self + t * other + } +} +impl Lerp for Color { + fn lerp(&self, other: &Self, t: f32) -> Self { + let a: LinearRgba = (*self).into(); + let b: LinearRgba = (*other).into(); + Color::from(a * (1.0 - t) + b * t) } } @@ -60,6 +94,19 @@ impl RandF32 { } } +#[derive(Deserialize, Serialize)] +pub struct RandUsize { + pub value: usize, + pub randomness: usize +} +impl RandUsize { + pub fn sample(&self, rng: &mut impl Rng) -> usize { + let lower_bound = self.value.checked_sub(self.randomness).unwrap_or(0); + rng.random_range(lower_bound .. self.value+self.randomness) + } +} + + #[derive(Deserialize, Serialize)] pub struct RandVec2 { pub x: RandF32,