use std::collections::HashMap;
use rhai::{Array, Dynamic, Map};
use crate::stability::CompleteStabilityResult;
#[derive(Debug, Clone)]
pub struct CriteriaContext {
result: CompleteStabilityResult,
vessel_name: String,
loading_condition: String,
params: HashMap<String, Dynamic>,
}
impl CriteriaContext {
pub fn new(
result: CompleteStabilityResult,
vessel_name: String,
loading_condition: String,
) -> Self {
Self {
result,
vessel_name,
loading_condition,
params: HashMap::new(),
}
}
pub fn set_param(&mut self, key: &str, value: Dynamic) {
self.params.insert(key.to_string(), value);
}
pub fn set_param_f64(&mut self, key: &str, value: f64) {
self.params.insert(key.to_string(), Dynamic::from(value));
}
pub fn set_param_str(&mut self, key: &str, value: &str) {
self.params
.insert(key.to_string(), Dynamic::from(value.to_string()));
}
pub fn set_param_bool(&mut self, key: &str, value: bool) {
self.params.insert(key.to_string(), Dynamic::from(value));
}
pub fn get_heels(&self) -> Array {
self.result
.gz_curve
.heels()
.into_iter()
.map(Dynamic::from)
.collect()
}
pub fn get_gz_values(&self) -> Array {
self.result
.gz_curve
.values()
.into_iter()
.map(Dynamic::from)
.collect()
}
pub fn area_under_curve(&self, from_angle: f64, to_angle: f64) -> f64 {
let heels = self.result.gz_curve.heels();
if heels.is_empty() {
return 0.0;
}
let mut area = 0.0;
for i in 0..heels.len() - 1 {
let h1 = heels[i];
let h2 = heels[i + 1];
if h2 <= from_angle || h1 >= to_angle {
continue;
}
let x1 = h1.max(from_angle);
let x2 = h2.min(to_angle);
let y1 = self.gz_at_angle(x1);
let y2 = self.gz_at_angle(x2);
let dx = (x2 - x1).to_radians();
area += 0.5 * (y1 + y2) * dx;
}
area
}
pub fn gz_at_angle(&self, angle: f64) -> f64 {
self.result.gz_curve.interpolate(angle).unwrap_or(0.0)
}
pub fn find_max_gz(&self) -> Map {
let mut map = Map::new();
if let Some(point) = self.result.gz_curve.max_value() {
map.insert("angle".into(), Dynamic::from(point.heel));
map.insert("value".into(), Dynamic::from(point.value));
} else {
map.insert("angle".into(), Dynamic::UNIT);
map.insert("value".into(), Dynamic::UNIT);
}
map
}
pub fn find_angle_of_vanishing_stability(&self) -> Dynamic {
let heels = self.result.gz_curve.heels();
let values = self.result.gz_curve.values();
if heels.is_empty() {
return Dynamic::UNIT;
}
let max_idx = values
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.map(|(i, _)| i)
.unwrap_or(0);
for i in max_idx..values.len() - 1 {
if values[i] > 0.0 && values[i + 1] <= 0.0 {
let t = values[i] / (values[i] - values[i + 1]);
let angle = heels[i] + t * (heels[i + 1] - heels[i]);
return Dynamic::from(angle);
}
}
Dynamic::UNIT
}
pub fn get_first_flooding_angle(&self) -> Dynamic {
for point in &self.result.gz_curve.points {
if point.is_flooding {
return Dynamic::from(point.heel);
}
}
Dynamic::UNIT
}
pub fn find_equilibrium_angle(&self, heeling_arm: f64) -> Dynamic {
let heels = self.result.gz_curve.heels();
let values = self.result.gz_curve.values();
if heels.is_empty() {
return Dynamic::UNIT;
}
for i in 0..values.len() - 1 {
let v1 = values[i];
let v2 = values[i + 1];
if (v1 <= heeling_arm && v2 >= heeling_arm) || (v1 >= heeling_arm && v2 <= heeling_arm)
{
let t = (heeling_arm - v1) / (v2 - v1);
let angle = heels[i] + t * (heels[i + 1] - heels[i]);
if angle >= 0.0 {
return Dynamic::from(angle);
}
}
}
Dynamic::UNIT
}
pub fn find_second_intercept(&self, heeling_arm: f64) -> Dynamic {
let heels = self.result.gz_curve.heels();
let values = self.result.gz_curve.values();
if heels.is_empty() {
return Dynamic::UNIT;
}
let mut intercept_count = 0;
for i in 0..values.len() - 1 {
let v1 = values[i];
let v2 = values[i + 1];
if (v1 <= heeling_arm && v2 >= heeling_arm) || (v1 >= heeling_arm && v2 <= heeling_arm)
{
let t = (heeling_arm - v1) / (v2 - v1);
let angle = heels[i] + t * (heels[i + 1] - heels[i]);
if angle >= 0.0 {
intercept_count += 1;
if intercept_count == 2 {
return Dynamic::from(angle);
}
}
}
}
Dynamic::UNIT
}
pub fn get_limiting_angle(&self, default: f64) -> f64 {
let mut limit = default;
if let Some(flooding) = self.get_first_flooding_angle().try_cast::<f64>() {
limit = limit.min(flooding);
}
if let Some(vanishing) = self.find_angle_of_vanishing_stability().try_cast::<f64>() {
limit = limit.min(vanishing);
}
limit
}
pub fn get_gm0(&self) -> Dynamic {
match self.result.gm0() {
Some(v) => Dynamic::from(v),
None => Dynamic::UNIT,
}
}
pub fn get_gm0_dry(&self) -> Dynamic {
match self.result.gm0_dry() {
Some(v) => Dynamic::from(v),
None => Dynamic::UNIT,
}
}
pub fn get_draft(&self) -> f64 {
self.result.hydrostatics.draft
}
pub fn get_trim(&self) -> f64 {
self.result.hydrostatics.trim
}
pub fn get_displacement(&self) -> f64 {
self.result.displacement
}
pub fn get_cog(&self) -> Array {
self.result.cog.iter().map(|&v| Dynamic::from(v)).collect()
}
pub fn get_cb(&self) -> f64 {
self.result.hydrostatics.cb
}
pub fn get_cm(&self) -> f64 {
self.result.hydrostatics.cm
}
pub fn get_cp(&self) -> f64 {
self.result.hydrostatics.cp
}
pub fn get_lwl(&self) -> f64 {
self.result.hydrostatics.lwl
}
pub fn get_bwl(&self) -> f64 {
self.result.hydrostatics.bwl
}
pub fn get_vcb(&self) -> f64 {
self.result.hydrostatics.vcb()
}
pub fn has_wind_data(&self) -> bool {
self.result.has_wind_data()
}
pub fn get_emerged_area(&self) -> Dynamic {
match &self.result.wind_data {
Some(wind) => Dynamic::from(wind.emerged_area),
None => Dynamic::UNIT,
}
}
pub fn get_wind_lever_arm(&self) -> Dynamic {
match &self.result.wind_data {
Some(wind) => Dynamic::from(wind.wind_lever_arm),
None => Dynamic::UNIT,
}
}
pub fn calculate_wind_heeling_lever(&self, wind_pressure: f64) -> Dynamic {
match &self.result.wind_data {
Some(wind) => {
let g = 9.81;
let lw1 = (wind_pressure * wind.emerged_area * wind.wind_lever_arm)
/ (self.result.displacement * g);
Dynamic::from(lw1)
}
None => Dynamic::UNIT,
}
}
pub fn get_param(&self, key: &str) -> Dynamic {
self.params.get(key).cloned().unwrap_or(Dynamic::UNIT)
}
pub fn has_param(&self, key: &str) -> bool {
self.params.contains_key(key)
}
pub fn get_vessel_name(&self) -> String {
self.vessel_name.clone()
}
pub fn get_loading_condition(&self) -> String {
self.loading_condition.clone()
}
pub fn result(&self) -> &CompleteStabilityResult {
&self.result
}
}