use super::*;
#[test]
fn line_segment_evaluation() {
let seg = HSeg::Line {
sx: 0.0,
sy: 0.0,
heading: 0.0,
length: 10.0,
cum_start: 0.0,
};
let (x, y, h) = h_eval(&seg, 10.0);
assert!((x - 10.0).abs() < 1e-9);
assert!(y.abs() < 1e-9);
assert!(h.abs() < 1e-9);
}
#[test]
fn fixture_828_arc_endpoint() {
let seg = HSeg::Arc {
sx: 0.0,
sy: 0.0,
heading: 13.35833333_f64.to_radians(),
radius: 9279.0,
length: 2965.68,
ccw: false,
cum_start: 0.0,
};
let (x, y, _) = h_eval(&seg, 2965.68);
assert!((x - 2945.13).abs() < 5.0, "x = {} expected ~2945.13", x);
assert!((y - 216.39).abs() < 5.0, "y = {} expected ~216.39", y);
}
#[test]
fn base_frame_axes_and_cant_stub() {
let curve = AlignmentCurve {
horizontal: vec![HSeg::Line {
sx: 0.0,
sy: 0.0,
heading: 0.0,
length: 100.0,
cum_start: 0.0,
}],
vertical: vec![],
};
let frame = curve.evaluate(50.0);
assert!((frame.right.x).abs() < 1e-9);
assert!((frame.right.y + 1.0).abs() < 1e-9);
assert!((frame.up.z - 1.0).abs() < 1e-9);
assert!(curve.cant_angle(50.0).abs() < 1e-9);
assert!(curve.cant_angle(150.0).abs() < 1e-9);
}
#[test]
fn polyline_directrix_evaluates_piecewise() {
let curve = AlignmentCurve {
horizontal: vec![
HSeg::Line {
sx: 0.0,
sy: 0.0,
heading: 0.0,
length: 10.0,
cum_start: 0.0,
},
HSeg::Line {
sx: 10.0,
sy: 0.0,
heading: std::f64::consts::FRAC_PI_2,
length: 10.0,
cum_start: 10.0,
},
],
vertical: vec![
VSeg::Line {
start: 0.0,
length: 10.0,
h0: 0.0,
g0: 0.1,
},
VSeg::Line {
start: 10.0,
length: 10.0,
h0: 1.0,
g0: 0.1,
},
],
};
let f1 = curve.evaluate(5.0);
assert!((f1.origin.x - 5.0).abs() < 1e-9);
assert!((f1.origin.y).abs() < 1e-9);
assert!((f1.origin.z - 0.5).abs() < 1e-9);
let f2 = curve.evaluate(15.0);
assert!((f2.origin.x - 10.0).abs() < 1e-9);
assert!((f2.origin.y - 5.0).abs() < 1e-9);
assert!((f2.origin.z - 1.5).abs() < 1e-9);
}
#[test]
fn transition_kind_heading_integral_normalised() {
for kind in [
TransitionKind::Clothoid,
TransitionKind::Bloss,
TransitionKind::Cosine,
TransitionKind::Sine,
TransitionKind::CubicParabola,
TransitionKind::BiquadraticParabola,
] {
assert!(kind.heading_integral(0.0).abs() < 1e-12, "{:?}", kind);
let mid = kind.heading_integral(0.5);
assert!(mid > 0.0 && mid < 0.5, "{:?} mid={}", kind, mid);
let end = kind.heading_integral(1.0);
assert!(end > 0.0 && end < 1.0, "{:?} end={}", kind, end);
}
}
#[test]
fn parabolic_vertical_segment() {
let seg = VSeg::Parabolic {
start: 3600.0,
length: 3685.68,
h0: 399.0,
g0: 0.0579,
parabola_constant: 36000.0,
is_convex: false,
};
let (z, slope) = v_eval(&seg, 1680.0);
assert!((z - 535.472).abs() < 0.01, "z = {}", z);
assert!((slope - 0.1046).abs() < 1e-3, "slope = {}", slope);
}
#[test]
fn nonzero_start_dist_along_rebases_vertical_to_horizontal() {
let content = "\
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('','',(''),(''),'','','');
FILE_SCHEMA(('IFC4X1'));
ENDSEC;
DATA;
#10=IFCCARTESIANPOINT((0.,0.));
#11=IFCLINESEGMENT2D(#10,0.,100.);
#12=IFCALIGNMENT2DHORIZONTALSEGMENT($,$,$,#11);
#13=IFCALIGNMENT2DHORIZONTAL(1000.,(#12));
#14=IFCALIGNMENT2DVERSEGLINE($,$,$,1000.,100.,50.,0.1);
#15=IFCALIGNMENT2DVERTICAL((#14));
#16=IFCALIGNMENTCURVE(#13,#15,$);
ENDSEC;
END-ISO-10303-21;
";
let entity_index = ifc_lite_core::build_entity_index(&content);
let mut decoder = EntityDecoder::with_index(&content, entity_index);
let directrix = decoder.decode_by_id(16).expect("decode IfcAlignmentCurve");
let curve = AlignmentCurve::parse(&directrix, &mut decoder)
.expect("parse alignment")
.expect("directrix recognised as alignment");
let f0 = curve.evaluate(0.0);
assert!((f0.origin.x - 0.0).abs() < 1e-6, "start x = {}", f0.origin.x);
assert!((f0.origin.z - 50.0).abs() < 1e-6, "start z = {}", f0.origin.z);
let f_mid = curve.evaluate(50.0);
assert!((f_mid.origin.x - 50.0).abs() < 1e-6, "mid x = {}", f_mid.origin.x);
assert!(
(f_mid.origin.z - 55.0).abs() < 1e-6,
"mid z = {} (expected 55; main desyncs vertical and returns ~50)",
f_mid.origin.z,
);
let f_end = curve.evaluate(100.0);
assert!((f_end.origin.x - 100.0).abs() < 1e-6, "end x = {}", f_end.origin.x);
assert!((f_end.origin.z - 60.0).abs() < 1e-6, "end z = {}", f_end.origin.z);
}
#[test]
fn negative_segment_length_errors_not_nan() {
let content = "\
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('','',(''),(''),'','','');
FILE_SCHEMA(('IFC4X1'));
ENDSEC;
DATA;
#10=IFCCARTESIANPOINT((0.,0.));
#11=IFCLINESEGMENT2D(#10,0.,-100.);
#12=IFCALIGNMENT2DHORIZONTALSEGMENT($,$,$,#11);
#13=IFCALIGNMENT2DHORIZONTAL(0.,(#12));
#14=IFCALIGNMENT2DVERSEGLINE($,$,$,0.,100.,50.,0.1);
#15=IFCALIGNMENT2DVERTICAL((#14));
#16=IFCALIGNMENTCURVE(#13,#15,$);
ENDSEC;
END-ISO-10303-21;
";
let entity_index = ifc_lite_core::build_entity_index(&content);
let mut decoder = EntityDecoder::with_index(&content, entity_index);
let directrix = decoder.decode_by_id(16).expect("decode IfcAlignmentCurve");
assert!(AlignmentCurve::parse(&directrix, &mut decoder).is_err());
}
#[test]
fn station_before_first_vertical_segment_extrapolates_from_the_first_segment() {
let content = "\
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('','',(''),(''),'','','');
FILE_SCHEMA(('IFC4X1'));
ENDSEC;
DATA;
#10=IFCCARTESIANPOINT((0.,0.));
#11=IFCLINESEGMENT2D(#10,0.,900.);
#12=IFCALIGNMENT2DHORIZONTALSEGMENT($,$,$,#11);
#13=IFCALIGNMENT2DHORIZONTAL(0.,(#12));
#14=IFCALIGNMENT2DVERSEGLINE($,$,$,100.,400.,10.,0.);
#15=IFCALIGNMENT2DVERSEGLINE($,$,$,500.,400.,10.,0.05);
#16=IFCALIGNMENT2DVERTICAL((#14,#15));
#17=IFCALIGNMENTCURVE(#13,#16,$);
ENDSEC;
END-ISO-10303-21;
";
let entity_index = ifc_lite_core::build_entity_index(&content);
let mut decoder = EntityDecoder::with_index(&content, entity_index);
let directrix = decoder.decode_by_id(17).expect("decode IfcAlignmentCurve");
let curve = AlignmentCurve::parse(&directrix, &mut decoder)
.expect("parse alignment")
.expect("directrix recognised as alignment");
assert!((curve.evaluate(100.0).origin.z - 10.0).abs() < 1e-9);
assert!((curve.evaluate(700.0).origin.z - 20.0).abs() < 1e-9);
assert!((curve.evaluate(950.0).origin.z - 32.5).abs() < 1e-9);
for station in [0.0, 50.0, 99.0] {
let frame = curve.evaluate(station);
assert!(
(frame.origin.z - 10.0).abs() < 1e-9,
"station {station}: z = {} (last-exit extrapolation gives {})",
frame.origin.z,
30.0 - 0.05 * (900.0 - station),
);
assert!(
frame.tangent.z.abs() < 1e-9,
"station {station}: slope must be the FIRST segment's 0, got tangent.z = {}",
frame.tangent.z,
);
}
}
#[test]
fn station_in_a_vertical_gap_uses_the_nearer_bracketing_segment() {
let curve = AlignmentCurve {
horizontal: vec![HSeg::Line {
sx: 0.0,
sy: 0.0,
heading: 0.0,
length: 400.0,
cum_start: 0.0,
}],
vertical: vec![
VSeg::Line {
start: 0.0,
length: 100.0,
h0: 0.0,
g0: 0.1,
},
VSeg::Line {
start: 300.0,
length: 100.0,
h0: 50.0,
g0: 0.0,
},
],
};
let near_first = curve.evaluate(150.0);
assert!((near_first.origin.z - 15.0).abs() < 1e-9, "z = {}", near_first.origin.z);
assert!((near_first.tangent.z - 0.1 / (1.0f64 + 0.01).sqrt()).abs() < 1e-9);
let near_second = curve.evaluate(280.0);
assert!((near_second.origin.z - 50.0).abs() < 1e-9, "z = {}", near_second.origin.z);
assert!(near_second.tangent.z.abs() < 1e-9);
}
#[test]
fn zero_length_transition_segment_evaluates_to_its_start_not_nan() {
let content = "\
ISO-10303-21;
HEADER;
FILE_DESCRIPTION((''),'2;1');
FILE_NAME('','',(''),(''),'','','');
FILE_SCHEMA(('IFC4X1'));
ENDSEC;
DATA;
#10=IFCCARTESIANPOINT((5.,7.));
#11=IFCTRANSITIONCURVESEGMENT2D(#10,0.,0.,$,100.,.T.,.T.,.CLOTHOIDCURVE.);
#12=IFCALIGNMENT2DHORIZONTALSEGMENT($,$,$,#11);
#20=IFCLINESEGMENT2D(#10,0.,100.);
#21=IFCALIGNMENT2DHORIZONTALSEGMENT($,$,$,#20);
#13=IFCALIGNMENT2DHORIZONTAL(0.,(#12,#21));
#14=IFCALIGNMENT2DVERSEGLINE($,$,$,0.,100.,0.,0.);
#15=IFCALIGNMENT2DVERTICAL((#14));
#16=IFCALIGNMENTCURVE(#13,#15,$);
ENDSEC;
END-ISO-10303-21;
";
let entity_index = ifc_lite_core::build_entity_index(&content);
let mut decoder = EntityDecoder::with_index(&content, entity_index);
let directrix = decoder.decode_by_id(16).expect("decode IfcAlignmentCurve");
let curve = AlignmentCurve::parse(&directrix, &mut decoder)
.expect("zero-length stubs are accepted at parse time")
.expect("directrix recognised as alignment");
let f0 = curve.evaluate(0.0);
assert!(
f0.origin.x.is_finite() && f0.origin.y.is_finite() && f0.tangent.x.is_finite(),
"station 0 must be finite, got origin {:?} tangent {:?}",
f0.origin,
f0.tangent
);
assert!((f0.origin.x - 5.0).abs() < 1e-9 && (f0.origin.y - 7.0).abs() < 1e-9);
assert!((f0.tangent.x - 1.0).abs() < 1e-9, "heading 0 along +X");
let f50 = curve.evaluate(50.0);
assert!((f50.origin.x - 55.0).abs() < 1e-9 && (f50.origin.y - 7.0).abs() < 1e-9);
}
#[test]
fn h_eval_zero_length_transition_is_its_start() {
let seg = HSeg::Transition {
sx: 1.0,
sy: 2.0,
heading: 0.3,
length: 0.0,
start_curv: 0.0,
end_curv: 0.01,
kind: TransitionKind::Clothoid,
cum_start: 0.0,
};
let (x, y, h) = h_eval(&seg, 0.0);
assert_eq!((x, y, h), (1.0, 2.0, 0.3));
}