diff --git a/src/simulator/aero.rs b/src/simulator/aero.rs new file mode 100644 index 0000000..7210397 --- /dev/null +++ b/src/simulator/aero.rs @@ -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) +} diff --git a/src/simulator/forces.rs b/src/simulator/forces.rs index 0d572df..c9151fb 100644 --- a/src/simulator/forces.rs +++ b/src/simulator/forces.rs @@ -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 @@ -15,13 +15,13 @@ pub struct ForcesPlugin; impl Plugin for ForcesPlugin { fn build(&self, app: &mut App) { - app.register_type::(); - app.register_type::(); - app.register_type::(); + app.register_type::(); + app.register_type::(); + app.register_type::(); - app.register_component_as::(); - app.register_component_as::(); - app.register_component_as::(); + app.register_component_as::(); + app.register_component_as::(); + app.register_component_as::(); // Disable the default forces since we apply our own. app.insert_resource(Gravity(Vec3::ZERO)); @@ -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( @@ -52,7 +52,7 @@ impl Plugin for ForcesPlugin { ); // for debugging, let's assume there will always be just one balloon. - app.init_resource::(); + // app.init_resource::(); } } @@ -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>) { 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) } } @@ -114,69 +133,123 @@ 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>, -) { - 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>) { + 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, - mut bodies: Query<(&mut BuoyantForce, &Position, &Volume), With>, + mut bodies: Query<(&mut Buoyancy, &Position, &Volume), With>, ) { - 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, - mut bodies: Query<(&mut DragForce, &Position, &LinearVelocity), With>, + mut bodies: Query<(&mut Drag, &Position, &LinearVelocity, &Collider), With>, ) { - // 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>, -} - -#[derive(Resource, Default, Reflect)] -struct NetForce(Vec3); - -/// Collect all the forces into the force collection. -fn collect_forces(mut forces: Query<(&mut ForceCollection, With>) { - 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); } } @@ -184,12 +257,15 @@ fn collect_forces(mut forces: Query<(&mut ForceCollection, With>) { /// 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>, - // TODO: this is a temporary resource that assumes there is only balloon. - mut net_force: ResMut, + mut body_forces: Query<(&mut ExternalForce, &dyn Force), With>, ) { - 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); } } diff --git a/src/simulator/mod.rs b/src/simulator/mod.rs index e6be410..58a1947 100644 --- a/src/simulator/mod.rs +++ b/src/simulator/mod.rs @@ -1,3 +1,4 @@ +pub mod aero; pub mod atmosphere; pub mod balloon; pub mod forces;