From 1c697fbe8ec0119f2d0326796fff133fa8d371ae Mon Sep 17 00:00:00 2001 From: Philip Linden <45497023+philiplinden@users.noreply.github.com> Date: Sat, 30 Nov 2024 16:35:59 -0500 Subject: [PATCH] Bevy 0.15 release (#5) * core: small reorg * checkpoint 2024-11-29 evening * deps: compiles with bevy 0.15.0 --- Cargo.toml | 4 +- docs/devlog.md | 25 ++++++ src/app3d/camera.rs | 2 +- src/app3d/controls.rs | 57 ++++++++++++++ src/app3d/dev_tools.rs | 143 ++++++++++++++++++++++++++++++++--- src/app3d/mod.rs | 71 +++-------------- src/simulator/balloon.rs | 5 +- src/simulator/forces/body.rs | 2 + src/simulator/ideal_gas.rs | 12 ++- src/simulator/time.rs | 48 ++++++++---- 10 files changed, 273 insertions(+), 96 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 541053a..9df74e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,8 @@ dev_native = [ ] [dependencies] -bevy = "0.15.0-rc.3" -avian3d = { git = "https://github.com/Jondolf/avian.git", branch = "bevy-0.15", features = ["debug-plugin"] } +bevy = "0.15.0" +avian3d = { git = "https://github.com/Jondolf/avian.git", branch = "main", features = ["debug-plugin"] } bevy-trait-query = { git = "https://github.com/JoJoJet/bevy-trait-query.git", branch = "bevy-0.15-rc" } iyes_perf_ui = { git = "https://github.com/JohnathanFL/iyes_perf_ui.git", branch = "main" } diff --git a/docs/devlog.md b/docs/devlog.md index c3245f9..e892ab1 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -1,5 +1,30 @@ # development log +## 2024-11-30 + +One last check for third party crates to see if they've officially updated to +Bevy 0.15 support. Looks like they haven't but there are branches for it. + +- [x] `avian3d` -> branch `main` +- [x] `bevy-trait-query` -> branch `bevy-0.15-rc` +- [x] `iyes_perf_ui` -> branch `main` + +## 2024-11-29 + +I should lean into the wireframe aesthetic and make this whole thing look like a +retro radar display. That sounds like a fun distraction. I borrowed some code +and shader assets from +[philiplinden/bevy-jam-5](https://github.com/philiplinden/bevy-jam-5). + +I got the app to compile and run with the CRT shader. The screen is bent as +expected, but nothing appears in the frame. I'm not sure why. + +I'm getting a lot better at Bevy. It takes much less time to make new +components, systems, Uis, etc. I'm also getting faster at finding and fixing +bugs too. + +Added togglable performance Ui. + ## 2024-11-28 I tinkered with camera controls and the new `require` attribute. Now the camera diff --git a/src/app3d/camera.rs b/src/app3d/camera.rs index bf13ffe..760a72d 100644 --- a/src/app3d/camera.rs +++ b/src/app3d/camera.rs @@ -21,7 +21,7 @@ impl Plugin for CameraPlugin { #[derive(Component, Default)] #[require(Camera3d, PerspectiveProjection)] -struct MainCamera; +pub struct MainCamera; /// A resource that stores the currently selected camera target. #[derive(Resource)] diff --git a/src/app3d/controls.rs b/src/app3d/controls.rs index c6c8225..56f5d08 100644 --- a/src/app3d/controls.rs +++ b/src/app3d/controls.rs @@ -1,10 +1,13 @@ use bevy::prelude::*; +use crate::simulator::{SimState, time::TimeScaleOptions}; + pub struct ControlsPlugin; impl Plugin for ControlsPlugin { fn build(&self, app: &mut App) { app.init_resource::(); + app.add_plugins((PausePlayPlugin, ChangeTimeScalePlugin)); } } @@ -42,6 +45,7 @@ pub struct TimeControls { pub faster: KeyCode, pub slower: KeyCode, pub reset_speed: KeyCode, + pub toggle_real_time: KeyCode, pub scale_step: f32, } @@ -81,7 +85,60 @@ impl Default for TimeControls { faster: KeyCode::ArrowUp, slower: KeyCode::ArrowDown, reset_speed: KeyCode::Backspace, + toggle_real_time: KeyCode::KeyR, scale_step: 0.1, } } } + +// ============================ CONTROL SYSTEMS ================================ + +struct PausePlayPlugin; + +impl Plugin for PausePlayPlugin { + fn build(&self, app: &mut App) { + app.add_systems(Update, toggle_pause); + } +} + +fn toggle_pause( + sim_state: Res>, + mut next_state: ResMut>, + key_input: Res>, + key_bindings: Res, +) { + if key_input.just_pressed(key_bindings.time_controls.toggle_pause) { + match sim_state.as_ref().get() { + SimState::Stopped => next_state.set(SimState::Running), + SimState::Running => next_state.set(SimState::Stopped), + _ => next_state.set(SimState::Running) + } + } +} + +struct ChangeTimeScalePlugin; + +impl Plugin for ChangeTimeScalePlugin { + fn build(&self, app: &mut App) { + app.add_systems(PreUpdate, modify_time_scale); + } +} + +fn modify_time_scale( + mut time_options: ResMut, + key_input: Res>, + key_bindings: Res, +) { + if key_input.just_pressed(key_bindings.time_controls.faster) { + time_options.multiplier += key_bindings.time_controls.scale_step; + } + if key_input.just_pressed(key_bindings.time_controls.slower) { + time_options.multiplier -= key_bindings.time_controls.scale_step; + } + if key_input.just_pressed(key_bindings.time_controls.reset_speed) { + time_options.reset(); + } + if key_input.just_pressed(key_bindings.time_controls.toggle_real_time) { + time_options.toggle_real_time(); + } +} diff --git a/src/app3d/dev_tools.rs b/src/app3d/dev_tools.rs index 9e82f41..d17b7ae 100644 --- a/src/app3d/dev_tools.rs +++ b/src/app3d/dev_tools.rs @@ -13,10 +13,10 @@ use bevy::{ input::common_conditions::input_just_pressed, prelude::*, }; +use iyes_perf_ui::{PerfUiSet, prelude::*}; -use crate::simulator::SimState; - -use super::{controls::KeyBindingsConfig, gizmos::ForceGizmos}; +use crate::simulator::{SimState, forces::Force}; +use super::controls::KeyBindingsConfig; pub struct DevToolsPlugin; @@ -26,6 +26,7 @@ impl Plugin for DevToolsPlugin { app.add_plugins(( // physics PhysicsDebugPlugin::default(), + ForceArrowsPlugin, // performance FrameTimeDiagnosticsPlugin, EntityCountDiagnosticsPlugin, @@ -38,13 +39,12 @@ impl Plugin for DevToolsPlugin { app.add_systems(Update, ( log_transitions::, + // show_performance_stats, show_force_gizmos, show_physics_gizmos, )); - // Wireframe doesn't work on WASM - #[cfg(not(target_arch = "wasm32"))] - app.add_systems(Update, toggle_debug_ui); + app.add_systems(Update, toggle_debug_ui.before(PerfUiSet::Setup)); // #[cfg(feature = "inspect")] // { // use bevy_inspector_egui::quick::WorldInspectorPlugin; @@ -55,6 +55,7 @@ impl Plugin for DevToolsPlugin { #[derive(Debug, Resource)] struct DebugState { + performance: bool, wireframe: bool, forces: bool, physics: bool, @@ -63,6 +64,7 @@ struct DebugState { impl Default for DebugState { fn default() -> Self { Self { + performance: true, wireframe: false, forces: true, physics: false, @@ -71,6 +73,10 @@ impl Default for DebugState { } impl DebugState { + fn toggle_performance(&mut self) { + self.performance = !self.performance; + warn!("performance debug: {}", self.performance); + } fn toggle_wireframe(&mut self) { self.wireframe = !self.wireframe; warn!("wireframe debug: {}", self.wireframe); @@ -85,10 +91,6 @@ impl DebugState { } } -#[allow(dead_code)] -#[derive(Component, Default)] -struct DebugUi; - #[cfg(not(target_arch = "wasm32"))] fn toggle_debug_ui( mut wireframe_config: ResMut, @@ -97,17 +99,54 @@ fn toggle_debug_ui( key_bindings: Res, ) { if key_input.just_pressed(key_bindings.debug_controls.toggle_1) { + debug_state.toggle_performance(); + } + if key_input.just_pressed(key_bindings.debug_controls.toggle_2) { + // Wireframe doesn't work on WASM + #[cfg(not(target_arch = "wasm32"))] debug_state.toggle_wireframe(); wireframe_config.global = !wireframe_config.global; } - if key_input.just_pressed(key_bindings.debug_controls.toggle_2) { + if key_input.just_pressed(key_bindings.debug_controls.toggle_3) { debug_state.toggle_forces(); } - if key_input.just_pressed(key_bindings.debug_controls.toggle_3) { + if key_input.just_pressed(key_bindings.debug_controls.toggle_4) { debug_state.toggle_physics(); } } +#[derive(Component, Default)] +struct PerformanceDebugUi; + +fn show_performance_stats( + mut commands: Commands, + debug_state: Res, + ui_root: Query>, +) { + if debug_state.is_changed() { + if debug_state.performance { + if let Ok(entity) = ui_root.get_single() { + commands.entity(entity).despawn_descendants(); + } + commands.spawn(( + PerformanceDebugUi, + PerfUiRoot { + position: PerfUiPosition::TopLeft, + ..default() + }, + PerfUiEntryFPS::default(), + PerfUiEntryFixedTimeStep::default(), + PerfUiEntryFixedOverstep::default(), + + )); + } else { + if let Ok(entity) = ui_root.get_single() { + commands.entity(entity).despawn_descendants(); + } + } + } +} + fn show_force_gizmos( debug_state: Res, mut gizmo_store: ResMut @@ -135,3 +174,83 @@ fn show_physics_gizmos( } } } + +const ARROW_SCALE: f32 = 0.1; + +pub struct ForceArrowsPlugin; + +impl Plugin for ForceArrowsPlugin { + fn build(&self, app: &mut App) { + app.init_gizmo_group::(); + app.register_type::(); + app.add_systems( + PostUpdate, + force_arrows.run_if( + |store: Res| { + store.config::().0.enabled + }), + ); + } +} + +fn force_arrows( + query: Query<&dyn Force>, + mut gizmos: Gizmos, +) { + for forces in query.iter() { + for force in forces.iter() { + let start = force.point_of_application(); + let end = start + force.force() * ARROW_SCALE; + let color = match force.color() { + Some(c) => c, + None => RED.into(), + }; + gizmos.arrow(start, end, color).with_tip_length(0.3); + } + } +} + +#[derive(Reflect, GizmoConfigGroup)] +pub struct ForceGizmos { + /// The scale of the force arrows. + pub arrow_scale: Option, + /// The color of the force arrows. If `None`, the arrows will not be rendered. + pub arrow_color: Option, + /// The length of the arrow tips. + pub tip_length: Option, + /// Determines if the forces should be hidden when not active. + pub enabled: bool, +} + +impl Default for ForceGizmos { + fn default() -> Self { + Self { + arrow_scale: Some(0.1), + arrow_color: Some(RED.into()), + tip_length: Some(0.3), + enabled: false, + } + } +} + +impl ForceGizmos { + /// Creates a [`ForceGizmos`] configuration with all rendering options enabled. + pub fn all() -> Self { + Self { + arrow_scale: Some(0.1), + arrow_color: Some(RED.into()), + tip_length: Some(0.3), + enabled: true, + } + } + + /// Creates a [`ForceGizmos`] configuration with debug rendering enabled but all options turned off. + pub fn none() -> Self { + Self { + arrow_scale: None, + arrow_color: None, + tip_length: None, + enabled: false, + } + } +} diff --git a/src/app3d/mod.rs b/src/app3d/mod.rs index 8df3737..9d8fd4e 100644 --- a/src/app3d/mod.rs +++ b/src/app3d/mod.rs @@ -1,27 +1,26 @@ mod camera; pub mod controls; -mod gizmos; -mod scene; mod dev_tools; mod monitors; +mod scene; use camera::CameraPlugin; -use controls::{ControlsPlugin, KeyBindingsConfig}; -use gizmos::ForceArrowsPlugin; -use scene::ScenePlugin; +use controls::ControlsPlugin; use dev_tools::DevToolsPlugin; use monitors::MonitorsPlugin; +use scene::ScenePlugin; -use bevy::{app::{PluginGroup, PluginGroupBuilder}, prelude::*}; - -use crate::simulator::{SimState, time::TimeScaleOptions}; +use bevy::{ + app::{PluginGroup, PluginGroupBuilder}, + prelude::*, +}; pub struct App3dPlugins; impl PluginGroup for App3dPlugins { fn build(self) -> PluginGroupBuilder { PluginGroupBuilder::start::() - .add(InterfacePlugin) + .add(UiPlugin) .add(ControlsPlugin) .add(ScenePlugin) .add(CameraPlugin) @@ -29,64 +28,14 @@ impl PluginGroup for App3dPlugins { } /// A plugin group that includes all interface-related plugins -pub struct InterfacePlugin; +pub struct UiPlugin; -impl Plugin for InterfacePlugin { +impl Plugin for UiPlugin { fn build(&self, app: &mut App) { app.add_plugins(( - PausePlayPlugin, - ChangeTimeScalePlugin, - ForceArrowsPlugin, MonitorsPlugin, #[cfg(feature = "dev")] DevToolsPlugin, )); } } - -struct PausePlayPlugin; - -impl Plugin for PausePlayPlugin { - fn build(&self, app: &mut App) { - app.add_systems(Update, toggle_pause); - } -} - -fn toggle_pause( - sim_state: Res>, - mut next_state: ResMut>, - key_input: Res>, - key_bindings: Res, -) { - if key_input.just_pressed(key_bindings.time_controls.toggle_pause) { - match sim_state.as_ref().get() { - SimState::Stopped => next_state.set(SimState::Running), - SimState::Running => next_state.set(SimState::Stopped), - _ => next_state.set(SimState::Running) - } - } -} - -struct ChangeTimeScalePlugin; - -impl Plugin for ChangeTimeScalePlugin { - fn build(&self, app: &mut App) { - app.add_systems(PreUpdate, modify_time_scale); - } -} - -fn modify_time_scale( - mut time_options: ResMut, - key_input: Res>, - key_bindings: Res, -) { - if key_input.just_pressed(key_bindings.time_controls.faster) { - time_options.multiplier += key_bindings.time_controls.scale_step; - } - if key_input.just_pressed(key_bindings.time_controls.slower) { - time_options.multiplier -= key_bindings.time_controls.scale_step; - } - if key_input.just_pressed(key_bindings.time_controls.reset_speed) { - time_options.multiplier = 1.0; - } -} diff --git a/src/simulator/balloon.rs b/src/simulator/balloon.rs index a6a4a8d..6c87c0f 100644 --- a/src/simulator/balloon.rs +++ b/src/simulator/balloon.rs @@ -4,7 +4,8 @@ use avian3d::{math::PI, prelude::*}; use bevy::prelude::*; use super::{ - ideal_gas::IdealGas, properties::sphere_radius_from_volume, SimulationUpdateOrder, Volume, + ideal_gas::IdealGas, properties::sphere_radius_from_volume, + SimulationUpdateOrder, Volume, }; pub struct BalloonPlugin; @@ -23,7 +24,7 @@ impl Plugin for BalloonPlugin { } #[derive(Component, Debug, Clone, PartialEq, Reflect)] -#[require(IdealGas, RigidBody, Mesh3d)] +#[require(IdealGas, RigidBody(|| RigidBody::Dynamic))] pub struct Balloon { pub material_properties: BalloonMaterial, pub shape: Sphere, diff --git a/src/simulator/forces/body.rs b/src/simulator/forces/body.rs index 3c22e57..0fee262 100644 --- a/src/simulator/forces/body.rs +++ b/src/simulator/forces/body.rs @@ -27,6 +27,7 @@ impl Plugin for BodyForcesPlugin { /// Downward force (N) vector due to gravity as a function of altitude (m) and /// mass (kg). The direction of this force is always world-space down. #[derive(Component, Reflect)] +#[require(Mass, Position)] pub struct Weight { position: Vec3, mass: f32, @@ -82,6 +83,7 @@ fn update_weight_parameters( /// Upward force (N) vector due to atmosphere displaced by the given gas volume. #[derive(Component, Reflect)] +#[require(Volume, Position)] pub struct Buoyancy { position: Vec3, displaced_volume: Volume, diff --git a/src/simulator/ideal_gas.rs b/src/simulator/ideal_gas.rs index cbb3e1d..03ea96c 100644 --- a/src/simulator/ideal_gas.rs +++ b/src/simulator/ideal_gas.rs @@ -49,7 +49,7 @@ impl GasSpecies { impl Default for GasSpecies { fn default() -> Self { - GasSpecies::air() + GasSpecies::helium() } } @@ -65,7 +65,7 @@ impl GasSpecies { } /// Properties of an ideal gas. For properties per unit mass, set the mass to 1. -#[derive(Component, Debug, Clone, PartialEq)] +#[derive(Component, Debug, Clone, PartialEq, Reflect)] pub struct IdealGas { pub temperature: Temperature, pub pressure: Pressure, @@ -128,13 +128,17 @@ impl IdealGas { self } - fn update_density(&mut self) { - self.density = ideal_gas_density(self.temperature, self.pressure, &self.species); + pub fn set_volume(&mut self, volume: Volume) { + self.mass = Mass::new(self.density.kg_per_m3() * volume.m3()); } pub fn volume(&self) -> Volume { ideal_gas_volume(self.temperature, self.pressure, self.mass, &self.species) } + + fn update_density(&mut self) { + self.density = ideal_gas_density(self.temperature, self.pressure, &self.species); + } } /// Volume (m³) of an ideal gas from its temperature (K), pressure (Pa), diff --git a/src/simulator/time.rs b/src/simulator/time.rs index da3790c..1cc667b 100644 --- a/src/simulator/time.rs +++ b/src/simulator/time.rs @@ -8,14 +8,8 @@ pub struct TimeScalePlugin; impl Plugin for TimeScalePlugin { fn build(&self, app: &mut App) { app.init_resource::(); - app.add_systems( - OnEnter(SimState::Stopped), - pause, - ); - app.add_systems( - OnExit(SimState::Stopped), - unpause, - ); + app.add_systems(OnEnter(SimState::Stopped), pause); + app.add_systems(OnExit(SimState::Stopped), unpause); app.add_systems( PreUpdate, modify_time_scale.run_if(in_state(SimState::Running)), @@ -23,24 +17,50 @@ impl Plugin for TimeScalePlugin { } } +const DEFAULT_MULTIPLIER: f32 = 2.0; + #[derive(Resource)] pub struct TimeScaleOptions { pub multiplier: f32, + pub real_time: bool, + pub max_multiplier: f32, + pub min_multiplier: f32, } impl Default for TimeScaleOptions { fn default() -> Self { - Self { multiplier: 1.0 } + Self { + multiplier: DEFAULT_MULTIPLIER, + real_time: false, + max_multiplier: 3.0, + min_multiplier: 0.1, + } + } +} + +impl TimeScaleOptions { + pub fn reset(&mut self) { + self.multiplier = DEFAULT_MULTIPLIER; + self.real_time = false; + } + + pub fn toggle_real_time(&mut self) { + self.real_time = !self.real_time; } } -fn modify_time_scale( - mut time: ResMut>, - options: Res, -) { +fn modify_time_scale(mut time: ResMut>, options: Res) { if options.is_changed() { info!("setting relative speed to {}", options.multiplier); - time.as_mut().set_relative_speed(options.multiplier); + if options.real_time { + time.as_mut().set_relative_speed(1.0); + } else { + time.as_mut().set_relative_speed( + options + .multiplier + .clamp(options.min_multiplier, options.max_multiplier), + ); + } } }