use crate::_bevy::*;
use avian3d::prelude::{PhysicsSchedule, PhysicsStepSystems};
use crate::aerodynamics::compute_aero_forces;
use crate::atmosphere::update_atmosphere;
use crate::kinematics::update_flight_state;
use crate::propulsion::compute_engine_zone_forces;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum AircraftFdmSystems {
Atmosphere,
FlightState,
Forces,
}
pub struct AircraftFdmPlugin {
pub validate_on_startup: bool,
}
impl Default for AircraftFdmPlugin {
fn default() -> Self {
Self {
validate_on_startup: cfg!(debug_assertions),
}
}
}
impl Plugin for AircraftFdmPlugin {
fn build(&self, app: &mut App) {
if app
.get_schedule(avian3d::prelude::PhysicsSchedule)
.is_none()
{
panic!(
"Failed to build `AircraftFdmPlugin`: \
Avian's `PhysicsSchedule` was not found. \
Add `PhysicsPlugins` before `AircraftFdmPlugin`."
);
}
use crate::components::{
AeroZone, AircraftGeometry, AtmosphereState, ControlInputs, EngineZone, Failure,
FlightState, GizmoContours, GizmoShape, InducedDrag, LodDamping, ZoneForce,
};
app.register_type::<AircraftGeometry>()
.register_type::<ControlInputs>()
.register_type::<FlightState>()
.register_type::<AtmosphereState>()
.register_type::<AeroZone>()
.register_type::<ZoneForce>()
.register_type::<GizmoShape>()
.register_type::<GizmoContours>()
.register_type::<Failure>()
.register_type::<InducedDrag>()
.register_type::<LodDamping>()
.register_type::<EngineZone>();
use crate::airfoil::{resolve_airfoil_names, AirfoilLibrary};
app.init_resource::<AirfoilLibrary>()
.add_systems(PreUpdate, resolve_airfoil_names);
register_fdm_systems(app);
if self.validate_on_startup {
app.add_systems(PostStartup, (validate_rigid_bodies, validate_aero_zones));
}
}
}
fn register_fdm_systems(app: &mut App) {
app.configure_sets(
PhysicsSchedule,
(
AircraftFdmSystems::Atmosphere,
AircraftFdmSystems::FlightState,
AircraftFdmSystems::Forces,
)
.chain()
.in_set(PhysicsStepSystems::BroadPhase),
);
app.add_systems(
PhysicsSchedule,
update_atmosphere.in_set(AircraftFdmSystems::Atmosphere),
);
app.add_systems(
PhysicsSchedule,
update_flight_state.in_set(AircraftFdmSystems::FlightState),
);
app.add_systems(
PhysicsSchedule,
(compute_engine_zone_forces, compute_aero_forces)
.chain()
.in_set(AircraftFdmSystems::Forces),
);
}
pub fn validate_rigid_bodies(
query: Query<(Entity, &avian3d::prelude::RigidBody), With<crate::components::AircraftGeometry>>,
) {
for (entity, rb) in &query {
if !rb.is_dynamic() {
warn!(
"Entity {entity} has AircraftGeometry but RigidBody is not Dynamic. \
Aerodynamic forces will be ignored by Avian. \
Set RigidBody::Dynamic on the aircraft root entity."
);
}
}
}
pub fn validate_aero_zones(query: Query<(Entity, &crate::components::AeroZone)>) {
let mut total_problems = 0;
for (entity, zone) in &query {
let label = format!("Entity {entity}");
let problems = zone.validate(&label);
for p in &problems {
warn!("AeroZone validation: {p}");
}
total_problems += problems.len();
}
if total_problems > 0 {
warn!(
"AeroZone validation found {total_problems} problem(s). \
Fix these before trusting simulation results."
);
}
}