diff --git a/docs/devlog.md b/docs/devlog.md index ec15902..58ca661 100644 --- a/docs/devlog.md +++ b/docs/devlog.md @@ -2,7 +2,87 @@ ## 2024-11-26 -- Added a `GasMonitor` to the UI for displaying the gas properties in real time. +I added a `GasMonitor` to the UI for displaying the gas properties in real time. +With that in place, I can now notice that the balloon's density is reported as +NaN, which is probably why buoyancy is not working. I think I found the bug. The +density was not being updated in the system that updates gas properties from +the atmosphere. Fixed that, but buoyancy is still pegged at 0. Curious, the +buoyant system was querying `With`, maybe something is messed up +with the query because this system is definitely running every frame. A default +`ForceBundle` is supposed to be added to the balloon when it is added to the +world, so I'm curious why the buoyancy component is not being added. The +`Weight` component is being added... Ah, the buoyancy system is also querying +`Volume` components, weight is not. Maybe the query isn't turning up the bodies +because the `Volume` component is not being added? With Bevy 0.15 we can enforce +such a thing with the `#[require(x)]` attribute. Turns out there's no system +that updates the volume because I wanted to calculate it from the balloon's +primitive shape. I'll change buoyancy to query the balloon and get its volume +instead. That fixed the buoyancy system so it runs, but the force results might +not be correct. + +```sh +INFO yahs::simulator::forces: Weight [0, -5.1347504, 0] +INFO yahs::simulator::forces: Buoyancy [0, 3888.388, 0] +INFO yahs::simulator::forces: Drag [NaN, NaN, NaN] +``` + +I traced the drag force NaN to the drag area returning nan from +`balloon.shape.diameter()` at startup before the shape is set up. + +```sh + INFO yahs::simulator::forces::aero: balloon shape: Sphere { radius: NaN } +ERROR yahs::simulator::forces: Drag has NaN magnitude! +ERROR yahs::simulator::forces: Buoyancy has NaN magnitude! + INFO yahs::simulator::forces::aero: balloon shape: Sphere { radius: 4.2564797 } + INFO yahs::simulator::forces::aero: balloon shape: Sphere { radius: 4.2810054 } + INFO yahs::simulator::forces::aero: balloon shape: Sphere { radius: 4.2810054 } +``` + +I enforced that the mesh volumes are updated before the forces are calculated, +but there might be a few frames at the beginning where the Mesh isn't +initialized yet. Maybe it works a little differently because it's an asset? +I noticed that the balloon was being spawned after the ground plane, and scene +setup was not constrained to run in any system set. So the forces probably ran +before the scene was done loading. I added a few conditions: + +- The app starts in the `Loading` state. +- The scene advances to the `Running` state when it is done setting up the scene. +- The physics systems only run when the app is in the `Running` state. + +Weird, something else is going on. The balloon shape is a real number when it is +spawned and before the forces run, but when the forces run it is NaN. + +```sh + INFO bevy_winit::system: Creating new window "🎈" (0v1#4294967296) + INFO yahs::app3d::scene: sphere: Sphere { radius: 0.5 } + INFO bevy_dev_tools::states: yahs::simulator::core::SimState transition: Some(Loading) => Some(Running) + INFO yahs::app3d::scene: sim state: Res(State(Running)) + INFO yahs::app3d::scene: balloon spawned: 19v1 Balloon { material_properties: BalloonMaterial { name: "Latex", max_temperature: 373.0, density: 920.0, emissivity: 0.9, absorptivity: 0.9, thermal_conductivity: 0.13, specific_heat: 2000.0, poissons_ratio: 0.5, elasticity: 10000000.0, max_strain: 0.8, max_stress: 500000.0, thickness: 0.0001 }, shape: Sphere { radius: 0.5 } } Sphere { radius: 0.5 } + INFO yahs::simulator::forces::aero: balloon shape: Sphere { radius: NaN } +ERROR yahs::simulator::forces: Drag has NaN magnitude! +ERROR yahs::simulator::forces: Buoyancy has NaN magnitude! + WARN avian3d::collision::narrow_phase: 18v1#4294967314 (Ground) and 19v1#4294967315 (Balloon) are overlapping at spawn, which can result in explosive behavior. + INFO yahs::app3d::scene: sim state: Res(State(Running)) + INFO yahs::simulator::forces::aero: balloon shape: Sphere { radius: 4.2564797 } + INFO yahs::app3d::scene: sim state: Res(State(Running)) + INFO yahs::simulator::forces::aero: balloon shape: Sphere { radius: 4.2810054 } + INFO yahs::app3d::scene: sim state: Res(State(Running)) + INFO yahs::simulator::forces::aero: balloon shape: Sphere { radius: 4.2810054 } +``` + +I noticed that density is initialized to NaN with the ideal gas and the volume +of the balloon is derived from the ideal gas volume. The volume is calculated +before the force, so that explains where this NaN is coming from. + +Found it. Ideal gas law has pressure in the denominator of the volume equation. +Pressure initializes to zero by default, so the volume is NaN. Changing the +default from zero to the standard atmospheric pressure fixes that issue. + +I think the sim was crashing because it kept defaulting to use way too much +mass in the balloon. I added `with_volume()` to the ideal gas to fix that, so we +can spawn the balloon with a known volume at the default density. + +It works! ## 2024-11-24 diff --git a/src/app3d/scene.rs b/src/app3d/scene.rs index 7a07c43..647fe1c 100644 --- a/src/app3d/scene.rs +++ b/src/app3d/scene.rs @@ -7,7 +7,10 @@ pub struct ScenePlugin; impl Plugin for ScenePlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, (simple_scene, spawn_balloon)); + app.add_systems(Startup, (spawn_balloon, simple_scene)); + // app.add_systems(PostStartup, |mut commands: Commands| { + // commands.set_state(SimState::Running); + // }); } } @@ -35,7 +38,7 @@ fn simple_scene( )); } -pub fn spawn_balloon( +fn spawn_balloon( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, @@ -57,7 +60,7 @@ pub fn spawn_balloon( material_properties: BalloonMaterial::default(), shape: sphere, }, - gas: IdealGas::new(species), + gas: IdealGas::new(species).with_mass(Mass::new(0.01)), }, RigidBody::Dynamic, Collider::sphere(sphere.radius), diff --git a/src/app3d/ui/core.rs b/src/app3d/ui/core.rs index 65ad6de..f9ee765 100644 --- a/src/app3d/ui/core.rs +++ b/src/app3d/ui/core.rs @@ -51,7 +51,7 @@ fn 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/dev_tools.rs b/src/app3d/ui/dev_tools.rs index 0bee825..1243a9f 100644 --- a/src/app3d/ui/dev_tools.rs +++ b/src/app3d/ui/dev_tools.rs @@ -15,7 +15,7 @@ use bevy::{ }; use iyes_perf_ui::prelude::*; -use crate::app3d::controls::KeyBindingsConfig; +use crate::{app3d::controls::KeyBindingsConfig, simulator::SimState}; pub(super) fn plugin(app: &mut App) { // Toggle the debug overlay for UI. @@ -36,6 +36,8 @@ pub(super) fn plugin(app: &mut App) { app.add_observer(spawn_perf_ui); app.add_observer(despawn_perf_ui); + app.add_systems(Update, log_transitions::); + // Wireframe doesn't work on WASM #[cfg(not(target_arch = "wasm32"))] app.add_systems(Update, toggle_debug_ui); diff --git a/src/app3d/ui/monitors.rs b/src/app3d/ui/monitors.rs index 1d7c39b..0542daa 100644 --- a/src/app3d/ui/monitors.rs +++ b/src/app3d/ui/monitors.rs @@ -10,6 +10,7 @@ use crate::simulator::{ forces::{Buoyancy, Drag, Force, Weight}, SimState, SimulatedBody, ideal_gas::IdealGas, + balloon::Balloon, }; pub struct MonitorsPlugin; @@ -21,8 +22,6 @@ impl Plugin for MonitorsPlugin { app.add_perf_ui_simple_entry::(); app.add_perf_ui_simple_entry::(); app.add_systems(Startup, spawn_monitors); - app.add_systems(Update, update_force_monitor_values); - app.init_resource::(); } } @@ -62,7 +61,7 @@ impl Default for SimStateMonitor { label: String::new(), display_units: false, threshold_highlight: Some(10.0), - color_gradient: ColorGradient::new_preset_gyr(0.0, 10.0, 100.0).unwrap(), + color_gradient: ColorGradient::new_preset_gyr(0.0, 5.0, 10.0).unwrap(), digits: 7, precision: 0, sort_key: iyes_perf_ui::utils::next_sort_key(), @@ -88,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"), - SimState::Anomaly => String::from("ANOMALY") } } @@ -120,7 +119,7 @@ impl PerfUiEntry for SimStateMonitor { 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(100.0), + _ => self.color_gradient.get_color_for_value(5.0), } } @@ -132,25 +131,6 @@ impl PerfUiEntry for SimStateMonitor { } } -#[derive(Resource, Reflect, Default)] -struct ForceMonitorResource { - pub weight: Vec3, - pub buoyancy: Vec3, - pub drag: Vec3, -} - -fn update_force_monitor_values( - mut force_resource: ResMut, - forces: Query<(&Weight, &Buoyancy, &Drag), With>, -) { - for (weight, bouyancy, drag) in forces.iter() { - // assume there's only one simulated body for now - force_resource.weight = weight.force(); - force_resource.buoyancy = bouyancy.force(); - force_resource.drag = drag.force(); - } -} - #[derive(Component)] struct ForceMonitor { /// The label text to display, to allow customization @@ -179,7 +159,7 @@ impl Default for ForceMonitor { 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(), } } @@ -187,7 +167,7 @@ impl Default for ForceMonitor { impl PerfUiEntry for ForceMonitor { type Value = (f32, f32, f32); - type SystemParam = SRes; // FIXME: use &(dyn Force + 'static) instead + type SystemParam = SQuery<(&'static Weight, &'static Buoyancy, &'static Drag), With>; fn label(&self) -> &str { if self.label.is_empty() { @@ -217,13 +197,16 @@ impl PerfUiEntry for ForceMonitor { fn update_value( &self, - force_resource: &mut ::Item<'_, '_>, + force_resources: &mut ::Item<'_, '_>, ) -> Option { - Some(( - force_resource.weight.length(), - force_resource.buoyancy.length(), - force_resource.drag.length(), - )) + for (weight, buoyancy, drag) in force_resources.iter() { + return Some(( + weight.force().y, + buoyancy.force().y, + drag.force().y, + )) + } + None } // (optional) We should add a width hint, so that the displayed @@ -277,7 +260,7 @@ impl Default for GasMonitor { impl PerfUiEntry for GasMonitor { type Value = (f32, f32, f32, f32, f32, String); - type SystemParam = SQuery<&'static IdealGas>; + type SystemParam = SQuery<(&'static Balloon, &'static IdealGas)>; fn label(&self) -> &str { if self.label.is_empty() { @@ -309,21 +292,21 @@ impl PerfUiEntry for GasMonitor { mass.push_str(" kg"); species.push_str(""); } - format!("{}\n{}\n{}\n{}\n{}\n{}", volume, pressure, temperature, density, mass, species) + format!("{}\n{}\n{}\n{}\n{}\n{}", species, volume, pressure, temperature, density, mass) } fn update_value( &self, - gas: &mut ::Item<'_, '_>, + items: &mut ::Item<'_, '_>, ) -> Option { - let instance = gas.get_single().unwrap(); + let (balloon, gas) = items.get_single().unwrap(); Some(( - instance.volume().m3(), - instance.pressure.kilopascals(), - instance.temperature.kelvin(), - instance.density.kilograms_per_cubic_meter(), - instance.mass.value(), - instance.species.name.clone(), + balloon.shape.volume(), + gas.pressure.kilopascals(), + gas.temperature.kelvin(), + gas.density.kilograms_per_cubic_meter(), + gas.mass.value(), + gas.species.name.clone(), )) } diff --git a/src/simulator/atmosphere.rs b/src/simulator/atmosphere.rs index ad94bb9..f5fd6e0 100644 --- a/src/simulator/atmosphere.rs +++ b/src/simulator/atmosphere.rs @@ -11,14 +11,29 @@ use bevy::prelude::*; use super::{ ideal_gas::{ideal_gas_density, GasSpecies}, properties::{Density, Pressure, Temperature}, - SimState, SimulatedBody, + SimulationUpdateOrder, SimState, SimulatedBody, }; pub struct AtmospherePlugin; impl Plugin for AtmospherePlugin { fn build(&self, app: &mut App) { app.insert_resource(Atmosphere); - app.add_systems(Update, fault_if_out_of_bounds); + app.add_systems( + Update, + pause_on_out_of_bounds.in_set(SimulationUpdateOrder::First), + ); + } +} + +fn pause_on_out_of_bounds( + positions: Query<&Position, With>, + mut state: ResMut>, +) { + for position in positions.iter() { + if position.y < Atmosphere::MIN_ALTITUDE || position.y > Atmosphere::MAX_ALTITUDE { + error!("Atmosphere out of bounds: {}", position.y); + state.set(SimState::Stopped); + } } } @@ -30,24 +45,22 @@ impl Atmosphere { pub const MAX_ALTITUDE: f32 = 84999.0; // small margin to avoid panics pub const MIN_ALTITUDE: f32 = -56.0; // small margin to avoid panics - pub fn out_of_bounds(&self, position: Vec3) -> bool { - match position.y { - y if (y > Atmosphere::MAX_ALTITUDE) => true, - y if (y < Atmosphere::MIN_ALTITUDE) => true, - _ => false, - } - } - /// Temperature (K) of the atmosphere at a position. pub fn temperature(&self, position: Vec3) -> Temperature { // TODO: Look up temperature based on latitude, longitude, not just altitude - coesa_temperature(position.y).unwrap() // we should handle this better + coesa_temperature(position.y).unwrap_or_else(|e| { + error!("Atmosphere temperature out of bounds: {}", e); + Temperature::STANDARD + }) // we should handle this better } /// Pressure (Pa) of the atmosphere at a position. pub fn pressure(&self, position: Vec3) -> Pressure { // TODO: Look up pressure based on latitude, longitude, not just altitude - coesa_pressure(position.y).unwrap() // we should handle this better + coesa_pressure(position.y).unwrap_or_else(|e| { + error!("Atmosphere pressure out of bounds: {}", e); + Pressure::STANDARD + }) // we should handle this better } /// Density (kg/m³) of the atmosphere at a position. @@ -69,20 +82,13 @@ enum AtmosphereError { // max = Atmosphere::MAX_ALTITUDE // )] // OutOfBounds(f32), - OutOfBounds, + #[allow(dead_code)] + OutOfBounds(f32), } -/// If any of the simulated bodies are out of bounds, set the app state to anomaly -/// TODO: we should use an event for this -fn fault_if_out_of_bounds( - atmosphere: Res, - bodies: Query<(Entity, &Position), With>, - mut next_state: ResMut>, -) { - for (_, position) in bodies.iter() { - if atmosphere.out_of_bounds(position.0) { - next_state.set(SimState::Anomaly) - }; +impl std::fmt::Display for AtmosphereError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) } } @@ -97,7 +103,7 @@ fn coesa_temperature(altitude: f32) -> Result { } else if (25000.0..85000.0).contains(&altitude) { Ok(Temperature::from_celsius(-131.21 + 0.00299 * altitude)) } else { - Err(AtmosphereError::OutOfBounds) + Err(AtmosphereError::OutOfBounds(altitude)) } } @@ -126,6 +132,6 @@ fn coesa_pressure(altitude: f32) -> Result { ), )) } else { - Err(AtmosphereError::OutOfBounds) + Err(AtmosphereError::OutOfBounds(altitude)) } } diff --git a/src/simulator/balloon.rs b/src/simulator/balloon.rs index 000fe62..095ab65 100644 --- a/src/simulator/balloon.rs +++ b/src/simulator/balloon.rs @@ -4,10 +4,8 @@ use avian3d::{math::PI, prelude::Position}; use bevy::prelude::*; use super::{ - SimulatedBody, - SimulationUpdateOrder, - ideal_gas::IdealGas, - properties::sphere_radius_from_volume, + ideal_gas::IdealGas, properties::sphere_radius_from_volume, SimulatedBody, + SimulationUpdateOrder, Volume, }; pub struct BalloonPlugin; @@ -31,6 +29,21 @@ pub struct Balloon { pub shape: Sphere, } +impl Default for Balloon { + fn default() -> Self { + Balloon { + material_properties: BalloonMaterial::default(), + shape: Sphere::default(), + } + } +} + +impl Balloon { + pub fn volume(&self) -> Volume { + Volume(self.shape.volume()) + } +} + /// The balloon is the surface of a [`Primitive3d`] that can be stretched /// radially [`GasSpecies`] based on the pressure of the gas it contains. #[derive(Bundle)] @@ -39,6 +52,17 @@ pub struct BalloonBundle { pub gas: IdealGas, } +impl Default for BalloonBundle { + fn default() -> Self { + let balloon = Balloon::default(); + let volume = balloon.volume(); + BalloonBundle { + balloon: Balloon::default(), + gas: IdealGas::default().with_volume(volume), + } + } +} + #[derive(Component, Debug, Clone, PartialEq, Reflect)] pub struct BalloonMaterial { pub name: String, @@ -52,7 +76,7 @@ pub struct BalloonMaterial { pub elasticity: f32, // Youngs Modulus aka Modulus of Elasticity (Pa) pub max_strain: f32, // elongation at failure (decimal, unitless) 1 = original size pub max_stress: f32, // tangential stress at failure (Pa) - pub thickness: f32, // thickness of the material (m) + pub thickness: f32, // thickness of the material (m) } impl Default for BalloonMaterial { @@ -74,9 +98,7 @@ impl Default for BalloonMaterial { } } -fn update_balloon_from_gas( - mut query: Query<(&mut Balloon, &IdealGas)>, -) { +fn update_balloon_from_gas(mut query: Query<(&mut Balloon, &IdealGas)>) { for (mut balloon, gas) in query.iter_mut() { let new_radius = sphere_radius_from_volume(gas.volume().m3()); balloon.shape.radius = new_radius; diff --git a/src/simulator/core.rs b/src/simulator/core.rs index 0c85f5a..924c874 100644 --- a/src/simulator/core.rs +++ b/src/simulator/core.rs @@ -48,7 +48,8 @@ impl Plugin for CorePhysicsPlugin { SimulationUpdateOrder::Forces, SimulationUpdateOrder::Last, ).chain() - .before(PhysicsSet::Prepare), + .before(PhysicsSet::Prepare) + .run_if(in_state(SimState::Running)), ); } } @@ -56,9 +57,9 @@ impl Plugin for CorePhysicsPlugin { #[derive(States, Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] pub enum SimState { #[default] + Loading, Running, Stopped, - Anomaly, } #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] diff --git a/src/simulator/forces/aero.rs b/src/simulator/forces/aero.rs index 4e0c5ca..1301d90 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, Density, ForceUpdateOrder, Force, SimulatedBody}; +use super::{Atmosphere, Balloon, Density, ForceUpdateOrder, Force, SimulatedBody}; pub struct AeroForcesPlugin; @@ -68,14 +68,13 @@ impl Force for Drag { fn update_drag_parameters( atmosphere: Res, - mut bodies: Query<(&mut Drag, &Position, &LinearVelocity, &Collider), With>, + mut bodies: Query<(&mut Drag, &Position, &LinearVelocity, &Balloon)>, ) { - for (mut drag, position, velocity, collider) in bodies.iter_mut() { - let bounding_sphere = collider.shape().compute_bounding_sphere(&position.0.into()); + for (mut drag, position, velocity, balloon) in bodies.iter_mut() { drag.update( velocity.0, atmosphere.density(position.0), - projected_spherical_area(bounding_sphere.radius()), + PI * balloon.shape.diameter(), 1.17, // default drag coefficient for a sphere ); } @@ -91,11 +90,6 @@ pub fn drag(velocity: Vec3, ambient_density: f32, drag_area: f32, drag_coeff: f3 drag_direction * drag_magnitude } -/// Get the projected area (m^2) of a sphere with a given radius (m) -fn projected_spherical_area(radius: f32) -> f32 { - f32::powf(radius, 2.0) * PI -} - // Get the drag coefficient for a given shape and ambient conditions. // fn drag_coefficient(shape: &dyn Shape, _atmosphere: &Atmosphere) -> f32 { // match shape.shape_type() { diff --git a/src/simulator/forces/body.rs b/src/simulator/forces/body.rs index 9aec62d..0050d00 100644 --- a/src/simulator/forces/body.rs +++ b/src/simulator/forces/body.rs @@ -1,10 +1,10 @@ //! Forces applied to rigid bodies due to gravity and buoyancy. -use avian3d::prelude::*; +use avian3d::{math::PI, prelude::*}; use bevy::prelude::*; use bevy_trait_query::{self, RegisterExt}; -use super::{Atmosphere, Density, Force, ForceUpdateOrder, Mass, SimulatedBody, Volume}; +use super::{Atmosphere, Balloon, Density, Force, ForceUpdateOrder, Mass, SimulatedBody, Volume}; use crate::simulator::properties::{EARTH_RADIUS_M, STANDARD_G}; pub struct BodyForcesPlugin; @@ -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(Position, Mass)] pub struct Weight { position: Vec3, mass: f32, @@ -79,6 +80,7 @@ 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, @@ -115,15 +117,16 @@ impl Force for Buoyancy { /// Upward force (N) vector due to atmosphere displaced by the given gas volume. /// The direction of this force is always world-space up (it opposes gravity). pub fn buoyancy(position: Vec3, displaced_volume: Volume, ambient_density: Density) -> Vec3 { - Vec3::Y * (displaced_volume.cubic_meters() * ambient_density.kg_per_m3() * g(position)) + Vec3::Y * (displaced_volume.m3() * ambient_density.kg_per_m3() * g(position)) } fn update_buoyant_parameters( atmosphere: Res, - mut bodies: Query<(&mut Buoyancy, &Position, &Volume), With>, + mut bodies: Query<(&mut Buoyancy, &Position, &Balloon), With>, ) { - for (mut buoyancy, position, volume) in bodies.iter_mut() { - let density = atmosphere.density(position.0); - buoyancy.update(position.0, *volume, density); + for (mut buoyancy, position, balloon) in bodies.iter_mut() { + let ambient_density = atmosphere.density(position.0); + let displaced_volume = balloon.volume(); + buoyancy.update(position.0, displaced_volume, ambient_density); } } diff --git a/src/simulator/forces/mod.rs b/src/simulator/forces/mod.rs index df25625..fbd2f5d 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, Density, SimulatedBody, SimulationUpdateOrder, Volume}; +use super::{Atmosphere, Balloon, Density, SimulatedBody, SimulationUpdateOrder, SimState, Volume}; pub struct ForcesPlugin; impl Plugin for ForcesPlugin { @@ -37,7 +37,9 @@ impl Plugin for ForcesPlugin { ); app.add_systems( Update, - update_total_external_force.in_set(ForceUpdateOrder::Apply), + update_total_external_force + .in_set(ForceUpdateOrder::Apply) + .run_if(in_state(SimState::Running)), ); app.add_plugins((aero::AeroForcesPlugin, body::BodyForcesPlugin)); @@ -102,7 +104,11 @@ fn update_total_external_force( // Iterate over each force vector component and compute its value. for force in acting_forces.iter() { - net_force += force.force(); + if force.magnitude().is_nan() { + error!("{} has NaN magnitude!", force.name()); + } else { + net_force += force.force(); + } } physics_force_component.set_force(net_force); } diff --git a/src/simulator/ideal_gas.rs b/src/simulator/ideal_gas.rs index 00764ef..cbb3e1d 100644 --- a/src/simulator/ideal_gas.rs +++ b/src/simulator/ideal_gas.rs @@ -123,6 +123,11 @@ impl IdealGas { self } + pub fn with_volume(mut self, volume: Volume) -> Self { + self.mass = Mass::new(self.density.kg_per_m3() * volume.m3()); + self + } + fn update_density(&mut self) { self.density = ideal_gas_density(self.temperature, self.pressure, &self.species); } @@ -173,5 +178,6 @@ fn update_ideal_gas_from_atmosphere( for (mut gas, position) in query.iter_mut() { gas.pressure = atmosphere.pressure(position.0); gas.temperature = atmosphere.temperature(position.0); + gas.update_density(); } } diff --git a/src/simulator/properties.rs b/src/simulator/properties.rs index a01c421..bf82cf6 100644 --- a/src/simulator/properties.rs +++ b/src/simulator/properties.rs @@ -43,7 +43,7 @@ impl Plugin for CorePropertiesPlugin { } /// Temperature (K) -#[derive(Component,Debug, Default, Clone, Copy, PartialEq, Reflect)] +#[derive(Component,Debug, Clone, Copy, PartialEq, Reflect)] pub struct Temperature(pub Scalar); impl Temperature { @@ -66,6 +66,12 @@ impl Temperature { } } +impl Default for Temperature { + fn default() -> Self { + Temperature::STANDARD + } +} + impl Add for Temperature { type Output = Temperature; @@ -99,7 +105,7 @@ impl Div for Temperature { } /// Pressure (Pa) -#[derive(Component, Debug, Default, Clone, Copy, PartialEq, Reflect)] +#[derive(Component, Debug, Clone, Copy, PartialEq, Reflect)] pub struct Pressure(pub Scalar); impl Pressure { @@ -122,6 +128,12 @@ impl Pressure { } } +impl Default for Pressure { + fn default() -> Self { + Pressure::STANDARD + } +} + impl Add for Pressure { type Output = Pressure;