use axiolid_core::{Frame3, Point3, Vec2, Vec3};
use crate::cant::layout::CantLayout;
use crate::error::{AlignmentError, AlignmentResult};
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct CantFrame {
pub distance_along: f64,
pub left: f64,
pub right: f64,
pub cant: f64,
pub axis_elevation: f64,
pub bank_angle: f64,
pub lateral: Vec2,
pub up: Vec2,
}
impl CantFrame {
pub fn orient(&self, point: Point3, tangent: Vec3) -> AlignmentResult<Frame3> {
let invalid = |detail| Err(AlignmentError::InvalidUnits { detail });
if !(point.is_finite() && tangent.is_finite()) {
return invalid("cant frame point and tangent must be finite");
}
let horizontal = (tangent.x * tangent.x + tangent.y * tangent.y).sqrt();
if horizontal == 0.0 {
return invalid("a vertical or zero tangent has no horizontal left normal");
}
let t = tangent.normalize();
let n = Vec3::new(-tangent.y / horizontal, tangent.x / horizontal, 0.0);
let u = t.cross(n);
Ok(Frame3 {
origin: point + Vec3::Z * self.axis_elevation,
x: t,
y: n * self.lateral.x + u * self.lateral.y,
z: n * self.up.x + u * self.up.y,
})
}
}
impl CantLayout {
pub fn frame_at_distance(&self, distance_along: f64) -> AlignmentResult<CantFrame> {
let at = self.cant_at_distance(distance_along)?;
let cant = at.left - at.right;
let ratio = cant / self.rail_head_distance;
if !(-1.0..=1.0).contains(&ratio) {
return Err(AlignmentError::SemanticViolation {
entity: Some(self.entity),
rule: "cant must not exceed the rail head distance (|D| <= b)",
});
}
let bank_angle = ratio.asin();
let (sin, cos) = bank_angle.sin_cos();
Ok(CantFrame {
distance_along,
left: at.left,
right: at.right,
cant,
axis_elevation: 0.5 * (at.left + at.right),
bank_angle,
lateral: Vec2::new(cos, sin),
up: Vec2::new(-sin, cos),
})
}
}