Skip to content

Commit

Permalink
physics: fix drag, volume, density, and buoyancy
Browse files Browse the repository at this point in the history
  • Loading branch information
philiplinden committed Nov 27, 2024
1 parent 3de7b41 commit a284399
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 106 deletions.
82 changes: 81 additions & 1 deletion docs/devlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<SimulatedBody>`, 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
Expand Down
9 changes: 6 additions & 3 deletions src/app3d/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
// });
}
}

Expand Down Expand Up @@ -35,7 +38,7 @@ fn simple_scene(
));
}

pub fn spawn_balloon(
fn spawn_balloon(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
Expand All @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion src/app3d/ui/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
4 changes: 3 additions & 1 deletion src/app3d/ui/dev_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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::<SimState>);

// Wireframe doesn't work on WASM
#[cfg(not(target_arch = "wasm32"))]
app.add_systems(Update, toggle_debug_ui);
Expand Down
67 changes: 25 additions & 42 deletions src/app3d/ui/monitors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::simulator::{
forces::{Buoyancy, Drag, Force, Weight},
SimState, SimulatedBody,
ideal_gas::IdealGas,
balloon::Balloon,
};

pub struct MonitorsPlugin;
Expand All @@ -21,8 +22,6 @@ impl Plugin for MonitorsPlugin {
app.add_perf_ui_simple_entry::<ForceMonitor>();
app.add_perf_ui_simple_entry::<GasMonitor>();
app.add_systems(Startup, spawn_monitors);
app.add_systems(Update, update_force_monitor_values);
app.init_resource::<ForceMonitorResource>();
}
}

Expand Down Expand Up @@ -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(),
Expand All @@ -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")
}
}

Expand Down Expand Up @@ -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),
}
}

Expand All @@ -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<ForceMonitorResource>,
forces: Query<(&Weight, &Buoyancy, &Drag), With<SimulatedBody>>,
) {
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
Expand Down Expand Up @@ -179,15 +159,15 @@ 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(),
}
}
}

impl PerfUiEntry for ForceMonitor {
type Value = (f32, f32, f32);
type SystemParam = SRes<ForceMonitorResource>; // FIXME: use &(dyn Force + 'static) instead
type SystemParam = SQuery<(&'static Weight, &'static Buoyancy, &'static Drag), With<SimulatedBody>>;

fn label(&self) -> &str {
if self.label.is_empty() {
Expand Down Expand Up @@ -217,13 +197,16 @@ impl PerfUiEntry for ForceMonitor {

fn update_value(
&self,
force_resource: &mut <Self::SystemParam as SystemParam>::Item<'_, '_>,
force_resources: &mut <Self::SystemParam as SystemParam>::Item<'_, '_>,
) -> Option<Self::Value> {
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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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 <Self::SystemParam as SystemParam>::Item<'_, '_>,
items: &mut <Self::SystemParam as SystemParam>::Item<'_, '_>,
) -> Option<Self::Value> {
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(),
))
}

Expand Down
Loading

0 comments on commit a284399

Please sign in to comment.