mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 07:29:20 +02:00
Switches: lay the pre-blade lead and the blade, not a tangent arc
A turnout's diverging track does not leave the through track tangentially. It runs straight along the through track to the blade tip (odcinek przediglicowy), breaks away there by the blade angle (kąt nagięcia iglicy, beta), runs straight over the blade to its heel, and only then curves - through alfa - beta, not the whole crossing angle - to the frog. Give TurnoutSegment a turn_in: a signed heading break applied before the segment is laid, which is the only way to express a track leaving by an angle rather than a tangent. lay_turnout then places the lead, the break and the blade ahead of the curve and hands the curve the angle that is actually left to spend; a zero blade length keeps the break and starts the curve at the tip. Reject a blade angle that is negative or already reaches the crossing angle. All-zero blade figures lay exactly the old tangent arc from PR, so existing layouts are untouched. Junction and turnout_preset carry the three new figures through to the panel; the presets themselves still hold zeros until the Id-1 catalogue numbers are in. Add test_turnout: the lead sits on the through track, the blade breaks by beta, and the frog still lands on the full crossing angle and the catalogue length, mirrored for a right-hand turnout.
This commit is contained in:
@@ -2337,6 +2337,9 @@ void plan_panel::render_switches()
|
||||
m_sw_crossing = preset.crossing_n;
|
||||
m_sw_radius = preset.radius;
|
||||
m_sw_length = preset.length;
|
||||
m_sw_pre_blade = preset.pre_blade;
|
||||
m_sw_blade_angle = preset.blade_angle;
|
||||
m_sw_blade_length = preset.blade_length;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
@@ -2559,6 +2562,10 @@ void plan_panel::add_switch_on(std::size_t const Through, double const Wx, doubl
|
||||
junction.facing = true;
|
||||
junction.crossing_n = m_sw_crossing;
|
||||
junction.length = m_sw_length; // catalogue length from the template, 0 for a custom switch
|
||||
// the blade figures come from the template too; all zero lays a tangent arc from PR
|
||||
junction.pre_blade = m_sw_pre_blade;
|
||||
junction.blade_angle = m_sw_blade_angle;
|
||||
junction.blade_length = m_sw_blade_length;
|
||||
junction.curve.mode = 1; // plain arc; the internal curve can be made compound later
|
||||
junction.curve.radius = m_sw_radius;
|
||||
junction.branch = static_cast<int>(m_document.niwelety.size());
|
||||
|
||||
@@ -104,6 +104,9 @@ class plan_panel : public ui_panel
|
||||
double m_sw_crossing{9.0}; // skos 1:n
|
||||
double m_sw_radius{190.0}; // internal arc radius (metres)
|
||||
double m_sw_length{0.0}; // catalogue length from the chosen template (0 = just the curve)
|
||||
double m_sw_pre_blade{0.0}; // odcinek przediglicowy from the template, metres
|
||||
double m_sw_blade_angle{0.0}; // kąt nagięcia iglicy (beta) from the template, radians
|
||||
double m_sw_blade_length{0.0};// the straight blade from the template, metres
|
||||
int m_drag_junction{-1}; // index of the switch being slid along its through track, or -1
|
||||
|
||||
int m_sel_straight{-1};
|
||||
|
||||
@@ -20,16 +20,17 @@ std::vector<turnout_preset> const &turnout_presets()
|
||||
// the type tables (metres). the diverging leaves not tangent but at the blade angle (beta) after a
|
||||
// straight pre-blade lead, then arcs by (alfa - beta) to the frog.
|
||||
//
|
||||
// blade_angle and pre_blade are NOT yet from Id-1 - left at 0 (a tangent arc from PR) until the
|
||||
// catalogue figures are in. Fill them per type when we have them; the geometry already supports it.
|
||||
// blade_angle, pre_blade and blade_length are NOT yet from Id-1 - left at 0, which lays a tangent
|
||||
// arc straight from PR. Fill them per type when the catalogue figures are in; the geometry
|
||||
// (domain::lay_turnout) already lays the pre-blade lead, the blade break and the shortened arc.
|
||||
static std::vector<turnout_preset> const presets{
|
||||
{"Rz 1:7,5 R190", 7.5, 190.0, 25.222, 0.0, 0.0},
|
||||
{"Rz 1:9 R190", 9.0, 190.0, 27.138, 0.0, 0.0},
|
||||
{"Rz 1:9 R300", 9.0, 300.0, 33.230, 0.0, 0.0},
|
||||
{"Rz 1:12 R500", 12.0, 500.0, 41.594, 0.0, 0.0},
|
||||
{"Rz 1:14 R500", 14.0, 500.0, 42.371, 0.0, 0.0},
|
||||
{"Rz 1:14 R760", 14.0, 760.0, 54.216, 0.0, 0.0},
|
||||
{"Rz 1:18,5 R1200", 18.5, 1200.0, 64.818, 0.0, 0.0}};
|
||||
{"Rz 1:7,5 R190", 7.5, 190.0, 25.222, 0.0, 0.0, 0.0},
|
||||
{"Rz 1:9 R190", 9.0, 190.0, 27.138, 0.0, 0.0, 0.0},
|
||||
{"Rz 1:9 R300", 9.0, 300.0, 33.230, 0.0, 0.0, 0.0},
|
||||
{"Rz 1:12 R500", 12.0, 500.0, 41.594, 0.0, 0.0, 0.0},
|
||||
{"Rz 1:14 R500", 14.0, 500.0, 42.371, 0.0, 0.0, 0.0},
|
||||
{"Rz 1:14 R760", 14.0, 760.0, 54.216, 0.0, 0.0, 0.0},
|
||||
{"Rz 1:18,5 R1200", 18.5, 1200.0, 64.818, 0.0, 0.0, 0.0}};
|
||||
return presets;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ struct turnout_preset
|
||||
double length; // catalogue length PR->KR along the through track, metres
|
||||
double blade_angle; // kąt nagięcia iglicy (beta), radians; the diverging leaves the straight here
|
||||
double pre_blade; // odcinek przediglicowy: straight lead PR -> blade tip, metres
|
||||
double blade_length; // the straight blade itself, tip -> heel where the arc starts, metres
|
||||
};
|
||||
|
||||
// typical Polish turnouts (rozjazdy zwyczajne). the (skos, R) pairs are the recognised standard;
|
||||
|
||||
@@ -52,12 +52,24 @@ struct Turnout {
|
||||
DivergeSide side{DivergeSide::Left};
|
||||
DivergingCurve curve;
|
||||
double length{0.0}; ///< catalogue length along the through track; 0 = a tangent-length rail
|
||||
/// Odcinek przediglicowy: the straight lead from the switch start to the blade
|
||||
/// tip, still along the through track. Zero for a curve tangent from the start.
|
||||
double pre_blade{0.0};
|
||||
/// Kąt nagięcia iglicy (beta), radians: the heading break at the blade tip. The
|
||||
/// diverging track leaves the through track here, not tangentially, and the
|
||||
/// curve then turns through only the remaining `alfa - beta`. Zero for a
|
||||
/// tangential (curved-blade) turnout.
|
||||
double blade_angle{0.0};
|
||||
/// The straight blade itself, from its tip to its heel where the curve begins.
|
||||
/// Zero starts the curve at the tip.
|
||||
double blade_length{0.0};
|
||||
};
|
||||
|
||||
/// One laid segment of the diverging path, as the constant/linear curvature the
|
||||
/// renderer and the fitter already share (see segment_layout): a straight when
|
||||
/// both curvatures are zero, an arc when they are equal, a clothoid otherwise.
|
||||
struct TurnoutSegment {
|
||||
double turn_in{0.0}; ///< signed heading break applied before laying this segment (the blade)
|
||||
double k0{0.0};
|
||||
double k1{0.0};
|
||||
double length{0.0};
|
||||
@@ -73,12 +85,13 @@ struct TurnoutGeometry {
|
||||
double diverging_length{0.0}; ///< total length of the diverging path (curve + frog rail)
|
||||
};
|
||||
|
||||
/// Lays a turnout forward from @p start — the switch start, tangent to the through
|
||||
/// track — turning toward its side until the heading has swung by the crossing
|
||||
/// angle, then running straight to the catalogue length. Never throws; returns
|
||||
/// Lays a turnout forward from @p start — the switch start (początek rozjazdu),
|
||||
/// tangent to the through track: the pre-blade straight, the blade breaking away
|
||||
/// by `blade_angle`, the curve turning through what is left of the crossing angle,
|
||||
/// then the frog rail out to the catalogue length. Never throws; returns
|
||||
/// `valid == false` when the parameters admit no geometry: a non-positive radius,
|
||||
/// or a curve whose fixed parts already overshoot the crossing angle before the
|
||||
/// closing arc.
|
||||
/// a blade angle that is negative or already reaches the crossing angle, or a
|
||||
/// curve whose fixed parts overshoot the remaining angle before the closing arc.
|
||||
[[nodiscard]] TurnoutGeometry lay_turnout(const geometry::Pose& start,
|
||||
const Turnout& turnout);
|
||||
|
||||
|
||||
@@ -99,6 +99,9 @@ struct Junction {
|
||||
bool facing{true}; ///< true opens toward increasing station, false back
|
||||
double crossing_n{9.0}; ///< crossing mark 1:n (skos) → leaving angle atan(1/n)
|
||||
double length{0.0}; ///< catalogue length (PR→KR along the through track); 0 = just the curve
|
||||
double pre_blade{0.0}; ///< odcinek przediglicowy: straight lead PR → blade tip, metres
|
||||
double blade_angle{0.0}; ///< kąt nagięcia iglicy (beta), radians; 0 = curve tangent from PR
|
||||
double blade_length{0.0};///< the straight blade, tip → heel where the curve starts, metres
|
||||
GapFit curve; ///< the internal diverging curve: arc / compound / basket
|
||||
int branch{-1}; ///< index of the branch niweleta, pinned to the frog
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ bool build_curve(const DivergingCurve& curve, double total_angle, double sign,
|
||||
double consumed = 0.0; // deflection of everything before the closing arc
|
||||
|
||||
if (curve.entry_transition > 0.0) {
|
||||
out.push_back({0.0, sign / first_radius, curve.entry_transition});
|
||||
out.push_back({0.0, 0.0, sign / first_radius, curve.entry_transition});
|
||||
consumed += std::abs(deflection(out.back()));
|
||||
}
|
||||
|
||||
@@ -41,12 +41,12 @@ bool build_curve(const DivergingCurve& curve, double total_angle, double sign,
|
||||
for (std::size_t i = 0; i + 1 < curve.arcs.size(); ++i) {
|
||||
const double radius = curve.arcs[i].radius;
|
||||
if (radius <= 0.0) return false;
|
||||
out.push_back({sign / radius, sign / radius, curve.arcs[i].length});
|
||||
out.push_back({0.0, sign / radius, sign / radius, curve.arcs[i].length});
|
||||
consumed += std::abs(deflection(out.back()));
|
||||
|
||||
const double next_radius = curve.arcs[i + 1].radius;
|
||||
if (curve.arcs[i].transition_to_next > 0.0 && next_radius > 0.0) {
|
||||
out.push_back({sign / radius, sign / next_radius,
|
||||
out.push_back({0.0, sign / radius, sign / next_radius,
|
||||
curve.arcs[i].transition_to_next});
|
||||
consumed += std::abs(deflection(out.back()));
|
||||
}
|
||||
@@ -56,17 +56,26 @@ bool build_curve(const DivergingCurve& curve, double total_angle, double sign,
|
||||
const double closing = total_angle - consumed - exit_deflection;
|
||||
if (closing <= 0.0) return false; // the fixed parts already reach the crossing angle
|
||||
|
||||
out.push_back({sign / last_radius, sign / last_radius, closing * last_radius});
|
||||
out.push_back({0.0, sign / last_radius, sign / last_radius, closing * last_radius});
|
||||
if (curve.exit_transition > 0.0) {
|
||||
out.push_back({sign / last_radius, 0.0, curve.exit_transition});
|
||||
out.push_back({0.0, sign / last_radius, 0.0, curve.exit_transition});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Advances @p pose along @p segment (without sampling points; that is the
|
||||
// renderer's job) and returns the end pose.
|
||||
// Turns @p pose in place by @p angle (a heading break, no travel).
|
||||
Pose turn(const Pose& pose, double angle) {
|
||||
if (angle == 0.0) return pose;
|
||||
const double c = std::cos(angle);
|
||||
const double s = std::sin(angle);
|
||||
return Pose{pose.x, pose.y, pose.hx * c - pose.hy * s, pose.hx * s + pose.hy * c};
|
||||
}
|
||||
|
||||
// Advances @p pose over @p segment (without sampling points; that is the
|
||||
// renderer's job): the heading break first, then the travel. Returns the end pose.
|
||||
Pose advance(const TurnoutSegment& segment, const Pose& pose) {
|
||||
return layout_segment(segment.k0, segment.k1, segment.length, pose, nullptr);
|
||||
return layout_segment(segment.k0, segment.k1, segment.length,
|
||||
turn(pose, segment.turn_in), nullptr);
|
||||
}
|
||||
|
||||
// Where the through tangent (through @p a along @p a heading) meets the diverging
|
||||
@@ -91,10 +100,27 @@ TurnoutGeometry lay_turnout(const Pose& start, const Turnout& turnout) {
|
||||
if (angle <= 0.0) return geometry;
|
||||
const double sign = turnout.side == DivergeSide::Left ? 1.0 : -1.0;
|
||||
|
||||
if (!build_curve(turnout.curve, angle, sign, geometry.path)) {
|
||||
const double blade = turnout.blade_angle;
|
||||
if (blade < 0.0 || blade >= angle) return geometry; // the blade may not reach the frog on its own
|
||||
|
||||
// odcinek przediglicowy: still along the through track, the blade tip sits at its end
|
||||
if (turnout.pre_blade > 0.0) {
|
||||
geometry.path.push_back(TurnoutSegment{0.0, 0.0, 0.0, turnout.pre_blade});
|
||||
}
|
||||
// the blade breaks away by beta and runs straight to its heel; with no blade
|
||||
// length the break still stands and the curve starts at the tip
|
||||
if (turnout.blade_length > 0.0) {
|
||||
geometry.path.push_back(TurnoutSegment{sign * blade, 0.0, 0.0, turnout.blade_length});
|
||||
}
|
||||
|
||||
// the curve only has `alfa - beta` left to turn through
|
||||
std::vector<TurnoutSegment> curve_path;
|
||||
if (!build_curve(turnout.curve, angle - blade, sign, curve_path)) {
|
||||
geometry.path.clear();
|
||||
return geometry;
|
||||
}
|
||||
if (turnout.blade_length <= 0.0) curve_path.front().turn_in += sign * blade;
|
||||
geometry.path.insert(geometry.path.end(), curve_path.begin(), curve_path.end());
|
||||
|
||||
// walk the curve to the frog direction (the crossing angle)
|
||||
Pose pose = start;
|
||||
@@ -112,7 +138,7 @@ TurnoutGeometry lay_turnout(const Pose& start, const Turnout& turnout) {
|
||||
? (turnout.length - through_projection) / cos_angle
|
||||
: turnout.curve.arcs.back().radius * std::tan(angle * 0.5);
|
||||
if (rail > 0.0) {
|
||||
const TurnoutSegment frog_rail{0.0, 0.0, rail};
|
||||
const TurnoutSegment frog_rail{0.0, 0.0, 0.0, rail};
|
||||
geometry.path.push_back(frog_rail);
|
||||
pose = advance(frog_rail, pose);
|
||||
}
|
||||
|
||||
@@ -281,7 +281,9 @@ domain::Turnout to_turnout(const Junction& junction) {
|
||||
return domain::Turnout{
|
||||
domain::CrossingMark{junction.crossing_n},
|
||||
junction.side == 0 ? domain::DivergeSide::Left : domain::DivergeSide::Right,
|
||||
std::move(curve), junction.length};
|
||||
std::move(curve), junction.length,
|
||||
junction.pre_blade, junction.blade_angle,
|
||||
junction.blade_length};
|
||||
}
|
||||
|
||||
// The drawable geometry of one switch. @p station is the switch's start on the
|
||||
@@ -319,6 +321,13 @@ JunctionGeom solve_junction(const Junction& junction,
|
||||
std::abs(segment.k1 - segment.k0) >= 1e-12 ? render::ElementKind::Transition
|
||||
: std::abs(segment.k0) >= 1e-12 ? render::ElementKind::Arc
|
||||
: render::ElementKind::Straight;
|
||||
// the blade's heading break happens in place, before the segment is laid
|
||||
if (segment.turn_in != 0.0) {
|
||||
const double c = std::cos(segment.turn_in);
|
||||
const double s = std::sin(segment.turn_in);
|
||||
pose = Pose{pose.x, pose.y, pose.hx * c - pose.hy * s,
|
||||
pose.hx * s + pose.hy * c};
|
||||
}
|
||||
pose = layout_segment(segment.k0, segment.k1, segment.length, pose, &pts);
|
||||
element.points.reserve(pts.size());
|
||||
for (const auto& p : pts) element.points.push_back(render::Point{p.x, p.y});
|
||||
|
||||
@@ -47,3 +47,8 @@ add_executable(maj0sted_test_document test_document.cpp)
|
||||
target_link_libraries(maj0sted_test_document PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_document PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME document COMMAND maj0sted_test_document)
|
||||
|
||||
add_executable(maj0sted_test_turnout test_turnout.cpp)
|
||||
target_link_libraries(maj0sted_test_turnout PRIVATE maj0sted::maj0sted)
|
||||
target_include_directories(maj0sted_test_turnout PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_test(NAME turnout COMMAND maj0sted_test_turnout)
|
||||
|
||||
130
maj0sted/tests/test_turnout.cpp
Normal file
130
maj0sted/tests/test_turnout.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "maj0sted/domain/geometry/turnout.hpp"
|
||||
|
||||
using namespace maj0sted::domain;
|
||||
using maj0sted::domain::geometry::Pose;
|
||||
|
||||
namespace {
|
||||
|
||||
Turnout rz_1_9_r190() {
|
||||
Turnout turnout{CrossingMark{9.0}, DivergeSide::Left, DivergingCurve{}, 27.138};
|
||||
turnout.curve.arcs.push_back(TurnoutArc{190.0, 0.0, 0.0});
|
||||
return turnout;
|
||||
}
|
||||
|
||||
// Walks the laid path and returns the pose at the end of segment @p index.
|
||||
Pose pose_after(const TurnoutGeometry& geometry, std::size_t index) {
|
||||
Pose pose{0.0, 0.0, 1.0, 0.0};
|
||||
for (std::size_t i = 0; i <= index; ++i) {
|
||||
const auto& segment = geometry.path[i];
|
||||
if (segment.turn_in != 0.0) {
|
||||
const double c = std::cos(segment.turn_in);
|
||||
const double s = std::sin(segment.turn_in);
|
||||
pose = Pose{pose.x, pose.y, pose.hx * c - pose.hy * s,
|
||||
pose.hx * s + pose.hy * c};
|
||||
}
|
||||
pose = geometry::layout_segment(segment.k0, segment.k1, segment.length, pose,
|
||||
nullptr);
|
||||
}
|
||||
return pose;
|
||||
}
|
||||
|
||||
void pre_blade_runs_along_the_through_track() {
|
||||
auto turnout = rz_1_9_r190();
|
||||
turnout.pre_blade = 3.5;
|
||||
turnout.blade_angle = 0.02;
|
||||
turnout.blade_length = 6.0;
|
||||
|
||||
const auto geometry = lay_turnout(Pose{0.0, 0.0, 1.0, 0.0}, turnout);
|
||||
CHECK(geometry.valid);
|
||||
|
||||
// the lead is straight, on the through track, and the blade tip sits at its end
|
||||
const Pose tip = pose_after(geometry, 0);
|
||||
CHECK(std::abs(tip.x - 3.5) < 1e-9);
|
||||
CHECK(std::abs(tip.y) < 1e-12);
|
||||
CHECK(std::abs(tip.hy) < 1e-12);
|
||||
|
||||
// the blade breaks away by beta exactly, and stays straight over its length
|
||||
const Pose heel = pose_after(geometry, 1);
|
||||
CHECK(std::abs(std::atan2(heel.hy, heel.hx) - 0.02) < 1e-9);
|
||||
CHECK(std::abs(std::hypot(heel.x - tip.x, heel.y - tip.y) - 6.0) < 1e-9);
|
||||
}
|
||||
|
||||
void the_frog_still_lands_on_the_crossing_angle() {
|
||||
const double alfa = std::atan(1.0 / 9.0);
|
||||
|
||||
auto tangential = rz_1_9_r190();
|
||||
auto bladed = rz_1_9_r190();
|
||||
bladed.pre_blade = 3.5;
|
||||
bladed.blade_angle = 0.02;
|
||||
bladed.blade_length = 6.0;
|
||||
|
||||
for (const auto& turnout : {tangential, bladed}) {
|
||||
const auto geometry = lay_turnout(Pose{0.0, 0.0, 1.0, 0.0}, turnout);
|
||||
CHECK(geometry.valid);
|
||||
// the curve spends only alfa - beta, so the frog heading is alfa either way
|
||||
CHECK(std::abs(std::atan2(geometry.frog.hy, geometry.frog.hx) - alfa) < 1e-9);
|
||||
// and the catalogue length is still measured along the through track
|
||||
CHECK(std::abs(geometry.frog.x - 27.138) < 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
void a_blade_reaching_the_crossing_angle_is_rejected() {
|
||||
auto turnout = rz_1_9_r190();
|
||||
turnout.blade_angle = std::atan(1.0 / 9.0); // nothing left for the curve to turn
|
||||
CHECK(!lay_turnout(Pose{0.0, 0.0, 1.0, 0.0}, turnout).valid);
|
||||
|
||||
turnout.blade_angle = -0.01;
|
||||
CHECK(!lay_turnout(Pose{0.0, 0.0, 1.0, 0.0}, turnout).valid);
|
||||
}
|
||||
|
||||
void a_bladeless_curve_still_starts_tangent() {
|
||||
const auto geometry = lay_turnout(Pose{0.0, 0.0, 1.0, 0.0}, rz_1_9_r190());
|
||||
CHECK(geometry.valid);
|
||||
for (const auto& segment : geometry.path) CHECK(segment.turn_in == 0.0);
|
||||
}
|
||||
|
||||
// With no blade length the break still stands: the curve simply starts at the tip.
|
||||
void a_zero_length_blade_keeps_its_break() {
|
||||
auto turnout = rz_1_9_r190();
|
||||
turnout.pre_blade = 3.5;
|
||||
turnout.blade_angle = 0.02;
|
||||
|
||||
const auto geometry = lay_turnout(Pose{0.0, 0.0, 1.0, 0.0}, turnout);
|
||||
CHECK(geometry.valid);
|
||||
CHECK(geometry.path.size() >= 2);
|
||||
CHECK(std::abs(geometry.path[1].turn_in - 0.02) < 1e-12);
|
||||
CHECK(std::abs(std::atan2(geometry.frog.hy, geometry.frog.hx) -
|
||||
std::atan(1.0 / 9.0)) < 1e-9);
|
||||
}
|
||||
|
||||
// A right-hand turnout is the mirror image: the blade breaks the other way.
|
||||
void a_right_hand_blade_breaks_the_other_way() {
|
||||
auto turnout = rz_1_9_r190();
|
||||
turnout.side = DivergeSide::Right;
|
||||
turnout.pre_blade = 3.5;
|
||||
turnout.blade_angle = 0.02;
|
||||
turnout.blade_length = 6.0;
|
||||
|
||||
const auto geometry = lay_turnout(Pose{0.0, 0.0, 1.0, 0.0}, turnout);
|
||||
CHECK(geometry.valid);
|
||||
CHECK(std::abs(std::atan2(pose_after(geometry, 1).hy, pose_after(geometry, 1).hx) +
|
||||
0.02) < 1e-9);
|
||||
CHECK(std::abs(std::atan2(geometry.frog.hy, geometry.frog.hx) +
|
||||
std::atan(1.0 / 9.0)) < 1e-9);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
RUN(pre_blade_runs_along_the_through_track);
|
||||
RUN(the_frog_still_lands_on_the_crossing_angle);
|
||||
RUN(a_blade_reaching_the_crossing_angle_is_rejected);
|
||||
RUN(a_bladeless_curve_still_starts_tangent);
|
||||
RUN(a_zero_length_blade_keeps_its_break);
|
||||
RUN(a_right_hand_blade_breaks_the_other_way);
|
||||
return REPORT();
|
||||
}
|
||||
Reference in New Issue
Block a user