From 119dade9ffb889d48ddcccb3381958bff7eae594 Mon Sep 17 00:00:00 2001 From: Philip Linden <45497023+philiplinden@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:17:36 -0500 Subject: [PATCH] ui: add gizmos, flatten app module (#3) --- docs/devlog.md | 47 +++++++++++++++ src/app3d/controls.rs | 20 +++---- src/app3d/{ui => }/dev_tools.rs | 103 +++++++++++++++++--------------- src/app3d/gizmos.rs | 24 ++++++++ src/app3d/mod.rs | 56 +++++++++++++++-- src/app3d/{ui => }/monitors.rs | 6 +- src/app3d/ui/core.rs | 57 ------------------ src/app3d/ui/mod.rs | 7 --- src/simulator/core.rs | 5 +- src/simulator/forces/aero.rs | 10 +++- src/simulator/forces/body.rs | 6 ++ src/simulator/forces/mod.rs | 10 +++- 12 files changed, 215 insertions(+), 136 deletions(-) rename src/app3d/{ui => }/dev_tools.rs (54%) create mode 100644 src/app3d/gizmos.rs rename src/app3d/{ui => }/monitors.rs (98%) delete mode 100644 src/app3d/ui/core.rs delete mode 100644 src/app3d/ui/mod.rs diff --git a/docs/devlog.md b/docs/devlog.md index 58ca661..1d993af 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -1,5 +1,52 @@ # development log +## 2024-11-27 + +Some of my dependencies may now have Bevy 0.15 support. + +- [x] avian3d: (Jondolf/avian:main)[https://github.com/Jondolf/avian/tree/main] +- [ ] bevy_heavy: not supported yet +- [x] iyes_perf_ui: + (JohnathanFL/iyes_perf_ui:main)[https://github.com/JohnathanFL/iyes_perf_ui/tree/main] + ([this is open PR from a fork](https://github.com/IyesGames/iyes_perf_ui/pull/22)) +- [x] iyes_progress: `0.13.0-rc.1` +- [x] bevy-trait-query: + [JoJoJet/bevy-trait-query:bevy-0.15-rc](https://github.com/JoJoJet/bevy-trait-query/tree/bevy-0.15-rc) + (sort of supported, last updated for `bevy-0.15.0-rc.2`) +- [ ] bevy-inspector-egui: not supported yet + +Here are some recent projects that I might be able to steal from: + +- [bevy-motion-matching](https://github.com/kahboon0425/bevy_motion_matching) - + angular velocity debug gizmos and egui UI examples for plotting +- [siege](https://github.com/xenon615/siege) - rope example in Avian3d +- [bevy-logging](https://bevy-logging.github.io/) - advanced debug logging & + tracing example/tutorial + +Now that the forces are working and look reasonable, I can start working on the +flight dynamics. I need to start by adding a way to speed up and slow down the +simulation time, because a real flight takes hours. Next I need to add plots so +I can monitor and validate the simulation results for things like altitude, +temperature, and pressure. + +- [ ] Physics time multiplier. +- [ ] Plotting gas properties. +- [ ] Plotting balloon kinematics. + +Bonus: + +- [ ] Add a payload hanging from a tether. +- [ ] Camera follow the balloon, and maybe a scale or some reference in the + background to illustrate the balloon's size and altitude. + +Stretch: + +- [ ] Calculate drag forces and moments on arbitrary shapes. +- [ ] Add a wind field. + +I figured out how to toggle the physics gizmos at runtime and added force gizmos +that I made myself. + ## 2024-11-26 I added a `GasMonitor` to the UI for displaying the gas properties in real time. diff --git a/src/app3d/controls.rs b/src/app3d/controls.rs index 2a0c010..117667d 100644 --- a/src/app3d/controls.rs +++ b/src/app3d/controls.rs @@ -26,11 +26,11 @@ pub struct CameraControls { #[derive(Reflect)] pub struct DebugControls { - pub toggle_inspector: KeyCode, - pub toggle_wireframe: KeyCode, - pub toggle_physics_debug: KeyCode, - pub toggle_perf_ui: KeyCode, - pub toggle_anything_else: KeyCode, + pub toggle_1: KeyCode, + pub toggle_2: KeyCode, + pub toggle_3: KeyCode, + pub toggle_4: KeyCode, + pub toggle_5: KeyCode, } #[derive(Reflect)] @@ -55,11 +55,11 @@ impl Default for CameraControls { impl Default for DebugControls { fn default() -> Self { Self { - toggle_wireframe: KeyCode::F1, - toggle_inspector: KeyCode::F2, - toggle_physics_debug: KeyCode::F3, - toggle_perf_ui: KeyCode::F4, - toggle_anything_else: KeyCode::F5, + toggle_1: KeyCode::F1, + toggle_2: KeyCode::F2, + toggle_3: KeyCode::F3, + toggle_4: KeyCode::F4, + toggle_5: KeyCode::F5, } } } diff --git a/src/app3d/ui/dev_tools.rs b/src/app3d/dev_tools.rs similarity index 54% rename from src/app3d/ui/dev_tools.rs rename to src/app3d/dev_tools.rs index 1243a9f..1fd3b32 100644 --- a/src/app3d/ui/dev_tools.rs +++ b/src/app3d/dev_tools.rs @@ -1,5 +1,5 @@ //! Development tools for the game. This plugin is only enabled in dev builds. -use avian3d::debug_render::PhysicsDebugPlugin; +use avian3d::debug_render::*; #[cfg(not(target_arch = "wasm32"))] use bevy::pbr::wireframe::{WireframeConfig, WireframePlugin}; #[allow(unused_imports)] @@ -13,13 +13,15 @@ use bevy::{ input::common_conditions::input_just_pressed, prelude::*, }; -use iyes_perf_ui::prelude::*; use crate::{app3d::controls::KeyBindingsConfig, simulator::SimState}; -pub(super) fn plugin(app: &mut App) { - // Toggle the debug overlay for UI. - app.add_plugins(( +pub struct DevToolsPlugin; + +impl Plugin for DevToolsPlugin { + fn build(&self, app: &mut App) { + // Toggle the debug overlay for UI. + app.add_plugins(( // physics PhysicsDebugPlugin::default(), // performance @@ -31,12 +33,9 @@ pub(super) fn plugin(app: &mut App) { )); app.init_resource::(); - app.add_event::(); - app.add_event::(); - app.add_observer(spawn_perf_ui); - app.add_observer(despawn_perf_ui); app.add_systems(Update, log_transitions::); + app.add_systems(Update, show_physics_gizmos); // Wireframe doesn't work on WASM #[cfg(not(target_arch = "wasm32"))] @@ -46,13 +45,39 @@ pub(super) fn plugin(app: &mut App) { // use bevy_inspector_egui::quick::WorldInspectorPlugin; // app.add_plugins(WorldInspectorPlugin::new()); // } + } } -#[derive(Debug, Default, Resource)] +#[derive(Debug, Resource)] struct DebugState { wireframe: bool, - physics_debug: bool, - perf_ui: bool, + forces: bool, + physics: bool, +} + +impl Default for DebugState { + fn default() -> Self { + Self { + wireframe: false, + forces: true, + physics: false, + } + } +} + +impl DebugState { + fn toggle_wireframe(&mut self) { + self.wireframe = !self.wireframe; + warn!("wireframe debug: {}", self.wireframe); + } + fn toggle_forces(&mut self) { + self.forces = !self.forces; + warn!("forces debug: {}", self.forces); + } + fn toggle_physics(&mut self) { + self.physics = !self.physics; + warn!("physics debug: {}", self.physics); + } } #[allow(dead_code)] @@ -61,53 +86,33 @@ struct DebugUi; #[cfg(not(target_arch = "wasm32"))] fn toggle_debug_ui( - mut commands: Commands, mut wireframe_config: ResMut, mut debug_state: ResMut, key_input: Res>, key_bindings: Res, ) { - if key_input.just_pressed(key_bindings.debug_controls.toggle_wireframe) { - debug_state.wireframe = !debug_state.wireframe; + if key_input.just_pressed(key_bindings.debug_controls.toggle_1) { + debug_state.toggle_wireframe(); wireframe_config.global = !wireframe_config.global; - warn!("wireframe: {}", debug_state.wireframe); } - - if key_input.just_pressed(key_bindings.debug_controls.toggle_physics_debug) { - debug_state.physics_debug = !debug_state.physics_debug; - warn!("physics debug: {} - not implemented", debug_state.physics_debug); + if key_input.just_pressed(key_bindings.debug_controls.toggle_2) { + debug_state.toggle_forces(); } - - if key_input.just_pressed(key_bindings.debug_controls.toggle_perf_ui) { - debug_state.perf_ui = !debug_state.perf_ui; - warn!("perf ui: {}", debug_state.perf_ui); - if debug_state.perf_ui { - commands.trigger(SpawnPerfUi); - } else { - commands.trigger(DespawnPerfUi); - } + if key_input.just_pressed(key_bindings.debug_controls.toggle_3) { + debug_state.toggle_physics(); } } -#[derive(Event, Default)] -struct SpawnPerfUi; - -fn spawn_perf_ui(_trigger: Trigger, mut commands: Commands) { - info!("spawn_perf_ui"); - warn!("spawning perf ui DOES NOT WORK"); - commands.spawn((DebugUi, - PerfUiRoot::default(), - PerfUiEntryFPS::default(), - PerfUiEntryClock::default(), - )); -} - -#[derive(Event, Default)] -struct DespawnPerfUi; - -fn despawn_perf_ui(_trigger: Trigger, mut commands: Commands, ui: Query, With)>) { - info!("despawn_perf_ui"); - for ui in ui.iter() { - commands.entity(ui).despawn_recursive(); +fn show_physics_gizmos( + debug_state: Res, + mut gizmo_store: ResMut +) { + if gizmo_store.is_changed() { + let (_, physics_config) = gizmo_store.config_mut::(); + if debug_state.physics { + *physics_config = PhysicsGizmos::all(); + } else { + *physics_config = PhysicsGizmos::none(); + } } } diff --git a/src/app3d/gizmos.rs b/src/app3d/gizmos.rs new file mode 100644 index 0000000..83aade3 --- /dev/null +++ b/src/app3d/gizmos.rs @@ -0,0 +1,24 @@ +use bevy::{color::palettes::basic::*, prelude::*}; + +use crate::simulator::{forces::Force, SimState}; + +const ARROW_SCALE: f32 = 0.1; + +pub struct ForceArrowsPlugin; + +impl Plugin for ForceArrowsPlugin { + fn build(&self, app: &mut App) { + app.add_systems(PostUpdate, force_arrows); + } +} + +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 = force.color().unwrap_or(RED.into()); + gizmos.arrow(start, end, color).with_tip_length(0.1); + } + } +} diff --git a/src/app3d/mod.rs b/src/app3d/mod.rs index 08683ca..4790e1c 100644 --- a/src/app3d/mod.rs +++ b/src/app3d/mod.rs @@ -1,14 +1,20 @@ -mod scene; mod camera; -mod ui; pub mod controls; +mod gizmos; +mod scene; +mod dev_tools; +mod monitors; -use scene::ScenePlugin; -use ui::InterfacePlugin; -use controls::ControlsPlugin; use camera::CameraPlugin; +use controls::{ControlsPlugin, KeyBindingsConfig}; +use gizmos::ForceArrowsPlugin; +use scene::ScenePlugin; +use dev_tools::DevToolsPlugin; +use monitors::MonitorsPlugin; -use bevy::app::{PluginGroup, PluginGroupBuilder}; +use bevy::{app::{PluginGroup, PluginGroupBuilder}, prelude::*}; + +use crate::simulator::SimState; pub struct App3dPlugins; @@ -21,3 +27,41 @@ impl PluginGroup for App3dPlugins { .add(CameraPlugin) } } + +/// A plugin group that includes all interface-related plugins +pub struct InterfacePlugin; + +impl Plugin for InterfacePlugin { + fn build(&self, app: &mut App) { + app.add_plugins(( + PausePlayPlugin, + ForceArrowsPlugin, + MonitorsPlugin, + #[cfg(feature = "dev")] + DevToolsPlugin, + )); + } +} + +pub 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) + } + } +} diff --git a/src/app3d/ui/monitors.rs b/src/app3d/monitors.rs similarity index 98% rename from src/app3d/ui/monitors.rs rename to src/app3d/monitors.rs index 0542daa..2a03d32 100644 --- a/src/app3d/ui/monitors.rs +++ b/src/app3d/monitors.rs @@ -87,9 +87,9 @@ impl PerfUiEntry for SimStateMonitor { fn format_value(&self, value: &Self::Value) -> String { match value { - SimState::Loading => String::from("Loading"), SimState::Running => String::from("Running"), SimState::Stopped => String::from("Stopped"), + _ => String::from("Unknown"), } } @@ -118,8 +118,8 @@ impl PerfUiEntry for SimStateMonitor { fn value_color(&self, value: &Self::Value) -> Option { match *value { SimState::Running => self.color_gradient.get_color_for_value(0.0), - SimState::Stopped => self.color_gradient.get_color_for_value(10.0), - _ => self.color_gradient.get_color_for_value(5.0), + SimState::Stopped => self.color_gradient.get_color_for_value(5.0), + _ => self.color_gradient.get_color_for_value(10.0), } } diff --git a/src/app3d/ui/core.rs b/src/app3d/ui/core.rs deleted file mode 100644 index f9ee765..0000000 --- a/src/app3d/ui/core.rs +++ /dev/null @@ -1,57 +0,0 @@ -use bevy::prelude::*; -// use iyes_perf_ui::prelude::*; - -use crate::app3d::controls::KeyBindingsConfig; -use crate::simulator::SimState; - -use super::*; - -/// A plugin group that includes all interface-related plugins -pub struct InterfacePlugin; - -impl Plugin for InterfacePlugin { - fn build(&self, app: &mut App) { - app.add_plugins(( - CoreUiPlugin, - PausePlayPlugin, - monitors::MonitorsPlugin, - )); - } -} - -/// Base UI plugin. This sets up the base plugins that all other ui plugins -/// need. .Placeholder for now -pub struct CoreUiPlugin; - -impl Plugin for CoreUiPlugin { - fn build(&self, app: &mut App) { - app.add_plugins(( - // PerfUiPlugin, - #[cfg(feature = "dev")] - dev_tools::plugin, - )); - } -} - -pub 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) - } - } -} diff --git a/src/app3d/ui/mod.rs b/src/app3d/ui/mod.rs deleted file mode 100644 index a4fcc13..0000000 --- a/src/app3d/ui/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod core; -mod monitors; - -#[cfg(feature = "dev")] -mod dev_tools; - -pub use core::InterfacePlugin; diff --git a/src/simulator/core.rs b/src/simulator/core.rs index 924c874..81a8cee 100644 --- a/src/simulator/core.rs +++ b/src/simulator/core.rs @@ -54,12 +54,13 @@ impl Plugin for CorePhysicsPlugin { } } +#[allow(dead_code)] #[derive(States, Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] pub enum SimState { #[default] - Loading, - Running, Stopped, + Running, + Faulted, } #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] diff --git a/src/simulator/forces/aero.rs b/src/simulator/forces/aero.rs index 1301d90..b4242b6 100644 --- a/src/simulator/forces/aero.rs +++ b/src/simulator/forces/aero.rs @@ -20,6 +20,7 @@ impl Plugin for AeroForcesPlugin { /// Force (N) due to drag as a solid body moves through a fluid. #[derive(Component, Reflect)] pub struct Drag { + position: Vec3, flow_velocity: Vec3, ambient_density: Density, drag_area: f32, @@ -28,6 +29,7 @@ pub struct Drag { impl Default for Drag { fn default() -> Self { Self { + position: Vec3::ZERO, flow_velocity: Vec3::ZERO, ambient_density: Density::ZERO, drag_area: 0.0, @@ -38,11 +40,13 @@ impl Default for Drag { impl Drag { pub fn update( &mut self, + position: Vec3, flow_velocity: Vec3, ambient_density: Density, drag_area: f32, drag_coeff: f32, ) { + self.position = position; self.flow_velocity = flow_velocity; self.ambient_density = ambient_density; self.drag_area = drag_area; @@ -62,7 +66,10 @@ impl Force for Drag { ) } fn point_of_application(&self) -> Vec3 { - Vec3::ZERO + self.position + } + fn color(&self) -> Option { + Some(Color::srgb(1.0, 0.0, 0.0)) } } @@ -72,6 +79,7 @@ fn update_drag_parameters( ) { for (mut drag, position, velocity, balloon) in bodies.iter_mut() { drag.update( + position.0, velocity.0, atmosphere.density(position.0), PI * balloon.shape.diameter(), diff --git a/src/simulator/forces/body.rs b/src/simulator/forces/body.rs index 0050d00..0eb60bb 100644 --- a/src/simulator/forces/body.rs +++ b/src/simulator/forces/body.rs @@ -56,6 +56,9 @@ impl Force for Weight { fn point_of_application(&self) -> Vec3 { self.position } + fn color(&self) -> Option { + Some(Color::srgb(0.0, 1.0, 0.0)) + } } /// Force (N) from gravity at an altitude (m) above mean sea level. @@ -112,6 +115,9 @@ impl Force for Buoyancy { fn point_of_application(&self) -> Vec3 { self.position } + fn color(&self) -> Option { + Some(Color::srgb(0.0, 0.0, 1.0)) + } } /// Upward force (N) vector due to atmosphere displaced by the given gas volume. diff --git a/src/simulator/forces/mod.rs b/src/simulator/forces/mod.rs index fbd2f5d..4dedef0 100644 --- a/src/simulator/forces/mod.rs +++ b/src/simulator/forces/mod.rs @@ -84,7 +84,15 @@ pub trait Force { fn magnitude(&self) -> f32 { self.force().length() } - fn point_of_application(&self) -> Vec3; + fn point_of_application(&self) -> Vec3 { + Vec3::ZERO + } + fn torque(&self) -> Vec3 { + Vec3::ZERO + } + fn color(&self) -> Option { + None + } } /// Set the `ExternalForce` to the sum of all forces in the `Forces` collection.