use axiolid_curve::{bank_angle, BankConvention, Banked3, CantLaw, CantPiece, Curve3};
use axiolid_model::{GeometryGraphBuilder, GeometryNode};
use ifc_model::{EntityId, Model};
use super::assemble::{finish, LoweredAlignmentCurve};
use super::gradient::{compose, sole_layout};
use super::seam::HorizontalSeam;
use super::tolerance::SeamTolerance;
use crate::cant::{CantLayout, CantSegment, CantSegmentType};
use crate::error::{AlignmentError, AlignmentResult};
use crate::horizontal::AlignmentUnits;
use crate::view::AlignmentView;
pub fn lower_segmented_reference_curve(
model: &Model,
alignment: EntityId,
units: AlignmentUnits,
) -> AlignmentResult<LoweredAlignmentCurve> {
let banked = banked(model, alignment, units)?;
let mut builder = GeometryGraphBuilder::new();
let root = builder
.push(GeometryNode::Curve3(Curve3::Banked(banked.curve)))
.map_err(|error| AlignmentError::Graph {
detail: error.to_string(),
})?;
let mut lowered = finish(builder, root, banked.sources)?;
lowered.seams = banked.seams;
Ok(lowered)
}
pub fn segmented_reference_curve3(
model: &Model,
alignment: EntityId,
units: AlignmentUnits,
) -> AlignmentResult<Curve3> {
banked(model, alignment, units).map(|banked| Curve3::Banked(banked.curve))
}
struct Lowered {
curve: Banked3,
sources: Vec<EntityId>,
seams: Vec<HorizontalSeam>,
}
fn banked(model: &Model, alignment: EntityId, units: AlignmentUnits) -> AlignmentResult<Lowered> {
let cant = CantLayout::for_alignment(model, alignment, units)?;
let view = AlignmentView::for_model(model)?;
let horizontal = sole_layout(&view, alignment, "IfcAlignmentHorizontal")?;
let vertical = sole_layout(&view, alignment, "IfcAlignmentVertical")?;
let composed = compose(model, &view, horizontal, vertical, units, Some(&cant))?;
let tolerance = SeamTolerance::for_model(model, units)?;
let (cant_law, pivot) = cant_laws(&cant, composed.plan_length, tolerance)?;
if !(cant_law.is_well_formed() && pivot.is_well_formed()) {
return Err(AlignmentError::Graph {
detail: format!(
"the cant layout {} did not assemble into well-formed laws",
cant.entity
),
});
}
let mut sources = vec![horizontal, vertical, cant.entity];
sources.extend(composed.vertical_ids);
sources.extend(cant.segments().iter().map(|segment| segment.entity));
Ok(Lowered {
curve: Banked3::new(
composed.centreline,
cant_law,
pivot,
cant.rail_head_distance,
BankConvention::TangentRotation,
),
sources,
seams: composed.seams,
})
}
fn cant_laws(
layout: &CantLayout,
plan_length: f64,
tolerance: SeamTolerance,
) -> AlignmentResult<(CantLaw, CantLaw)> {
let segments: Vec<&CantSegment> = layout
.segments()
.iter()
.filter(|segment| segment.horizontal_length > 0.0)
.collect();
let start = segments.first().map_or(0.0, |s| s.start_dist_along);
let end = segments
.last()
.map_or(0.0, |s| s.start_dist_along + s.horizontal_length);
if !tolerance.same_length(start, 0.0) || !tolerance.same_length(end, plan_length) {
return Err(AlignmentError::Unsupported {
entity: layout.entity,
type_name: "IfcAlignmentCant".to_owned(),
detail: "the cant layout does not cover the whole plan; a banked curve's span is its \
cant law's, and a station without cant is not zero cant",
});
}
let b = layout.rail_head_distance;
let mut cant = Vec::with_capacity(segments.len());
let mut pivot = Vec::with_capacity(segments.len());
for segment in segments {
let (left, right) = ends(segment)?;
let d = [left[0] - right[0], left[1] - right[1]];
let e = [0.5 * (left[0] + right[0]), 0.5 * (left[1] + right[1])];
if d.iter().any(|value| bank_angle(*value, b).is_err()) {
return Err(AlignmentError::SemanticViolation {
entity: Some(segment.entity),
rule: "cant must not exceed the rail head distance (|D| <= b)",
});
}
let length = segment.horizontal_length;
match &segment.predefined_type {
CantSegmentType::ConstantCant => {
cant.push(CantPiece::constant(length, d[0]));
pivot.push(CantPiece::constant(length, e[0]));
}
CantSegmentType::LinearTransition => {
cant.push(CantPiece::linear(length, d[0], d[1]));
pivot.push(CantPiece::linear(length, e[0], e[1]));
}
CantSegmentType::BlossCurve => {
cant.push(CantPiece::bloss(length, d[0], d[1]));
pivot.push(CantPiece::bloss(length, e[0], e[1]));
}
CantSegmentType::HelmertCurve => {
cant.extend(CantPiece::helmert(length, d[0], d[1]));
pivot.extend(CantPiece::helmert(length, e[0], e[1]));
}
CantSegmentType::CosineCurve => {
cant.push(CantPiece::cosine(length, d[0], d[1]));
pivot.push(CantPiece::cosine(length, e[0], e[1]));
}
CantSegmentType::SineCurve => {
cant.push(CantPiece::sine(length, d[0], d[1]));
pivot.push(CantPiece::sine(length, e[0], e[1]));
}
CantSegmentType::VienneseBend => {
if !tolerance.same_length(e[0], e[1]) {
return Err(AlignmentError::Unsupported {
entity: segment.entity,
type_name: "VIENNESEBEND".to_owned(),
detail: "the section's rotation point moves through a Viennese bend: \
the bend gives the bank angle, so a pivot such as the low rail \
follows b sin(psi) / 2, an angle form a height-form pivot law \
cannot carry (Axiolid BankError::AngleInPivot)",
});
}
let psi = |value: f64| (value / b).asin();
cant.push(CantPiece::viennese_bend(length, psi(d[0]), psi(d[1])));
pivot.push(CantPiece::constant(length, e[0]));
}
_ => {
return Err(AlignmentError::Unsupported {
entity: segment.entity,
type_name: segment.predefined_type_name().to_owned(),
detail: "cant PredefinedType has no defined base formula",
})
}
}
}
Ok((CantLaw::new(cant), CantLaw::new(pivot)))
}
fn ends(segment: &CantSegment) -> AlignmentResult<([f64; 2], [f64; 2])> {
let constant = segment.predefined_type == CantSegmentType::ConstantCant;
match (segment.end_cant_left, segment.end_cant_right) {
(Some(left), Some(right)) => {
if constant && (left != segment.start_cant_left || right != segment.start_cant_right) {
return Err(AlignmentError::InvalidSegment {
entity: segment.entity,
detail: "CONSTANTCANT requires equal (or absent) start and end cant",
});
}
Ok((
[segment.start_cant_left, left],
[segment.start_cant_right, right],
))
}
_ if constant => Ok(([segment.start_cant_left; 2], [segment.start_cant_right; 2])),
_ => Err(AlignmentError::InvalidSegment {
entity: segment.entity,
detail: "transition cant segments require both end cant values",
}),
}
}