mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-26 15:54:53 +01:00
1026 lines
41 KiB
C++
1026 lines
41 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "application/editormodekm.h"
|
|
#include "application/editorgestures.h"
|
|
#include "application/editormodeshared.h"
|
|
#include "application/editorprojection.h"
|
|
#include "editor/editorFormat.hpp"
|
|
#include "editor/editorGeometry.hpp"
|
|
#include "editor/editorTrack.hpp"
|
|
|
|
#include "simulation/simulation.h"
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/Logs.h"
|
|
#include "utilities/utilities.h"
|
|
#include "world/Track.h"
|
|
|
|
#include "imgui/imgui.h"
|
|
#include "utilities/translation.h"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cctype>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
|
|
namespace
|
|
{
|
|
|
|
using geometry::plan_distance;
|
|
using geometry::plan_of;
|
|
|
|
double constexpr kOverlayRange{3000.0};
|
|
double constexpr kTickRange{1500.0}; // m, of the hectometres shown in the scenery
|
|
double constexpr kLabelRange{700.0}; // m, of the km written there, farther ones crowd the horizon
|
|
double constexpr kDigitRange{250.0}; // m, of the hectometres written there
|
|
double constexpr kRailHead{0.18}; // m, above the axis of a path
|
|
double constexpr kPickReach{15.0}; // m from a point of the km a click on a track takes it
|
|
double constexpr kReadoutReach{30.0}; // m from the axis of a line its km is shown by the mouse
|
|
double constexpr kPostReach{60000.0}; // m along the line each way, of the posts read
|
|
double constexpr kPostCorridor{15.0}; // m from the track, posts farther away belong to another line
|
|
double constexpr kPostTolerance{10.0}; // m, of the distance between two posts against their numbers
|
|
double constexpr kCurve{10000.0}; // m, of the radius a jump of the km is on a curve under
|
|
double constexpr kStation{300.0}; // m from a switch a jump of the km is at a station
|
|
|
|
ImU32 constexpr kAxis{IM_COL32(110, 200, 255, 220)};
|
|
ImU32 constexpr kGuessed{IM_COL32(110, 200, 255, 90)};
|
|
ImU32 constexpr kTick{IM_COL32(235, 240, 250, 230)};
|
|
ImU32 constexpr kKm{IM_COL32(255, 215, 90, 255)};
|
|
ImU32 constexpr kJump{IM_COL32(255, 95, 215, 255)};
|
|
ImU32 constexpr kMark{IM_COL32(95, 220, 140, 255)};
|
|
ImU32 constexpr kAstray{IM_COL32(240, 90, 80, 255)};
|
|
|
|
// the digits after the rotation make the number of a hectometre post, the last one the hectometre
|
|
bool hectometres_of(std::vector<std::string> const &Values, int &Hectometres)
|
|
{
|
|
if (Values.size() < 6)
|
|
return false;
|
|
Hectometres = 0;
|
|
for (std::size_t i = 5; i < Values.size(); ++i)
|
|
{
|
|
double digit;
|
|
if (false == numeric(Values[i], digit) || digit < 0.0 || digit > 9.0)
|
|
return false;
|
|
Hectometres = Hectometres * 10 + static_cast<int>(digit);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// chainage of the point of the samples nearest to the point, and how far from them it is
|
|
double project(std::vector<editor_track::route_sample> const &Samples, glm::dvec3 const &Point, double &Distance)
|
|
{
|
|
Distance = std::numeric_limits<double>::max();
|
|
double chainage{0.0};
|
|
auto const point{plan_of(Point)};
|
|
for (std::size_t i = 1; i < Samples.size(); ++i)
|
|
{
|
|
auto const a{plan_of(Samples[i - 1].position)};
|
|
auto const ab{plan_of(Samples[i].position) - a};
|
|
auto const length2{glm::dot(ab, ab)};
|
|
auto const t{length2 > 1e-12 ? std::clamp(glm::dot(point - a, ab) / length2, 0.0, 1.0) : 0.0};
|
|
if (auto const distance{glm::length(point - (a + ab * t))}; distance < Distance)
|
|
{
|
|
Distance = distance;
|
|
chainage = Samples[i - 1].chainage + (Samples[i].chainage - Samples[i - 1].chainage) * t;
|
|
}
|
|
}
|
|
return chainage;
|
|
}
|
|
|
|
std::size_t sample_at(std::vector<editor_track::route_sample> const &Samples, double const Chainage)
|
|
{
|
|
auto const found{std::lower_bound(Samples.begin(), Samples.end(), Chainage, [](editor_track::route_sample const &Sample, double const Value) { return Sample.chainage < Value; })};
|
|
return std::min<std::size_t>(static_cast<std::size_t>(found - Samples.begin()), Samples.size() - 1);
|
|
}
|
|
|
|
// the number of a line: blanks taken out
|
|
std::string line_of(std::string const &Text)
|
|
{
|
|
std::string result;
|
|
for (auto const c : Text)
|
|
if (false == std::isspace(static_cast<unsigned char>(c)))
|
|
result += c;
|
|
return result;
|
|
}
|
|
|
|
// a km typed as PKP PLK write it, 100,234, or with a point
|
|
bool km_of(char const *Text, double &Km)
|
|
{
|
|
std::string text{Text};
|
|
std::replace(text.begin(), text.end(), ',', '.');
|
|
char *end{nullptr};
|
|
auto const value{std::strtod(text.c_str(), &end)};
|
|
if (end == text.c_str() || false == std::isfinite(value))
|
|
return false;
|
|
Km = value;
|
|
return true;
|
|
}
|
|
|
|
std::string mark_text(kilometrage::mark const &Mark)
|
|
{
|
|
return Mark.beyond.has_value() ? kilometrage::text(Mark.km) + " = " + kilometrage::text(*Mark.beyond) : kilometrage::text(Mark.km);
|
|
}
|
|
|
|
void draw_label(ImDrawList *List, ImVec2 const At, ImU32 const Ground, ImU32 const Colour, std::string const &Text)
|
|
{
|
|
auto const size{ImGui::CalcTextSize(Text.c_str())};
|
|
List->AddRectFilled(ImVec2(At.x - size.x * 0.5f - 4.0f, At.y - size.y - 4.0f), ImVec2(At.x + size.x * 0.5f + 4.0f, At.y), Ground, 3.0f);
|
|
List->AddText(ImVec2(At.x - size.x * 0.5f, At.y - size.y - 2.0f), Colour, Text.c_str());
|
|
}
|
|
|
|
void draw_arrow(screen_projection const &Projection, ImDrawList *List, glm::dvec3 const &From, glm::dvec2 const &Way, double const Length, ImU32 const Colour)
|
|
{
|
|
glm::dvec3 const along{Way.x, 0.0, Way.y};
|
|
glm::dvec3 const across{-Way.y, 0.0, Way.x};
|
|
auto const tip{From + along * Length};
|
|
Projection.line(List, From, tip, Colour, 3.0f);
|
|
Projection.line(List, tip, tip - along * (Length * 0.3) + across * (Length * 0.15), Colour, 3.0f);
|
|
Projection.line(List, tip, tip - along * (Length * 0.3) - across * (Length * 0.15), Colour, 3.0f);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void km_tool::refresh()
|
|
{
|
|
m_drawn.clear();
|
|
for (auto const &line : kilometrage::lines())
|
|
{
|
|
auto const *axis{kilometrage::axis_of(line)};
|
|
if (axis == nullptr || axis->points.size() < 2)
|
|
continue;
|
|
drawn entry;
|
|
entry.line = line;
|
|
entry.points = axis->points;
|
|
auto const low{axis->anchors.front().chainage - 1.0};
|
|
auto const high{axis->anchors.back().chainage + 1.0};
|
|
for (auto const chainage : axis->chainage)
|
|
entry.guessed.push_back(chainage < low || chainage > high ? 1 : 0);
|
|
for (auto const &anchor : axis->anchors)
|
|
if (std::abs(anchor.after - anchor.before) >= 1e-6)
|
|
{
|
|
auto const way{kilometrage::direction_on(*axis, anchor.chainage)};
|
|
entry.ticks.push_back({kilometrage::point_on(*axis, anchor.chainage), {-way.y, 0.0, way.x}, 0, true});
|
|
}
|
|
for (auto const &hectometre : kilometrage::hectometres(*axis))
|
|
entry.ticks.push_back({hectometre.at, {-hectometre.rising.y, 0.0, hectometre.rising.x}, hectometre.number, false});
|
|
m_drawn.push_back(std::move(entry));
|
|
// the shorter track where the tracks run apart
|
|
for (auto const &side : axis->sides)
|
|
{
|
|
drawn apart;
|
|
apart.line = line;
|
|
apart.points = side.points;
|
|
apart.guessed.assign(side.points.size(), 0);
|
|
m_drawn.push_back(std::move(apart));
|
|
}
|
|
}
|
|
m_revision = kilometrage::revision();
|
|
m_drawn_once = true;
|
|
}
|
|
|
|
void km_tool::update()
|
|
{
|
|
if (false == m_open)
|
|
return;
|
|
auto const &marks{kilometrage::marks()};
|
|
// after an undo the point edited may be gone
|
|
if (m_edited >= 0 && (m_edited >= static_cast<int>(marks.size()) || false == m_pick.has_value() || plan_distance(marks[m_edited].point, m_pick->point) > 0.5))
|
|
m_edited = -1;
|
|
if (false == m_drawn_once || kilometrage::revision() != m_revision)
|
|
refresh();
|
|
}
|
|
|
|
void km_tool::forget(scene::basic_node const *Node)
|
|
{
|
|
if (m_pick.has_value() && m_pick->track == Node)
|
|
m_pick->track = nullptr;
|
|
}
|
|
|
|
glm::dvec2 km_tool::rising() const
|
|
{
|
|
if (false == m_pick.has_value())
|
|
return {0.0, 1.0};
|
|
return m_pick->along * (m_against ? -1.0 : 1.0);
|
|
}
|
|
|
|
std::string km_tool::jump_warning(glm::dvec3 const &Point) const
|
|
{
|
|
auto *track{kilometrage::track_near(Point, 6.0)};
|
|
if (track == nullptr)
|
|
return {};
|
|
auto route{editor_track::run_route(*track, kStation + 50.0)};
|
|
auto const samples{editor_track::sample_route(route, 2.0)};
|
|
if (samples.size() < 2)
|
|
return {};
|
|
double gap{0.0};
|
|
auto const at{project(samples, Point, gap)};
|
|
bool curve{false};
|
|
bool station{false};
|
|
for (auto const &sample : samples)
|
|
{
|
|
auto const apart{std::abs(sample.chainage - at)};
|
|
if (apart <= 30.0 && std::abs(sample.curvature) > 1.0 / kCurve)
|
|
curve = true;
|
|
if (apart <= kStation && sample.span < route.spans.size() && route.spans[sample.span].track->eType != tt_Normal)
|
|
station = true;
|
|
}
|
|
if (curve && station)
|
|
return STR("Id-1 § 19: the km shouldn't jump on a curve nor at a station, move the jump to a straight on the open line");
|
|
if (curve)
|
|
return STR("Id-1 § 19: the km shouldn't jump on a curve, move the jump to a straight");
|
|
if (station)
|
|
return STR("Id-1 § 19: the km shouldn't jump at a station, move the jump out to the open line");
|
|
return {};
|
|
}
|
|
|
|
void km_tool::pick(TTrack &Track, glm::dvec3 const &Point)
|
|
{
|
|
auto const t{editor_track::nearest_parameter(Track, Point)};
|
|
picked entry;
|
|
entry.track = &Track;
|
|
entry.point = editor_track::point_at(Track, t);
|
|
auto const ahead{plan_of(editor_track::point_at(Track, std::min(1.0, t + 0.01)) - editor_track::point_at(Track, std::max(0.0, t - 0.01)))};
|
|
entry.along = glm::length(ahead) > 1e-9 ? glm::normalize(ahead) : glm::dvec2{0.0, 1.0};
|
|
entry.path = kilometrage::path_near(Track, Point, entry.on);
|
|
m_status.clear();
|
|
m_route_status.clear();
|
|
m_edited = -1;
|
|
auto const &marks{kilometrage::marks()};
|
|
auto nearest{kPickReach};
|
|
for (std::size_t index = 0; index < marks.size(); ++index)
|
|
if (auto const distance{plan_distance(marks[index].point, entry.point)}; distance < nearest)
|
|
{
|
|
nearest = distance;
|
|
m_edited = static_cast<int>(index);
|
|
}
|
|
if (m_edited >= 0)
|
|
{
|
|
auto const &mark{marks[m_edited]};
|
|
entry.point = mark.point;
|
|
m_line = mark.line;
|
|
m_km = mark.km;
|
|
m_jump = mark.beyond.has_value();
|
|
m_beyond = mark.beyond.value_or(mark.km);
|
|
m_against = glm::dot(mark.way, entry.along) < 0.0;
|
|
}
|
|
else
|
|
{
|
|
// the line the point lies on, and its km there
|
|
if (auto const line{kilometrage::line_at(entry.point, kReadoutReach)}; false == line.empty())
|
|
m_line = line;
|
|
double km{0.0};
|
|
if (false == m_line.empty() && kilometrage::km_at(m_line, entry.point, km, kReadoutReach))
|
|
{
|
|
m_km = std::round(km * 1000.0) / 1000.0;
|
|
auto const *axis{kilometrage::axis_of(m_line)};
|
|
double gap{0.0};
|
|
auto const along{kilometrage::chainage_at(*axis, entry.point, gap)};
|
|
m_against = glm::dot(kilometrage::direction_on(*axis, along) * static_cast<double>(axis->growth), entry.along) < 0.0;
|
|
}
|
|
m_jump = false;
|
|
m_beyond = m_km;
|
|
}
|
|
m_pick = entry;
|
|
}
|
|
|
|
std::string km_tool::readout(glm::dvec3 const &Point) const
|
|
{
|
|
std::string result;
|
|
for (auto const &line : kilometrage::lines())
|
|
{
|
|
double km{0.0};
|
|
if (false == kilometrage::km_at(line, Point, km, kReadoutReach))
|
|
continue;
|
|
if (false == result.empty())
|
|
result += '\n';
|
|
result += format(STR_C("line %s: km %s"), line.c_str(), kilometrage::text(km).c_str());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void km_tool::put()
|
|
{
|
|
m_status.clear();
|
|
if (false == m_pick.has_value())
|
|
return;
|
|
auto const line{line_of(m_line)};
|
|
if (line.empty())
|
|
{
|
|
m_status = STR("Give the number of the line first");
|
|
return;
|
|
}
|
|
if (m_jump && std::abs(m_beyond - m_km) < 0.0005)
|
|
{
|
|
m_status = STR("A jump needs another km on from the point");
|
|
return;
|
|
}
|
|
kilometrage::mark entry;
|
|
entry.line = line;
|
|
entry.point = m_pick->point;
|
|
entry.way = rising();
|
|
entry.km = m_km;
|
|
if (m_jump)
|
|
entry.beyond = m_beyond;
|
|
if (m_edited >= 0)
|
|
kilometrage::change(static_cast<std::size_t>(m_edited), entry);
|
|
else
|
|
{
|
|
kilometrage::put(entry);
|
|
m_edited = static_cast<int>(kilometrage::marks().size()) - 1;
|
|
}
|
|
m_line = line;
|
|
m_history.push_kilometrage_snapshot();
|
|
m_status = format(STR_C("Line %s: km %s at the point"), line.c_str(), mark_text(entry).c_str());
|
|
}
|
|
|
|
void km_tool::remove()
|
|
{
|
|
m_status.clear();
|
|
if (m_edited < 0)
|
|
return;
|
|
kilometrage::remove(static_cast<std::size_t>(m_edited));
|
|
m_edited = -1;
|
|
m_history.push_kilometrage_snapshot();
|
|
m_status = STR("The point of the km removed");
|
|
}
|
|
|
|
void km_tool::show(std::size_t const Index)
|
|
{
|
|
auto const &marks{kilometrage::marks()};
|
|
if (Index >= marks.size())
|
|
return;
|
|
auto const point{marks[Index].point};
|
|
if (auto *track{kilometrage::track_near(point, 6.0)}; track != nullptr)
|
|
pick(*track, point);
|
|
m_view.fly_to(point, glm::normalize(glm::dvec3{-1.0, -0.8, -1.0}), 40.0);
|
|
}
|
|
|
|
void km_tool::show_via(std::size_t const Index)
|
|
{
|
|
auto const &vias{kilometrage::vias()};
|
|
if (Index >= vias.size())
|
|
return;
|
|
auto const point{vias[Index].point};
|
|
auto const line{vias[Index].line};
|
|
int path{0};
|
|
if (auto *track{kilometrage::track_near(point, 6.0, path)}; track != nullptr)
|
|
{
|
|
pick(*track, point);
|
|
m_line = line;
|
|
}
|
|
m_view.fly_to(point, glm::normalize(glm::dvec3{-1.0, -0.8, -1.0}), 40.0);
|
|
}
|
|
|
|
int km_tool::via_here() const
|
|
{
|
|
if (false == m_pick.has_value() || m_pick->track == nullptr)
|
|
return -1;
|
|
auto const &vias{kilometrage::vias()};
|
|
for (std::size_t index = 0; index < vias.size(); ++index)
|
|
{
|
|
if (vias[index].line != m_line)
|
|
continue;
|
|
int path{0};
|
|
if (kilometrage::track_near(vias[index].point, 6.0, path) == m_pick->track && path == m_pick->path)
|
|
return static_cast<int>(index);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void km_tool::lead_along()
|
|
{
|
|
m_route_status.clear();
|
|
if (false == m_pick.has_value() || m_pick->track == nullptr)
|
|
return;
|
|
auto const line{line_of(m_line)};
|
|
if (line.empty())
|
|
{
|
|
m_route_status = STR("Give the number of the line first");
|
|
return;
|
|
}
|
|
auto const astray_of = [&]() {
|
|
auto const *axis{kilometrage::axis_of(line)};
|
|
return axis != nullptr ? axis->astray.size() : kilometrage::marks().size();
|
|
};
|
|
auto const before{kilometrage::current()};
|
|
auto const astray{astray_of()};
|
|
kilometrage::via entry;
|
|
entry.line = line;
|
|
entry.point = m_pick->on;
|
|
kilometrage::put(entry);
|
|
auto const *axis{kilometrage::axis_of(line)};
|
|
if (axis == nullptr || false == axis->runs_along(m_pick->track, m_pick->path))
|
|
{
|
|
kilometrage::restore(before);
|
|
m_route_status = STR("The line can't run along this track: no way leads from it onto the line");
|
|
return;
|
|
}
|
|
m_line = line;
|
|
m_history.push_kilometrage_snapshot();
|
|
m_route_status = format(STR_C("Line %s runs along this track now"), line.c_str());
|
|
if (auto const lost{astray_of()}; lost > astray)
|
|
m_route_status += "\n" + format(STR_P("%zu point of the km is off the line now: the track leads it away from it", "%zu points of the km are off the line now: the track leads it away from them", lost - astray), lost - astray);
|
|
}
|
|
|
|
void km_tool::drop_via()
|
|
{
|
|
m_route_status.clear();
|
|
auto const index{via_here()};
|
|
if (index < 0)
|
|
return;
|
|
kilometrage::remove_via(static_cast<std::size_t>(index));
|
|
m_history.push_kilometrage_snapshot();
|
|
m_route_status = format(STR_C("Line %s runs along the straight paths of the switches here again"), m_line.c_str());
|
|
}
|
|
|
|
void km_tool::read_posts()
|
|
{
|
|
m_status.clear();
|
|
auto const line{line_of(m_line)};
|
|
if (false == m_pick.has_value() || m_pick->track == nullptr)
|
|
{
|
|
m_status = STR("Click a track of the line first");
|
|
return;
|
|
}
|
|
if (line.empty())
|
|
{
|
|
m_status = STR("Give the number of the line first");
|
|
return;
|
|
}
|
|
auto route{editor_track::run_route(*m_pick->track, kPostReach)};
|
|
auto const samples{editor_track::sample_route(route, 2.0)};
|
|
if (samples.size() < 2)
|
|
{
|
|
m_status = STR("No line through this track");
|
|
return;
|
|
}
|
|
auto const length{samples.back().chainage};
|
|
struct post
|
|
{
|
|
double chainage{0.0};
|
|
int hectometres{0};
|
|
};
|
|
std::vector<post> posts;
|
|
for (auto const &standing : m_templates.standing_templates([](std::string const &File) { return File.find("hekto") != std::string::npos; }))
|
|
{
|
|
post entry;
|
|
if (false == hectometres_of(standing.values, entry.hectometres))
|
|
continue;
|
|
double distance{0.0};
|
|
entry.chainage = project(samples, standing.location, distance);
|
|
if (distance > kPostCorridor || entry.chainage <= 0.0 || entry.chainage >= length)
|
|
continue;
|
|
posts.push_back(entry);
|
|
}
|
|
if (posts.size() < 2)
|
|
{
|
|
m_status = STR("Fewer than two hectometre posts stand along the line, nothing to read");
|
|
return;
|
|
}
|
|
std::sort(posts.begin(), posts.end(), [](post const &A, post const &B) { return A.chainage < B.chainage; });
|
|
// runs of posts numbered by the distance between them, a post of the same number by the other track taken once
|
|
auto const fits = [](post const &From, post const &To, int const Sign) {
|
|
auto const counted{(To.hectometres - From.hectometres) * 100.0};
|
|
auto const apart{To.chainage - From.chainage};
|
|
return counted != 0.0 && (Sign == 0 || (counted > 0.0 ? 1 : -1) == Sign) && std::abs(std::abs(counted) - apart) <= std::max(kPostTolerance, apart * 0.02);
|
|
};
|
|
auto const sign_of = [](std::vector<post> const &Run) { return Run.size() < 2 ? 0 : Run.back().hectometres > Run.front().hectometres ? 1 : -1; };
|
|
std::vector<std::vector<post>> runs;
|
|
std::size_t twins{0};
|
|
for (auto const &entry : posts)
|
|
{
|
|
if (false == runs.empty())
|
|
{
|
|
auto &run{runs.back()};
|
|
if (entry.hectometres == run.back().hectometres && entry.chainage - run.back().chainage < kPostTolerance * 3.0)
|
|
{
|
|
++twins;
|
|
continue;
|
|
}
|
|
if (fits(run.back(), entry, sign_of(run)))
|
|
{
|
|
run.push_back(entry);
|
|
continue;
|
|
}
|
|
}
|
|
runs.push_back({entry});
|
|
}
|
|
// a post alone between the runs is one out of place
|
|
std::size_t stray{0};
|
|
std::vector<std::vector<post>> kept;
|
|
for (auto &run : runs)
|
|
{
|
|
if (run.size() < 2)
|
|
{
|
|
++stray;
|
|
continue;
|
|
}
|
|
if (false == kept.empty() && fits(kept.back().back(), run.front(), sign_of(kept.back())) && sign_of(kept.back()) == sign_of(run))
|
|
kept.back().insert(kept.back().end(), run.begin(), run.end());
|
|
else
|
|
kept.push_back(std::move(run));
|
|
}
|
|
std::size_t rising{0};
|
|
std::size_t falling{0};
|
|
for (auto const &run : kept)
|
|
(sign_of(run) > 0 ? rising : falling) += run.size();
|
|
auto const growth{rising >= falling ? 1 : -1};
|
|
kept.erase(std::remove_if(kept.begin(), kept.end(), [&](std::vector<post> const &Run) {
|
|
if (sign_of(Run) == growth)
|
|
return false;
|
|
stray += Run.size();
|
|
return true;
|
|
}), kept.end());
|
|
if (kept.empty())
|
|
{
|
|
m_status = STR("The numbers of the posts don't go by 100 m anywhere along the line, nothing read");
|
|
return;
|
|
}
|
|
auto const mark_at = [&](double const Chainage, double const Km, std::optional<double> const Beyond) {
|
|
kilometrage::mark entry;
|
|
entry.line = line;
|
|
entry.point = editor_track::sampled_position(samples, Chainage);
|
|
entry.way = samples[sample_at(samples, Chainage)].direction * static_cast<double>(growth);
|
|
entry.km = Km;
|
|
entry.beyond = Beyond;
|
|
return entry;
|
|
};
|
|
std::vector<kilometrage::mark> made;
|
|
std::size_t jumps{0};
|
|
std::size_t misplaced{0};
|
|
for (std::size_t r = 0; r < kept.size(); ++r)
|
|
{
|
|
auto const &run{kept[r]};
|
|
auto const &first{run.front()};
|
|
auto const &last{run.back()};
|
|
// the jump to the next run at the post it starts the irregular hectometre at, along the rising km
|
|
auto const jump_before{r > 0 && growth > 0};
|
|
auto const jump_after{r + 1 < kept.size() && growth < 0};
|
|
if (jump_before)
|
|
{
|
|
auto const &previous{kept[r - 1].back()};
|
|
made.push_back(mark_at(first.chainage, previous.hectometres / 10.0 + (first.chainage - previous.chainage) / 1000.0, first.hectometres / 10.0));
|
|
}
|
|
else
|
|
made.push_back(mark_at(first.chainage, first.hectometres / 10.0, std::nullopt));
|
|
if (jump_after)
|
|
{
|
|
auto const &next{kept[r + 1].front()};
|
|
made.push_back(mark_at(last.chainage, next.hectometres / 10.0 + (next.chainage - last.chainage) / 1000.0, last.hectometres / 10.0));
|
|
}
|
|
else
|
|
made.push_back(mark_at(last.chainage, last.hectometres / 10.0, std::nullopt));
|
|
if (jump_before || jump_after)
|
|
{
|
|
++jumps;
|
|
if (false == jump_warning((jump_before ? made[made.size() - 2] : made.back()).point).empty())
|
|
++misplaced;
|
|
}
|
|
}
|
|
// the points of the line along the stretch read give way to the ones read
|
|
auto const &standing{kilometrage::marks()};
|
|
for (auto index{static_cast<int>(standing.size()) - 1}; index >= 0; --index)
|
|
{
|
|
if (standing[index].line != line)
|
|
continue;
|
|
double gap{0.0};
|
|
project(samples, standing[index].point, gap);
|
|
if (gap <= kPostCorridor)
|
|
kilometrage::remove(static_cast<std::size_t>(index));
|
|
}
|
|
for (auto &entry : made)
|
|
kilometrage::put(std::move(entry));
|
|
m_line = line;
|
|
m_edited = -1;
|
|
m_history.push_kilometrage_snapshot();
|
|
m_status = format(STR_C("%zu posts read: %zu points of the km, %zu jumps"), posts.size() - twins, made.size(), jumps);
|
|
if (stray > 0)
|
|
m_status += "\n" + format(STR_P("%zu post doesn't fit its neighbours and was left out", "%zu posts don't fit their neighbours and were left out", stray), stray);
|
|
if (misplaced > 0)
|
|
m_status += "\n" + format(STR_P("%zu jump lies on a curve or at a station, Id-1 § 19 wants none there", "%zu jumps lie on a curve or at a station, Id-1 § 19 wants none there", misplaced), misplaced);
|
|
}
|
|
|
|
void km_tool::render_options()
|
|
{
|
|
ImGui::TextDisabled("%s", STR_C("The km of a railway line: the points where the km is known, from a post or a document, and where it jumps. The km of the rest go from them along the axis of the line: between the tracks of a double-track one, along the longer track where the tracks run on formations of their own, the shorter one getting an irregular hectometre"));
|
|
ImGui::Spacing();
|
|
gesture_rows({{"LMB on a track", "a point of the km there, or the one standing by it"}});
|
|
auto const &marks{kilometrage::marks()};
|
|
auto const lines{kilometrage::lines()};
|
|
std::size_t jumps{0};
|
|
for (auto const &mark : marks)
|
|
if (mark.beyond.has_value())
|
|
++jumps;
|
|
metric_tiles({{format("%zu", lines.size()), STR_C("lines"), kAxis}, {format("%zu", marks.size()), STR_C("points"), kMark}, {format("%zu", jumps), STR_C("jumps"), kJump}});
|
|
|
|
step_heading(1, STR_C("A point of the km"));
|
|
if (false == m_pick.has_value())
|
|
ImGui::TextDisabled("%s", STR_C("Click a track where the km is known: by a post, a station or a bridge"));
|
|
else
|
|
{
|
|
if (begin_fields("##kmpoint"))
|
|
{
|
|
field_row("Line");
|
|
std::array<char, 32> buffer{};
|
|
m_line.copy(buffer.data(), buffer.size() - 1);
|
|
if (ImGui::InputTextWithHint("##kmline", "91", buffer.data(), buffer.size()))
|
|
m_line = line_of(buffer.data());
|
|
item_tooltip("The number of the railway line, as 91: a scenery can hold several lines, each with its own km");
|
|
auto const km_field = [&](char const *Label, char const *Id, double &Value) {
|
|
field_row(Label);
|
|
std::array<char, 32> text{};
|
|
kilometrage::text(Value).copy(text.data(), text.size() - 1);
|
|
if (ImGui::InputText(Id, text.data(), text.size()))
|
|
km_of(text.data(), Value);
|
|
};
|
|
km_field("Km", "##kmkm", m_km);
|
|
item_tooltip("The km of the point, as PKP PLK write it: 100,234 is km 100 and 234 m. At a jump: the km the point is reached at, coming along the rising km");
|
|
field_row("Jump");
|
|
ImGui::Checkbox("##kmjump", &m_jump);
|
|
item_tooltip("The km jumps at the point: a line built anew, shortened or lengthened keeps the km of the rest. The irregular hectometre is "
|
|
"marked by a black stripe under the digit of the post (Id-1 § 19)");
|
|
if (m_jump)
|
|
{
|
|
km_field("Km on", "##kmbeyond", m_beyond);
|
|
item_tooltip("The km on from the point, along the rising km");
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
auto const *axis{m_line.empty() ? nullptr : kilometrage::axis_of(m_line)};
|
|
auto const fixed{axis != nullptr && axis->anchors.size() > 1 && std::abs(axis->anchors.back().before - axis->anchors.front().before) > 1e-6};
|
|
if (false == fixed)
|
|
{
|
|
int way{m_against ? 1 : 0};
|
|
if (segmented("##kmway", way, {{0, STR_C("Rise this way"), "The km rise the way the arrow at the point shows"}, {1, STR_C("Rise the other way"), "The km rise against the arrow"}}))
|
|
m_against = way == 1;
|
|
}
|
|
if (m_jump)
|
|
if (auto const warning{jump_warning(m_pick->point)}; false == warning.empty())
|
|
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", warning.c_str());
|
|
if (main_button(m_edited >= 0 ? STR_C("Change the point") : STR_C("Put the point of the km"), ImVec2(-1.0f, tall_button_height())))
|
|
put();
|
|
item_tooltip("The point goes to the file of the scenery with the track, the simulation skips it. One step of undo");
|
|
if (m_edited >= 0)
|
|
{
|
|
if (ImGui::Button(STR_C("Remove the point"), ImVec2(-1.0f, 0.0f)))
|
|
remove();
|
|
item_tooltip("One step of undo");
|
|
}
|
|
}
|
|
|
|
step_heading(2, STR_C("The way through a station"));
|
|
auto const *line_axis{m_line.empty() ? nullptr : kilometrage::axis_of(m_line)};
|
|
if (false == m_pick.has_value() || m_pick->track == nullptr)
|
|
ImGui::TextDisabled("%s", STR_C("Through a station the km run along the straight paths of the switches. Click a track of the station to lead them along it instead"));
|
|
else if (line_axis == nullptr)
|
|
ImGui::TextDisabled("%s", STR_C("Put a point of the km of the line first"));
|
|
else if (auto const via{via_here()}; via >= 0)
|
|
{
|
|
if (std::find(line_axis->blocked.begin(), line_axis->blocked.end(), static_cast<std::size_t>(via)) != line_axis->blocked.end())
|
|
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.4f, 1.0f), "%s", STR_C("The line doesn't run along this track: another track chosen leads it elsewhere"));
|
|
else
|
|
ImGui::TextUnformatted(format(STR_C("Line %s runs along this track, as chosen"), m_line.c_str()).c_str());
|
|
if (ImGui::Button(STR_C("Back onto the straight paths"), ImVec2(-1.0f, 0.0f)))
|
|
drop_via();
|
|
item_tooltip("The track chosen is forgotten, the line runs along the straight paths of the switches here again. One step of undo");
|
|
}
|
|
else if (line_axis->runs_along(m_pick->track, m_pick->path))
|
|
ImGui::TextDisabled("%s", format(STR_C("Line %s runs along this track"), m_line.c_str()).c_str());
|
|
else
|
|
{
|
|
ImGui::TextDisabled("%s", format(STR_C("Line %s doesn't run along this track"), m_line.c_str()).c_str());
|
|
if (ImGui::Button(STR_C("Lead the km along this track"), ImVec2(-1.0f, 0.0f)))
|
|
lead_along();
|
|
item_tooltip("The line runs along the track clicked, along the path of the switch clicked, and on along the straight paths of the switches "
|
|
"until it meets its tracks again; where it doesn't, as at a junction, it goes on that way. One step of undo");
|
|
}
|
|
if (false == m_route_status.empty())
|
|
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", m_route_status.c_str());
|
|
|
|
step_heading(3, STR_C("From the posts"));
|
|
if (ImGui::Button(STR_C("Read the km from the posts"), ImVec2(-1.0f, 0.0f)))
|
|
read_posts();
|
|
item_tooltip("Reads the hectometre posts standing along the line through the track clicked: a point of the km at the first and the last "
|
|
"post of each run numbered by 100 m, a jump where two runs meet. The points of the line along it before give way. One step of undo");
|
|
if (false == m_status.empty())
|
|
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%s", m_status.c_str());
|
|
|
|
step_heading(4, STR_C("The points of the lines"));
|
|
if (lines.empty())
|
|
{
|
|
ImGui::TextDisabled("%s", STR_C("None yet"));
|
|
return;
|
|
}
|
|
for (auto const &line : lines)
|
|
{
|
|
auto const *axis{kilometrage::axis_of(line)};
|
|
auto const count{static_cast<std::size_t>(std::count_if(marks.begin(), marks.end(), [&](kilometrage::mark const &Mark) { return Mark.line == line; }))};
|
|
if (false == ImGui::CollapsingHeader(format("%s %s (%zu)###kmline%s", STR_C("Line"), line.c_str(), count, line.c_str()).c_str(), ImGuiTreeNodeFlags_DefaultOpen))
|
|
continue;
|
|
if (axis == nullptr)
|
|
{
|
|
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.4f, 1.0f), "%s", STR_C("No track by the points of the line"));
|
|
continue;
|
|
}
|
|
ImGui::TextDisabled("%s", format(STR_C("%.1f km of the axis, %s"), axis->length() / 1000.0, axis->between ? STR_C("between the tracks") : STR_C("along the track")).c_str());
|
|
if (false == axis->sides.empty())
|
|
{
|
|
ImGui::TextDisabled("%s", format(STR_P("%zu stretch with the tracks on formations of their own", "%zu stretches with the tracks on formations of their own", axis->sides.size()), axis->sides.size()).c_str());
|
|
item_tooltip("Where the tracks part more than 15 m and come together again, the km run along the longer one, the shorter one goes by "
|
|
"its own length and its last hectometre before they meet takes up the difference, as PKP GK-1 gives it (6.1.12)");
|
|
}
|
|
auto const &vias{kilometrage::vias()};
|
|
for (std::size_t index = 0; index < vias.size(); ++index)
|
|
{
|
|
if (vias[index].line != line)
|
|
continue;
|
|
auto const blocked{std::find(axis->blocked.begin(), axis->blocked.end(), index) != axis->blocked.end()};
|
|
double km{0.0};
|
|
auto const label{kilometrage::km_at(line, vias[index].point, km, kReadoutReach) ? format(STR_C("Along the track chosen by km %s"), kilometrage::text(km).c_str()) : std::string{STR_C("Along the track chosen")}};
|
|
if (blocked)
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.45f, 0.4f, 1.0f));
|
|
if (ImGui::Selectable((label + "##kmvia" + std::to_string(index)).c_str(), false))
|
|
show_via(index);
|
|
if (blocked)
|
|
ImGui::PopStyleColor();
|
|
item_tooltip(blocked ? "The line doesn't run along this track: another track chosen leads it elsewhere" : "Through the station the line runs along this track, not along the straight paths of the switches");
|
|
}
|
|
auto const rows{axis->anchors.size() + axis->astray.size()};
|
|
if (false == ImGui::BeginTable(("##kmmarks" + line).c_str(), 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY, ImVec2(0.0f, std::min(260.0f, ImGui::GetTextLineHeightWithSpacing() * (rows + 1.5f)))))
|
|
continue;
|
|
ImGui::TableSetupScrollFreeze(0, 1);
|
|
ImGui::TableSetupColumn(STR_C("Km"), ImGuiTableColumnFlags_WidthStretch, 0.4f);
|
|
ImGui::TableSetupColumn(STR_C("In the scenery"), ImGuiTableColumnFlags_WidthStretch, 0.22f);
|
|
ImGui::TableSetupColumn(STR_C("By the km"), ImGuiTableColumnFlags_WidthStretch, 0.22f);
|
|
ImGui::TableSetupColumn(STR_C("Off by"), ImGuiTableColumnFlags_WidthStretch, 0.16f);
|
|
ImGui::TableHeadersRow();
|
|
auto const growth{axis->growth};
|
|
for (std::size_t i = 0; i < axis->anchors.size(); ++i)
|
|
{
|
|
auto const &anchor{axis->anchors[i]};
|
|
auto const &mark{marks[anchor.mark]};
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
if (ImGui::Selectable((mark_text(mark) + "##kmmark" + std::to_string(anchor.mark)).c_str(), static_cast<int>(anchor.mark) == m_edited, ImGuiSelectableFlags_SpanAllColumns))
|
|
show(anchor.mark);
|
|
if (i == 0)
|
|
continue;
|
|
auto const &previous{axis->anchors[i - 1]};
|
|
auto const scenery{anchor.chainage - previous.chainage};
|
|
auto const counted{std::abs((growth > 0 ? anchor.before - previous.after : previous.before - anchor.after) * 1000.0)};
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%.0f m", scenery);
|
|
ImGui::TableNextColumn();
|
|
ImGui::Text("%.0f m", counted);
|
|
ImGui::TableNextColumn();
|
|
auto off{scenery > 1.0 ? (scenery - counted) / scenery * 100.0 : 0.0};
|
|
if (std::abs(off) < 0.05)
|
|
off = 0.0;
|
|
if (std::abs(off) > 2.0)
|
|
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), "%+.1f%%", off);
|
|
else
|
|
ImGui::Text("%+.1f%%", off);
|
|
if (ImGui::IsItemHovered())
|
|
ImGui::SetTooltip("%s", STR_C("How much longer the stretch is in the scenery than by the km: the km between the points are stretched to it evenly"));
|
|
}
|
|
for (auto const index : axis->astray)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.45f, 0.4f, 1.0f));
|
|
if (ImGui::Selectable((mark_text(marks[index]) + "##kmmark" + std::to_string(index)).c_str(), static_cast<int>(index) == m_edited, ImGuiSelectableFlags_SpanAllColumns))
|
|
show(index);
|
|
ImGui::PopStyleColor();
|
|
if (ImGui::IsItemHovered())
|
|
ImGui::SetTooltip("%s", STR_C("The point is away from the axis of the line the other points make: the main paths of the switches don't lead to it"));
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
}
|
|
|
|
void km_tool::draw_overlay() const
|
|
{
|
|
if (false == m_open)
|
|
return;
|
|
screen_projection const projection;
|
|
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
|
|
glm::dvec3 const camera{Global.pCamera.Pos};
|
|
auto const near_enough = [&](glm::dvec3 const &Point, double const Range) { return Global.EditorOrtho || glm::distance(Point, camera) < Range; };
|
|
glm::dvec3 const lift{0.0, kRailHead, 0.0};
|
|
for (auto const &entry : m_drawn)
|
|
{
|
|
// the axis, faint where the km run on past the last point
|
|
for (std::size_t i = 1; i < entry.points.size(); i += 5)
|
|
{
|
|
auto const next{std::min(i + 4, entry.points.size() - 1)};
|
|
if (near_enough(entry.points[next], kOverlayRange))
|
|
projection.line(drawlist, entry.points[i - 1] + lift, entry.points[next] + lift, entry.guessed[next] != 0 ? kGuessed : kAxis, Global.EditorOrtho ? 4.0f : 2.5f);
|
|
}
|
|
for (auto const &tick : entry.ticks)
|
|
{
|
|
if (false == near_enough(tick.at, kTickRange))
|
|
continue;
|
|
auto const centre{tick.at + lift};
|
|
auto const whole{tick.hectometres % 10 == 0};
|
|
auto const reach{tick.jump ? 3.5 : whole ? 2.6 : 1.4};
|
|
projection.line(drawlist, centre - tick.across * reach, centre + tick.across * reach, tick.jump ? kJump : whole ? kKm : kTick, tick.jump || whole ? 3.5f : 2.0f);
|
|
if (tick.jump)
|
|
continue;
|
|
ImVec2 screen;
|
|
if (whole && near_enough(tick.at, kLabelRange) && projection.project(centre + tick.across * reach + glm::dvec3{0.0, 0.6, 0.0}, screen))
|
|
draw_label(drawlist, screen, IM_COL32(0, 0, 0, 170), kKm, format("km %d", tick.hectometres / 10));
|
|
else if (false == whole && near_enough(tick.at, kDigitRange) && projection.project(centre + tick.across * reach + glm::dvec3{0.0, 0.4, 0.0}, screen))
|
|
draw_label(drawlist, screen, IM_COL32(0, 0, 0, 140), kTick, std::to_string(std::abs(tick.hectometres) % 10));
|
|
}
|
|
}
|
|
// the points of the km: a pole with the km over it, the arrow the way the km rise
|
|
auto const &marks{kilometrage::marks()};
|
|
for (std::size_t index = 0; index < marks.size(); ++index)
|
|
{
|
|
auto const &mark{marks[index]};
|
|
if (false == near_enough(mark.point, kLabelRange * 2.0))
|
|
continue;
|
|
auto astray{true};
|
|
for (auto const &entry : m_drawn)
|
|
if (entry.line == mark.line)
|
|
if (auto const *axis{kilometrage::axis_of(mark.line)}; axis != nullptr)
|
|
astray = std::find(axis->astray.begin(), axis->astray.end(), index) != axis->astray.end();
|
|
auto const colour{astray ? kAstray : mark.beyond.has_value() ? kJump : kMark};
|
|
auto const foot{mark.point + lift};
|
|
auto const top{foot + glm::dvec3{0.0, 3.0, 0.0}};
|
|
projection.line(drawlist, foot, top, colour, static_cast<int>(index) == m_edited ? 4.0f : 2.5f);
|
|
draw_arrow(projection, drawlist, foot, mark.way, 6.0, colour);
|
|
ImVec2 screen;
|
|
if (projection.project(top, screen))
|
|
draw_label(drawlist, screen, colour, IM_COL32(16, 18, 22, 255), mark.line + ": " + mark_text(mark));
|
|
}
|
|
// the tracks chosen for the way through a station: a rhombus on the track
|
|
auto const &vias{kilometrage::vias()};
|
|
for (std::size_t index = 0; index < vias.size(); ++index)
|
|
{
|
|
auto const &via{vias[index]};
|
|
ImVec2 screen;
|
|
if (false == near_enough(via.point, kLabelRange * 2.0) || false == projection.project(via.point + lift, screen))
|
|
continue;
|
|
auto blocked{false};
|
|
if (auto const *axis{kilometrage::axis_of(via.line)}; axis != nullptr)
|
|
blocked = std::find(axis->blocked.begin(), axis->blocked.end(), index) != axis->blocked.end();
|
|
auto const colour{blocked ? kAstray : kAxis};
|
|
auto const r{8.0f};
|
|
drawlist->AddQuadFilled(ImVec2(screen.x, screen.y - r), ImVec2(screen.x + r, screen.y), ImVec2(screen.x, screen.y + r), ImVec2(screen.x - r, screen.y), colour);
|
|
drawlist->AddQuad(ImVec2(screen.x, screen.y - r), ImVec2(screen.x + r, screen.y), ImVec2(screen.x, screen.y + r), ImVec2(screen.x - r, screen.y), IM_COL32(16, 18, 22, 255), 1.5f);
|
|
draw_label(drawlist, ImVec2(screen.x, screen.y - r - 2.0f), colour, IM_COL32(16, 18, 22, 255), format(STR_C("%s: this way"), via.line.c_str()));
|
|
}
|
|
if (m_pick.has_value() && m_edited < 0)
|
|
{
|
|
auto const foot{m_pick->point + lift};
|
|
ImVec2 screen;
|
|
if (projection.project(foot, screen))
|
|
drawlist->AddCircle(screen, 9.0f, kKm, 20, 2.5f);
|
|
draw_arrow(projection, drawlist, foot, rising(), 8.0, kKm);
|
|
}
|
|
}
|
|
|
|
editor_selftest_result km_tool::selftest(std::string const &Command, std::istringstream &Words)
|
|
{
|
|
if (Command == "kmpick")
|
|
{
|
|
// kmpick track=<name> [at=<0..1>]: a click on the track
|
|
auto const values{selftest_values(Words)};
|
|
auto const found{values.find("track")};
|
|
auto *track{found != values.end() ? simulation::Paths.find(found->second) : nullptr};
|
|
if (track == nullptr)
|
|
{
|
|
WriteLog("SELFTEST kmpick: no such track");
|
|
return editor_selftest_result::handled;
|
|
}
|
|
auto const at{values.count("at") ? std::atof(values.at("at").c_str()) : 0.5};
|
|
auto const path{values.count("path") ? std::atoi(values.at("path").c_str()) : 0};
|
|
pick(*track, path > 0 && path < static_cast<int>(track->m_paths.size()) ? geometry::bezier{track->m_paths[path]}.point(at) : editor_track::point_at(*track, at));
|
|
auto const *axis{m_line.empty() ? nullptr : kilometrage::axis_of(m_line)};
|
|
WriteLog(format("SELFTEST kmpick %s: line %s km %s jump %d against %d edited %d at %.1f %.1f path %d along %d via %d", track->name().c_str(), m_line.c_str(), kilometrage::text(m_km).c_str(), m_jump ? 1 : 0, m_against ? 1 : 0, m_edited, m_pick->point.x, m_pick->point.z,
|
|
m_pick->path, axis != nullptr && axis->runs_along(m_pick->track, m_pick->path) ? 1 : 0, via_here()));
|
|
return editor_selftest_result::handled;
|
|
}
|
|
if (Command == "kmset")
|
|
{
|
|
// kmset line=<number> km=<km> [beyond=<km>] [against=1]: the fields set and the point put
|
|
auto const values{selftest_values(Words)};
|
|
if (auto const line{values.find("line")}; line != values.end())
|
|
m_line = line->second;
|
|
if (auto const km{values.find("km")}; km != values.end())
|
|
km_of(km->second.c_str(), m_km);
|
|
m_jump = values.count("beyond") > 0;
|
|
if (m_jump)
|
|
km_of(values.at("beyond").c_str(), m_beyond);
|
|
if (auto const against{values.find("against")}; against != values.end())
|
|
m_against = against->second == "1";
|
|
put();
|
|
WriteLog("SELFTEST kmset: " + m_status);
|
|
return editor_selftest_result::handled;
|
|
}
|
|
if (Command == "kmvia")
|
|
{
|
|
// kmvia [line=<number>] [remove=1]: the line led along the track picked, or back onto the straight paths there
|
|
auto const values{selftest_values(Words)};
|
|
if (auto const line{values.find("line")}; line != values.end())
|
|
m_line = line->second;
|
|
if (values.count("remove") > 0)
|
|
drop_via();
|
|
else
|
|
lead_along();
|
|
auto text{m_route_status};
|
|
std::replace(text.begin(), text.end(), '\n', ';');
|
|
WriteLog("SELFTEST kmvia: " + text);
|
|
return editor_selftest_result::handled;
|
|
}
|
|
if (Command == "kmremove")
|
|
{
|
|
remove();
|
|
WriteLog("SELFTEST kmremove: " + m_status);
|
|
return editor_selftest_result::handled;
|
|
}
|
|
if (Command == "kmposts")
|
|
{
|
|
// kmposts line=<number>: the km read from the posts along the line through the track picked
|
|
auto const values{selftest_values(Words)};
|
|
if (auto const line{values.find("line")}; line != values.end())
|
|
m_line = line->second;
|
|
read_posts();
|
|
WriteLog("SELFTEST kmposts: " + m_status);
|
|
return editor_selftest_result::handled;
|
|
}
|
|
if (Command == "kmstate")
|
|
{
|
|
WriteLog(format("SELFTEST kmstate: picked %d line %s km %s jump %d edited %d", m_pick.has_value() ? 1 : 0, m_line.c_str(), kilometrage::text(m_km).c_str(), m_jump ? 1 : 0, m_edited));
|
|
return editor_selftest_result::handled;
|
|
}
|
|
if (Command == "kmshow")
|
|
{
|
|
// kmshow mark=<index>: the point of the km picked, the camera over it
|
|
auto const values{selftest_values(Words)};
|
|
auto const index{values.count("mark") ? std::atoi(values.at("mark").c_str()) : 0};
|
|
show(static_cast<std::size_t>(std::max(index, 0)));
|
|
WriteLog(format("SELFTEST kmshow %d: line %s km %s edited %d", index, m_line.c_str(), kilometrage::text(m_km).c_str(), m_edited));
|
|
return editor_selftest_result::handled;
|
|
}
|
|
if (Command == "kmlist")
|
|
{
|
|
refresh();
|
|
for (auto const &entry : m_drawn)
|
|
{
|
|
std::string list;
|
|
for (std::size_t i = 0; i < entry.ticks.size() && entry.points.size() < 2000; ++i)
|
|
list += format(" %d@%.0f,%.0f", entry.ticks[i].hectometres, entry.ticks[i].at.x, entry.ticks[i].at.z);
|
|
WriteLog(format("SELFTEST kmlist ticks %s: %zu points, %zu ticks", entry.line.c_str(), entry.points.size(), entry.ticks.size()) + list);
|
|
}
|
|
auto const &marks{kilometrage::marks()};
|
|
WriteLog(format("SELFTEST kmlist: %zu points", marks.size()));
|
|
for (auto const &line : kilometrage::lines())
|
|
{
|
|
auto const *axis{kilometrage::axis_of(line)};
|
|
if (axis == nullptr)
|
|
{
|
|
WriteLog("SELFTEST kmlist line " + line + ": no axis");
|
|
continue;
|
|
}
|
|
std::string list;
|
|
for (auto const &anchor : axis->anchors)
|
|
list += format(" %s@%.0f/%.1f", mark_text(marks[anchor.mark]).c_str(), anchor.chainage, anchor.gap);
|
|
for (auto const &side : axis->sides)
|
|
WriteLog(format("SELFTEST kmlist apart: %.1f m of track by %.1f m of axis (%.1f..%.1f), km %s..%s", side.length, side.to - side.from, side.from, side.to, kilometrage::text(kilometrage::km_on(*axis, side, 0.0)).c_str(),
|
|
kilometrage::text(kilometrage::km_on(*axis, side, side.length)).c_str()));
|
|
WriteLog(format("SELFTEST kmlist line %s: axis %.0f m %s growth %d, %zu astray, %zu blocked, km %s..%s:", line.c_str(), axis->length(), axis->between ? "between" : "along", axis->growth, axis->astray.size(), axis->blocked.size(), kilometrage::text(kilometrage::km_on(*axis, 0.0)).c_str(),
|
|
kilometrage::text(kilometrage::km_on(*axis, axis->length())).c_str()) +
|
|
list);
|
|
}
|
|
return editor_selftest_result::handled;
|
|
}
|
|
if (Command == "kmat")
|
|
{
|
|
// kmat track=<name> [at=<0..1>]: the km of the lines there
|
|
auto const values{selftest_values(Words)};
|
|
auto const found{values.find("track")};
|
|
auto *track{found != values.end() ? simulation::Paths.find(found->second) : nullptr};
|
|
if (track == nullptr)
|
|
{
|
|
WriteLog("SELFTEST kmat: no such track");
|
|
return editor_selftest_result::handled;
|
|
}
|
|
auto const at{values.count("at") ? std::atof(values.at("at").c_str()) : 0.5};
|
|
auto text{readout(editor_track::point_at(*track, at))};
|
|
std::replace(text.begin(), text.end(), '\n', ';');
|
|
WriteLog(format("SELFTEST kmat %s %.2f: ", track->name().c_str(), at) + (text.empty() ? std::string{"-"} : text));
|
|
return editor_selftest_result::handled;
|
|
}
|
|
return editor_selftest_result::unhandled;
|
|
}
|