Skip to content

Commit

Permalink
physics: start drag calc module
Browse files Browse the repository at this point in the history
  • Loading branch information
philiplinden committed Nov 17, 2024
1 parent e4d7121 commit 499710a
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 80 deletions.
14 changes: 14 additions & 0 deletions src/simulator/aero.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use avian3d::prelude::*;
use bevy::prelude::*;

pub struct AeroPlugin;

impl Plugin for AeroPlugin {
fn build(&self, _app: &mut App) {
// TODO: Add systems to update drag parameters.
}
}

pub fn aero_drag_from_collider(collider: &Collider, velocity: LinearVelocity) -> (Vec3, f32, f32) {
(collider.drag_normal, collider.drag_area, collider.drag_coeff)
}
236 changes: 156 additions & 80 deletions src/simulator/forces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use avian3d::prelude::*;
use bevy::prelude::*;
use bevy_trait_query::{self, RegisterExt};

use super::{Atmosphere, Density, Mass, Volume};
use super::{aero::aero_drag_from_collider, Atmosphere, Density, Mass, Volume};

pub const STANDARD_G: f32 = 9.80665; // [m/s^2] standard gravitational acceleration
pub const EARTH_RADIUS_M: f32 = 6371007.2; // [m] mean radius of Earth
Expand All @@ -15,13 +15,13 @@ pub struct ForcesPlugin;

impl Plugin for ForcesPlugin {
fn build(&self, app: &mut App) {
app.register_type::<WeightForce>();
app.register_type::<BuoyantForce>();
app.register_type::<DragForce>();
app.register_type::<Weight>();
app.register_type::<Buoyancy>();
app.register_type::<Drag>();

app.register_component_as::<dyn Force, WeightForce>();
app.register_component_as::<dyn Force, BuoyantForce>();
app.register_component_as::<dyn Force, DragForce>();
app.register_component_as::<dyn Force, Weight>();
app.register_component_as::<dyn Force, Buoyancy>();
app.register_component_as::<dyn Force, Drag>();

// Disable the default forces since we apply our own.
app.insert_resource(Gravity(Vec3::ZERO));
Expand All @@ -36,14 +36,14 @@ impl Plugin for ForcesPlugin {
)
.before(PhysicsStepSet::First),
);
app.add_systems(Update, on_rigid_body_added.in_set(ForceUpdateOrder::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(collect_forces)
(
update_weight_parameters,
update_buoyant_parameters,
update_drag_parameters,
)
.in_set(ForceUpdateOrder::Prepare),
);
app.add_systems(
Expand All @@ -52,7 +52,7 @@ impl Plugin for ForcesPlugin {
);

// for debugging, let's assume there will always be just one balloon.
app.init_resource::<NetForce>();
// app.init_resource::<NetForce>();
}
}

Expand All @@ -65,40 +65,59 @@ enum ForceUpdateOrder {

#[derive(Bundle)]
pub struct ForceBundle {
collection: ForceCollection,
weight: WeightForce,
buoyancy: BuoyantForce,
drag: DragForce,
weight: Weight,
buoyancy: Buoyancy,
drag: Drag,
}

/// 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((ForceBundle {
collection: ForceCollection::default(),
weight: WeightForce::default(),
buoyancy: BuoyantForce::default(),
drag: DragForce::default(),
}));
commands.entity(entity).insert(ForceBundle {
weight: Weight::default(),
buoyancy: Buoyancy::default(),
drag: Drag::default(),
});
}
}

/// This trait is used to identify a force component.
/// This trait is used to identify a force vector component. All forces are
/// collected and summed to determine the net force acting on a rigid body.
#[bevy_trait_query::queryable]
pub trait Force {
fn force(&self) -> Vec3 { self.0 }
fn update(&mut self);
fn direction(&self) -> Vec3 { self.force().normalize() }
fn magnitude(&self) -> f32 { self.force().length() }
fn force(&self) -> Vec3;
fn direction(&self) -> Vec3 {
self.force().normalize()
}
fn magnitude(&self) -> f32 {
self.force().length()
}
}

/// 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, Default, Reflect)]
pub struct WeightForce(Vec3);
impl Force for WeightForce {
fn update(&mut self, position: Vec3, mass: f32) {
self.0 = weight(position, mass);
#[derive(Component, Reflect)]
pub struct Weight {
position: Vec3,
mass: f32,
}
impl Default for Weight {
fn default() -> Self {
Self {
position: Vec3::ZERO,
mass: 0.0,
}
}
}
impl Weight {
pub fn update(&mut self, position: Vec3, mass: f32) {
self.position = position;
self.mass = mass;
}
}
impl Force for Weight {
fn force(&self) -> Vec3 {
weight(self.position, self.mass)
}
}

Expand All @@ -114,82 +133,139 @@ 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.0 = weight(position.0, mass.kg());
fn update_weight_parameters(mut bodies: Query<(&mut Weight, &Position, &Mass), With<RigidBody>>) {
for (mut weight, position, mass) in bodies.iter_mut() {
weight.update(position.0, mass.kg());
}
}

/// Upward force (N) vector due to atmosphere displaced by the given gas volume.
#[derive(Component, Default, Reflect)]
pub struct BuoyantForce(Vec3);
#[derive(Component, Reflect)]
pub struct Buoyancy {
position: Vec3,
displaced_volume: Volume,
ambient_density: Density,
}
impl Default for Buoyancy {
fn default() -> Self {
Self {
position: Vec3::ZERO,
displaced_volume: Volume::ZERO,
ambient_density: Density::ZERO,
}
}
}
impl Buoyancy {
pub fn update(&mut self, position: Vec3, displaced_volume: Volume, ambient_density: Density) {
self.position = position;
self.displaced_volume = displaced_volume;
self.ambient_density = ambient_density;
}
}
impl Force for Buoyancy {
fn force(&self) -> Vec3 {
buoyancy(self.position, self.displaced_volume, self.ambient_density)
}
}

/// 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 {
Vec3::Y * (volume.cubic_meters() * ambient_density.kg_per_m3() * g(position))
/// 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))
}

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

/// Force (N) due to drag as a solid body moves through a fluid.
#[derive(Component, Default, Reflect)]
pub struct DragForce(Vec3);
#[derive(Component, Reflect)]
pub struct Drag {
local_gravity: f32,
drag_normal: Vec3,
ambient_density: Density,
drag_area: f32,
drag_coeff: f32,
}
impl Default for Drag {
fn default() -> Self {
Self {
local_gravity: 0.0,
drag_normal: Vec3::ZERO,
ambient_density: Density::ZERO,
drag_area: 0.0,
drag_coeff: 0.0,
}
}
}
impl Drag {
pub fn update(
&mut self,
local_gravity: f32,
drag_normal: Vec3,
ambient_density: Density,
drag_area: f32,
drag_coeff: f32,
) {
self.local_gravity = local_gravity;
self.ambient_density = ambient_density;
self.drag_normal = drag_normal;
self.drag_area = drag_area;
self.drag_coeff = drag_coeff;
}
}
impl Force for Drag {
fn force(&self) -> Vec3 {
drag(
self.ambient_density.kg_per_m3(),
self.drag_normal,
self.drag_area,
self.drag_coeff,
)
}
}

/// 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
pub fn drag(ambient_density: f32, drag_normal: Vec3, drag_area: f32, drag_coeff: f32) -> Vec3 {
drag_normal * drag_coeff / 2.0
* ambient_density
* f32::powf(drag_normal.length(), 2.0)
* drag_area
}

#[allow(unused_variables)]
#[allow(unused_mut)]
fn update_drag_force(
fn update_drag_parameters(
atmosphere: Res<Atmosphere>,
mut bodies: Query<(&mut DragForce, &Position, &LinearVelocity), With<RigidBody>>,
mut bodies: Query<(&mut Drag, &Position, &LinearVelocity, &Collider), With<RigidBody>>,
) {
// Todo: update drag force
}

/// Dump all the forces into a single vector that can be queried and summed.
/// TODO: maybe use observer pattern to find and update this collection, or
/// populate it by emitting an event from each force system to populate the
/// array.
#[derive(Component, Default, Reflect)]
struct ForceCollection {
forces: Vec<Box<dyn Force>>,
}

#[derive(Resource, Default, Reflect)]
struct NetForce(Vec3);

/// Collect all the forces into the force collection.
fn collect_forces(mut forces: Query<(&mut ForceCollection, With<RigidBody>>) {
for mut collection in forces.iter_mut() {
collection.0.push(Vec3::ZERO);
for (mut drag, position, velocity, collider) in bodies.iter_mut() {
let density = atmosphere.density(position.0);
let local_gravity = g(position.0);
let (drag_normal, drag_area, drag_coeff) = aero_drag_from_collider(collider, *velocity);
drag.update(local_gravity, drag_normal, density, drag_area, drag_coeff);
}
}

/// 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 external_forces: Query<(&mut ExternalForce, &ForceCollection), With<RigidBody>>,
// TODO: this is a temporary resource that assumes there is only balloon.
mut net_force: ResMut<NetForce>,
mut body_forces: Query<(&mut ExternalForce, &dyn Force), With<RigidBody>>,
) {
for (mut physics_force, forces) in external_forces.iter_mut() {
net_force.0 = forces.0.iter().sum();
physics_force.set_force(net_force.0);
// Iterate over each entity that has force vector components.
for (mut physics_force_component, acting_forces) in body_forces.iter_mut() {
let mut net_force = Vec3::ZERO; // reset the net force to zero
// Iterate over each force vector component and compute its value.
for force in acting_forces {
net_force += force.force();
}
physics_force_component.set_force(net_force);
}
}
1 change: 1 addition & 0 deletions src/simulator/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod aero;
pub mod atmosphere;
pub mod balloon;
pub mod forces;
Expand Down

0 comments on commit 499710a

Please sign in to comment.