Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: flexible engines mounting #7676

Merged
merged 15 commits into from
Jan 13, 2023
Merged
6 changes: 6 additions & 0 deletions src/systems/a380_systems/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use systems::{
AuxiliaryPowerUnitFireOverheadPanel, AuxiliaryPowerUnitOverheadPanel,
},
electrical::{Electricity, ElectricitySource, ExternalPowerSource},
engine::engine_wing_flex::EnginesFlexiblePhysics,
engine::{leap_engine::LeapEngine, EngineFireOverheadPanel},
hydraulic::brake_circuit::AutobrakePanel,
landing_gear::{LandingGear, LandingGearControlInterfaceUnitSet},
Expand Down Expand Up @@ -67,6 +68,7 @@ pub struct A380 {
pressurization_overhead: PressurizationOverheadPanel,
pneumatic: A380Pneumatic,
radio_altimeters: A380RadioAltimeters,
engines_flex_physics: EnginesFlexiblePhysics<4>,
}
impl A380 {
pub fn new(context: &mut InitContext) -> A380 {
Expand Down Expand Up @@ -108,6 +110,7 @@ impl A380 {
pressurization_overhead: PressurizationOverheadPanel::new(context),
pneumatic: A380Pneumatic::new(context),
radio_altimeters: A380RadioAltimeters::new(context),
engines_flex_physics: EnginesFlexiblePhysics::new(context),
}
}
}
Expand Down Expand Up @@ -218,6 +221,8 @@ impl Aircraft for A380 {
&self.pressurization_overhead,
[self.lgcius.lgciu1(), self.lgcius.lgciu2()],
);

self.engines_flex_physics.update(context);
}
}
impl SimulationElement for A380 {
Expand Down Expand Up @@ -249,6 +254,7 @@ impl SimulationElement for A380 {
self.pressurization.accept(visitor);
self.pressurization_overhead.accept(visitor);
self.pneumatic.accept(visitor);
self.engines_flex_physics.accept(visitor);

visitor.visit(self);
}
Expand Down
277 changes: 277 additions & 0 deletions src/systems/systems/src/engine/engine_wing_flex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
use std::time::Duration;

use crate::{
hydraulic::SpringPhysics,
shared::random_from_normal_distribution,
shared::update_iterator::MaxStepLoop,
simulation::{
InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader,
SimulatorWriter, UpdateContext, VariableIdentifier, Write,
},
};

use uom::si::{f64::*, mass::kilogram};

use nalgebra::Vector3;
use std::fmt::Debug;

pub struct EngineFlexPhysics {
x_position_id: VariableIdentifier,

spring_const_id: VariableIdentifier,
damping_const_id: VariableIdentifier,
mass_id: VariableIdentifier,
output_gain_id: VariableIdentifier,
lateral_damping_id: VariableIdentifier,
vertical_damping_id: VariableIdentifier,
dev_mode_enable_id: VariableIdentifier,

reference_point_cg: Vector3<f64>,
cg_position: Vector3<f64>,
cg_speed: Vector3<f64>,

virtual_mass: Mass,
spring: SpringPhysics,
anisotropic_damping_constant: Vector3<f64>,

position_output_gain: f64,
}
impl EngineFlexPhysics {
pub fn new(context: &mut InitContext, engine_number: usize) -> Self {
Self {
x_position_id: context
.get_identifier(format!("ENGINE_{}_WOBBLE_X_POSITION", engine_number)),
spring_const_id: context.get_identifier("ENGINE_WOBBLE_DEV_K_CONST".to_owned()),
damping_const_id: context.get_identifier("ENGINE_WOBBLE_DEV_DAMP_CONST".to_owned()),
mass_id: context.get_identifier("ENGINE_WOBBLE_DEV_MASS".to_owned()),
output_gain_id: context.get_identifier("ENGINE_WOBBLE_DEV_OUT_GAIN".to_owned()),
lateral_damping_id: context.get_identifier("ENGINE_WOBBLE_DEV_XDAMP".to_owned()),
vertical_damping_id: context.get_identifier("ENGINE_WOBBLE_DEV_YDAMP".to_owned()),
dev_mode_enable_id: context.get_identifier("ENGINE_WOBBLE_DEV_ENABLE".to_owned()),

reference_point_cg: Vector3::default(),
cg_position: Vector3::default(),
cg_speed: Vector3::default(),

virtual_mass: Mass::new::<kilogram>(random_from_normal_distribution(2000., 100.)),
spring: SpringPhysics::new(
random_from_normal_distribution(800000., 50000.),
random_from_normal_distribution(500., 20.),
),
anisotropic_damping_constant: Vector3::new(
random_from_normal_distribution(500., 50.),
random_from_normal_distribution(500., 50.),
random_from_normal_distribution(500., 50.),
),
position_output_gain: 90.,
}
}

pub fn update(&mut self, context: &UpdateContext) {
self.update_speed_position(context);
}

fn update_forces(&mut self, context: &UpdateContext) -> Vector3<f64> {
let acceleration_force =
context.local_acceleration_without_gravity() * self.virtual_mass.get::<kilogram>();

let spring_force =
self.spring
.update_force(context, self.reference_point_cg, self.cg_position);

let viscosity_damping = -self
.cg_speed
.component_mul(&self.anisotropic_damping_constant);

acceleration_force + spring_force + viscosity_damping
}

fn update_speed_position(&mut self, context: &UpdateContext) {
let acceleration = self.update_forces(context) / self.virtual_mass.get::<kilogram>();

self.cg_speed += acceleration * context.delta_as_secs_f64();

self.cg_position += self.cg_speed * context.delta_as_secs_f64();
}

fn animation_position(&self) -> f64 {
let limited_pos = (self.position_output_gain * (self.cg_position[0] + self.cg_position[1]))
.min(1.)
.max(-1.);

(limited_pos + 1.) / 2.
}
}
impl SimulationElement for EngineFlexPhysics {
fn read(&mut self, reader: &mut SimulatorReader) {
let activate_dev_mode: f64 = reader.read(&self.dev_mode_enable_id);

if activate_dev_mode > 0. {
self.spring.set_k_and_damping(
reader.read(&self.spring_const_id),
reader.read(&self.damping_const_id),
);
self.virtual_mass = reader.read(&self.mass_id);
self.position_output_gain = reader.read(&self.output_gain_id);
self.anisotropic_damping_constant[0] = reader.read(&self.lateral_damping_id);
self.anisotropic_damping_constant[1] = reader.read(&self.vertical_damping_id);

// Z dimension not really used we use same param as Y damping
self.anisotropic_damping_constant[2] = reader.read(&self.vertical_damping_id);
}
}

fn write(&self, writer: &mut SimulatorWriter) {
writer.write(&self.x_position_id, self.animation_position());
crocket63 marked this conversation as resolved.
Show resolved Hide resolved
}
}
impl Debug for EngineFlexPhysics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"\nEngine Flex=> CG [{:.2};{:.2};{:.2}]",
self.cg_position[0] * self.position_output_gain,
self.cg_position[1] * self.position_output_gain,
self.cg_position[2] * self.position_output_gain
)
}
}

pub struct EnginesFlexiblePhysics<const N: usize> {
engines_flex_updater: MaxStepLoop,
engines_flex: Vec<EngineFlexPhysics>,
}
impl<const N: usize> EnginesFlexiblePhysics<N> {
const ENGINES_FLEX_SIM_TIME_STEP: Duration = Duration::from_millis(10);

pub fn new(context: &mut InitContext) -> Self {
let mut all_engines: Vec<EngineFlexPhysics> = vec![];
for engine_number in 1..=N {
all_engines.push(EngineFlexPhysics::new(context, engine_number));
}

Self {
engines_flex_updater: MaxStepLoop::new(Self::ENGINES_FLEX_SIM_TIME_STEP),
engines_flex: all_engines,
}
}
crocket63 marked this conversation as resolved.
Show resolved Hide resolved

pub fn update(&mut self, context: &UpdateContext) {
self.engines_flex_updater.update(context);

for cur_time_step in self.engines_flex_updater {
for engine_flex in &mut self.engines_flex {
engine_flex.update(&context.with_delta(cur_time_step));
}
}
}
}
impl SimulationElement for EnginesFlexiblePhysics<4> {
fn accept<T: SimulationElementVisitor>(&mut self, visitor: &mut T) {
accept_iterable!(self.engines_flex, visitor);

visitor.visit(self);
}
}

#[cfg(test)]
mod tests {
use super::*;

use crate::simulation::{
test::{ReadByName, SimulationTestBed, TestBed, WriteByName},
Aircraft, InitContext, SimulationElement, SimulationElementVisitor, UpdateContext,
};

use ntest::assert_about_eq;
use std::time::Duration;

struct EngineFlexTestAircraft {
engines_flex: EnginesFlexiblePhysics<4>,
}
impl EngineFlexTestAircraft {
fn new(context: &mut InitContext) -> Self {
Self {
engines_flex: EnginesFlexiblePhysics::new(context),
}
}

fn update(&mut self, context: &UpdateContext) {
self.engines_flex.update(context);
}
}
impl Aircraft for EngineFlexTestAircraft {
fn update_after_power_distribution(&mut self, context: &UpdateContext) {
self.update(context);
}
}
impl SimulationElement for EngineFlexTestAircraft {
fn accept<T: SimulationElementVisitor>(&mut self, visitor: &mut T) {
self.engines_flex.accept(visitor);
visitor.visit(self);
}
}

fn show_animation_positions(test_bed: &mut SimulationTestBed<EngineFlexTestAircraft>) {
let engine_1_position: f64 = test_bed.read_by_name("ENGINE_1_WOBBLE_X_POSITION");
let engine_2_position: f64 = test_bed.read_by_name("ENGINE_2_WOBBLE_X_POSITION");
let engine_3_position: f64 = test_bed.read_by_name("ENGINE_3_WOBBLE_X_POSITION");
let engine_4_position: f64 = test_bed.read_by_name("ENGINE_4_WOBBLE_X_POSITION");

println!(
"E1 {:.2} E2 {:.2} E3 {:.2} E4 {:.2}",
engine_1_position, engine_2_position, engine_3_position, engine_4_position
);
}

#[test]
fn check_init_positions_are_zero_point_five() {
let mut test_bed = SimulationTestBed::new(EngineFlexTestAircraft::new);

test_bed.run();

let engine_1_position: f64 = test_bed.read_by_name("ENGINE_1_WOBBLE_X_POSITION");
let engine_2_position: f64 = test_bed.read_by_name("ENGINE_2_WOBBLE_X_POSITION");
let engine_3_position: f64 = test_bed.read_by_name("ENGINE_3_WOBBLE_X_POSITION");
let engine_4_position: f64 = test_bed.read_by_name("ENGINE_4_WOBBLE_X_POSITION");

assert_about_eq!(engine_1_position, 0.5);
assert_about_eq!(engine_2_position, 0.5);
assert_about_eq!(engine_3_position, 0.5);
assert_about_eq!(engine_4_position, 0.5);
}

// Following test is ignored because it's hard to set static boundaries to desired results
// Tuning is better done visually using dev mode to edit physical properties
#[test]
#[ignore]
fn check_engines_move_in_light_turbulances() {
let mut test_bed = SimulationTestBed::new(EngineFlexTestAircraft::new);

for _ in 0..500 {
test_bed.write_by_name("ACCELERATION BODY X", 5.);
test_bed.run_with_delta(Duration::from_secs_f64(0.5));
show_animation_positions(&mut test_bed);
test_bed.write_by_name("ACCELERATION BODY X", -5.);
test_bed.run_with_delta(Duration::from_secs_f64(0.5));
show_animation_positions(&mut test_bed);
}
}

// Following test is ignored because it's hard to set static boundaries to desired results
// Tuning is better done visually using dev mode to edit physical properties
#[test]
#[ignore]
fn check_engines_move_in_strong_turbulances() {
let mut test_bed = SimulationTestBed::new(EngineFlexTestAircraft::new);

for _ in 0..500 {
test_bed.write_by_name("ACCELERATION BODY X", 15.);
test_bed.run_with_delta(Duration::from_secs_f64(0.5));
show_animation_positions(&mut test_bed);
test_bed.write_by_name("ACCELERATION BODY X", -15.);
test_bed.run_with_delta(Duration::from_secs_f64(0.5));
show_animation_positions(&mut test_bed);
}
}
}
1 change: 1 addition & 0 deletions src/systems/systems/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
simulation::{SimulationElement, SimulationElementVisitor},
};

pub mod engine_wing_flex;
pub mod leap_engine;

pub trait Engine: EngineCorrectedN2 + EngineUncorrectedN2 + EngineCorrectedN1 {
Expand Down
31 changes: 21 additions & 10 deletions src/systems/systems/src/hydraulic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1960,18 +1960,21 @@ impl SimulationElement for LeakMeasurementValve {
}
}

struct SpringPhysics {
pub struct SpringPhysics {
last_length: f64,
spring_constant: f64,
damping_constant: f64,
}
impl SpringPhysics {
const SPRING_K_CONSTANT: f64 = 5000.;
const SPRING_DAMPING_CONSTANT: f64 = 500.;

fn default() -> Self {
Self { last_length: 0. }
pub fn new(spring_constant: f64, damping_constant: f64) -> Self {
Self {
last_length: 0.,
spring_constant,
damping_constant,
}
}

fn update_force(
pub fn update_force(
&mut self,
context: &UpdateContext,
position1: Vector3<f64>,
Expand All @@ -1989,13 +1992,18 @@ impl SpringPhysics {
let spring_vector_normalized = spring_vector.normalize();
let velocity = (spring_length - self.last_length) / context.delta_as_secs_f64();

let k_force = spring_length * Self::SPRING_K_CONSTANT;
let damping_force = velocity * Self::SPRING_DAMPING_CONSTANT;
let k_force = spring_length * self.spring_constant;
let damping_force = velocity * self.damping_constant;

self.last_length = spring_length;

(k_force + damping_force) * spring_vector_normalized
}

pub fn set_k_and_damping(&mut self, spring_constant: f64, damping_constant: f64) {
self.spring_constant = spring_constant;
self.damping_constant = damping_constant;
}
}

struct FluidPhysics {
Expand All @@ -2013,14 +2021,17 @@ impl FluidPhysics {
const MEAN_G_TRAP_CAVITY_TIME_DURATION_SECONDS: f64 = 20.;
const STD_DEV_G_TRAP_CAVITY_TIME_DURATION_SECONDS: f64 = 4.;

const SPRING_K_CONSTANT: f64 = 5000.;
const SPRING_DAMPING_CONSTANT: f64 = 500.;

fn new() -> Self {
Self {
reference_point_cg: Vector3::default(),
fluid_cg_position: Vector3::new(0., -0.2, 0.),
fluid_cg_speed: Vector3::default(),

virtual_mass: Mass::new::<kilogram>(100.),
spring: SpringPhysics::default(),
spring: SpringPhysics::new(Self::SPRING_K_CONSTANT, Self::SPRING_DAMPING_CONSTANT),
anisotropic_damping_constant: Vector3::new(25., 20., 25.),

g_trap_is_empty: DelayedTrueLogicGate::new(Duration::from_secs_f64(
Expand Down
Loading