From 2faba0e2f3202cad01557e5d512bbfe290f291e5 Mon Sep 17 00:00:00 2001 From: Philip Linden Date: Wed, 27 Nov 2024 20:15:40 -0500 Subject: [PATCH 1/4] ui: add gizmos, flatten app module --- 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. From cd413a6fffc689f143f145d70dc1893d1bd8ae3a Mon Sep 17 00:00:00 2001 From: Philip Linden Date: Thu, 28 Nov 2024 23:03:25 -0500 Subject: [PATCH 2/4] debug: cleanup and gizmos --- docs/devlog.md | 5 ++ src/app3d/camera.rs | 102 +++++++++++++++++++++++++++++++++-- src/app3d/controls.rs | 14 +++-- src/app3d/dev_tools.rs | 27 ++++++++-- src/app3d/gizmos.rs | 69 ++++++++++++++++++++++-- src/app3d/monitors.rs | 22 ++++---- src/app3d/scene.rs | 4 +- src/simulator/atmosphere.rs | 4 +- src/simulator/balloon.rs | 5 +- src/simulator/core.rs | 4 -- src/simulator/forces/aero.rs | 2 +- src/simulator/forces/body.rs | 8 ++- src/simulator/forces/mod.rs | 13 +++-- src/simulator/mod.rs | 3 +- src/simulator/payload.rs | 5 +- 15 files changed, 236 insertions(+), 51 deletions(-) diff --git a/docs/devlog.md b/docs/devlog.md index 1d993af..be4954a 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -1,5 +1,10 @@ # development log +## 2024-11-28 + +I tinkered with camera controls and the new `require` attribute. Now the camera +follows the balloon. I also fixed the toggles for debug gizmos! + ## 2024-11-27 Some of my dependencies may now have Bevy 0.15 support. diff --git a/src/app3d/camera.rs b/src/app3d/camera.rs index 1f8f018..bf13ffe 100644 --- a/src/app3d/camera.rs +++ b/src/app3d/camera.rs @@ -1,20 +1,114 @@ -use bevy::prelude::*; +use bevy::{ + input::mouse::{MouseScrollUnit, MouseWheel}, + prelude::*, +}; -// use crate::controls::CameraControls; +use super::controls::KeyBindingsConfig; +use crate::simulator::Balloon; + +const INVERT_ZOOM: bool = true; pub struct CameraPlugin; impl Plugin for CameraPlugin { fn build(&self, app: &mut App) { + app.init_resource::(); app.add_systems(Startup, setup); + app.add_systems(Update, zoom_camera); + app.add_plugins(CameraFollowPlugin); } } +#[derive(Component, Default)] +#[require(Camera3d, PerspectiveProjection)] +struct MainCamera; + +/// A resource that stores the currently selected camera target. +#[derive(Resource)] +struct CameraSelection { + entity: Entity, + offset: Vec3, +} + +impl Default for CameraSelection { + fn default() -> Self { + Self { + entity: Entity::PLACEHOLDER, + offset: Vec3::new(0., 0., 10.), + } + } +} + +/// A marker component for entities that can be selected as a camera target. +#[derive(Component, Default, Reflect)] +pub struct CameraTarget; + fn setup(mut commands: Commands) { commands.spawn(( - // Note we're setting the initial position below with yaw, pitch, and radius, hence - // we don't set transform on the camera. + Name::new("Main Camera"), + MainCamera, Camera3d::default(), Transform::from_xyz(0.0, 20., 50.0).looking_at(Vec3::new(0., 20., 0.), Vec3::Y), )); } + +fn zoom_camera( + mut camera: Query<&mut PerspectiveProjection, (With, With)>, + mut evr_scroll: EventReader, + key_bindings: Res, +) { + let mut projection = camera.single_mut(); + let ctrl = &key_bindings.camera_controls; + let direction = if INVERT_ZOOM { -1.0 } else { 1.0 }; + for ev in evr_scroll.read() { + match ev.unit { + MouseScrollUnit::Line => { + projection.fov = projection.fov.clamp(ctrl.min_fov, ctrl.max_fov) + + ev.y * ctrl.zoom_step * direction; + } + MouseScrollUnit::Pixel => { + projection.fov = projection.fov.clamp(ctrl.min_fov, ctrl.max_fov) + + ev.y * ctrl.zoom_step * direction; + } + } + } +} + +struct CameraFollowPlugin; + +impl Plugin for CameraFollowPlugin { + fn build(&self, app: &mut App) { + app.add_systems(Update, (mark_new_targets, follow_selected_target)); + } +} + +fn mark_new_targets( + mut commands: Commands, + balloons: Query>, + mut selection: ResMut, +) { + for entity in &balloons { + commands.entity(entity).insert(CameraTarget); + // Focus on the newest balloon + selection.entity = entity; + } +} + +fn follow_selected_target( + selection: Res, + targets: Query<&Transform, (With, Without)>, + mut camera: Query<&mut Transform, With>, +) { + let mut cam = camera.single_mut(); + match targets.get(selection.entity) { + Ok(t) => { + // If the target exists, move the camera next to it + cam.translation = t.translation + selection.offset; + // Look at the target position + cam.look_at(t.translation, Vec3::Y); + } + Err(_) => { + // If there is no selected entity, stay where you are + } + } +} diff --git a/src/app3d/controls.rs b/src/app3d/controls.rs index 117667d..6e7d930 100644 --- a/src/app3d/controls.rs +++ b/src/app3d/controls.rs @@ -18,10 +18,13 @@ pub struct KeyBindingsConfig { #[derive(Reflect)] pub struct CameraControls { - pub modifier_pan: Option, + pub cycle_target: KeyCode, + pub modifier_pan: KeyCode, pub button_pan: MouseButton, pub button_orbit: MouseButton, - pub toggle_zoom_direction: KeyCode, + pub zoom_step: f32, + pub max_fov: f32, + pub min_fov: f32, } #[derive(Reflect)] @@ -44,10 +47,13 @@ pub struct TimeControls { impl Default for CameraControls { fn default() -> Self { Self { - modifier_pan: Some(KeyCode::ShiftLeft), + cycle_target: KeyCode::Tab, + modifier_pan: KeyCode::ShiftLeft, button_pan: MouseButton::Middle, button_orbit: MouseButton::Middle, - toggle_zoom_direction: KeyCode::KeyZ, + zoom_step: 0.01, + max_fov: 1.0, + min_fov: 0.01, } } } diff --git a/src/app3d/dev_tools.rs b/src/app3d/dev_tools.rs index 1fd3b32..9e82f41 100644 --- a/src/app3d/dev_tools.rs +++ b/src/app3d/dev_tools.rs @@ -14,7 +14,9 @@ use bevy::{ prelude::*, }; -use crate::{app3d::controls::KeyBindingsConfig, simulator::SimState}; +use crate::simulator::SimState; + +use super::{controls::KeyBindingsConfig, gizmos::ForceGizmos}; pub struct DevToolsPlugin; @@ -34,8 +36,11 @@ impl Plugin for DevToolsPlugin { app.init_resource::(); - app.add_systems(Update, log_transitions::); - app.add_systems(Update, show_physics_gizmos); + app.add_systems(Update, ( + log_transitions::, + show_force_gizmos, + show_physics_gizmos, + )); // Wireframe doesn't work on WASM #[cfg(not(target_arch = "wasm32"))] @@ -103,11 +108,25 @@ fn toggle_debug_ui( } } +fn show_force_gizmos( + debug_state: Res, + mut gizmo_store: ResMut +) { + if debug_state.is_changed() { + let (_, force_config) = gizmo_store.config_mut::(); + if debug_state.forces { + *force_config = ForceGizmos::all(); + } else { + *force_config = ForceGizmos::none(); + } + } +} + fn show_physics_gizmos( debug_state: Res, mut gizmo_store: ResMut ) { - if gizmo_store.is_changed() { + if debug_state.is_changed() { let (_, physics_config) = gizmo_store.config_mut::(); if debug_state.physics { *physics_config = PhysicsGizmos::all(); diff --git a/src/app3d/gizmos.rs b/src/app3d/gizmos.rs index 83aade3..c30d914 100644 --- a/src/app3d/gizmos.rs +++ b/src/app3d/gizmos.rs @@ -1,6 +1,6 @@ use bevy::{color::palettes::basic::*, prelude::*}; -use crate::simulator::{forces::Force, SimState}; +use crate::simulator::forces::Force; const ARROW_SCALE: f32 = 0.1; @@ -8,17 +8,76 @@ pub struct ForceArrowsPlugin; impl Plugin for ForceArrowsPlugin { fn build(&self, app: &mut App) { - app.add_systems(PostUpdate, force_arrows); + 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) { +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); + 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/monitors.rs b/src/app3d/monitors.rs index 2a03d32..1d085ad 100644 --- a/src/app3d/monitors.rs +++ b/src/app3d/monitors.rs @@ -1,16 +1,19 @@ //! UI for monitoring the simulation. #![allow(unused_imports)] use bevy::{ - ecs::system::{SystemParam, lifetimeless::{SQuery, SRes}}, + ecs::system::{ + lifetimeless::{SQuery, SRes}, + SystemParam, + }, prelude::*, }; use iyes_perf_ui::{entry::PerfUiEntry, prelude::*, utils::format_pretty_float}; use crate::simulator::{ + balloon::Balloon, forces::{Buoyancy, Drag, Force, Weight}, - SimState, SimulatedBody, ideal_gas::IdealGas, - balloon::Balloon, + SimState, }; pub struct MonitorsPlugin; @@ -167,7 +170,7 @@ impl Default for ForceMonitor { impl PerfUiEntry for ForceMonitor { type Value = (f32, f32, f32); - type SystemParam = SQuery<(&'static Weight, &'static Buoyancy, &'static Drag), With>; + type SystemParam = SQuery<(&'static Weight, &'static Buoyancy, &'static Drag), With>; fn label(&self) -> &str { if self.label.is_empty() { @@ -200,11 +203,7 @@ impl PerfUiEntry for ForceMonitor { force_resources: &mut ::Item<'_, '_>, ) -> Option { for (weight, buoyancy, drag) in force_resources.iter() { - return Some(( - weight.force().y, - buoyancy.force().y, - drag.force().y, - )) + return Some((weight.force().y, buoyancy.force().y, drag.force().y)); } None } @@ -292,7 +291,10 @@ impl PerfUiEntry for GasMonitor { mass.push_str(" kg"); species.push_str(""); } - format!("{}\n{}\n{}\n{}\n{}\n{}", species, volume, pressure, temperature, density, mass) + format!( + "{}\n{}\n{}\n{}\n{}\n{}", + species, volume, pressure, temperature, density, mass + ) } fn update_value( diff --git a/src/app3d/scene.rs b/src/app3d/scene.rs index 647fe1c..0a0cc38 100644 --- a/src/app3d/scene.rs +++ b/src/app3d/scene.rs @@ -23,7 +23,7 @@ fn simple_scene( let ground_size = Vec3::new(50.0, 0.1, 50.0); let plane = meshes.add(Plane3d::default().mesh().size(ground_size.x, ground_size.z).subdivisions(10)); let plane_material = materials.add(StandardMaterial { - base_color: Color::srgb(0.5, 0.5, 0.5), + base_color: Color::srgb(0.5, 0.5, 0.0), perceptual_roughness: 0.5, ..default() }); @@ -47,6 +47,7 @@ fn spawn_balloon( base_color: Color::srgba(1.0, 0.0, 0.0, 0.5), perceptual_roughness: 0.0, metallic: 1.0, + alpha_mode: AlphaMode::Blend, ..default() }); let sphere = Sphere::default(); @@ -54,7 +55,6 @@ fn spawn_balloon( let species = GasSpecies::helium(); commands.spawn(( Name::new("Balloon"), - SimulatedBody, BalloonBundle { balloon: Balloon { material_properties: BalloonMaterial::default(), diff --git a/src/simulator/atmosphere.rs b/src/simulator/atmosphere.rs index f5fd6e0..d0849e0 100644 --- a/src/simulator/atmosphere.rs +++ b/src/simulator/atmosphere.rs @@ -11,7 +11,7 @@ use bevy::prelude::*; use super::{ ideal_gas::{ideal_gas_density, GasSpecies}, properties::{Density, Pressure, Temperature}, - SimulationUpdateOrder, SimState, SimulatedBody, + SimulationUpdateOrder, SimState, Balloon, }; pub struct AtmospherePlugin; @@ -26,7 +26,7 @@ impl Plugin for AtmospherePlugin { } fn pause_on_out_of_bounds( - positions: Query<&Position, With>, + positions: Query<&Position, With>, mut state: ResMut>, ) { for position in positions.iter() { diff --git a/src/simulator/balloon.rs b/src/simulator/balloon.rs index 095ab65..3c78cb2 100644 --- a/src/simulator/balloon.rs +++ b/src/simulator/balloon.rs @@ -1,10 +1,10 @@ //! Properties, attributes and functions related to the balloon. -use avian3d::{math::PI, prelude::Position}; +use avian3d::{math::PI, prelude::*}; use bevy::prelude::*; use super::{ - ideal_gas::IdealGas, properties::sphere_radius_from_volume, SimulatedBody, + ideal_gas::IdealGas, properties::sphere_radius_from_volume, SimulationUpdateOrder, Volume, }; @@ -24,6 +24,7 @@ impl Plugin for BalloonPlugin { } #[derive(Component, Debug, Clone, PartialEq, Reflect)] +#[require(IdealGas, RigidBody, Mesh3d)] pub struct Balloon { pub material_properties: BalloonMaterial, pub shape: Sphere, diff --git a/src/simulator/core.rs b/src/simulator/core.rs index 81a8cee..ce61e4e 100644 --- a/src/simulator/core.rs +++ b/src/simulator/core.rs @@ -5,10 +5,6 @@ use bevy::{ prelude::*, }; -/// A marker component for entities that are simulated. -#[derive(Component, Default)] -pub struct SimulatedBody; - pub struct SimulatorPlugins; impl PluginGroup for SimulatorPlugins { diff --git a/src/simulator/forces/aero.rs b/src/simulator/forces/aero.rs index b4242b6..e01c4b1 100644 --- a/src/simulator/forces/aero.rs +++ b/src/simulator/forces/aero.rs @@ -4,7 +4,7 @@ use avian3d::{math::PI, prelude::*}; use bevy::prelude::*; use bevy_trait_query::{self, RegisterExt}; -use super::{Atmosphere, Balloon, Density, ForceUpdateOrder, Force, SimulatedBody}; +use super::{Atmosphere, Balloon, Density, ForceUpdateOrder, Force}; pub struct AeroForcesPlugin; diff --git a/src/simulator/forces/body.rs b/src/simulator/forces/body.rs index 0eb60bb..3c22e57 100644 --- a/src/simulator/forces/body.rs +++ b/src/simulator/forces/body.rs @@ -4,7 +4,7 @@ use avian3d::{math::PI, prelude::*}; use bevy::prelude::*; use bevy_trait_query::{self, RegisterExt}; -use super::{Atmosphere, Balloon, Density, Force, ForceUpdateOrder, Mass, SimulatedBody, Volume}; +use super::{Atmosphere, Balloon, Density, Force, ForceUpdateOrder, Mass, Volume}; use crate::simulator::properties::{EARTH_RADIUS_M, STANDARD_G}; pub struct BodyForcesPlugin; @@ -27,7 +27,6 @@ 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(Position, Mass)] pub struct Weight { position: Vec3, mass: f32, @@ -74,7 +73,7 @@ pub fn weight(position: Vec3, mass: f32) -> Vec3 { } fn update_weight_parameters( - mut bodies: Query<(&mut Weight, &Position, &Mass), With>, + mut bodies: Query<(&mut Weight, &Position, &Mass), With>, ) { for (mut weight, position, mass) in bodies.iter_mut() { weight.update(position.0, mass.value()); @@ -83,7 +82,6 @@ fn update_weight_parameters( /// Upward force (N) vector due to atmosphere displaced by the given gas volume. #[derive(Component, Reflect)] -#[require(Balloon, Position)] pub struct Buoyancy { position: Vec3, displaced_volume: Volume, @@ -128,7 +126,7 @@ pub fn buoyancy(position: Vec3, displaced_volume: Volume, ambient_density: Densi fn update_buoyant_parameters( atmosphere: Res, - mut bodies: Query<(&mut Buoyancy, &Position, &Balloon), With>, + mut bodies: Query<(&mut Buoyancy, &Position, &Balloon)>, ) { for (mut buoyancy, position, balloon) in bodies.iter_mut() { let ambient_density = atmosphere.density(position.0); diff --git a/src/simulator/forces/mod.rs b/src/simulator/forces/mod.rs index 4dedef0..d68bf3a 100644 --- a/src/simulator/forces/mod.rs +++ b/src/simulator/forces/mod.rs @@ -12,7 +12,7 @@ pub use aero::Drag; #[allow(unused_imports)] pub use body::{Buoyancy, Weight}; -use super::{Atmosphere, Balloon, Density, SimulatedBody, SimulationUpdateOrder, SimState, Volume}; +use super::{Atmosphere, Balloon, Density, SimulationUpdateOrder, SimState, Volume}; pub struct ForcesPlugin; impl Plugin for ForcesPlugin { @@ -62,9 +62,12 @@ pub struct ForceBundle { drag: aero::Drag, } -fn on_simulated_body_added(mut commands: Commands, query: Query>) { - for entity in &query { - commands.entity(entity).insert(ForceBundle::default()); +fn on_simulated_body_added(mut commands: Commands, query: Query<(Entity, &RigidBody), Added>) { + for (entity, rigid_body) in &query { + let mut this_entity = commands.entity(entity); + if rigid_body.is_dynamic() { + this_entity.insert(ForceBundle::default()); + } } } @@ -102,7 +105,7 @@ pub trait Force { /// TODO: preserve the position of the total force vector and apply it at that /// point instead of the center of mass. fn update_total_external_force( - mut body_forces: Query<(&mut ExternalForce, &dyn Force, &RigidBody), With>, + mut body_forces: Query<(&mut ExternalForce, &dyn Force, &RigidBody)>, ) { // Iterate over each entity that has force vector components. for (mut physics_force_component, acting_forces, rigid_body) in body_forces.iter_mut() { diff --git a/src/simulator/mod.rs b/src/simulator/mod.rs index f0e957a..492fcd1 100644 --- a/src/simulator/mod.rs +++ b/src/simulator/mod.rs @@ -8,9 +8,10 @@ pub mod payload; pub mod properties; // Re-export the properties module at the top level. -pub use core::{SimulatorPlugins, SimState, SimulatedBody, SimulationUpdateOrder}; +pub use core::{SimulatorPlugins, SimState, SimulationUpdateOrder}; pub use properties::{Density, Pressure, Temperature, Volume, MolarMass}; pub use atmosphere::Atmosphere; pub use forces::{Weight, Buoyancy, Drag}; pub use balloon::{Balloon, BalloonBundle, BalloonMaterial}; pub use ideal_gas::{GasSpecies, IdealGas}; +pub use payload::Payload; diff --git a/src/simulator/payload.rs b/src/simulator/payload.rs index 3c107c5..ffcc056 100644 --- a/src/simulator/payload.rs +++ b/src/simulator/payload.rs @@ -12,10 +12,11 @@ impl Plugin for PayloadPlugin { } /// A thing carried by the balloon. -#[derive(Component)] +#[derive(Component, Default)] pub struct Payload; /// A tether that connects the balloon to the payload. -#[derive(Component)] +#[derive(Component, Default)] +#[require(Payload)] pub struct Tether; From 6af9c59055ecc9edea016e4f7c0e2f1bcd5bcbff Mon Sep 17 00:00:00 2001 From: Philip Linden Date: Fri, 29 Nov 2024 00:06:42 -0500 Subject: [PATCH 3/4] physics: time control --- docs/devlog.md | 6 +++ src/app3d/controls.rs | 8 +++ src/app3d/mod.rs | 29 ++++++++++- src/app3d/monitors.rs | 108 +++++++++++++++++++++++++++++++++++++-- src/simulator/balloon.rs | 3 +- src/simulator/core.rs | 9 +--- src/simulator/mod.rs | 1 + src/simulator/time.rs | 55 ++++++++++++++++++++ 8 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 src/simulator/time.rs diff --git a/docs/devlog.md b/docs/devlog.md index be4954a..c3245f9 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -5,6 +5,12 @@ I tinkered with camera controls and the new `require` attribute. Now the camera follows the balloon. I also fixed the toggles for debug gizmos! +I added controls for changing the physics time multiplier (and a debug ui) and +it is correctly changing the physics clock's relative speed, but the physics +breaks when the physics clock's relative speed goes above 1.5. Maybe it has +something to do with schedules, but more likely the timestep is simply too +large. + ## 2024-11-27 Some of my dependencies may now have Bevy 0.15 support. diff --git a/src/app3d/controls.rs b/src/app3d/controls.rs index 6e7d930..c6c8225 100644 --- a/src/app3d/controls.rs +++ b/src/app3d/controls.rs @@ -39,6 +39,10 @@ pub struct DebugControls { #[derive(Reflect)] pub struct TimeControls { pub toggle_pause: KeyCode, + pub faster: KeyCode, + pub slower: KeyCode, + pub reset_speed: KeyCode, + pub scale_step: f32, } // ============================ DEFAULT KEYBINDINGS ============================ @@ -74,6 +78,10 @@ impl Default for TimeControls { fn default() -> Self { Self { toggle_pause: KeyCode::Space, + faster: KeyCode::ArrowUp, + slower: KeyCode::ArrowDown, + reset_speed: KeyCode::Backspace, + scale_step: 0.1, } } } diff --git a/src/app3d/mod.rs b/src/app3d/mod.rs index 4790e1c..8df3737 100644 --- a/src/app3d/mod.rs +++ b/src/app3d/mod.rs @@ -14,7 +14,7 @@ use monitors::MonitorsPlugin; use bevy::{app::{PluginGroup, PluginGroupBuilder}, prelude::*}; -use crate::simulator::SimState; +use crate::simulator::{SimState, time::TimeScaleOptions}; pub struct App3dPlugins; @@ -35,6 +35,7 @@ impl Plugin for InterfacePlugin { fn build(&self, app: &mut App) { app.add_plugins(( PausePlayPlugin, + ChangeTimeScalePlugin, ForceArrowsPlugin, MonitorsPlugin, #[cfg(feature = "dev")] @@ -43,7 +44,7 @@ impl Plugin for InterfacePlugin { } } -pub struct PausePlayPlugin; +struct PausePlayPlugin; impl Plugin for PausePlayPlugin { fn build(&self, app: &mut App) { @@ -65,3 +66,27 @@ fn toggle_pause( } } } + +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/app3d/monitors.rs b/src/app3d/monitors.rs index 1d085ad..7e99306 100644 --- a/src/app3d/monitors.rs +++ b/src/app3d/monitors.rs @@ -7,6 +7,7 @@ use bevy::{ }, prelude::*, }; +use avian3d::prelude::*; use iyes_perf_ui::{entry::PerfUiEntry, prelude::*, utils::format_pretty_float}; use crate::simulator::{ @@ -24,6 +25,7 @@ impl Plugin for MonitorsPlugin { app.add_perf_ui_simple_entry::(); app.add_perf_ui_simple_entry::(); app.add_perf_ui_simple_entry::(); + app.add_perf_ui_simple_entry::(); app.add_systems(Startup, spawn_monitors); } } @@ -37,6 +39,7 @@ fn spawn_monitors(mut commands: Commands) { SimStateMonitor::default(), ForceMonitor::default(), GasMonitor::default(), + TimeScaleMonitor::default(), )); } @@ -129,7 +132,7 @@ impl PerfUiEntry for SimStateMonitor { // (optional) Called every frame to determine if the value should be highlighted fn value_highlight(&self, value: &Self::Value) -> bool { self.threshold_highlight - .map(|_| value == &SimState::Stopped) + .map(|_| value == &SimState::Faulted) .unwrap_or(false) } } @@ -251,7 +254,7 @@ impl Default for GasMonitor { threshold_highlight: Some(10.0), color_gradient: ColorGradient::new_preset_gyr(0.0, 10.0, 100.0).unwrap(), digits: 5, - precision: 2, + precision: 3, sort_key: iyes_perf_ui::utils::next_sort_key(), } } @@ -280,7 +283,7 @@ impl PerfUiEntry for GasMonitor { let mut temperature = format_pretty_float(self.digits, self.precision, value.2 as f64); let mut density = format_pretty_float(self.digits, self.precision, value.3 as f64); let mut mass = format_pretty_float(self.digits, self.precision, value.4 as f64); - let mut species = value.5.clone(); + let species = value.5.clone(); // (and append units to it) if self.display_units { @@ -289,7 +292,6 @@ impl PerfUiEntry for GasMonitor { temperature.push_str(" K"); density.push_str(" kg/m3"); mass.push_str(" kg"); - species.push_str(""); } format!( "{}\n{}\n{}\n{}\n{}\n{}", @@ -326,3 +328,101 @@ impl PerfUiEntry for GasMonitor { } } } + +#[derive(Component)] +struct TimeScaleMonitor { + /// The label text to display, to allow customization + pub label: String, + /// Should we display units? + pub display_units: bool, + /// Highlight the value if it goes above this threshold + #[allow(dead_code)] + pub threshold_highlight: Option, + /// Support color gradients! + #[allow(dead_code)] + pub color_gradient: ColorGradient, + /// Width for formatting the string + pub digits: u8, + /// Precision for formatting the string + pub precision: u8, + /// Required to ensure the entry appears in the correct place in the Perf UI + pub sort_key: i32, +} + +impl Default for TimeScaleMonitor { + fn default() -> Self { + let rygyr_gradient = ColorGradient::new().with_stops(vec![ + (0.01, Color::srgb(1.0, 0.0, 0.0).into()), + (0.5, Color::srgb(1.0, 1.0, 0.0).into()), + (1.0, Color::srgb(0.0, 1.0, 0.0).into()), + (1.5, Color::srgb(1.0, 1.0, 0.0).into()), + (10.0, Color::srgb(1.0, 0.0, 0.0).into()), + ]); + TimeScaleMonitor { + label: String::new(), + display_units: true, + threshold_highlight: Some(10.0), + color_gradient: rygyr_gradient, + digits: 5, + precision: 3, + sort_key: iyes_perf_ui::utils::next_sort_key(), + } + } +} + +impl PerfUiEntry for TimeScaleMonitor { + type Value = (f32, f32, f32); + type SystemParam = ( + SRes>, + SRes>, + ); + + fn label(&self) -> &str { + if self.label.is_empty() { + "Time" + } else { + &self.label + } + } + + fn sort_key(&self) -> i32 { + self.sort_key + } + + fn format_value(&self, value: &Self::Value) -> String { + let mut virtual_time = format_pretty_float(self.digits, self.precision, value.0 as f64); + let mut physics_time = format_pretty_float(self.digits, self.precision, value.1 as f64); + let multiplier = format_pretty_float(2, 1, value.2 as f64); + // (and append units to it) + if self.display_units { + virtual_time.push_str(" s"); + physics_time.push_str(" s"); + } + format!("real: {}\nphysics: {} (x{})", virtual_time, physics_time, multiplier) + } + + fn update_value( + &self, + (virtual_time, physics_time): &mut ::Item<'_, '_>, + ) -> Option { + Some(( + virtual_time.as_ref().elapsed_secs(), + physics_time.as_ref().elapsed_secs(), + physics_time.as_ref().relative_speed(), + )) + } + + // (optional) We should add a width hint, so that the displayed + // strings in the UI can be correctly aligned. + // This value represents the largest length the formatted string + // is expected to have. + fn width_hint(&self) -> usize { + // there is a helper we can use, since we use `format_pretty_float` + let w = iyes_perf_ui::utils::width_hint_pretty_float(self.digits, self.precision); + if self.display_units { + w + 2 + } else { + w + } + } +} diff --git a/src/simulator/balloon.rs b/src/simulator/balloon.rs index 3c78cb2..a6a4a8d 100644 --- a/src/simulator/balloon.rs +++ b/src/simulator/balloon.rs @@ -4,8 +4,7 @@ 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; diff --git a/src/simulator/core.rs b/src/simulator/core.rs index ce61e4e..1c9cc90 100644 --- a/src/simulator/core.rs +++ b/src/simulator/core.rs @@ -25,16 +25,9 @@ impl Plugin for CorePhysicsPlugin { properties::CorePropertiesPlugin, ideal_gas::IdealGasPlugin, forces::ForcesPlugin, + time::TimeScalePlugin, )); app.init_state::(); - app.add_systems( - OnEnter(SimState::Running), - |mut time: ResMut>| time.as_mut().unpause(), - ); - app.add_systems( - OnExit(SimState::Running), - |mut time: ResMut>| time.as_mut().pause(), - ); app.configure_sets( Update, ( diff --git a/src/simulator/mod.rs b/src/simulator/mod.rs index 492fcd1..00b3486 100644 --- a/src/simulator/mod.rs +++ b/src/simulator/mod.rs @@ -6,6 +6,7 @@ pub mod forces; pub mod ideal_gas; pub mod payload; pub mod properties; +pub mod time; // Re-export the properties module at the top level. pub use core::{SimulatorPlugins, SimState, SimulationUpdateOrder}; diff --git a/src/simulator/time.rs b/src/simulator/time.rs new file mode 100644 index 0000000..da3790c --- /dev/null +++ b/src/simulator/time.rs @@ -0,0 +1,55 @@ +use avian3d::prelude::*; +use bevy::prelude::*; + +use super::SimState; + +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( + PreUpdate, + modify_time_scale.run_if(in_state(SimState::Running)), + ); + } +} + +#[derive(Resource)] +pub struct TimeScaleOptions { + pub multiplier: f32, +} + +impl Default for TimeScaleOptions { + fn default() -> Self { + Self { multiplier: 1.0 } + } +} + +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); + } +} + +fn pause(mut physics_time: ResMut>, mut virtual_time: ResMut>) { + physics_time.as_mut().pause(); + virtual_time.as_mut().pause(); +} + +fn unpause(mut physics_time: ResMut>, mut virtual_time: ResMut>) { + physics_time.as_mut().unpause(); + virtual_time.as_mut().unpause(); +} From 3bf45d333b0eaa570bcfb8311caae3fc788046d4 Mon Sep 17 00:00:00 2001 From: Philip Linden Date: Wed, 27 Nov 2024 17:21:20 -0500 Subject: [PATCH 4/4] ci: skip deploys --- .github/workflows/cargo.yml | 62 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index 40da11b..2a0c0dc 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -65,34 +65,34 @@ jobs: name: doc path: target/doc - deploy: - if: github.ref == 'refs/heads/main' - needs: doc - # Grant GITHUB_TOKEN the permissions required to make a Pages deployment - permissions: - pages: write # to deploy to Pages - id-token: write # to verify the deployment originates from an appropriate source - # Deploy to the github-pages environment - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: actions/download-artifact@v4 - with: - name: doc - path: ${{ github.workspace }} - - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 - with: - path: target/doc - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 - if: github.event_name != 'pull_request' + # deploy: + # if: github.ref == 'refs/heads/main' + # needs: doc + # # Grant GITHUB_TOKEN the permissions required to make a Pages deployment + # permissions: + # pages: write # to deploy to Pages + # id-token: write # to verify the deployment originates from an appropriate source + # # Deploy to the github-pages environment + # environment: + # name: github-pages + # url: ${{ steps.deployment.outputs.page_url }} + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # with: + # fetch-depth: 0 + + # - uses: actions/download-artifact@v4 + # with: + # name: doc + # path: ${{ github.workspace }} + + # - name: Upload Pages artifact + # uses: actions/upload-pages-artifact@v3 + # with: + # path: target/doc + + # - name: Deploy to GitHub Pages + # id: deployment + # uses: actions/deploy-pages@v4 + # if: github.event_name != 'pull_request'