feat: bloom!
14 files changed, 333 insertions(+), 34 deletions(-) M Cargo.lock M crates/unified/Cargo.toml M crates/unified/assets/config/planets.pc.toml M crates/unified/assets/config/world.wc.toml A crates/unified/assets/textures/hearty_heart.png A crates/unified/assets/vector_textures/hearty_heart.svg M crates/unified/src/client/key_input.rs M crates/unified/src/client/mod.rs M crates/unified/src/client/planet/incoming_planets.rs M crates/unified/src/client/planet/indicators.rs M crates/unified/src/client_plugins.rs M crates/unified/src/config/planet.rs M crates/unified/src/config/world.rs M crates/unified/src/server/player.rs
M Cargo.lock => Cargo.lock +0 -4
@@ 7647,7 7647,6 @@ version = "0.1.0" dependencies = [ "aeronet", "aeronet_replicon", "aeronet_transport", "aeronet_websocket", "bevy 0.16.1", "bevy_common_assets", @@ 7657,15 7656,12 @@ dependencies = [ "clap", "console_error_panic_hook", "getrandom 0.3.3", "log", "ordered-float 5.0.0", "rand 0.9.1", "serde", "tracing-subscriber", "tracing-wasm", "url", "wasm-bindgen", "web-time 1.1.0", ] [[package]]
M crates/unified/Cargo.toml => crates/unified/Cargo.toml +0 -6
@@ 35,24 35,18 @@ clap = { version = "4", features = ["derive", "cargo"] } url = "2" tracing-subscriber = "0.3" log = { version = "*", features = ["max_level_debug", "release_max_level_warn"] } serde = { version = "1", features = ["derive"] } rand = "0.9" getrandom = { version = "0.3", features = [] } web-time = "1" aeronet = "0.14" aeronet_replicon = { version = "0.15", features = ["client"] } aeronet_transport = "0.14" aeronet_websocket = { version = "0.14", features = ["client"] } bevy_enoki = "0.4" ordered-float = "5" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
M crates/unified/assets/config/planets.pc.toml => crates/unified/assets/config/planets.pc.toml +1 -0
@@ 4,6 4,7 @@ sprite = "textures/sun.png" radius = 20_000.0 # m mass = 16_000_000.0 # kg default_transform = [0.0, 0.0, 0.0] special_sprite_properties = { DrawAsCircle = { Oklcha = { lightness = 10.0, chroma = 0.058, hue = 104.26, alpha = 1.0 } } } [[planets]] name = "Mercury"
M crates/unified/assets/config/world.wc.toml => crates/unified/assets/config/world.wc.toml +1 -0
@@ 8,3 8,4 @@ default_mass = 100 [hearty] thrust = 50000 spawn_at = "Earth" \ No newline at end of file
A crates/unified/assets/textures/hearty_heart.png => crates/unified/assets/textures/hearty_heart.png +0 -0
A crates/unified/assets/vector_textures/hearty_heart.svg => crates/unified/assets/vector_textures/hearty_heart.svg +246 -0
@@ 0,0 1,246 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- Created with Inkscape (http://www.inkscape.org/) --> <svg width="512" height="512" viewBox="0 0 135.46666 135.46667" version="1.1" id="svg5" inkscape:export-filename="/home/tm85/prj/stk_sprites/hearty.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96" inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" sodipodi:docname="hearty_heart.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <sodipodi:namedview id="namedview7" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:document-units="mm" showgrid="false" units="px" width="512px" showguides="true" inkscape:snap-object-midpoints="true" inkscape:snap-global="true" inkscape:guide-bbox="true" inkscape:zoom="1" inkscape:cx="309.5" inkscape:cy="245" inkscape:window-width="1920" inkscape:window-height="1055" inkscape:window-x="1920" inkscape:window-y="25" inkscape:window-maximized="1" inkscape:current-layer="layer2" inkscape:showpageshadow="2" inkscape:deskcolor="#d1d1d1" /> <defs id="defs2"> <inkscape:path-effect effect="fillet_chamfer" id="path-effect17083" is_visible="true" lpeversion="1" satellites_param="F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1" unit="px" method="auto" mode="F" radius="8" chamfer_steps="1" flexible="false" use_knot_distance="true" apply_no_radius="true" apply_with_radius="true" only_selected="false" hide_knots="false" nodesatellites_param="F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1" /> <inkscape:path-effect effect="fillet_chamfer" id="path-effect16803" is_visible="true" lpeversion="1" satellites_param="F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1" unit="px" method="auto" mode="F" radius="8" chamfer_steps="1" flexible="false" use_knot_distance="true" apply_no_radius="true" apply_with_radius="true" only_selected="false" hide_knots="false" nodesatellites_param="F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1" /> <inkscape:path-effect effect="fillet_chamfer" id="path-effect15774" is_visible="true" lpeversion="1" satellites_param="F,0,0,1,0,4.2333333,0,1 @ F,0,0,1,0,4.2333333,0,1 @ F,0,0,1,0,4.2333333,0,1 @ F,0,0,1,0,4.2333333,0,1" unit="px" method="auto" mode="F" radius="16" chamfer_steps="1" flexible="false" use_knot_distance="true" apply_no_radius="true" apply_with_radius="true" only_selected="false" hide_knots="false" nodesatellites_param="F,0,0,1,0,4.2333333,0,1 @ F,0,0,1,0,4.2333333,0,1 @ F,0,0,1,0,4.2333333,0,1 @ F,0,0,1,0,4.2333333,0,1" /> <inkscape:path-effect effect="fillet_chamfer" id="path-effect15612" is_visible="true" lpeversion="1" satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1" unit="px" method="auto" mode="F" radius="16" chamfer_steps="1" flexible="false" use_knot_distance="true" apply_no_radius="true" apply_with_radius="true" only_selected="false" hide_knots="false" nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1" /> <inkscape:path-effect effect="fillet_chamfer" id="path-effect15006" is_visible="true" lpeversion="1" satellites_param="F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,2.1166667,0,1" unit="px" method="auto" mode="F" radius="16" chamfer_steps="1" flexible="false" use_knot_distance="true" apply_no_radius="true" apply_with_radius="true" only_selected="false" hide_knots="false" nodesatellites_param="F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,2.1166667,0,1 @ F,0,0,1,0,2.1166667,0,1" /> <inkscape:path-effect effect="fillet_chamfer" id="path-effect12397" is_visible="true" lpeversion="1" satellites_param="F,0,0,1,0,2.6458333,0,5 @ F,0,0,1,0,2.6458333,0,5 @ F,0,0,1,0,2.6458333,0,5 | F,0,0,1,0,2.6458333,0,5 @ F,0,0,1,0,2.6458333,0,5 @ F,0,0,1,0,2.6458333,0,5" unit="px" method="auto" mode="F" radius="10" chamfer_steps="5" flexible="false" use_knot_distance="true" apply_no_radius="true" apply_with_radius="true" only_selected="false" hide_knots="false" nodesatellites_param="F,0,0,1,0,2.6458333,0,5 @ F,0,0,1,0,2.6458333,0,5 @ F,0,0,1,0,2.6458333,0,5 | F,0,0,1,0,2.6458333,0,5 @ F,0,0,1,0,2.6458333,0,5 @ F,0,0,1,0,2.6458333,0,5" /> <inkscape:path-effect effect="fillet_chamfer" id="path-effect12289" is_visible="true" lpeversion="1" satellites_param="C,0,0,1,0,2.6458333,0,5 @ C,0,0,1,0,2.6458333,0,5 @ C,0,0,1,0,2.6458333,0,5 | C,0,0,1,0,2.6458333,0,5 @ C,0,0,1,0,2.6458333,0,5 @ C,0,0,1,0,2.6458333,0,5" unit="px" method="auto" mode="F" radius="10" chamfer_steps="5" flexible="false" use_knot_distance="true" apply_no_radius="true" apply_with_radius="true" only_selected="false" hide_knots="false" nodesatellites_param="C,0,0,1,0,2.6458333,0,5 @ C,0,0,1,0,2.6458333,0,5 @ C,0,0,1,0,2.6458333,0,5 | C,0,0,1,0,2.6458333,0,5 @ C,0,0,1,0,2.6458333,0,5 @ C,0,0,1,0,2.6458333,0,5" /> <inkscape:path-effect effect="fillet_chamfer" id="path-effect12074" is_visible="true" lpeversion="1" satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1" unit="px" method="auto" mode="F" radius="0" chamfer_steps="1" flexible="false" use_knot_distance="true" apply_no_radius="true" apply_with_radius="true" only_selected="false" hide_knots="false" nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1" /> <filter inkscape:menu-tooltip="In and out glow with a possible offset and colorizable flood" inkscape:menu="Shadows and Glows" inkscape:label="Cutout Glow" style="color-interpolation-filters:sRGB;" id="filter13281" x="-0.23629366" y="-0.23629366" width="1.4725873" height="1.4725873"> <feGaussianBlur stdDeviation="29.536707281068317" result="blur" id="feGaussianBlur13273" /> <feComposite in="flood" in2="SourceGraphic" operator="in" result="composite" id="feComposite13277" /> <feBlend in="blur" in2="composite" mode="normal" id="feBlend13279" /> </filter> </defs> <g inkscape:groupmode="layer" id="layer2" inkscape:label="Thrusters" style="display:inline" /> <g inkscape:label="Body" inkscape:groupmode="layer" id="layer1" style="display:inline"> <g id="use12910" transform="matrix(-0.14028033,-0.14028033,0.14028033,-0.14028033,67.733335,98.694702)" style="fill:#ff0000;stroke:none;stroke-width:0"> <path d="M 0,200 V 0 h 200 a 100,100 90 0 1 0,200 100,100 90 0 1 -200,0 z" id="path13158" /> </g> <g id="use12910-2" transform="matrix(-0.14028033,-0.14028033,0.14028033,-0.14028033,67.733335,98.694702)" style="display:inline;fill:#ff0000;stroke:none;stroke-width:0;filter:url(#filter13281)"> <path d="M 0,200 V 0 h 200 a 100,100 90 0 1 0,200 100,100 90 0 1 -200,0 z" id="path13158-7" /> </g> </g> </svg>
M crates/unified/src/client/key_input.rs => crates/unified/src/client/key_input.rs +15 -3
@@ 3,14 3,26 @@ use bevy::{ ecs::{event::EventWriter, system::Res}, input::{ButtonInput, keyboard::KeyCode}, }; use bevy::prelude::ResMut; use bevy_rapier2d::render::DebugRenderContext; use crate::ecs::ThrustEvent; pub fn key_input_plugin(app: &mut App) { app.add_systems(Update, directional_keys); app.add_systems(Update, directional_keys) .add_systems(Update, debug_render_keybind); } pub fn directional_keys( fn debug_render_keybind( keys: Res<ButtonInput<KeyCode>>, mut debug_render: ResMut<DebugRenderContext> ) { if keys.just_pressed(KeyCode::F3) { debug_render.enabled = !debug_render.enabled; } } fn directional_keys( keys: Res<ButtonInput<KeyCode>>, mut thrust_event: EventWriter<ThrustEvent>, ) {
M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +22 -4
@@ 13,9 13,11 @@ use planet::incoming_planets::incoming_planets_plugin; use crate::client::key_input::key_input_plugin; use crate::client::starfield::starfield_plugin; use crate::client::ui::ui_plugin; use crate::ecs::{CursorWorldCoordinates, MainCamera, Player}; use crate::ecs::{CursorWorldCoordinates, MainCamera, Part, Player}; use aeronet_websocket::client::WebSocketClient; use bevy::core_pipeline::bloom::Bloom; use bevy::core_pipeline::fxaa::Fxaa; use bevy::core_pipeline::tonemapping::DebandDither; use bevy::prelude::*; use bevy::window::PrimaryWindow; use bevy_replicon::shared::server_entity_map::ServerEntityMap; @@ 56,14 58,24 @@ pub struct Me; fn find_me( mut commands: Commands, q_clients: Query<(Entity, &Player), Added<Player>>, q_clients: Query<(Entity, &Player, &Part), Added<Player>>, entity_map: Res<ServerEntityMap>, asset_server: Res<AssetServer>, ) { for (entity, player) in q_clients.iter() { for (entity, player, part) in q_clients.iter() { let this_id_clientside = entity_map.to_client().get(&player.client).unwrap(); if *this_id_clientside == entity { info!("hi! i've been found!"); commands.entity(entity).insert(Me); let mut heart_sprite = Sprite::from_image(asset_server.load("sprites/heart_sprite.png")); heart_sprite.custom_size = Some(Vec2::new(part.width, part.height)); heart_sprite.color = Color::srgb(20.0, 0.0, 0.0); commands.spawn(( ChildOf(entity), heart_sprite, Transform::from_xyz(0.0, 0.0, 10.0) )); } } } @@ 71,7 83,13 @@ fn find_me( fn setup_graphics(mut commands: Commands) { commands .spawn(Camera2d) .insert(Camera { ..default() }) .insert(Camera { hdr: true, clear_color: ClearColorConfig::Custom(Color::BLACK), ..default() }) .insert(Bloom::default()) .insert(DebandDither::Enabled) .insert(Fxaa::default()) .insert(MainCamera); }
M crates/unified/src/client/planet/incoming_planets.rs => crates/unified/src/client/planet/incoming_planets.rs +32 -10
@@ 1,4 1,4 @@ use crate::config::planet::Planet; use crate::config::planet::{Planet, SpecialSpriteProperties}; use bevy::prelude::*; use bevy_rapier2d::prelude::AdditionalMassProperties; @@ 10,15 10,26 @@ fn handle_incoming_planets( mut commands: Commands, new_planets: Query<(Entity, &Planet), Added<Planet>>, asset_server: Res<AssetServer>, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<ColorMaterial>> ) { for (new_entity, new_planet) in new_planets.iter() { let mut sprite = Sprite::from_image(asset_server.load(&new_planet.sprite)); sprite.custom_size = Some(Vec2::new(new_planet.radius * 2.0, new_planet.radius * 2.0)); commands .entity(new_entity) .insert(sprite) .insert(AdditionalMassProperties::Mass(new_planet.mass)); match new_planet.special_sprite_properties { Some(SpecialSpriteProperties::ForceColor(c)) => { sprite.color = c; }, _ => {} } let mut commands = commands .entity(new_entity); commands.insert(AdditionalMassProperties::Mass(new_planet.mass)) .insert(sprite); info!(?new_planet, "prepared new planet"); } } @@ 26,6 37,8 @@ fn handle_updated_planets( mut commands: Commands, updated_planets: Query<(Entity, &Planet), Changed<Planet>>, asset_server: Res<AssetServer>, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<ColorMaterial>> ) { for (updated_entity, updated_planet) in updated_planets.iter() { let mut sprite = Sprite::from_image(asset_server.load(&updated_planet.sprite)); @@ 34,12 47,21 @@ fn handle_updated_planets( updated_planet.radius * 2.0, )); commands .entity(updated_entity) match updated_planet.special_sprite_properties { Some(SpecialSpriteProperties::ForceColor(c)) => { sprite.color = c; }, _ => {} } let mut commands = commands.entity(updated_entity); commands.remove::<AdditionalMassProperties>() .insert(AdditionalMassProperties::Mass(updated_planet.mass)) .remove::<Sprite>() .remove::<AdditionalMassProperties>() .insert(sprite) .insert(AdditionalMassProperties::Mass(updated_planet.mass)); .insert(sprite); info!(?updated_planet, "updated planet"); } }
M crates/unified/src/client/planet/indicators.rs => crates/unified/src/client/planet/indicators.rs +0 -1
@@ 1,6 1,5 @@ use bevy::prelude::*; use bevy::window::PrimaryWindow; use ordered_float::OrderedFloat; use crate::client::Me; use crate::config::planet::Planet;
M crates/unified/src/client_plugins.rs => crates/unified/src/client_plugins.rs +2 -1
@@ 5,6 5,7 @@ use bevy::DefaultPlugins; use bevy::app::{PluginGroup, PluginGroupBuilder}; use bevy::log::LogPlugin; use bevy_enoki::EnokiPlugin; use bevy_rapier2d::prelude::RapierDebugRenderPlugin; use bevy_replicon::RepliconPlugins; pub struct ClientPluginGroup { @@ 21,6 22,6 @@ impl PluginGroup for ClientPluginGroup { .add(ClientPlugin { server: self.server, }) //.add(RapierDebugRenderPlugin::default()) .add(RapierDebugRenderPlugin::default().disabled()) } }
M crates/unified/src/config/planet.rs => crates/unified/src/config/planet.rs +8 -0
@@ 1,4 1,5 @@ use bevy::asset::Asset; use bevy::color::Color; use bevy::prelude::{Bundle, Component, Transform, TypePath}; use bevy_rapier2d::prelude::{AdditionalMassProperties, Collider, ReadMassProperties, RigidBody}; use serde::{Deserialize, Serialize}; @@ 12,8 13,15 @@ pub struct Planet { pub radius: f32, pub mass: f32, pub default_transform: [f32; 3], pub special_sprite_properties: Option<SpecialSpriteProperties>, } #[derive(Deserialize, TypePath, Serialize, Clone, Debug)] pub enum SpecialSpriteProperties { ForceColor(Color) } #[derive(Bundle)] pub struct PlanetBundle { pub planet: Planet,
M crates/unified/src/config/world.rs => crates/unified/src/config/world.rs +1 -0
@@ 24,4 24,5 @@ pub struct PartConfig { #[derive(Deserialize, Asset, TypePath, Clone)] pub struct HeartyConfig { pub thrust: f32, pub spawn_at: String, }
M crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +5 -5
@@ 35,16 35,16 @@ fn handle_new_players( for joined_player in &q_new_clients { info!(?joined_player, "detected joined player!"); // find earth let (earth_pos, earth_planet) = planets let (spawn_planet_pos, spawn_planet) = planets .iter() .find(|p| p.1.name == "Earth") .expect("earth is missing? (check that the planet is named 'Earth')"); .find(|p| p.1.name == wc.hearty.spawn_at) .expect(format!("spawn planet {} is missing? (check that the planet is named exactly '{}')", wc.hearty.spawn_at, wc.hearty.spawn_at).as_str()); let angle = rand::random::<f32>() * std::f32::consts::TAU; let offset = earth_planet.radius + 150.0; let offset = spawn_planet.radius + 150.0; let mut new_transform = Transform::from_xyz(angle.cos() * offset, angle.sin() * offset, 0.0); new_transform.rotate_z(angle); new_transform.translation += earth_pos.translation; new_transform.translation += spawn_planet_pos.translation; info!(?new_transform, "set player's position!");