Skip to content

Commit

Permalink
physics: restructure force systems
Browse files Browse the repository at this point in the history
  • Loading branch information
philiplinden committed Nov 16, 2024
1 parent 8cf8096 commit 365e37e
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 56 deletions.
9 changes: 4 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,23 @@ dev = [
# library.
"bevy/dynamic_linking",
"bevy/bevy_dev_tools",
"iyes_perf_ui",
]
dev_native = [
"dev",
# Enable system information plugin for native dev builds.
"bevy/sysinfo_plugin",
]
ron = ["ron", "bevy_common_assets"]
inspect = ["bevy-inspector-egui"]

[dependencies]
serde = { version = "1.0", features = ["derive"] }
ron = "0.8.1"
avian3d = { version = "0.1.2", features = ["debug-plugin"] }
bevy = "0.14.2"
bevy_common_assets = { version = "0.11.0", features = ["ron"] }
bevy_asset_loader = { version = "0.21.0", features = ["progress_tracking"] }
iyes_perf_ui = { version = "0.3.0" }
ron = { version = "0.8.1", optional = true }
bevy_common_assets = { version = "0.11.0", features = ["ron"], optional = true }
bevy-inspector-egui = { version = "0.27.0", features = ["highlight_changes"], optional = true }
iyes_perf_ui = { version = "0.3.0", optional = true }

[[bin]]
name = "yahs"
Expand Down
6 changes: 6 additions & 0 deletions docs/devlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ is what gets applied to the rigid body.
Remember that `WeightForce`, `BuoyantForce`, and `DragForce` are _components_,
not forces themselves. We name them so we can query the _kinds_ of forces,
otherwise generic force vectors would not be distinguishable from each other.
The alternative (when we can't tell which vector is which) is to add the vectors
to a "blind" array and then update the `ExternalForce` component as before,
except that when we sum them we don't know which vector is which. Now that I
think about it, that might be easier and simpler in the long run. If we need to
know each force's value and identity, we can add it to a resource or something
that the UI can query.

I fell in love with [iyes_perf_ui](https://github.com/IyesGames/iyes_perf_ui).
It is simple and easy to work with. Egui was adding a lot of overhead, more than
Expand Down
3 changes: 0 additions & 3 deletions src/simulator/balloon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use serde::Deserialize;

use super::{
ideal_gas::{GasSpecies, IdealGasBundle},
forces::ForcesBundle,
properties::*,
};

Expand All @@ -27,7 +26,6 @@ pub struct BalloonBundle {
pub balloon: Balloon,
pub gas: IdealGasBundle,
pub pbr: PbrBundle,
pub forces: ForcesBundle,
}

#[derive(Debug, Deserialize, Clone, PartialEq, Reflect)]
Expand Down Expand Up @@ -106,7 +104,6 @@ fn spawn_balloon(
transform: Transform::from_xyz(0.0, 2.0, 0.0),
..default()
},
forces: ForcesBundle::default(),
},
RigidBody::Dynamic,
));
Expand Down
98 changes: 57 additions & 41 deletions src/simulator/forces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,61 @@ pub struct ForcesPlugin;

impl Plugin for ForcesPlugin {
fn build(&self, app: &mut App) {
app.register_type::<WeightForce>();
app.register_type::<BuoyantForce>();
app.register_type::<Force>();
app.register_type::<ForceCollection>();

// Disable the default forces since we apply our own.
app.insert_resource(Gravity(Vec3::ZERO));

// Update forces before solving physics.
// Update force vectors before solving physics.
app.configure_sets(
Update,
(
ForceUpdateOrder::First,
ForceUpdateOrder::Prepare,
ForceUpdateOrder::Apply,
)
.before(PhysicsStepSet::First),
);
app.add_systems(Update, on_rigid_body_added.in_set(ForceUpdateOrder::First));
app.add_systems(
Update,
(update_weight_force, update_buoyant_force, update_drag_force)
.before(update_total_force)
.in_set(PhysicsStepSet::First),
.in_set(ForceUpdateOrder::Prepare),
);
app.add_systems(
Update,
update_total_external_force.in_set(ForceUpdateOrder::Apply),
);
}
}

/// All the forces that act on a rigid body.
#[derive(Bundle)]
pub struct ForcesBundle {
weight: WeightForce,
buoyancy: BuoyantForce,
drag: DragForce,
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
enum ForceUpdateOrder {
First,
Prepare,
Apply,
}
/// A generic struct representing any force vector applied to an entity.
/// This is not a physics force, but rather a force vector. We could use bare
/// `Vec3` structs, but this provides a type for clarity.
pub type Force = Vec3;

/// A component to store all `Force`s acting on an entity. This component collects
/// all the force vectors that act on an entity, such as weight and buoyancy.
#[derive(Component, Default, Reflect)]
pub struct ForceCollection {
pub items: Vec<Force>,
}

impl Default for ForcesBundle {
fn default() -> Self {
ForcesBundle {
weight: WeightForce(Vec3::ZERO),
buoyancy: BuoyantForce(Vec3::ZERO),
drag: DragForce(Vec3::ZERO),
}
/// Add a `ForceCollection` to entities with a `RigidBody` when they are added.
fn on_rigid_body_added(mut commands: Commands, query: Query<Entity, Added<RigidBody>>) {
for entity in &query {
commands.entity(entity).insert(ForceCollection::default());
info!("RigidBody added to entity {:?}", entity);
}
}

#[derive(Component, Reflect)]
pub struct WeightForce(Vec3);

/// Force (N) from gravity at an altitude (m) above mean sea level.
fn g(position: Vec3) -> f32 {
let altitude = position.y; // [m]
Expand All @@ -63,15 +81,14 @@ pub fn weight(position: Vec3, mass: f32) -> Vec3 {
Vec3::NEG_Y * g(position) * mass // [N]
}

fn update_weight_force(mut bodies: Query<(&mut WeightForce, &Position, &Mass), With<RigidBody>>) {
for (mut force, position, mass) in bodies.iter_mut() {
*force = WeightForce(weight(position.0, mass.kg()));
fn update_weight_force(
mut bodies: Query<(&mut ForceCollection, &Position, &Mass), With<RigidBody>>,
) {
for (mut forces, position, mass) in bodies.iter_mut() {
forces.items.push(weight(position.0, mass.kg()));
}
}

#[derive(Component, Reflect)]
pub struct BuoyantForce(Vec3);

/// Upward force (N) vector due to atmosphere displaced by the given gas volume.
/// The direction of this force is always world-space up.
pub fn buoyancy(position: Vec3, volume: Volume, ambient_density: Density) -> Vec3 {
Expand All @@ -80,28 +97,27 @@ pub fn buoyancy(position: Vec3, volume: Volume, ambient_density: Density) -> Vec

fn update_buoyant_force(
atmosphere: Res<Atmosphere>,
mut bodies: Query<(&mut BuoyantForce, &Position, &Volume), With<RigidBody>>,
mut bodies: Query<(&mut ForceCollection, &Position, &Volume), With<RigidBody>>,
) {
for (mut force, position, volume) in bodies.iter_mut() {
for (mut forces, position, volume) in bodies.iter_mut() {
let density = atmosphere.density(position.0);
*force = BuoyantForce(buoyancy(position.0, *volume, density));
forces.items.push(buoyancy(position.0, *volume, density));
}
}

#[derive(Component, Reflect)]
pub struct DragForce(Vec3);

/// Force (N) due to drag as a solid body moves through a fluid.
pub fn drag(ambient_density: f32, velocity: Vec3, drag_area: f32, drag_coeff: f32) -> Vec3 {
let direction = -velocity.normalize();
direction * drag_coeff / 2.0 * ambient_density * f32::powf(velocity.length(), 2.0) * drag_area
}

#[allow(unused_variables)]
#[allow(unused_mut)]
fn update_drag_force(
atmosphere: Res<Atmosphere>,
mut bodies: Query<
(
&mut DragForce,
&ForceCollection,
&Position,
&LinearVelocity,
// &DragArea,
Expand All @@ -113,13 +129,13 @@ fn update_drag_force(
// Todo: update drag force
}

fn update_total_force(
mut forces: Query<
(&mut ExternalForce, &WeightForce, &BuoyantForce, &DragForce),
With<RigidBody>,
>,
/// Set the `ExternalForce` to the sum of all forces in the `Forces` collection.
/// This effectively applies all the calculated force vectors to the physics
/// rigid body without regard to where the forces came from.
fn update_total_external_force(
mut forces: Query<(&mut ExternalForce, &ForceCollection), With<RigidBody>>,
) {
for (mut total_force, weight, buoyancy, drag) in forces.iter_mut() {
total_force.set_force(weight.0 + buoyancy.0 + drag.0);
for (mut physics_force, collection) in forces.iter_mut() {
physics_force.set_force(collection.items.iter().sum());
}
}
12 changes: 5 additions & 7 deletions src/ui/dev_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ const TOGGLE_WIREFRAME_KEY: KeyCode = KeyCode::F4;
pub(super) fn plugin(app: &mut App) {
// Toggle the debug overlay for UI.
app.add_plugins((
#[cfg(not(target_arch = "wasm32"))]
WireframePlugin,
// physics
PhysicsDebugPlugin::default(),
// performance
FrameTimeDiagnosticsPlugin,
EntityCountDiagnosticsPlugin,
SystemInformationDiagnosticsPlugin,
PerfUiPlugin,
// rendering
#[cfg(not(target_arch = "wasm32"))]
WireframePlugin,
));
app.add_systems(
Update,
Expand Down Expand Up @@ -74,13 +77,8 @@ fn toggle_debug_ui(
PerfUiWidgetBar::new(PerfUiEntryFrameTime::default()),
PerfUiWidgetBar::new(PerfUiEntryFrameTimeWorst::default()),
PerfUiWidgetBar::new(PerfUiEntryEntityCount::default()),
PerfUiWidgetBar::new(PerfUiEntryCpuUsage::default()),
PerfUiWidgetBar::new(PerfUiEntryMemUsage::default()),
// PerfUiEntryFrameCount::default(),
),
(
PerfUiEntryFixedTimeStep::default(),
PerfUiWidgetBar::new(PerfUiEntryFixedOverstep::default()),
PerfUiEntryRunningTime::default(),
PerfUiEntryClock::default(),
),
Expand Down

0 comments on commit 365e37e

Please sign in to comment.