use std::f64::consts::PI;
mod loader;
pub use loader::OpeningLoadError;
use loader::{load_dxf_openings, load_vtk_openings};
use std::path::Path;
#[derive(Clone, Debug, PartialEq)]
pub enum OpeningType {
Vent,
AirPipe,
Hatch,
Door,
Window,
Other(String),
}
#[derive(Clone, Debug)]
pub enum OpeningGeometry {
Point([f64; 3]),
Contour(Vec<[f64; 3]>),
}
#[derive(Clone, Debug)]
pub struct DownfloodingOpening {
name: String,
geometry: OpeningGeometry,
opening_type: OpeningType,
active: bool,
}
impl DownfloodingOpening {
pub fn from_point(name: String, position: [f64; 3], opening_type: OpeningType) -> Self {
Self {
name,
geometry: OpeningGeometry::Point(position),
opening_type,
active: true,
}
}
pub fn from_contour(name: String, points: Vec<[f64; 3]>, opening_type: OpeningType) -> Self {
Self {
name,
geometry: OpeningGeometry::Contour(points),
opening_type,
active: true,
}
}
pub fn from_file(
path: &Path,
default_type: OpeningType,
) -> Result<Vec<Self>, OpeningLoadError> {
let ext = path
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_lowercase())
.unwrap_or_default();
match ext.as_str() {
"dxf" => load_dxf_openings(path, default_type),
"vtk" | "vtp" | "vtu" => load_vtk_openings(path, default_type),
_ => Err(OpeningLoadError::UnsupportedFormat),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub fn opening_type(&self) -> &OpeningType {
&self.opening_type
}
pub fn set_type(&mut self, opening_type: OpeningType) {
self.opening_type = opening_type;
}
pub fn is_active(&self) -> bool {
self.active
}
pub fn set_active(&mut self, active: bool) {
self.active = active;
}
pub fn geometry(&self) -> &OpeningGeometry {
&self.geometry
}
pub fn get_points(&self) -> Vec<[f64; 3]> {
match &self.geometry {
OpeningGeometry::Point(p) => vec![*p],
OpeningGeometry::Contour(pts) => pts.clone(),
}
}
pub fn is_submerged(&self, heel: f64, trim: f64, pivot: [f64; 3], waterline_z: f64) -> bool {
let points = self.get_points();
for point in &points {
let rotated = rotate_point(*point, heel, trim, pivot);
if rotated[2] < waterline_z {
return true;
}
}
false
}
pub fn get_lowest_z(&self, heel: f64, trim: f64, pivot: [f64; 3]) -> f64 {
let points = self.get_points();
points
.iter()
.map(|p| rotate_point(*p, heel, trim, pivot)[2])
.fold(f64::MAX, f64::min)
}
}
fn rotate_point(point: [f64; 3], heel: f64, trim: f64, pivot: [f64; 3]) -> [f64; 3] {
let heel_rad = heel * PI / 180.0;
let trim_rad = trim * PI / 180.0;
let dx = point[0] - pivot[0];
let dy = point[1] - pivot[1];
let dz = point[2] - pivot[2];
let cos_h = heel_rad.cos();
let sin_h = heel_rad.sin();
let y1 = dy * cos_h - dz * sin_h;
let z1 = dy * sin_h + dz * cos_h;
let cos_t = trim_rad.cos();
let sin_t = trim_rad.sin();
let x2 = dx * cos_t + z1 * sin_t;
let z2 = -dx * sin_t + z1 * cos_t;
[x2 + pivot[0], y1 + pivot[1], z2 + pivot[2]]
}
pub fn check_openings_submerged(
openings: &[DownfloodingOpening],
heel: f64,
trim: f64,
pivot: [f64; 3],
waterline_z: f64,
) -> Vec<String> {
openings
.iter()
.filter(|o| o.is_active() && o.is_submerged(heel, trim, pivot, waterline_z))
.map(|o| o.name().to_string())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point_opening() {
let opening = DownfloodingOpening::from_point(
"Test Vent".to_string(),
[50.0, 5.0, 10.0],
OpeningType::Vent,
);
assert_eq!(opening.name(), "Test Vent");
assert!(opening.is_active());
assert_eq!(opening.get_points().len(), 1);
}
#[test]
fn test_contour_opening() {
let points = vec![
[10.0, -5.0, 15.0],
[20.0, -5.0, 15.0],
[20.0, 5.0, 15.0],
[10.0, 5.0, 15.0],
];
let opening = DownfloodingOpening::from_contour(
"Cargo Hatch".to_string(),
points,
OpeningType::Hatch,
);
assert_eq!(opening.get_points().len(), 4);
}
#[test]
fn test_submerged_at_heel() {
let opening = DownfloodingOpening::from_point(
"Starboard Vent".to_string(),
[50.0, -5.0, 10.0],
OpeningType::Vent,
);
let pivot = [50.0, 0.0, 5.0];
assert!(!opening.is_submerged(0.0, 0.0, pivot, 5.0));
let z_at_45 = opening.get_lowest_z(45.0, 0.0, pivot);
assert!(
opening.is_submerged(45.0, 0.0, pivot, z_at_45 + 1.0),
"At 45° heel, z={:.2}, should be submerged at waterline {:.2}",
z_at_45,
z_at_45 + 1.0
);
}
}