mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-26 19:24:54 +01:00
feat(editor): the station schematic generalized: each station track on a row of its own, the stretches between the switches shortened, the connections slanted between the rows, the signals and the useful lengths on the drawn tracks
This commit is contained in:
@@ -834,6 +834,7 @@ void srk_tool::open(TTrack &Track)
|
||||
return;
|
||||
}
|
||||
m_layout = std::move(found);
|
||||
m_plot = station::generalize(*m_layout);
|
||||
m_hover = -1;
|
||||
m_view_state.exaggeration = 0.0f;
|
||||
m_history_revision = m_history.history_revision();
|
||||
@@ -3656,6 +3657,18 @@ void srk_tool::survey_signals()
|
||||
auto const ahead{station::project(*m_layout, signal.position + glm::dvec3{travel.x, 0.0, travel.y} * 5.0)};
|
||||
auto const unfit{std::any_of(m_heads.begin(), m_heads.end(), [&](head_check const &Head) { return Head.instance == signal.instance && (false == Head.fits || false == Head.problems.empty()); })};
|
||||
m_signal_marks.push_back({at, ahead.x > at.x, signal_shown(signal.name, stem), signal.position + glm::dvec3{0.0, signal.kind == "shunt" ? 3.0 : 7.0, 0.0}, unfit});
|
||||
// in the plot on the track it stands by, to its side
|
||||
auto &mark{m_signal_marks.back()};
|
||||
mark.drawn = m_plot.at(at);
|
||||
auto nearest{6.0};
|
||||
for (std::size_t path = 0; path < m_layout->paths.size(); ++path)
|
||||
for (std::size_t k = 0; k < m_layout->paths[path].schematic.size(); ++k)
|
||||
if (auto const &point{m_layout->paths[path].schematic[k]}; glm::distance(point, at) < nearest)
|
||||
{
|
||||
nearest = glm::distance(point, at);
|
||||
mark.drawn = m_plot.paths[path][k];
|
||||
mark.side = at.y > point.y ? 1.0f : -1.0f;
|
||||
}
|
||||
if (signal.kind != "main" && signal.kind != "shunt")
|
||||
continue;
|
||||
station::signal stop;
|
||||
@@ -4095,6 +4108,7 @@ void srk_tool::survey()
|
||||
m_circuit_of.assign(m_layout.has_value() ? m_layout->paths.size() : 0, -1);
|
||||
m_circuits.clear();
|
||||
m_ticks.clear();
|
||||
m_ticks_drawn.clear();
|
||||
m_lengths.clear();
|
||||
m_measured = false;
|
||||
m_numbered = false;
|
||||
@@ -4143,6 +4157,7 @@ void srk_tool::survey()
|
||||
nearest = distance;
|
||||
on = layout.paths[path].schematic[k];
|
||||
mark.world = layout.paths[path].points[k];
|
||||
mark.drawn = m_plot.paths[path][k];
|
||||
}
|
||||
mark.at = on;
|
||||
auto const entry{entry_circuit(mark.text, stem)};
|
||||
@@ -4161,6 +4176,7 @@ void srk_tool::survey()
|
||||
glm::dvec3 point;
|
||||
glm::dvec3 way;
|
||||
int circuit;
|
||||
glm::dvec2 drawn;
|
||||
};
|
||||
std::vector<end> ends;
|
||||
for (std::size_t index = 0; index < layout.paths.size(); ++index)
|
||||
@@ -4169,8 +4185,8 @@ void srk_tool::survey()
|
||||
if (points.size() < 2)
|
||||
continue;
|
||||
auto const circuit{m_circuit_of[index]};
|
||||
ends.push_back({points.front(), points[1] - points.front(), circuit});
|
||||
ends.push_back({points.back(), points.back() - points[points.size() - 2], circuit});
|
||||
ends.push_back({points.front(), points[1] - points.front(), circuit, m_plot.paths[index].front()});
|
||||
ends.push_back({points.back(), points.back() - points[points.size() - 2], circuit, m_plot.paths[index].back()});
|
||||
}
|
||||
m_joints.clear();
|
||||
for (std::size_t i = 0; i < ends.size(); ++i)
|
||||
@@ -4179,6 +4195,7 @@ void srk_tool::survey()
|
||||
std::none_of(m_joints.begin(), m_joints.end(), [&](joint_mark const &Joint) { return glm::distance(Joint.at, ends[i].point) < 0.05; }))
|
||||
{
|
||||
m_ticks.push_back(station::project(layout, ends[i].point));
|
||||
m_ticks_drawn.push_back(ends[i].drawn);
|
||||
glm::dvec2 const way{glm::normalize(glm::dvec2{ends[i].way.x, ends[i].way.z} + glm::dvec2{1e-12, 0.0})};
|
||||
m_joints.push_back({ends[i].point, glm::dvec3{-way.y, 0.0, way.x}});
|
||||
}
|
||||
@@ -4203,12 +4220,25 @@ void srk_tool::fit_view(float const Width, float const Height)
|
||||
from = std::min(from, row.chainage + row.outward * row.length);
|
||||
to = std::max(to, row.chainage + row.outward * row.length);
|
||||
}
|
||||
from = m_plot.x(from);
|
||||
to = m_plot.x(to);
|
||||
auto const span{std::max(50.0, to - from)};
|
||||
view.from = from - span * 0.04 - 30.0;
|
||||
view.to = to + span * 0.04 + 30.0;
|
||||
view.centre = (layout.low + layout.high) * 0.5;
|
||||
auto low{std::numeric_limits<double>::max()};
|
||||
auto high{std::numeric_limits<double>::lowest()};
|
||||
for (std::size_t index = 0; index < layout.paths.size() && index < m_plot.paths.size(); ++index)
|
||||
if (false == layout.paths[index].approach)
|
||||
for (auto const &point : m_plot.paths[index])
|
||||
{
|
||||
low = std::min(low, point.y);
|
||||
high = std::max(high, point.y);
|
||||
}
|
||||
if (low > high)
|
||||
low = high = 0.0;
|
||||
view.centre = (low + high) * 0.5;
|
||||
auto const across{(view.to - view.from) / std::max(1.0f, Width)};
|
||||
auto const vertical{(layout.high - layout.low + 20.0) / std::max(1.0f, Height * 0.75f)};
|
||||
auto const vertical{(high - low + 2.0 * station::row_height) / std::max(1.0f, Height * 0.85f)};
|
||||
view.exaggeration = static_cast<float>(std::clamp(across / vertical, 1.0, 500.0));
|
||||
}
|
||||
|
||||
@@ -4245,9 +4275,16 @@ void srk_tool::render_canvas(float const Height)
|
||||
view.to -= io.MouseDelta.x * across;
|
||||
view.centre += io.MouseDelta.y * vertical;
|
||||
}
|
||||
auto const x_of = [&](double const Chainage) { return corner.x + static_cast<float>((Chainage - view.from) / across); };
|
||||
auto const y_of = [&](double const Offset) { return corner.y + size.y * 0.5f - static_cast<float>((Offset - view.centre) / vertical); };
|
||||
auto const screen_of = [&](glm::dvec2 const &Point) { return ImVec2(x_of(Point.x), y_of(Point.y)); };
|
||||
// the plot draws the generalized schematic: its x shortens the chainage between the switches, its height puts the tracks on rows
|
||||
auto const plot_x = [&](double const X) { return corner.x + static_cast<float>((X - view.from) / across); };
|
||||
auto const plot_y = [&](double const Height) { return corner.y + size.y * 0.5f - static_cast<float>((Height - view.centre) / vertical); };
|
||||
auto const x_of = [&](double const Chainage) { return plot_x(m_plot.x(Chainage)); };
|
||||
auto const screen_of = [&](glm::dvec2 const &Point) { return ImVec2(plot_x(Point.x), plot_y(Point.y)); };
|
||||
auto const chainage_at = [&](float const Mouse) { return m_plot.chainage(view.from + (Mouse - corner.x) * across); };
|
||||
auto const drawn_of = [&](std::size_t const Index) -> std::vector<glm::dvec2> const & {
|
||||
static std::vector<glm::dvec2> const none;
|
||||
return Index < m_plot.paths.size() ? m_plot.paths[Index] : none;
|
||||
};
|
||||
|
||||
// the path under the mouse
|
||||
if (hovered && false == m_dragging)
|
||||
@@ -4257,7 +4294,7 @@ void srk_tool::render_canvas(float const Height)
|
||||
auto const &mouse{io.MousePos};
|
||||
for (int index = 0; index < static_cast<int>(layout.paths.size()); ++index)
|
||||
{
|
||||
auto const &points{layout.paths[index].schematic};
|
||||
auto const &points{drawn_of(index)};
|
||||
for (std::size_t i = 1; i < points.size(); ++i)
|
||||
{
|
||||
auto const a{screen_of(points[i - 1])};
|
||||
@@ -4286,8 +4323,8 @@ void srk_tool::render_canvas(float const Height)
|
||||
for (std::size_t index = 0; index < m_lines.size() && index < m_line_ways.size() && line_hover < 0; ++index)
|
||||
{
|
||||
auto const &row{m_lines[index]};
|
||||
auto const distance{(view.from + (io.MousePos.x - corner.x) * across - row.chainage) * row.outward};
|
||||
if (std::abs(io.MousePos.y - y_of(row.offset)) < 6.0f && distance >= 0.0 && distance <= row.length)
|
||||
auto const distance{(chainage_at(io.MousePos.x) - row.chainage) * row.outward};
|
||||
if (std::abs(io.MousePos.y - plot_y(m_plot.at({row.chainage, row.offset}).y)) < 6.0f && distance >= 0.0 && distance <= row.length)
|
||||
{
|
||||
line_hover = static_cast<int>(index);
|
||||
line_at = distance;
|
||||
@@ -4318,37 +4355,44 @@ void srk_tool::render_canvas(float const Height)
|
||||
draw->PushClipRect(corner, corner_end, true);
|
||||
// the km, or the chainage, along the top
|
||||
{
|
||||
auto const metres{across * 90.0};
|
||||
auto const first{chainage_at(corner.x)};
|
||||
auto const last{chainage_at(corner_end.x)};
|
||||
auto const metres{(last - first) / size.x * 90.0};
|
||||
double step{10.0};
|
||||
for (auto const candidate : {10.0, 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 2000.0, 5000.0})
|
||||
if ((step = candidate) >= metres)
|
||||
break;
|
||||
// the km of the station where it has them, else the chainage
|
||||
// the km of the station where it has them, else the chainage; the shortened stretches skip the marks too near
|
||||
double km_from{0.0};
|
||||
double km_to{0.0};
|
||||
auto const kilometrage{km_of_open(view.from, km_from) && km_of_open(view.to, km_to) && std::abs(km_to - km_from) > 1e-6};
|
||||
auto const low{kilometrage ? std::min(km_from, km_to) * 1000.0 : view.from};
|
||||
auto const high{kilometrage ? std::max(km_from, km_to) * 1000.0 : view.to};
|
||||
auto const kilometrage{km_of_open(first, km_from) && km_of_open(last, km_to) && std::abs(km_to - km_from) > 1e-6};
|
||||
auto const low{kilometrage ? std::min(km_from, km_to) * 1000.0 : first};
|
||||
auto const high{kilometrage ? std::max(km_from, km_to) * 1000.0 : last};
|
||||
auto shown{std::numeric_limits<float>::lowest()};
|
||||
for (auto mark{std::ceil(low / step) * step}; mark <= high; mark += step)
|
||||
{
|
||||
auto const x{x_of(kilometrage ? view.from + (mark / 1000.0 - km_from) / (km_to - km_from) * (view.to - view.from) : mark)};
|
||||
draw->AddLine(ImVec2(x, corner.y), ImVec2(x, corner_end.y), IM_COL32(255, 255, 255, 16));
|
||||
auto const x{x_of(kilometrage ? first + (mark / 1000.0 - km_from) / (km_to - km_from) * (last - first) : mark)};
|
||||
auto const label{kilometrage ? format("km %.3f", mark / 1000.0) : format("%.0f m", mark)};
|
||||
if (std::abs(x - shown) < ImGui::CalcTextSize(label.c_str()).x + 12.0f)
|
||||
continue;
|
||||
shown = x;
|
||||
draw->AddLine(ImVec2(x, corner.y), ImVec2(x, corner_end.y), IM_COL32(255, 255, 255, 16));
|
||||
draw->AddText(ImVec2(x + 3.0f, corner.y + 2.0f), IM_COL32(150, 155, 165, 255), label.c_str());
|
||||
}
|
||||
}
|
||||
auto const *selected{m_selection.selected_track()};
|
||||
auto const draw_path = [&](station::path const &Path, ImU32 const Colour, float const Width) {
|
||||
for (std::size_t i = 1; i < Path.schematic.size(); ++i)
|
||||
draw->AddLine(screen_of(Path.schematic[i - 1]), screen_of(Path.schematic[i]), Colour, Width);
|
||||
auto const draw_path = [&](std::size_t const Index, ImU32 const Colour, float const Width) {
|
||||
auto const &points{drawn_of(Index)};
|
||||
for (std::size_t i = 1; i < points.size(); ++i)
|
||||
draw->AddLine(screen_of(points[i - 1]), screen_of(points[i]), Colour, Width);
|
||||
};
|
||||
for (auto const &path : layout.paths)
|
||||
if (path.approach)
|
||||
draw_path(path, kApproach, 2.0f);
|
||||
for (std::size_t index = 0; index < layout.paths.size(); ++index)
|
||||
if (layout.paths[index].approach)
|
||||
draw_path(index, kApproach, 2.0f);
|
||||
for (std::size_t index = 0; index < layout.paths.size(); ++index)
|
||||
if (auto const &path{layout.paths[index]}; false == path.approach || m_circuit_of[index] >= 0)
|
||||
draw_path(path, m_circuit_of[index] >= 0 ? m_circuits[m_circuit_of[index]].colour : is_switch(*path.track) ? kSwitch : kPlain, 2.5f);
|
||||
for (auto const &tick : m_ticks)
|
||||
draw_path(index, m_circuit_of[index] >= 0 ? m_circuits[m_circuit_of[index]].colour : is_switch(*path.track) ? kSwitch : kPlain, 2.5f);
|
||||
for (auto const &tick : m_ticks_drawn)
|
||||
{
|
||||
auto const at{screen_of(tick)};
|
||||
draw->AddLine(ImVec2(at.x, at.y - 5.0f), ImVec2(at.x, at.y + 5.0f), IM_COL32(255, 255, 255, 220), 2.0f);
|
||||
@@ -4358,7 +4402,7 @@ void srk_tool::render_canvas(float const Height)
|
||||
for (std::size_t index = 0; index < m_lines.size(); ++index)
|
||||
{
|
||||
auto const &row{m_lines[index]};
|
||||
auto const y{y_of(row.offset)};
|
||||
auto const y{plot_y(m_plot.at({row.chainage, row.offset}).y)};
|
||||
draw->AddLine(ImVec2(line_x(row, 0.0), y), ImVec2(line_x(row, row.length), y), kApproach, 2.0f);
|
||||
for (std::size_t k = 0; k < row.sections.size(); ++k)
|
||||
{
|
||||
@@ -4376,11 +4420,11 @@ void srk_tool::render_canvas(float const Height)
|
||||
draw->AddTriangleFilled(ImVec2(x - 4.0f * way, y - 4.0f), ImVec2(x - 4.0f * way, y + 4.0f), ImVec2(x + 4.0f * way, y), kSignal);
|
||||
}
|
||||
}
|
||||
for (auto const &path : layout.paths)
|
||||
if (path.track == selected)
|
||||
draw_path(path, overlay_color::selected, 4.0f);
|
||||
for (std::size_t index = 0; index < layout.paths.size(); ++index)
|
||||
if (layout.paths[index].track == selected)
|
||||
draw_path(index, overlay_color::selected, 4.0f);
|
||||
if (m_hover >= 0)
|
||||
draw_path(layout.paths[m_hover], kHovered, 4.0f);
|
||||
draw_path(static_cast<std::size_t>(m_hover), kHovered, 4.0f);
|
||||
// the useful lengths of the station track shown: of the trains to the right over it, to the left under it, an arrowhead where
|
||||
// they stop
|
||||
std::vector<std::pair<std::string, ImVec2>> useful;
|
||||
@@ -4396,7 +4440,7 @@ void srk_tool::render_canvas(float const Height)
|
||||
std::vector<ImVec2> line;
|
||||
for (auto const &point : stretch_of(track, part.rear.along, part.head.along))
|
||||
{
|
||||
auto at{screen_of(station::project(layout, point))};
|
||||
auto at{screen_of(m_plot.at(station::project(layout, point)))};
|
||||
at.y += shift;
|
||||
line.push_back(at);
|
||||
}
|
||||
@@ -4433,7 +4477,10 @@ void srk_tool::render_canvas(float const Height)
|
||||
for (std::size_t index = 0; index < layout.switches.size(); ++index)
|
||||
{
|
||||
auto const *track{layout.switches[index]};
|
||||
auto const at{screen_of(station::project(layout, track->m_paths.front().points[segment_data::point::start]))};
|
||||
auto at{screen_of(m_plot.at(station::project(layout, track->m_paths.front().points[segment_data::point::start])))};
|
||||
for (std::size_t path = 0; path < layout.paths.size(); ++path)
|
||||
if (layout.paths[path].track == track && layout.paths[path].index == 0 && false == drawn_of(path).empty())
|
||||
at = screen_of(drawn_of(path).front());
|
||||
draw->AddCircleFilled(at, 3.5f, kSwitch);
|
||||
auto text{track->name()};
|
||||
if (false == stem.empty() && text.size() > stem.size() && text.compare(0, stem.size(), stem) == 0)
|
||||
@@ -4448,7 +4495,8 @@ void srk_tool::render_canvas(float const Height)
|
||||
// the signals: a flag pointing the way of the trains they face
|
||||
for (auto const &mark : m_signal_marks)
|
||||
{
|
||||
auto const at{screen_of(mark.at)};
|
||||
auto at{screen_of(mark.drawn)};
|
||||
at.y -= mark.side * 6.0f;
|
||||
auto const way{mark.ahead ? 1.0f : -1.0f};
|
||||
auto const colour{mark.unfit ? kUnfit : kSignal};
|
||||
draw->AddTriangleFilled(ImVec2(at.x - 4.0f * way, at.y - 4.0f), ImVec2(at.x - 4.0f * way, at.y + 4.0f), ImVec2(at.x + 4.0f * way, at.y), colour);
|
||||
@@ -4458,7 +4506,7 @@ void srk_tool::render_canvas(float const Height)
|
||||
// track of a double-track one, under the lower
|
||||
for (auto const &row : m_lines)
|
||||
{
|
||||
auto const y{y_of(row.offset)};
|
||||
auto const y{plot_y(m_plot.at({row.chainage, row.offset}).y)};
|
||||
auto const up{row.partner < 0 || row.partner >= static_cast<int>(m_lines.size()) || row.offset >= m_lines[row.partner].offset};
|
||||
auto const outside = [&](float const Tall, float const Level) { return up ? y - 5.0f - (Tall + 3.0f) * Level + 3.0f : y + 5.0f + (Tall + 3.0f) * (Level - 1.0f); };
|
||||
auto const tall{height * (row.km_known ? 2.0f : 1.0f)};
|
||||
@@ -4483,14 +4531,14 @@ void srk_tool::render_canvas(float const Height)
|
||||
for (auto const index : order)
|
||||
{
|
||||
auto const &mark{m_circuits[index]};
|
||||
auto const at{screen_of(mark.at)};
|
||||
auto const at{screen_of(mark.drawn)};
|
||||
label(mark.text, mark.colour, {ImVec2(at.x, at.y + 4.0f), ImVec2(at.x, at.y - height - 4.0f)});
|
||||
}
|
||||
draw->PopClipRect();
|
||||
if (hovered && m_hover >= 0 && false == m_dragging)
|
||||
{
|
||||
auto const &path{layout.paths[m_hover]};
|
||||
auto const along{view.from + (io.MousePos.x - corner.x) * across};
|
||||
auto const along{chainage_at(io.MousePos.x)};
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::TextUnformatted(path.track->name().empty() ? STR_C("(no name)") : path.track->name().c_str());
|
||||
ImGui::TextDisabled("%s, %.1f m", is_switch(*path.track) ? STR_C("switch") : path.approach ? STR_C("line out of the station") : STR_C("track"), path.length);
|
||||
@@ -5057,6 +5105,66 @@ editor_selftest_result srk_tool::selftest(std::string const &Command, std::istri
|
||||
WriteLog(format("SELFTEST srkfix: %zu problems before, status %s", before, m_status.c_str()));
|
||||
return editor_selftest_result::handled;
|
||||
}
|
||||
if (Command == "srkdrawing")
|
||||
{
|
||||
// srkdrawing [track=<circuit>]: the schematic as the plot draws it: the rows of the tracks, the shortened chainage, the points
|
||||
// on the rows, on slants, the corners; the points of the paths of the circuit, offset > row
|
||||
if (false == m_layout.has_value())
|
||||
{
|
||||
WriteLog("SELFTEST srkdrawing: no station open");
|
||||
return editor_selftest_result::handled;
|
||||
}
|
||||
auto const &layout{*m_layout};
|
||||
std::map<int, std::string> rows;
|
||||
for (auto const &run : m_plot.runs)
|
||||
if (false == run.paths.empty())
|
||||
rows[run.row] += format(" %s@%.1f", layout.paths[run.paths.front()].track->name().c_str(), run.offset);
|
||||
std::string list;
|
||||
for (auto const &[row, names] : rows)
|
||||
list += format(" |%d:", row) + names;
|
||||
WriteLog(format("SELFTEST srkdrawing tracks %zu, rows %zu, spacing %.2f", m_plot.runs.size(), rows.size(), m_plot.spacing));
|
||||
WriteLog("SELFTEST srkdrawing rows" + list);
|
||||
WriteLog(format("SELFTEST srkdrawing switches over %.0f..%.0f m drawn %.0f..%.0f, back %.1f..%.1f", layout.from, layout.to, m_plot.x(layout.from), m_plot.x(layout.to), m_plot.chainage(m_plot.x(layout.from)), m_plot.chainage(m_plot.x(layout.to))));
|
||||
std::size_t on_rows{0};
|
||||
std::size_t between{0};
|
||||
std::size_t bends{0};
|
||||
std::size_t backwards{0};
|
||||
for (std::size_t index = 0; index < layout.paths.size(); ++index)
|
||||
{
|
||||
if (layout.paths[index].approach)
|
||||
continue;
|
||||
auto const &points{m_plot.paths[index]};
|
||||
for (auto const &point : points)
|
||||
{
|
||||
auto const rows{point.y / station::row_height};
|
||||
if (std::abs(rows - std::round(rows)) < 1e-6)
|
||||
++on_rows;
|
||||
else
|
||||
++between;
|
||||
}
|
||||
for (std::size_t k = 2; k < points.size(); ++k)
|
||||
{
|
||||
auto const a{points[k - 1] - points[k - 2]};
|
||||
auto const b{points[k] - points[k - 1]};
|
||||
if (std::abs(a.x) > 1e-6 && std::abs(b.x) > 1e-6 && (std::abs(a.y) > 1e-6 || std::abs(b.y) > 1e-6) && std::abs(a.y / a.x - b.y / b.x) > 1e-3)
|
||||
++bends;
|
||||
}
|
||||
for (std::size_t k = 1; k < points.size(); ++k)
|
||||
if ((points[k].x - points[k - 1].x) * (layout.paths[index].schematic[k].x - layout.paths[index].schematic[k - 1].x) < 0.0)
|
||||
++backwards;
|
||||
}
|
||||
WriteLog(format("SELFTEST srkdrawing points on rows %zu, between %zu, bends %zu, x backwards %zu", on_rows, between, bends, backwards));
|
||||
if (auto const values{selftest_values(Words)}; values.count("track"))
|
||||
for (std::size_t index = 0; index < layout.paths.size(); ++index)
|
||||
if (auto const &path{layout.paths[index]}; false == path.track->Isolated.empty() && path.track->Isolated.front()->asName == values.at("track"))
|
||||
{
|
||||
std::string list;
|
||||
for (std::size_t k = 0; k < path.schematic.size(); k += 4)
|
||||
list += format(" %.0f/%.2f>%.2f", path.schematic[k].x, path.schematic[k].y, m_plot.paths[index][k].y / station::row_height);
|
||||
WriteLog(format("SELFTEST srkdrawing %s/%d:", path.track->name().c_str(), path.index) + list);
|
||||
}
|
||||
return editor_selftest_result::handled;
|
||||
}
|
||||
if (Command == "srkstrip")
|
||||
{
|
||||
// srkstrip shown=0: the schematic hidden, the 3d view whole for a screenshot
|
||||
|
||||
@@ -96,6 +96,7 @@ class srk_tool final : public editor_tool
|
||||
bool m_changed{false};
|
||||
std::vector<record> m_records;
|
||||
std::optional<station::layout> m_layout;
|
||||
station::drawing m_plot; // the schematic of the layout as the plot draws it
|
||||
int m_record{-1}; // of the open station, -1 while it has none
|
||||
TTrack *m_anchor{nullptr}; // the switch the open station was found from
|
||||
bool m_reversed{false}; // the switches numbered from the right end of the schematic
|
||||
@@ -114,10 +115,12 @@ class srk_tool final : public editor_tool
|
||||
ImU32 colour{0};
|
||||
double length{0.0};
|
||||
bool labelled{true}; // the circuits of the switches go without, named after them
|
||||
glm::dvec2 drawn{0.0}; // in the plot
|
||||
};
|
||||
std::vector<int> m_circuit_of; // of each path of the layout, an index into m_circuits, -1 for none
|
||||
std::vector<circuit_mark> m_circuits;
|
||||
std::vector<glm::dvec2> m_ticks; // the joints between the circuits, in the schematic
|
||||
std::vector<glm::dvec2> m_ticks_drawn; // the same in the plot
|
||||
struct joint_mark
|
||||
{
|
||||
glm::dvec3 at{0.0};
|
||||
@@ -131,6 +134,8 @@ class srk_tool final : public editor_tool
|
||||
std::string text;
|
||||
glm::dvec3 head{0.0}; // above its lamps, in the scenery
|
||||
bool unfit{false}; // its lights don't show what its routes need
|
||||
glm::dvec2 drawn{0.0}; // on its track in the plot
|
||||
float side{0.0f}; // 1: it stands to the left of the track in the plot, -1: to the right
|
||||
};
|
||||
std::vector<signal_mark> m_signal_marks;
|
||||
bool m_signals_known{false}; // the signals standing surveyed, once their templates are found
|
||||
|
||||
@@ -641,6 +641,435 @@ double km_of(layout const &Layout, double const Chainage)
|
||||
return Layout.km + Chainage / 1000.0;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// a track rising more than this to the chainage runs across the rows
|
||||
double constexpr kAcross{0.04};
|
||||
// m, two tracks this far apart lie side by side
|
||||
double constexpr kSideBySide{1.0};
|
||||
// m of the chainage, points of two tracks compared side by side
|
||||
double constexpr kAbreast{3.0};
|
||||
// m, a plain stretch between the switches keeps this much of its length in the plot, the rest shortened to kKept of it
|
||||
double constexpr kWhole{60.0};
|
||||
double constexpr kKept{0.3};
|
||||
// m, how far a slant is followed over the tracks to the row it ends on
|
||||
double constexpr kSlantSearch{400.0};
|
||||
// m, the plot puts a point of the schematic on a path no farther from it
|
||||
double constexpr kNearPath{15.0};
|
||||
|
||||
double shortened(double const Length)
|
||||
{
|
||||
return Length <= kWhole ? Length : kWhole + (Length - kWhole) * kKept;
|
||||
}
|
||||
|
||||
double lengthened(double const Length)
|
||||
{
|
||||
return Length <= kWhole ? Length : kWhole + (Length - kWhole) / kKept;
|
||||
}
|
||||
|
||||
// rows, for the offset gone off a row: at most three rows further
|
||||
double rows_off(double const Steps)
|
||||
{
|
||||
auto const size{std::abs(Steps)};
|
||||
return std::copysign(size <= 1.0 ? size : 1.0 + 2.0 * (1.0 - std::exp((1.0 - size) * 0.5)), Steps);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
double drawing::x(double const Chainage) const
|
||||
{
|
||||
if (warp.empty())
|
||||
return Chainage;
|
||||
if (Chainage <= warp.front().x)
|
||||
return warp.front().y - shortened(warp.front().x - Chainage);
|
||||
if (Chainage >= warp.back().x)
|
||||
return warp.back().y + shortened(Chainage - warp.back().x);
|
||||
auto const next{std::upper_bound(warp.begin(), warp.end(), Chainage, [](double const Value, glm::dvec2 const &Point) { return Value < Point.x; })};
|
||||
auto const &a{*(next - 1)};
|
||||
auto const &b{*next};
|
||||
return a.y + (b.y - a.y) * (Chainage - a.x) / std::max(1e-9, b.x - a.x);
|
||||
}
|
||||
|
||||
double drawing::chainage(double const X) const
|
||||
{
|
||||
if (warp.empty())
|
||||
return X;
|
||||
if (X <= warp.front().y)
|
||||
return warp.front().x - lengthened(warp.front().y - X);
|
||||
if (X >= warp.back().y)
|
||||
return warp.back().x + lengthened(X - warp.back().y);
|
||||
auto const next{std::upper_bound(warp.begin(), warp.end(), X, [](double const Value, glm::dvec2 const &Point) { return Value < Point.y; })};
|
||||
auto const &a{*(next - 1)};
|
||||
auto const &b{*next};
|
||||
return a.x + (b.x - a.x) * (X - a.y) / std::max(1e-9, b.y - a.y);
|
||||
}
|
||||
|
||||
glm::dvec2 drawing::at(glm::dvec2 const &Schematic) const
|
||||
{
|
||||
auto best{kNearPath * kNearPath};
|
||||
glm::dvec2 result{x(Schematic.x), Schematic.y / spacing * row_height};
|
||||
if (false == runs.empty())
|
||||
{
|
||||
auto const [low, high]{std::minmax_element(runs.begin(), runs.end(), [](run const &A, run const &B) { return A.row < B.row; })};
|
||||
result.y = std::clamp(result.y, (low->row - 3.0) * row_height, (high->row + 3.0) * row_height);
|
||||
}
|
||||
for (std::size_t p = 0; p < schematic.size() && p < paths.size(); ++p)
|
||||
{
|
||||
auto const &points{schematic[p]};
|
||||
if (points.empty() || std::max(points.front().x, points.back().x) + 2.0 * kNearPath < Schematic.x || std::min(points.front().x, points.back().x) - 2.0 * kNearPath > Schematic.x)
|
||||
continue;
|
||||
for (std::size_t i = 1; i < points.size(); ++i)
|
||||
{
|
||||
auto const &a{points[i - 1]};
|
||||
auto const along{points[i] - a};
|
||||
auto const length2{glm::dot(along, along)};
|
||||
auto const t{length2 > 1e-12 ? std::clamp(glm::dot(Schematic - a, along) / length2, 0.0, 1.0) : 0.0};
|
||||
auto const gap{a + along * t - Schematic};
|
||||
if (auto const distance2{glm::dot(gap, gap)}; distance2 < best)
|
||||
{
|
||||
best = distance2;
|
||||
result = paths[p][i - 1] + (paths[p][i] - paths[p][i - 1]) * t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
drawing generalize(layout const &Layout)
|
||||
{
|
||||
drawing result;
|
||||
auto const &paths{Layout.paths};
|
||||
auto const count{paths.size()};
|
||||
for (auto const &path : paths)
|
||||
result.schematic.push_back(path.schematic);
|
||||
auto const plain = [&](std::size_t const Path) { return paths[Path].track == nullptr || false == node(*paths[Path].track); };
|
||||
auto const usable = [&](std::size_t const Path) { return paths[Path].points.size() >= 2 && paths[Path].points.size() == paths[Path].schematic.size(); };
|
||||
auto const end_point = [&](std::size_t const Path, bool const Atend) { return Atend ? paths[Path].points.back() : paths[Path].points.front(); };
|
||||
// the ends of the paths which meet: of each path, at its start and at its end
|
||||
std::vector<std::array<std::vector<std::pair<std::size_t, bool>>, 2>> met(count);
|
||||
for (std::size_t p = 0; p < count; ++p)
|
||||
for (std::size_t q = p + 1; q < count && usable(p); ++q)
|
||||
for (auto const pend : {false, true})
|
||||
for (auto const qend : {false, true})
|
||||
if (usable(q) && plan_distance(end_point(p, pend), end_point(q, qend)) < 0.05)
|
||||
{
|
||||
met[p][pend].emplace_back(q, qend);
|
||||
met[q][qend].emplace_back(p, pend);
|
||||
}
|
||||
// the points running along the axis rather than across it
|
||||
std::vector<std::vector<char>> along(count);
|
||||
for (std::size_t p = 0; p < count; ++p)
|
||||
{
|
||||
auto const &points{paths[p].schematic};
|
||||
for (std::size_t i = 0; i < points.size(); ++i)
|
||||
{
|
||||
auto const &a{points[i > 0 ? i - 1 : i]};
|
||||
auto const &b{points[i + 1 < points.size() ? i + 1 : i]};
|
||||
along[p].push_back(std::abs(b.x - a.x) > 1e-3 && std::abs(b.y - a.y) < std::abs(b.x - a.x) * kAcross);
|
||||
}
|
||||
}
|
||||
// the station tracks: plain paths joined end to end where nothing else meets them, each on a row of its own
|
||||
std::vector<int> run_of(count, -1);
|
||||
for (std::size_t p = 0; p < count; ++p)
|
||||
{
|
||||
if (run_of[p] >= 0 || false == plain(p) || false == usable(p))
|
||||
continue;
|
||||
auto const index{static_cast<int>(result.runs.size())};
|
||||
result.runs.emplace_back();
|
||||
run_of[p] = index;
|
||||
std::vector<std::size_t> stack{p};
|
||||
while (false == stack.empty())
|
||||
{
|
||||
auto const current{stack.back()};
|
||||
stack.pop_back();
|
||||
result.runs[index].paths.push_back(current);
|
||||
for (auto const atend : {false, true})
|
||||
{
|
||||
if (met[current][atend].size() != 1)
|
||||
continue;
|
||||
auto const [other, otherend]{met[current][atend].front()};
|
||||
if (plain(other) && run_of[other] < 0 && met[other][otherend].size() == 1)
|
||||
{
|
||||
run_of[other] = index;
|
||||
stack.push_back(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// of each run the points along the axis, by the chainage: the middle offset of them, which other runs lie beside it on the right
|
||||
auto &runs{result.runs};
|
||||
std::vector<std::vector<glm::dvec2>> samples(runs.size());
|
||||
std::vector<char> level(runs.size(), 0);
|
||||
for (std::size_t r = 0; r < runs.size(); ++r)
|
||||
{
|
||||
for (auto const p : runs[r].paths)
|
||||
for (std::size_t i = 0; i < paths[p].schematic.size(); ++i)
|
||||
if (along[p][i])
|
||||
samples[r].push_back(paths[p].schematic[i]);
|
||||
if (samples[r].empty())
|
||||
continue;
|
||||
level[r] = 1;
|
||||
std::sort(samples[r].begin(), samples[r].end(), [](glm::dvec2 const &A, glm::dvec2 const &B) { return A.x < B.x; });
|
||||
std::vector<double> offsets;
|
||||
for (auto const &sample : samples[r])
|
||||
offsets.push_back(sample.y);
|
||||
std::nth_element(offsets.begin(), offsets.begin() + offsets.size() / 2, offsets.end());
|
||||
runs[r].offset = offsets[offsets.size() / 2];
|
||||
}
|
||||
std::vector<std::vector<std::size_t>> below(runs.size());
|
||||
std::vector<double> gaps;
|
||||
for (std::size_t a = 0; a < runs.size(); ++a)
|
||||
for (std::size_t b = a + 1; b < runs.size(); ++b)
|
||||
{
|
||||
if (0 == level[a] || 0 == level[b] || samples[a].back().x + kAbreast < samples[b].front().x || samples[b].back().x + kAbreast < samples[a].front().x)
|
||||
continue;
|
||||
auto votes{0};
|
||||
for (auto const &sample : samples[a])
|
||||
{
|
||||
auto const found{std::lower_bound(samples[b].begin(), samples[b].end(), sample.x - kAbreast, [](glm::dvec2 const &Sample, double const Value) { return Sample.x < Value; })};
|
||||
if (found == samples[b].end() || found->x > sample.x + kAbreast || std::abs(sample.y - found->y) <= kSideBySide)
|
||||
continue;
|
||||
votes += sample.y > found->y ? 1 : -1;
|
||||
// of the tracks next to each other
|
||||
if (auto const gap{std::abs(sample.y - found->y)}; gap > 3.0 && gap < 8.0)
|
||||
gaps.push_back(gap);
|
||||
}
|
||||
if (votes > 0)
|
||||
below[a].push_back(b);
|
||||
else if (votes < 0)
|
||||
below[b].push_back(a);
|
||||
}
|
||||
if (false == gaps.empty())
|
||||
{
|
||||
std::nth_element(gaps.begin(), gaps.begin() + gaps.size() / 2, gaps.end());
|
||||
result.spacing = std::clamp(gaps[gaps.size() / 2], 3.5, 12.0);
|
||||
}
|
||||
// the rows: by the offsets, then pushed apart away from the axis where a track lies beside another on the same row or beyond
|
||||
for (std::size_t r = 0; r < runs.size(); ++r)
|
||||
runs[r].row = static_cast<int>(std::lround(runs[r].offset / result.spacing));
|
||||
for (std::size_t pass = 0; pass < runs.size() + 1; ++pass)
|
||||
{
|
||||
auto changed{false};
|
||||
for (std::size_t a = 0; a < runs.size(); ++a)
|
||||
for (auto const b : below[a])
|
||||
if (runs[a].row <= runs[b].row)
|
||||
{
|
||||
if (runs[a].offset + runs[b].offset >= 0.0)
|
||||
runs[a].row = runs[b].row + 1;
|
||||
else
|
||||
runs[b].row = runs[a].row - 1;
|
||||
changed = true;
|
||||
}
|
||||
if (false == changed)
|
||||
break;
|
||||
}
|
||||
// the main track through the station on the middle row
|
||||
if (false == Layout.main.empty())
|
||||
for (std::size_t p = 0; p < count; ++p)
|
||||
if (run_of[p] >= 0 && level[run_of[p]] && paths[p].track == Layout.main.front())
|
||||
{
|
||||
auto const shift{runs[run_of[p]].row};
|
||||
for (auto &run : runs)
|
||||
run.row -= shift;
|
||||
break;
|
||||
}
|
||||
// a group of tracks far aside, as a yard, closer: no more than one row left empty before it
|
||||
{
|
||||
std::vector<int> used;
|
||||
for (std::size_t r = 0; r < runs.size(); ++r)
|
||||
if (level[r])
|
||||
used.push_back(runs[r].row);
|
||||
used.push_back(0);
|
||||
std::sort(used.begin(), used.end());
|
||||
used.erase(std::unique(used.begin(), used.end()), used.end());
|
||||
std::map<int, int> closer{{0, 0}};
|
||||
auto const middle{static_cast<std::size_t>(std::find(used.begin(), used.end(), 0) - used.begin())};
|
||||
for (auto i{middle + 1}; i < used.size(); ++i)
|
||||
closer[used[i]] = closer[used[i - 1]] + std::min(used[i] - used[i - 1], 2);
|
||||
for (auto i{middle}; i > 0; --i)
|
||||
closer[used[i - 1]] = closer[used[i]] - std::min(used[i] - used[i - 1], 2);
|
||||
for (std::size_t r = 0; r < runs.size(); ++r)
|
||||
if (level[r])
|
||||
runs[r].row = closer[runs[r].row];
|
||||
}
|
||||
// the breakpoints at the ends of the switches, the stretches between them shortened
|
||||
std::vector<double> stops;
|
||||
for (std::size_t p = 0; p < count; ++p)
|
||||
if (false == paths[p].approach && false == plain(p) && false == paths[p].schematic.empty())
|
||||
{
|
||||
stops.push_back(paths[p].schematic.front().x);
|
||||
stops.push_back(paths[p].schematic.back().x);
|
||||
}
|
||||
if (stops.empty())
|
||||
stops = {Layout.from, Layout.to};
|
||||
std::sort(stops.begin(), stops.end());
|
||||
for (auto const stop : stops)
|
||||
if (result.warp.empty())
|
||||
result.warp.emplace_back(stop, stop);
|
||||
else if (stop - result.warp.back().x > 1.0)
|
||||
result.warp.emplace_back(stop, result.warp.back().y + shortened(stop - result.warp.back().x));
|
||||
// the row of each point: the row of its track where it runs along it; a switch going straight on takes the row of the track it
|
||||
// joins, its curved path keeps it at its ends only
|
||||
auto constexpr none{std::numeric_limits<int>::min()};
|
||||
std::vector<std::vector<int>> rows(count);
|
||||
for (std::size_t p = 0; p < count; ++p)
|
||||
{
|
||||
rows[p].assign(paths[p].schematic.size(), none);
|
||||
if (run_of[p] >= 0 && level[run_of[p]])
|
||||
for (std::size_t i = 0; i < rows[p].size(); ++i)
|
||||
if (along[p][i])
|
||||
rows[p][i] = runs[run_of[p]].row;
|
||||
}
|
||||
for (int pass = 0; pass < 32; ++pass)
|
||||
{
|
||||
auto changed{false};
|
||||
for (std::size_t p = 0; p < count; ++p)
|
||||
{
|
||||
if (plain(p) || false == usable(p))
|
||||
continue;
|
||||
auto const joined = [&](bool const Atend) {
|
||||
for (auto const &[other, otherend] : met[p][Atend])
|
||||
if (auto const value{otherend ? rows[other].back() : rows[other].front()}; value != none)
|
||||
return value;
|
||||
return none;
|
||||
};
|
||||
auto &row{rows[p]};
|
||||
auto const straight{std::all_of(along[p].begin(), along[p].end(), [](char const Along) { return Along != 0; })};
|
||||
auto const first{along[p].front() ? joined(false) : none};
|
||||
auto const last{along[p].back() ? joined(true) : none};
|
||||
std::vector<int> wanted(row.size(), none);
|
||||
if (straight && first != none && (last == none || last == first))
|
||||
std::fill(wanted.begin(), wanted.end(), first);
|
||||
else if (straight && last != none && first == none)
|
||||
std::fill(wanted.begin(), wanted.end(), last);
|
||||
else
|
||||
{
|
||||
wanted.front() = first;
|
||||
wanted.back() = last;
|
||||
}
|
||||
if (wanted != row)
|
||||
{
|
||||
row = std::move(wanted);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (false == changed)
|
||||
break;
|
||||
}
|
||||
auto const height = [&](int const Row) { return static_cast<double>(Row) * row_height; };
|
||||
// the way along the path from its end, the chord of its first metres
|
||||
auto const leaving = [&](std::size_t const Path, bool const Atend) {
|
||||
auto const &points{paths[Path].points};
|
||||
auto const reach{std::min<std::size_t>(5, points.size() - 1)};
|
||||
auto const way{Atend ? points[points.size() - 1 - reach] - points.back() : points[reach] - points.front()};
|
||||
return glm::normalize(glm::dvec2{way.x, way.z} + glm::dvec2{1e-12, 0.0});
|
||||
};
|
||||
struct on_row
|
||||
{
|
||||
glm::dvec2 drawn{0.0};
|
||||
double offset{0.0}; // in the schematic
|
||||
};
|
||||
// the first point on a row along the tracks on from the end of the path, away from it: at a switch along the path going on the
|
||||
// straightest, so a slant over a ladder of switches runs to its far end
|
||||
auto const outward = [&](std::size_t const From, bool const Atend, on_row &Found) {
|
||||
auto current{From};
|
||||
auto exit{Atend};
|
||||
auto walked{0.0};
|
||||
std::unordered_set<std::size_t> seen{From};
|
||||
while (walked <= kSlantSearch)
|
||||
{
|
||||
auto const away{-leaving(current, exit)};
|
||||
auto best{0.0};
|
||||
auto next{count};
|
||||
auto entered{false};
|
||||
for (auto const &[other, otherend] : met[current][exit])
|
||||
if (seen.count(other) == 0)
|
||||
if (auto const straightness{glm::dot(leaving(other, otherend), away)}; straightness > best)
|
||||
{
|
||||
best = straightness;
|
||||
next = other;
|
||||
entered = otherend;
|
||||
}
|
||||
if (next == count)
|
||||
return false;
|
||||
seen.insert(next);
|
||||
auto const &row{rows[next]};
|
||||
auto const &schematic{paths[next].schematic};
|
||||
auto const size{row.size()};
|
||||
for (std::size_t k = 0; k < size; ++k)
|
||||
{
|
||||
auto const index{entered ? size - 1 - k : k};
|
||||
if (k > 0)
|
||||
walked += glm::distance(schematic[index], schematic[entered ? index + 1 : index - 1]);
|
||||
if (walked > kSlantSearch)
|
||||
return false;
|
||||
if (row[index] != none)
|
||||
{
|
||||
Found = {{result.x(schematic[index].x), height(row[index])}, schematic[index].y};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
current = next;
|
||||
exit = false == entered;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// the points in the plot: on their rows, between them on a straight slant from the last row to the next; far from any row a few
|
||||
// rows beyond the outer ones at most
|
||||
auto lowest{0};
|
||||
auto highest{0};
|
||||
for (std::size_t r = 0; r < runs.size(); ++r)
|
||||
if (level[r])
|
||||
{
|
||||
lowest = std::min(lowest, runs[r].row);
|
||||
highest = std::max(highest, runs[r].row);
|
||||
}
|
||||
auto const loose = [&](double const Offset) { return std::clamp(Offset / result.spacing, lowest - 3.0, highest + 3.0) * row_height; };
|
||||
result.paths.resize(count);
|
||||
for (std::size_t p = 0; p < count; ++p)
|
||||
{
|
||||
auto const &points{paths[p].schematic};
|
||||
auto const &row{rows[p]};
|
||||
auto &drawn{result.paths[p]};
|
||||
for (std::size_t i = 0; i < points.size(); ++i)
|
||||
drawn.emplace_back(result.x(points[i].x), row[i] != none ? height(row[i]) : loose(points[i].y));
|
||||
if (false == usable(p))
|
||||
continue;
|
||||
for (std::size_t i = 0; i < points.size();)
|
||||
{
|
||||
if (row[i] != none)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
auto end{i};
|
||||
while (end + 1 < points.size() && row[end + 1] == none)
|
||||
++end;
|
||||
on_row before{i > 0 ? on_row{drawn[i - 1], points[i - 1].y} : on_row{}};
|
||||
on_row after{end + 1 < points.size() ? on_row{drawn[end + 1], points[end + 1].y} : on_row{}};
|
||||
auto const from{i > 0 || outward(p, false, before)};
|
||||
auto const to{end + 1 < points.size() || outward(p, true, after)};
|
||||
for (auto k{i}; k <= end; ++k)
|
||||
{
|
||||
if (from && to && std::abs(after.drawn.x - before.drawn.x) > 0.5)
|
||||
{
|
||||
auto const t{std::clamp((drawn[k].x - before.drawn.x) / (after.drawn.x - before.drawn.x), 0.0, 1.0)};
|
||||
drawn[k].y = before.drawn.y + (after.drawn.y - before.drawn.y) * t;
|
||||
}
|
||||
// off a row towards no other, a line out leaving the station aside
|
||||
else if (from || to)
|
||||
{
|
||||
auto const &known{from ? before : after};
|
||||
drawn[k].y = known.drawn.y + row_height * rows_off((points[k].y - known.offset) / result.spacing);
|
||||
}
|
||||
}
|
||||
i = end + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<TTrack *> stations(options const &Options)
|
||||
{
|
||||
std::vector<TTrack *> result;
|
||||
|
||||
@@ -83,6 +83,30 @@ struct circuit
|
||||
double length{0.0};
|
||||
};
|
||||
|
||||
// the schematic generalized for the plot: each station track on a row of its own, level, the rows evenly apart in the order the
|
||||
// tracks lie side by side, a path between them on a straight slant; the plain stretches between the switches shortened
|
||||
struct drawing
|
||||
{
|
||||
struct run
|
||||
{
|
||||
std::vector<std::size_t> paths; // of the layout, joined end to end without a switch between
|
||||
double offset{0.0}; // the middle one of its stretches along the axis
|
||||
int row{0};
|
||||
};
|
||||
std::vector<run> runs;
|
||||
double spacing{4.75}; // m, of the tracks of the station
|
||||
std::vector<glm::dvec2> warp; // the chainage and the x of the plot at the breakpoints, by the chainage
|
||||
std::vector<std::vector<glm::dvec2>> paths; // of each path of the layout, its points in the plot: the x and the height
|
||||
std::vector<std::vector<glm::dvec2>> schematic; // the same in the schematic
|
||||
double x(double const Chainage) const;
|
||||
double chainage(double const X) const;
|
||||
// the point of the schematic in the plot: on the path nearest to it, unless none runs near
|
||||
glm::dvec2 at(glm::dvec2 const &Schematic) const;
|
||||
};
|
||||
// m of the plot between two rows
|
||||
double constexpr row_height{5.0};
|
||||
drawing generalize(layout const &Layout);
|
||||
|
||||
// the station of the switch, or of the plain track near one
|
||||
std::optional<layout> find(TTrack &From, options const &Options = {});
|
||||
// the chainage along the axis and the offset to the left of it, as the map shows it
|
||||
|
||||
Reference in New Issue
Block a user