~starkingdoms/starkingdoms

321ab93273805e5822bf7d843f129a7efe36ade4 — core 5 months ago 265fc54
fix: new LifetimeCurve for peffect
M Cargo.lock => Cargo.lock +107 -0
@@ 440,6 440,26 @@ dependencies = [
]

[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1131,6 1151,45 @@ dependencies = [
]

[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 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",
]


@@ 2924,6 2985,15 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 3836,6 3906,12 @@ dependencies = [
]

[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5913,6 5989,7 @@ dependencies = [
 "bitflags 2.9.1",
 "objc2 0.6.1",
 "objc2-core-foundation",
 "objc2-core-graphics",
 "objc2-foundation 0.3.1",
]



@@ 5964,6 6041,19 @@ dependencies = [
]

[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 6033,6 6123,17 @@ dependencies = [
]

[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 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",

M crates/unified/Cargo.toml => crates/unified/Cargo.toml +6 -1
@@ 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

M crates/unified/src/lib.rs => crates/unified/src/lib.rs +3 -1
@@ 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

M crates/unified/src/main.rs => crates/unified/src/main.rs +8 -2
@@ 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()
}

A crates/unified/src/particle_editor/mod.rs => crates/unified/src/particle_editor/mod.rs +46 -0
@@ 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

M crates/unified/src/particles.rs => crates/unified/src/particles.rs +62 -15
@@ 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<f32>,

    // -- color -- //

    // Color curve over the lifetime of the particle
    pub color: ColorSpline,
    pub color: LifetimeCurve<Color>,
}

#[derive(Deserialize, Serialize)]
pub struct ScaleSpline(Vec<f32>);
impl From<ScaleSpline> for LinearSpline<f32> {
    fn from(value: ScaleSpline) -> Self {
        Self::new(value.0)
#[derive(Serialize, Deserialize)]
pub struct LifetimeCurve<P: Lerp>(BTreeMap<OrderedFloat<f32>, P>);
impl<P: Lerp> LifetimeCurve<P> {
    pub fn new(points: impl IntoIterator<Item = (f32, P)>) -> 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<P> {
        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<LinearRgba>);
impl From<ColorSpline> for LinearSpline<LinearRgba> {
    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)
    }
}



@@ 61,6 95,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,
    pub y: RandF32,