use super::*;
use stormath::{
type_aliases::Float,
consts::{PI, TAU},
transition_functions
};
use crate::error::Error;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Foil {
#[serde(default)]
pub cl_zero_angle: Float,
#[serde(default="Foil::default_cl_initial_slope")]
pub cl_initial_slope: Float,
#[serde(default)]
pub cl_high_order_factor_positive: Float,
#[serde(default)]
pub cl_high_order_factor_negative: Float,
#[serde(default)]
pub cl_high_order_power: Float,
#[serde(default="Foil::default_one")]
pub cl_max_after_stall: Float,
#[serde(default)]
pub cd_min: Float,
#[serde(default)]
pub angle_cd_min: Float,
#[serde(default)]
pub cd_second_order_factor: Float,
#[serde(default="Foil::default_one")]
pub cd_max_after_stall: Float,
#[serde(default="Foil::default_cd_power_after_stall")]
pub cd_power_after_stall: Float,
#[serde(default)]
pub cdi_correction_factor: Float,
#[serde(default="Foil::default_mean_stall_angle")]
pub mean_positive_stall_angle: Float,
#[serde(default="Foil::default_mean_stall_angle")]
pub mean_negative_stall_angle: Float,
#[serde(default="Foil::default_stall_range")]
pub stall_range: Float,
#[serde(default)]
pub cd_bump_during_stall: Float,
#[serde(default)]
pub cd_stall_angle_offset: Float,
#[serde(default)]
pub added_mass_factor: Float,
}
fn get_stall_angle(angle_of_attack: Float) -> Float {
let mut effective = angle_of_attack.abs();
while effective > PI {
effective -= PI;
}
effective *= angle_of_attack.signum();
effective
}
impl Foil {
fn default_one() -> Float {1.0}
pub fn default_cl_initial_slope() -> Float {TAU}
pub fn default_mean_stall_angle() -> Float {Float::from(20.0).to_radians()}
pub fn default_stall_range() -> Float {Float::from(6.0).to_radians()}
pub fn default_cd_power_after_stall() -> Float {1.6}
pub fn new_from_string(string: &str) -> Result<Self, Error> {
let serde_res = serde_json::from_str(string)?;
Ok(serde_res)
}
pub fn to_string(&self) -> String {
serde_json::to_string(self).unwrap()
}
pub fn lift_coefficient(&self, angle_of_attack: Float) -> Float {
let cl_pre_stall = self.lift_coefficient_pre_stall_with_stall_drop_off(angle_of_attack);
let cl_post_stall = self.lift_coefficient_post_stall_with_stall_weight(angle_of_attack);
cl_post_stall + cl_pre_stall
}
#[inline(always)]
pub fn lift_coefficient_linear(&self, angle_of_attack: Float) -> Float {
self.cl_zero_angle + self.cl_initial_slope * angle_of_attack.sin()
}
#[inline(always)]
pub fn lift_coefficient_pre_stall_raw(&self, angle_of_attack: Float) -> Float {
let angle_high_power = if self.cl_high_order_power > 0.0 {
angle_of_attack.abs().powf(self.cl_high_order_power) * angle_of_attack.signum()
} else {
0.0
};
let high_order_factor = if angle_of_attack >= 0.0 {
self.cl_high_order_factor_positive
} else {
self.cl_high_order_factor_negative
};
self.lift_coefficient_linear(angle_of_attack) +
high_order_factor * angle_high_power
}
#[inline(always)]
pub fn lift_coefficient_pre_stall_with_stall_drop_off(&self, angle_of_attack: Float) -> Float {
let amount_of_stall = self.amount_of_stall(angle_of_attack);
let cl_pre_stall_raw = self.lift_coefficient_pre_stall_raw(angle_of_attack);
cl_pre_stall_raw * (1.0 - amount_of_stall)
}
#[inline(always)]
pub fn lift_coefficient_post_stall_raw(&self, angle_of_attack: Float) -> Float {
let stall_angle = get_stall_angle(angle_of_attack);
self.cl_max_after_stall * (2.0 * stall_angle).sin()
}
#[inline(always)]
pub fn lift_coefficient_post_stall_with_stall_weight(&self, angle_of_attack: Float) -> Float {
let cl_post_stall_raw = self.lift_coefficient_post_stall_raw(angle_of_attack);
let amount_of_stall = self.amount_of_stall(angle_of_attack);
cl_post_stall_raw * amount_of_stall
}
pub fn drag_coefficient(&self, angle_of_attack: Float) -> Float {
let stall_angle = get_stall_angle(angle_of_attack);
let pre_stall_effective_angle = (angle_of_attack + self.angle_cd_min).abs();
let cd_pre_stall = self.cd_min + self.cd_second_order_factor * pre_stall_effective_angle.powi(2);
let cd_post_stall = self.cd_max_after_stall * stall_angle.sin().abs().powf(self.cd_power_after_stall);
let angle_for_stall_transition = angle_of_attack + self.cd_stall_angle_offset * angle_of_attack.signum();
let cd_raw = self.combine_pre_and_post_stall(angle_for_stall_transition, cd_pre_stall, cd_post_stall);
let cd_during_stall = self.additional_cd_during_stall(angle_for_stall_transition);
if self.cdi_correction_factor != 0.0{
let cl = self.lift_coefficient(angle_of_attack);
let cdi_correction = self.cdi_correction_factor * cl.abs().powi(2);
cd_raw + cdi_correction + cd_during_stall
} else {
cd_raw + cd_during_stall
}
}
fn additional_cd_during_stall(&self, angle_of_attack: Float) -> Float {
let amount_of_stall = self.amount_of_stall(angle_of_attack);
let stall_drag_factor = (1.0 - (amount_of_stall * TAU).cos()) * 0.5;
self.cd_bump_during_stall * stall_drag_factor
}
pub fn added_mass_coefficient(&self, heave_acceleration: Float) -> Float {
self.added_mass_factor * heave_acceleration
}
pub fn amount_of_stall(&self, angle_of_attack: Float) -> Float {
let effective_angle = angle_of_attack + self.angle_cd_min;
let mean_stall_angle = if effective_angle >= 0.0 {
self.mean_positive_stall_angle.abs()
} else {
self.mean_negative_stall_angle.abs()
};
transition_functions::sigmoid_zero_to_one(
effective_angle.abs(),
mean_stall_angle,
self.stall_range
)
}
fn combine_pre_and_post_stall(&self, angle_of_attack: Float, pre_stall: Float, post_stall: Float) -> Float {
let amount_of_stall = self.amount_of_stall(angle_of_attack);
pre_stall * (1.0 - amount_of_stall) + amount_of_stall * post_stall
}
}
impl Default for Foil {
fn default() -> Self {
Self {
cl_zero_angle: 0.0,
cl_initial_slope: Self::default_cl_initial_slope(),
cl_high_order_factor_positive: 0.0,
cl_high_order_factor_negative: 0.0,
cl_high_order_power: 0.0,
cl_max_after_stall: Self::default_one(),
cd_min: 0.0,
angle_cd_min: 0.0,
cd_second_order_factor: 0.0,
cd_max_after_stall: Self::default_one(),
cd_power_after_stall: Self::default_cd_power_after_stall(),
cdi_correction_factor: 0.0,
mean_positive_stall_angle: Self::default_mean_stall_angle(),
mean_negative_stall_angle: Self::default_mean_stall_angle(),
stall_range: Self::default_stall_range(),
cd_stall_angle_offset: 0.0,
cd_bump_during_stall: 0.0,
added_mass_factor: 0.0
}
}
}