17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 19:24:54 +01:00

refactor(editor): the hectometre posts and the fouling points are tools of their own

The fifth and the sixth tool out of editor_mode, the two lineside ones
sharing their file. Placing, moving and taking out the templates which
stand by the track become services of the editor, with the template
types of their own, and so do the tab of the track tools shown, the
ground under a point and the first step done. The context menu, the
click on a fouling point in the view and the lineside tabs go through
the public calls of the tools. The self-test commands hekto and fouling
move with them and log the same as before.
This commit is contained in:
maj00r
2026-10-21 09:50:38 +02:00
parent c45c4713d6
commit 6ee771bb22
9 changed files with 372 additions and 300 deletions

View File

@@ -1453,8 +1453,8 @@ bool editor_mode::update()
draw_parallel_preview();
if (m_track_tab == track_tab::lineside)
{
draw_hekto_overlay();
draw_fouling_overlay();
m_hekto.draw_overlay();
m_fouling.draw_overlay();
m_vehicle.draw_overlay();
}
render_track_context();

View File

@@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/.
#include "vehicle/Camera.h"
#include "application/editormodegauge.h"
#include "application/editormodejoints.h"
#include "application/editormodelineside.h"
#include "application/editormodespeed.h"
#include "application/editormodevehicle.h"
#include "application/editortool.h"
@@ -225,7 +226,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
int m_selected_history_idx{-1};
glm::dvec3 clamp_mouse_offset_to_max(const glm::dvec3 &offset);
// the ground under a point the cursor landed on, for things which are to stand on the ground
glm::dvec3 placement_on_ground(glm::dvec3 Location);
glm::dvec3 placement_on_ground(glm::dvec3 Location) override;
// puts every instance of the model the selected node shows on the ground under it
void drop_model_instances();
void render_object_menu();
@@ -1431,7 +1432,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
void update_build_tools();
void draw_build_overlay() const;
// modes of the track editor, one at a time: path is the plain selection
enum class track_tab { straights, route, path, turnout, profile, speed, joints, infra, lay, lineside, turntable, signals, parallel };
using track_tab = editor_track_tab;
struct track_mode
{
char const *label;
@@ -1477,7 +1478,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri
// the track tools in the tool options window, and the selected path in the inspector
void render_track_tool_options();
void render_track_selection();
void show_track_tab(track_tab const Wanted);
void show_track_tab(track_tab const Wanted) override;
bool track_analysis_tab() const { return m_track_tab == track_tab::profile || m_track_tab == track_tab::speed || m_track_tab == track_tab::joints || m_track_tab == track_tab::infra; }
bool track_shortcut(int const Key);
bool track_busy() const; // something going on in the mode, which Esc drops before it leaves the mode
@@ -1769,28 +1770,10 @@ class editor_mode : public application_mode, private editor_track::observer, pri
bool parallel_preview_visible() const;
void draw_parallel_preview() const;
// scenery templates standing by the track, placed and checked by the editor
struct template_item
{
std::string file;
glm::dvec3 location{0.0}; // world space
double yaw{0.0};
std::vector<std::string> rest; // parameters after the rotation
bool described{false};
std::string track;
glm::dvec2 tilt{0.0}; // rot.x and rot.z of a described template, degrees
std::map<std::string, std::string> values;
};
struct standing_template
{
scene::instance_handle instance{0};
std::string file; // lower case, forward slashes
glm::dvec3 location{0.0};
double yaw{0.0};
std::vector<std::string> values;
};
using template_item = ::template_item;
using standing_template = ::standing_template;
// includes of the templates whose file names the test accepts, with the location in p2..p4 and the rotation in p5
std::vector<standing_template> standing_templates(std::function<bool(std::string const &)> const &Accept) const;
std::vector<standing_template> standing_templates(std::function<bool(std::string const &)> const &Accept) const override;
struct switch_drive
{
standing_template standing;
@@ -1804,116 +1787,11 @@ class editor_mode : public application_mode, private editor_track::observer, pri
void switch_drive_captured(TTrack const &Track);
void switch_drives_committed(std::vector<TTrack *> const &Tracks);
// places them in the active layer, as one step of the undo. returns: how many were placed
std::size_t place_templates(std::vector<template_item> const &Items, std::string &Error);
std::size_t place_templates(std::vector<template_item> const &Items, std::string &Error) override;
// moves them, as one step of the undo
std::size_t move_templates(std::vector<std::pair<standing_template, template_item>> const &Moves);
std::size_t move_templates(std::vector<std::pair<standing_template, template_item>> const &Moves) override;
// takes them out, as one step of the undo
std::size_t remove_templates(std::vector<scene::instance_handle> const &Instances);
// hectometre posts along the line, at every 100 m of the kilometrage
struct hekto_post
{
int hectometres{0};
double chainage{0.0};
glm::dvec3 position{0.0};
double yaw{0.0};
int standing{-1}; // post already there with this number
};
struct hekto_found
{
standing_template post;
int hectometres{0};
double chainage{0.0}; // where it stands along the line
double expected{0.0}; // where its number belongs
double error{0.0}; // m along the line
double distance{0.0}; // m from where it belongs, in the plan
bool wrong{false};
bool duplicate{false}; // another post with the same number stands nearer to its place
bool outside{false}; // its number belongs past the ends of the line
};
struct hekto_tool
{
bool open{false};
bool expand{false};
TTrack *track{nullptr}; // the line runs through it
glm::dvec3 point{0.0}; // where the kilometrage is given
double km{0.0};
bool km_read{false}; // the kilometrage was read from the posts already standing
int growth{1}; // 1: grows along the line, -1: against
int side{1}; // 1: to the right of the growing kilometrage, -1: to the left, 0: alternately
double offset{3.0}; // m from the axis
int style{0}; // nowy, stary, zgnity, mixed
int turn{0}; // degrees added to the rotation
double reach{10000.0}; // m each way
double tolerance{5.0}; // m along the line, for the posts standing
bool dirty{true};
std::size_t history{0};
editor_track::route route;
std::vector<editor_track::route_sample> samples;
double origin{0.0}; // chainage of the point
std::vector<hekto_post> posts;
std::vector<hekto_found> found;
std::string status;
std::string error;
};
hekto_tool m_hekto;
void hekto_start(TTrack &Track, glm::dvec3 const &Point);
void hekto_update();
void hekto_read_kilometrage();
void hekto_place();
void hekto_fix();
void hekto_remove_duplicates();
void render_hekto_ui();
void draw_hekto_overlay() const;
// fouling point markers (W17) where the tracks meeting at a switch are the spacing apart
struct fouling_point
{
TTrack *track{nullptr}; // the switch
glm::dvec3 position{0.0};
double yaw{0.0};
int standing{-1}; // marker already there
};
struct fouling_found
{
standing_template marker;
int point{-1}; // the fouling point it belongs to
double error{0.0}; // m from where it belongs
double height_error{0.0}; // m over where it belongs
bool wrong{false};
};
struct fouling_tool
{
bool open{false};
bool expand{false};
int scope{1}; // 0: the selected switch, 1: around the camera, 2: the whole scenery
double reach{1500.0};
double spacing{3.5}; // m between the axes
double tolerance{2.0}; // m, a marker farther than this from its point is wrong
int height_mode{1}; // 0: on the ground, 1: at the height given over the rail heads
double height{0.0}; // m over the rail heads
double height_tolerance{0.03}; // m, a marker standing higher or lower than this is wrong
int model{0}; // w17, w17_new, w17_old, w17_dwuteo
int turn{0};
bool dirty{true};
std::size_t history{0};
TTrack *selected{nullptr};
std::vector<fouling_point> points;
std::vector<fouling_found> found;
std::string status;
std::string error;
};
fouling_tool m_fouling;
void fouling_update();
// the markers put at the fouling points without one, or at the one only
void fouling_place(int const Only = -1);
// the markers standing wrong moved to their places, or the one found only
void fouling_fix(int const Only = -1);
// the fouling point without a marker or the marker standing wrong under the cursor: the index of the point or of the
// marker found, and whether it's a marker
std::optional<std::pair<int, bool>> fouling_hit() const;
void render_fouling_ui();
void draw_fouling_overlay() const;
std::size_t remove_templates(std::vector<scene::instance_handle> const &Instances) override;
struct signal_template
{
@@ -2393,7 +2271,9 @@ class editor_mode : public application_mode, private editor_track::observer, pri
speed_tool m_speed{*this};
joints_tool m_joints{*this};
vehicle_tool m_vehicle{*this};
std::vector<editor_tool *> m_tools{&m_gauge, &m_speed, &m_joints, &m_vehicle};
hekto_tool m_hekto{*this};
fouling_tool m_fouling{*this};
std::vector<editor_tool *> m_tools{&m_gauge, &m_speed, &m_joints, &m_vehicle, &m_hekto, &m_fouling};
void infra_rebase();
void render_infra_search();

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "application/editormode.h"
#include "application/editormodelineside.h"
#include "application/editormodeshared.h"
#include "application/editoruilayer.h"
#include "application/editorprojection.h"
@@ -365,18 +366,18 @@ std::size_t editor_mode::remove_templates(std::vector<scene::instance_handle> co
return count;
}
void editor_mode::hekto_start(TTrack &Track, glm::dvec3 const &Point)
void hekto_tool::start(TTrack &Track, glm::dvec3 const &Point)
{
auto &tool{m_hekto};
tool.track = &Track;
tool.point = Point;
tool.km_read = false;
tool.dirty = true;
tool.expand = true;
m_tab.expand = true;
tool.status.clear();
}
void editor_mode::hekto_read_kilometrage()
void hekto_tool::hekto_read_kilometrage()
{
auto &tool{m_hekto};
auto const &found{tool.found};
@@ -417,13 +418,13 @@ void editor_mode::hekto_read_kilometrage()
tool.status = format(STR_C("Kilometrage read from %zu posts standing along the line"), found.size());
}
void editor_mode::hekto_update()
void hekto_tool::hekto_update()
{
auto &tool{m_hekto};
if (false == tool.dirty && tool.history == m_history.size())
if (false == tool.dirty && tool.history == m_services.history_size())
return;
tool.dirty = false;
tool.history = m_history.size();
tool.history = m_services.history_size();
tool.posts.clear();
tool.found.clear();
tool.error.clear();
@@ -448,7 +449,7 @@ void editor_mode::hekto_update()
double distance;
tool.origin = project(tool.samples, tool.point, distance);
auto const length{tool.samples.back().chainage};
for (auto const &standing : standing_templates([](std::string const &File) { return File.find("hekto") != std::string::npos; }))
for (auto const &standing : m_services.standing_templates([](std::string const &File) { return File.find("hekto") != std::string::npos; }))
{
hekto_found post;
post.post = standing;
@@ -487,7 +488,7 @@ void editor_mode::hekto_update()
// alternately: the even hectometres on the right, the odd ones on the left of the growing kilometrage
auto const side{tool.side != 0 ? tool.side : (hectometres % 2 == 0 ? 1 : -1)};
auto const planar{plan_of(axis) + left * (side * tool.offset)};
post.position = placement_on_ground({planar.x, axis.y, planar.y});
post.position = m_services.placement_on_ground({planar.x, axis.y, planar.y});
post.yaw = std::fmod(heading_of(growing) + tool.turn, 360.0);
// of the posts with this number the one nearest to the place stands for it, the others are duplicates. it stands wrong
// when it's farther from the place than allowed: along the line, across it, or on the other side
@@ -517,7 +518,7 @@ void editor_mode::hekto_update()
}
}
void editor_mode::hekto_remove_duplicates()
void hekto_tool::hekto_remove_duplicates()
{
auto &tool{m_hekto};
hekto_update();
@@ -525,13 +526,13 @@ void editor_mode::hekto_remove_duplicates()
for (auto const &found : tool.found)
if (found.duplicate)
instances.push_back(found.post.instance);
auto const removed{remove_templates(instances)};
auto const removed{m_services.remove_templates(instances)};
tool.status = format(STR_P("%zu duplicate hectometre post removed, Ctrl+Z brings it back", "%zu duplicate hectometre posts removed, Ctrl+Z brings them back", static_cast<long long>(removed)), removed);
WriteLog("Editor: " + tool.status);
tool.dirty = true;
}
void editor_mode::hekto_place()
void hekto_tool::hekto_place()
{
auto &tool{m_hekto};
hekto_update();
@@ -548,15 +549,15 @@ void editor_mode::hekto_place()
items.push_back(std::move(item));
}
std::string error;
auto const placed{place_templates(items, error)};
auto const placed{m_services.place_templates(items, error)};
tool.status = placed > 0 ? format(STR_P("%zu hectometre post placed, Ctrl+Z takes it back", "%zu hectometre posts placed, Ctrl+Z takes them back", static_cast<long long>(placed)), placed) : error;
if (placed > 0)
first_step_done(first_step::posts);
m_services.first_step_done(editor_first_step::posts);
WriteLog("Editor: " + tool.status, logtype::generic);
tool.dirty = true;
}
void editor_mode::hekto_fix()
void hekto_tool::hekto_fix()
{
auto &tool{m_hekto};
hekto_update();
@@ -570,16 +571,16 @@ void editor_mode::hekto_fix()
item.yaw = post.yaw;
moves.emplace_back(tool.found[post.standing].post, item);
}
auto const moved{move_templates(moves)};
auto const moved{m_services.move_templates(moves)};
tool.status = format(STR_P("%zu hectometre post moved to its place, Ctrl+Z takes it back", "%zu hectometre posts moved to their places, Ctrl+Z takes it back", static_cast<long long>(moved)), moved);
WriteLog("Editor: " + tool.status, logtype::generic);
tool.dirty = true;
}
void editor_mode::render_hekto_ui()
void hekto_tool::render_options()
{
auto &tool{m_hekto};
auto *selected{selected_track()};
auto *selected{m_services.selected_track()};
if (tool.track == nullptr || (selected != nullptr && selected != tool.track && ImGui::SmallButton(STR_C("Along the line of the selected path"))))
{
if (selected == nullptr)
@@ -587,7 +588,7 @@ void editor_mode::render_hekto_ui()
ImGui::TextDisabled("%s", STR_C("Select a path of the line, or RMB on it: Hectometre posts"));
return;
}
hekto_start(*selected, bezier{selected->m_paths.front()}.point(0.0));
start(*selected, bezier{selected->m_paths.front()}.point(0.0));
}
auto const changed = [&](bool const Changed) {
if (Changed)
@@ -678,7 +679,7 @@ void editor_mode::render_hekto_ui()
auto const label{post.duplicate ? format(STR_C("km %s duplicate##%u"), km_text(post.hectometres).c_str(), post.post.instance) : format(STR_C("km %s stands %.1f m from its place##%u"), km_text(post.hectometres).c_str(), post.distance, post.post.instance)};
if (ImGui::Selectable(label.c_str()))
{
fly_to(post.post.location, glm::normalize(glm::dvec3{-1.0, -0.8, -1.0}), 20.0);
m_services.fly_to(post.post.location, glm::normalize(glm::dvec3{-1.0, -0.8, -1.0}), 20.0);
}
}
ImGui::EndChild();
@@ -687,10 +688,10 @@ void editor_mode::render_hekto_ui()
ImGui::TextWrapped("%s", tool.status.c_str());
}
void editor_mode::draw_hekto_overlay() const
void hekto_tool::draw_overlay() const
{
auto const &tool{m_hekto};
if (false == tool.open || tool.track == nullptr)
if (false == m_tab.open || tool.track == nullptr)
return;
screen_projection const projection;
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
@@ -731,16 +732,16 @@ void editor_mode::draw_hekto_overlay() const
}
}
void editor_mode::fouling_update()
void fouling_tool::fouling_update()
{
auto &tool{m_fouling};
auto *selected{selected_track()};
auto *selected{m_services.selected_track()};
if (tool.scope == 0 && selected != tool.selected)
tool.dirty = true;
if (false == tool.dirty && tool.history == m_history.size())
if (false == tool.dirty && tool.history == m_services.history_size())
return;
tool.dirty = false;
tool.history = m_history.size();
tool.history = m_services.history_size();
tool.selected = selected;
tool.points.clear();
tool.found.clear();
@@ -791,7 +792,7 @@ void editor_mode::fouling_update()
point.track = track;
auto const middle{(onbranch + onmain) * 0.5};
// the points of the paths are at the rail foot, the heads of the rails are the height of the rail over them
point.position = tool.height_mode == 1 ? glm::dvec3{middle.x, 0.5 * (onbranch.y + onmain.y) + kRailHeight + tool.height, middle.z} : placement_on_ground({middle.x, std::max(onbranch.y, onmain.y), middle.z});
point.position = tool.height_mode == 1 ? glm::dvec3{middle.x, 0.5 * (onbranch.y + onmain.y) + kRailHeight + tool.height, middle.z} : m_services.placement_on_ground({middle.x, std::max(onbranch.y, onmain.y), middle.z});
auto const back{plan_of(previous - branch[i])};
point.yaw = std::fmod(heading_of(glm::length(back) > 1e-9 ? glm::normalize(back) : glm::dvec2{0.0, 1.0}) + tool.turn, 360.0);
tool.points.push_back(point);
@@ -799,7 +800,7 @@ void editor_mode::fouling_update()
}
}
// markers standing: each goes to the nearest fouling point, the others are left alone
auto const markers{standing_templates([](std::string const &File) { return File.find("w17") != std::string::npos || File.find("ukres") != std::string::npos; })};
auto const markers{m_services.standing_templates([](std::string const &File) { return File.find("w17") != std::string::npos || File.find("ukres") != std::string::npos; })};
for (auto const &marker : markers)
{
int nearest{-1};
@@ -837,7 +838,7 @@ void editor_mode::fouling_update()
tool.status = format(STR_C("%zu switches, %zu fouling points: %d without a marker, %d markers standing wrong"), switches.size(), tool.points.size(), static_cast<int>(missing), static_cast<int>(wrong));
}
void editor_mode::fouling_place(int const Only)
void fouling_tool::place(int const Only)
{
auto &tool{m_fouling};
fouling_update();
@@ -856,13 +857,13 @@ void editor_mode::fouling_place(int const Only)
items.push_back(std::move(item));
}
std::string error;
auto const placed{place_templates(items, error)};
auto const placed{m_services.place_templates(items, error)};
tool.status = placed > 0 ? format(STR_P("%zu fouling point marker placed, Ctrl+Z takes it back", "%zu fouling point markers placed, Ctrl+Z takes them back", static_cast<long long>(placed)), placed) : error;
WriteLog("Editor: " + tool.status, logtype::generic);
tool.dirty = true;
}
void editor_mode::fouling_fix(int const Only)
void fouling_tool::fix(int const Only)
{
auto &tool{m_fouling};
fouling_update();
@@ -887,13 +888,13 @@ void editor_mode::fouling_fix(int const Only)
}
moves.emplace_back(found.marker, item);
}
auto const moved{move_templates(moves)};
auto const moved{m_services.move_templates(moves)};
tool.status = format(STR_P("%zu fouling point marker moved to its place, Ctrl+Z takes it back", "%zu fouling point markers moved to their places, Ctrl+Z takes it back", static_cast<long long>(moved)), moved);
WriteLog("Editor: " + tool.status, logtype::generic);
tool.dirty = true;
}
std::optional<std::pair<int, bool>> editor_mode::fouling_hit() const
std::optional<std::pair<int, bool>> fouling_tool::hit() const
{
auto const &tool{m_fouling};
screen_projection const projection;
@@ -921,7 +922,7 @@ std::optional<std::pair<int, bool>> editor_mode::fouling_hit() const
return result;
}
void editor_mode::render_fouling_ui()
void fouling_tool::render_options()
{
auto &tool{m_fouling};
auto const changed = [&](bool const Changed) {
@@ -943,7 +944,7 @@ void editor_mode::render_fouling_ui()
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.55f, 0.25f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.65f, 0.3f, 1.0f));
if (ImGui::Button(format(STR_P("Place %d marker", "Place %d markers", static_cast<long long>(missing)), static_cast<int>(missing)).c_str(), ImVec2(-1.0f, tall)))
fouling_place();
place();
ImGui::PopStyleColor(2);
}
if (wrong > 0)
@@ -951,7 +952,7 @@ void editor_mode::render_fouling_ui()
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.4f, 0.1f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.48f, 0.15f, 1.0f));
if (ImGui::Button(format(STR_P("Move %d wrong marker to its place", "Move %d wrong markers to their places", static_cast<long long>(wrong)), static_cast<int>(wrong)).c_str(), ImVec2(-1.0f, tall)))
fouling_fix();
fix();
ImGui::PopStyleColor(2);
}
if (missing + wrong > 0)
@@ -967,10 +968,10 @@ void editor_mode::render_fouling_ui()
continue;
ImGui::PushID(i);
if (ImGui::SmallButton(STR_C("Place")))
fouling_place(i);
place(i);
ImGui::SameLine();
if (ImGui::Selectable(format(STR_C("%s: no W17"), point.track->name().c_str()).c_str()))
fly_to(point.position, look, 25.0);
m_services.fly_to(point.position, look, 25.0);
ImGui::PopID();
}
for (int i = 0; i < static_cast<int>(tool.found.size()); ++i)
@@ -980,10 +981,10 @@ void editor_mode::render_fouling_ui()
continue;
ImGui::PushID(1000000 + i);
if (ImGui::SmallButton(STR_C("Fix")))
fouling_fix(i);
fix(i);
ImGui::SameLine();
if (ImGui::Selectable(format(STR_C("%s: W17 %.1f m off, %+.2f m high"), tool.points[found.point].track->name().c_str(), found.error, found.height_error).c_str()))
fly_to(found.marker.location, look, 25.0);
m_services.fly_to(found.marker.location, look, 25.0);
ImGui::PopID();
}
ImGui::EndChild();
@@ -1031,10 +1032,10 @@ void editor_mode::render_fouling_ui()
tool.tolerance = std::clamp(tool.tolerance, 0.2, 20.0);
}
void editor_mode::draw_fouling_overlay() const
void fouling_tool::draw_overlay() const
{
auto const &tool{m_fouling};
if (false == tool.open)
if (false == m_tab.open)
return;
screen_projection const projection;
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
@@ -1063,3 +1064,129 @@ void editor_mode::draw_fouling_overlay() const
projection.line(drawlist, found.marker.location, tool.points[found.point].position, IM_COL32(255, 70, 60, 160), 1.5f);
}
}
editor_selftest_result hekto_tool::selftest(std::string const &Command, std::istringstream &Words)
{
if (Command == "hekto")
{
std::map<std::string, std::string> values;
std::string pair;
while (Words >> pair)
{
auto const equals{pair.find('=')};
if (equals != std::string::npos)
values[pair.substr(0, equals)] = pair.substr(equals + 1);
}
auto *track{simulation::Paths.find(values["track"])};
if (track == nullptr)
{
WriteLog("SELFTEST hekto: no such track");
return editor_selftest_result::handled;
}
start(*track, track->m_paths.front().points[segment_data::point::start]);
auto &tool{m_hekto};
tool.dirty = true;
hekto_update();
if (values.count("km"))
{
tool.km = std::stod(values["km"]);
tool.growth = values["growth"] == "-1" ? -1 : 1;
}
tool.side = values["side"] == "alt" ? 0 : values["side"] == "left" ? -1 : 1;
tool.dirty = true;
hekto_update();
auto const missing{std::count_if(tool.posts.begin(), tool.posts.end(), [](hekto_post const &Post) { return Post.standing < 0; })};
WriteLog(format("SELFTEST hekto: km %.3f growth %d, %zu posts along %.0f m, %d to place, %zu standing. %s", tool.km, tool.growth, tool.posts.size(), tool.samples.empty() ? 0.0 : tool.samples.back().chainage, static_cast<int>(missing), tool.found.size(), tool.status.c_str()));
for (std::size_t i = 0; i < tool.posts.size() && i < 4; ++i)
{
auto const &post{tool.posts[i]};
double lateral;
glm::dvec3 const axis{editor_track::sampled_position(tool.samples, post.chainage)};
auto const direction{editor_track::sampled_position(tool.samples, post.chainage + 1.0) - axis};
glm::dvec2 const offset{post.position.x - axis.x, post.position.z - axis.z};
lateral = glm::length(offset);
auto const right{(direction.x * offset.y - direction.z * offset.x) * tool.growth >= 0.0};
WriteLog(format("SELFTEST hekto post %d.%d at %.1f m, %.2f m from the axis, %s of the growing kilometrage, at %.1f %.1f %.1f", post.hectometres / 10, post.hectometres % 10, post.chainage, lateral, right ? "right" : "left", post.position.x, post.position.y,
post.position.z));
}
if (values["place"] == "1")
{
hekto_place();
WriteLog("SELFTEST hekto: " + tool.status);
}
return editor_selftest_result::handled;
}
return editor_selftest_result::unhandled;
}
glm::dvec3 fouling_tool::position_of(std::pair<int, bool> const &Hit) const
{
return Hit.second ? m_fouling.found[Hit.first].marker.location : m_fouling.points[Hit.first].position;
}
void fouling_tool::check_selected_switch()
{
m_fouling.scope = 0;
m_fouling.dirty = true;
m_tab.expand = true;
}
editor_selftest_result fouling_tool::selftest(std::string const &Command, std::istringstream &Words)
{
if (Command == "fouling")
{
std::map<std::string, std::string> values;
std::string pair;
while (Words >> pair)
{
auto const equals{pair.find('=')};
if (equals != std::string::npos)
values[pair.substr(0, equals)] = pair.substr(equals + 1);
}
auto &tool{m_fouling};
if (values["show"] == "1")
{
m_services.show_track_tab(editor_track_tab::lineside);
m_tab.expand = true;
}
tool.scope = std::stoi(values.count("scope") ? values["scope"] : "2");
tool.height_mode = values["mode"] == "ground" ? 0 : 1;
tool.height = std::stod(values.count("height") ? values["height"] : "0");
tool.dirty = true;
fouling_update();
WriteLog("SELFTEST fouling: " + tool.status);
int shown{0};
for (auto const &found : tool.found)
if (found.wrong && shown++ < 5)
WriteLog(format("SELFTEST fouling: %s %.2f m off, %+.3f m high", found.marker.file.c_str(), found.error, found.height_error));
if (values["place"] == "one")
{
// the first fouling point without a marker alone, as a click on it in the view
for (int index = 0; index < static_cast<int>(tool.points.size()); ++index)
if (tool.points[index].standing < 0)
{
place(index);
break;
}
tool.dirty = true;
fouling_update();
WriteLog("SELFTEST fouling one: " + tool.status);
}
if (values["place"] == "1")
{
place();
tool.dirty = true;
fouling_update();
WriteLog("SELFTEST fouling after placing: " + tool.status);
}
if (values["fix"] == "1")
{
fix();
tool.dirty = true;
fouling_update();
WriteLog("SELFTEST fouling after the fix: " + tool.status);
}
return editor_selftest_result::handled;
}
return editor_selftest_result::unhandled;
}

View File

@@ -0,0 +1,142 @@
/*
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/.
*/
#pragma once
#include "application/editortool.h"
#include <optional>
#include <string>
#include <utility>
#include <vector>
// hectometre posts along the line, at every 100 m of the kilometrage
class hekto_tool final : public editor_tool
{
public:
explicit hekto_tool(editor_services &Services) : m_services{Services} {}
void draw_overlay() const override;
void render_options() override;
editor_selftest_result selftest(std::string const &Command, std::istringstream &Words) override;
tool_tab &tab() { return m_tab; }
void start(TTrack &Track, glm::dvec3 const &Point);
private:
editor_services &m_services;
tool_tab m_tab;
struct hekto_post
{
int hectometres{0};
double chainage{0.0};
glm::dvec3 position{0.0};
double yaw{0.0};
int standing{-1}; // post already there with this number
};
struct hekto_found
{
standing_template post;
int hectometres{0};
double chainage{0.0}; // where it stands along the line
double expected{0.0}; // where its number belongs
double error{0.0}; // m along the line
double distance{0.0}; // m from where it belongs, in the plan
bool wrong{false};
bool duplicate{false}; // another post with the same number stands nearer to its place
bool outside{false}; // its number belongs past the ends of the line
};
struct hekto_state
{
TTrack *track{nullptr}; // the line runs through it
glm::dvec3 point{0.0}; // where the kilometrage is given
double km{0.0};
bool km_read{false}; // the kilometrage was read from the posts already standing
int growth{1}; // 1: grows along the line, -1: against
int side{1}; // 1: to the right of the growing kilometrage, -1: to the left, 0: alternately
double offset{3.0}; // m from the axis
int style{0}; // nowy, stary, zgnity, mixed
int turn{0}; // degrees added to the rotation
double reach{10000.0}; // m each way
double tolerance{5.0}; // m along the line, for the posts standing
bool dirty{true};
std::size_t history{0};
editor_track::route route;
std::vector<editor_track::route_sample> samples;
double origin{0.0}; // chainage of the point
std::vector<hekto_post> posts;
std::vector<hekto_found> found;
std::string status;
std::string error;
};
hekto_state m_hekto;
void hekto_update();
void hekto_read_kilometrage();
void hekto_place();
void hekto_fix();
void hekto_remove_duplicates();
};
// fouling point markers (W17) where the tracks meeting at a switch are the spacing apart
class fouling_tool final : public editor_tool
{
public:
explicit fouling_tool(editor_services &Services) : m_services{Services} {}
void draw_overlay() const override;
void render_options() override;
editor_selftest_result selftest(std::string const &Command, std::istringstream &Words) override;
tool_tab &tab() { return m_tab; }
// the markers put at the fouling points without one, or at the one only
void place(int const Only = -1);
// the markers standing wrong moved to their places, or the one found only
void fix(int const Only = -1);
// the fouling point without a marker or the marker standing wrong under the cursor: the index of the point or of the
// marker found, and whether it's a marker
std::optional<std::pair<int, bool>> hit() const;
glm::dvec3 position_of(std::pair<int, bool> const &Hit) const;
void check_selected_switch();
private:
editor_services &m_services;
tool_tab m_tab;
struct fouling_point
{
TTrack *track{nullptr}; // the switch
glm::dvec3 position{0.0};
double yaw{0.0};
int standing{-1}; // marker already there
};
struct fouling_found
{
standing_template marker;
int point{-1}; // the fouling point it belongs to
double error{0.0}; // m from where it belongs
double height_error{0.0}; // m over where it belongs
bool wrong{false};
};
struct fouling_state
{
int scope{1}; // 0: the selected switch, 1: around the camera, 2: the whole scenery
double reach{1500.0};
double spacing{3.5}; // m between the axes
double tolerance{2.0}; // m, a marker farther than this from its point is wrong
int height_mode{1}; // 0: on the ground, 1: at the height given over the rail heads
double height{0.0}; // m over the rail heads
double height_tolerance{0.03}; // m, a marker standing higher or lower than this is wrong
int model{0}; // w17, w17_new, w17_old, w17_dwuteo
int turn{0};
bool dirty{true};
std::size_t history{0};
TTrack *selected{nullptr};
std::vector<fouling_point> points;
std::vector<fouling_found> found;
std::string status;
std::string error;
};
fouling_state m_fouling;
void fouling_update();
};

View File

@@ -126,110 +126,6 @@ editor_mode::selftest_result editor_mode::selftest_sweep(std::string const &comm
WriteLog("SELFTEST drag: " + m_sweep.status);
return selftest_result::handled;
}
if (command == "fouling")
{
std::map<std::string, std::string> values;
std::string pair;
while (words >> pair)
{
auto const equals{pair.find('=')};
if (equals != std::string::npos)
values[pair.substr(0, equals)] = pair.substr(equals + 1);
}
auto &tool{m_fouling};
if (values["show"] == "1")
{
show_track_tab(track_tab::lineside);
tool.expand = true;
}
tool.scope = std::stoi(values.count("scope") ? values["scope"] : "2");
tool.height_mode = values["mode"] == "ground" ? 0 : 1;
tool.height = std::stod(values.count("height") ? values["height"] : "0");
tool.dirty = true;
fouling_update();
WriteLog("SELFTEST fouling: " + tool.status);
int shown{0};
for (auto const &found : tool.found)
if (found.wrong && shown++ < 5)
WriteLog(format("SELFTEST fouling: %s %.2f m off, %+.3f m high", found.marker.file.c_str(), found.error, found.height_error));
if (values["place"] == "one")
{
// the first fouling point without a marker alone, as a click on it in the view
for (int index = 0; index < static_cast<int>(tool.points.size()); ++index)
if (tool.points[index].standing < 0)
{
fouling_place(index);
break;
}
tool.dirty = true;
fouling_update();
WriteLog("SELFTEST fouling one: " + tool.status);
}
if (values["place"] == "1")
{
fouling_place();
tool.dirty = true;
fouling_update();
WriteLog("SELFTEST fouling after placing: " + tool.status);
}
if (values["fix"] == "1")
{
fouling_fix();
tool.dirty = true;
fouling_update();
WriteLog("SELFTEST fouling after the fix: " + tool.status);
}
return selftest_result::handled;
}
if (command == "hekto")
{
std::map<std::string, std::string> values;
std::string pair;
while (words >> pair)
{
auto const equals{pair.find('=')};
if (equals != std::string::npos)
values[pair.substr(0, equals)] = pair.substr(equals + 1);
}
auto *track{simulation::Paths.find(values["track"])};
if (track == nullptr)
{
WriteLog("SELFTEST hekto: no such track");
return selftest_result::handled;
}
hekto_start(*track, track->m_paths.front().points[segment_data::point::start]);
auto &tool{m_hekto};
tool.dirty = true;
hekto_update();
if (values.count("km"))
{
tool.km = std::stod(values["km"]);
tool.growth = values["growth"] == "-1" ? -1 : 1;
}
tool.side = values["side"] == "alt" ? 0 : values["side"] == "left" ? -1 : 1;
tool.dirty = true;
hekto_update();
auto const missing{std::count_if(tool.posts.begin(), tool.posts.end(), [](hekto_post const &Post) { return Post.standing < 0; })};
WriteLog(format("SELFTEST hekto: km %.3f growth %d, %zu posts along %.0f m, %d to place, %zu standing. %s", tool.km, tool.growth, tool.posts.size(), tool.samples.empty() ? 0.0 : tool.samples.back().chainage, static_cast<int>(missing), tool.found.size(), tool.status.c_str()));
for (std::size_t i = 0; i < tool.posts.size() && i < 4; ++i)
{
auto const &post{tool.posts[i]};
double lateral;
glm::dvec3 const axis{editor_track::sampled_position(tool.samples, post.chainage)};
auto const direction{editor_track::sampled_position(tool.samples, post.chainage + 1.0) - axis};
glm::dvec2 const offset{post.position.x - axis.x, post.position.z - axis.z};
lateral = glm::length(offset);
auto const right{(direction.x * offset.y - direction.z * offset.x) * tool.growth >= 0.0};
WriteLog(format("SELFTEST hekto post %d.%d at %.1f m, %.2f m from the axis, %s of the growing kilometrage, at %.1f %.1f %.1f", post.hectometres / 10, post.hectometres % 10, post.chainage, lateral, right ? "right" : "left", post.position.x, post.position.y,
post.position.z));
}
if (values["place"] == "1")
{
hekto_place();
WriteLog("SELFTEST hekto: " + tool.status);
}
return selftest_result::handled;
}
if (command == "spread")
{
std::string name;
@@ -388,7 +284,7 @@ editor_mode::selftest_result editor_mode::selftest_sweep(std::string const &comm
std::string tab;
words >> tab;
show_track_tab(tab == "parallel" ? track_tab::parallel : track_tab::lineside);
(tab == "vehicle" ? m_vehicle.tab().expand : tab == "hekto" ? m_hekto.expand : tab == "fouling" ? m_fouling.expand : m_parallel.expand) = true;
(tab == "vehicle" ? m_vehicle.tab().expand : tab == "hekto" ? m_hekto.tab().expand : tab == "fouling" ? m_fouling.tab().expand : m_parallel.expand) = true;
return selftest_result::handled;
}
if (command == "bendpanel")

View File

@@ -139,8 +139,8 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm
words >> tab;
show_track_tab(track_tab::lineside);
m_sweep.expand = tab == 0;
m_hekto.expand = tab == 1;
m_fouling.expand = tab == 2;
m_hekto.tab().expand = tab == 1;
m_fouling.tab().expand = tab == 2;
m_parallel.expand = tab == 3;
m_vehicle.tab().expand = tab == 4;
return selftest_result::handled;

View File

@@ -556,8 +556,8 @@ void editor_mode::render_track_inspector()
if (false == m_track_window_open || m_track_tab != track_tab::lineside)
{
m_sweep.open = false;
m_hekto.open = false;
m_fouling.open = false;
m_hekto.tab().open = false;
m_fouling.tab().open = false;
m_parallel.open = false;
m_vehicle.tab().open = false;
}

View File

@@ -445,12 +445,12 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
return make(kind::switch_at, "Press and drag: a switch here, along sets its direction, sideways its side");
}
}
if (m_track_tab == track_tab::lineside && m_fouling.open)
if (auto const hit{fouling_hit()}; hit.has_value())
if (m_track_tab == track_tab::lineside && m_fouling.tab().open)
if (auto const hit{m_fouling.hit()}; hit.has_value())
{
intent.index = hit->first;
intent.path = hit->second ? 1 : 0;
intent.position = hit->second ? m_fouling.found[hit->first].marker.location : m_fouling.points[hit->first].position;
intent.position = m_fouling.position_of(*hit);
return make(kind::fouling, hit->second ? "Click: move this W17 to its place" : "Click: put the W17 here");
}
if (m_hover.track != nullptr)
@@ -570,9 +570,9 @@ void editor_mode::track_press(track_intent const &Intent)
case kind::switch_at: start_switch_placement(); return;
case kind::fouling:
if (Intent.path == 1)
fouling_fix(Intent.index);
m_fouling.fix(Intent.index);
else
fouling_place(Intent.index);
m_fouling.place(Intent.index);
return;
case kind::extend_end:
if (false == start_extend(Intent.snap))
@@ -798,14 +798,12 @@ void editor_mode::render_track_context()
if (ImGui::MenuItem(STR_C("Hectometre posts from here")))
{
show_track_tab(track_tab::lineside);
hekto_start(*track, point);
m_hekto.start(*track, point);
}
if (ImGui::MenuItem(STR_C("Fouling point markers"), nullptr, false, track->eType == tt_Switch))
{
show_track_tab(track_tab::lineside);
m_fouling.scope = 0;
m_fouling.dirty = true;
m_fouling.expand = true;
m_fouling.check_selected_switch();
}
if (ImGui::MenuItem(STR_C("Vertical profile of the line"), "P"))
{
@@ -1477,8 +1475,8 @@ void editor_mode::render_lineside_ui()
std::function<void()> render;
};
std::array<tab, 3> const tabs{{
{"Hectometre posts", &m_hekto.open, &m_hekto.expand, [this]() { render_hekto_ui(); }},
{"Fouling points", &m_fouling.open, &m_fouling.expand, [this]() { render_fouling_ui(); }},
{"Hectometre posts", &m_hekto.tab().open, &m_hekto.tab().expand, [this]() { m_hekto.render_options(); }},
{"Fouling points", &m_fouling.tab().open, &m_fouling.tab().expand, [this]() { m_fouling.render_options(); }},
{"Trainset", &m_vehicle.tab().open, &m_vehicle.tab().expand, [this]() { m_vehicle.render_options(); }},
}};
if (selected_track() == nullptr)

View File

@@ -10,6 +10,9 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
#include <sstream>
#include <string>
#include <utility>
@@ -18,13 +21,10 @@ http://mozilla.org/MPL/2.0/.
#include <glm/glm.hpp>
#include "editor/editorTrack.hpp"
#include "utilities/Classes.h"
class TTrack;
class editor_nmt;
namespace scene
{
class basic_node;
}
enum class editor_selftest_result
{
@@ -36,6 +36,8 @@ enum class editor_selftest_result
enum class editor_first_step { tracks, lay, laid, wheel, join, turnout, undo, save, drive, look, top, select, shape, profile, profile_point, profile_apply, terrain, nmt, plan, plan_band, plan_apply, signal, posts, count };
enum class editor_track_tab { straights, route, path, turnout, profile, speed, joints, infra, lay, lineside, turntable, signals, parallel };
struct track_hover
{
TTrack *track{nullptr};
@@ -50,6 +52,27 @@ struct tool_tab
bool expand{false};
};
// scenery templates standing by the track, placed and checked by the editor
struct template_item
{
std::string file;
glm::dvec3 location{0.0}; // world space
double yaw{0.0};
std::vector<std::string> rest; // parameters after the rotation
bool described{false};
std::string track;
glm::dvec2 tilt{0.0}; // rot.x and rot.z of a described template, degrees
std::map<std::string, std::string> values;
};
struct standing_template
{
scene::instance_handle instance{0};
std::string file; // lower case, forward slashes
glm::dvec3 location{0.0};
double yaw{0.0};
std::vector<std::string> values;
};
struct nmt_place
{
glm::dvec3 position{0.0}; // on the axis of the path
@@ -70,6 +93,8 @@ class editor_services
virtual glm::dvec3 camera_position() const = 0;
virtual track_hover const &hovered_track() const = 0;
virtual void first_step_done(editor_first_step const Step) = 0;
virtual void show_track_tab(editor_track_tab const Wanted) = 0;
virtual glm::dvec3 placement_on_ground(glm::dvec3 Location) = 0;
virtual void fly_to(glm::dvec3 const &Target, glm::dvec3 const &Look, double const Distance, double const Duration = 0.6) = 0;
virtual void focus_track(TTrack &Track, int const Path, glm::dvec3 const &Point) = 0;
virtual void set_current_node(scene::basic_node *Node) = 0;
@@ -80,6 +105,10 @@ class editor_services
virtual editor_nmt &terrain_model() = 0;
virtual double terrain_model_offset() const = 0;
virtual bool terrain_model_fitted() const = 0;
virtual std::vector<standing_template> standing_templates(std::function<bool(std::string const &)> const &Accept) const = 0;
virtual std::size_t place_templates(std::vector<template_item> const &Items, std::string &Error) = 0;
virtual std::size_t move_templates(std::vector<std::pair<standing_template, template_item>> const &Moves) = 0;
virtual std::size_t remove_templates(std::vector<scene::instance_handle> const &Instances) = 0;
virtual void push_track_snapshot(std::vector<std::pair<TTrack *, editor_track::state>> States, std::vector<TTrack *> Created = {}, std::vector<TTrack *> Removed = {}) = 0;
protected: