17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-24 19:11:52 +02:00

feat(editor): Signals mode of the Tracks window (H): semaphores, block, warning, shunting, stop, substitute signals and repeaters placed by the track from the templates of the scenery, described by //$e signal: (kind, mount, lamps, lean, the event the train reads them by) which tools/editor/signal_headers.py writes into them; a click by a track puts the signal on that side 2.25 m from the axis at the rail heads, facing the trains which have it on their right, a drag along turns it to the trains going the way of the drag; its (p1)_sem_info goes to event1 or event2 of the path, warning signals and repeaters, which the train doesn't read, get none; a head leaning away from the track swapped for its counterpart; the plate named like the signal; signals dragged along the track with their event, removed with it, renamed in the node properties with it, each a single step of the undo history; Polish translation

This commit is contained in:
maj00r
2026-10-08 03:12:16 +02:00
parent 4a7d839e5d
commit b02fe0ac2b
14 changed files with 1828 additions and 13 deletions

View File

@@ -1355,6 +1355,12 @@ bool editor_mode::update()
point_drag_update();
if (m_track_tab == track_tab::turntable)
draw_turntable_overlay();
if (m_track_tab == track_tab::signals)
{
signal_refresh();
signal_move_update();
draw_signal_overlay();
}
if (m_track_tab == track_tab::lineside)
{
draw_parallel_preview();
@@ -2423,6 +2429,17 @@ void editor_mode::apply_include()
auto const directive = editor_includes::compose_directive(m_include.target, m_include.values);
if (directive == before)
return;
std::string read_before, read_after;
if (false == m_include.info.signal_read.empty())
{
std::string file;
std::vector<std::string> previous;
if (editor_includes::parse_directive(before, file, previous))
{
read_before = editor_includes::substitute(m_include.info.signal_read, previous);
read_after = editor_includes::substitute(m_include.info.signal_read, m_include.values);
}
}
if (false == m_include_gesture)
{
// a single undo step for the whole drag
@@ -2430,10 +2447,14 @@ void editor_mode::apply_include()
snap.instance = m_instance;
snap.node_name = *scene::Layers.instance(m_instance).file;
snap.serialized = std::move(before);
if (read_before != read_after)
signal_renamed(read_before, read_after, snap);
m_history.push_back(std::move(snap));
g_redo.clear();
m_include_gesture = true;
}
else if (read_before != read_after && false == m_history.empty())
signal_renamed(read_before, read_after, m_history.back());
set_include_directive(m_instance, directive);
}
@@ -4209,6 +4230,10 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
{
break;
}
if (is_press(Action) && m_instance != 0 && signal_delete(m_instance))
{
break;
}
if (is_press(Action) && m_instance != 0)
{
// include of a scenery template. what it shows goes out of sight, the directive is erased on save
@@ -4534,6 +4559,8 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
finish_switch_placement();
if (m_turntable.placing)
turntable_finish_placement();
if (m_signal.placing || m_signal.moving)
signal_release();
}
m_dragging = false;

View File

@@ -29,6 +29,7 @@ http://mozilla.org/MPL/2.0/.
#include "world/Sweep.h"
#include <array>
#include <map>
#include <chrono>
#include <functional>
#include <memory>
@@ -814,7 +815,7 @@ class editor_mode : public application_mode, private editor_track::observer
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 };
enum class track_tab { straights, route, path, turnout, profile, speed, joints, infra, lay, lineside, turntable, signals };
struct track_mode
{
char const *label;
@@ -822,7 +823,7 @@ class editor_mode : public application_mode, private editor_track::observer
track_tab tab;
char const *tooltip;
};
static std::array<track_mode, 11> const &track_modes();
static std::array<track_mode, 12> const &track_modes();
// the objects along the track: a tab for each tool
int m_lineside_tab{0};
void render_lineside_ui();
@@ -878,7 +879,7 @@ class editor_mode : public application_mode, private editor_track::observer
void draw_track_hover() const;
struct track_intent
{
enum class kind { none, gizmo, select, point, box, straight_set, straight_break, detour, straight_tool, straight_handle, route_vertex, route_grip, switch_at, lay, lay_end, sweep, turntable_place, turntable_exit, turntable_fit, spread, extend_end };
enum class kind { none, gizmo, select, point, box, straight_set, straight_break, detour, straight_tool, straight_handle, route_vertex, route_grip, switch_at, lay, lay_end, sweep, turntable_place, turntable_exit, turntable_fit, spread, extend_end, signal_place, signal_move };
kind what{kind::none};
TTrack *track{nullptr};
int path{0};
@@ -1043,6 +1044,7 @@ class editor_mode : public application_mode, private editor_track::observer
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
{
@@ -1192,6 +1194,93 @@ class editor_mode : public application_mode, private editor_track::observer
void render_vehicle_ui();
void draw_vehicle_marker() const;
struct signal_template
{
std::string file;
std::string name;
std::string description;
std::string kind;
std::string mount;
std::string lamps;
std::string lean;
std::string read;
int parameters{0};
bool plate{false};
bool linked{false};
};
struct signal_spot
{
TTrack *track{nullptr};
int path{0};
glm::dvec3 axis{0.0};
glm::dvec2 travel{0.0, 1.0};
int side{1};
int list{2};
double offset{0.0};
glm::dvec3 position{0.0};
double yaw{0.0};
};
struct signal_standing
{
standing_template include;
int kind{-1};
std::string name;
std::string read;
TTrack *track{nullptr};
int list{0};
signal_spot spot;
bool on_track{false};
};
struct signal_tool
{
std::vector<signal_template> templates;
int revision{-1};
int kind{0};
std::array<int, 7> chosen{-1, -1, -1, -1, -1, -1, -1};
char filter[64]{};
char name[64]{"A"};
char plate[64]{};
char linked[64]{"none"};
float height{0.0f};
bool placing{false};
signal_spot press;
glm::vec2 pressed{0.f};
int facing{0};
std::vector<signal_standing> standing;
std::size_t standing_history{static_cast<std::size_t>(-1)};
std::size_t standing_redo{static_cast<std::size_t>(-1)};
double standing_time{-1.0};
int hover{-1};
scene::instance_handle selected{0};
bool moving{false};
signal_spot moved;
bool moved_valid{false};
std::string status;
};
signal_tool m_signal;
void signal_scan();
signal_template const *signal_armed() const;
signal_template const *signal_template_of(std::string const &File) const;
bool signal_spot_at(TTrack &Track, int const Path, glm::dvec3 const &Point, glm::dvec3 const &Cursor, int const Facing, int const Side, signal_template const &Template, signal_spot &Spot) const;
void signal_refresh(bool const Force = false);
int signal_hit() const;
void signal_press(track_intent const &Intent);
void signal_release();
bool signal_place(signal_spot const &Spot, signal_template const &Template);
void signal_move_update();
void signal_move_finish();
bool signal_delete(scene::instance_handle const Instance);
void signal_detach_events(std::string const &Read, std::vector<std::pair<TTrack *, editor_track::state>> &States);
void signal_renamed(std::string const &Before, std::string const &After, EditorSnapshot &Snapshot);
void signal_select(scene::instance_handle const Instance);
std::string signal_next_name(std::string const &Name) const;
void render_signal_ui();
void render_signal_palette();
void render_signal_selected();
void draw_signal_overlay() const;
std::string signal_intent_label(signal_template const &Template, signal_spot const &Spot) const;
std::string signal_variant(signal_template const &Template, signal_spot const &Spot) const;
// a model laid along the track: bent to follow it, or repeated along it
struct sweep_tool
{

View File

@@ -288,7 +288,7 @@ std::size_t editor_mode::place_templates(std::vector<template_item> const &Items
include_info info;
if (false == editor_includes::load(item.file, info, Error))
continue;
directive = editor_includes::directive(item.file, info, editor_includes::parameter_count(item.file), local, static_cast<float>(item.yaw), item.track, item.tilt);
directive = editor_includes::directive(item.file, info, editor_includes::parameter_count(item.file), local, static_cast<float>(item.yaw), item.track, item.tilt, item.values);
}
else
{

View File

@@ -918,6 +918,152 @@ void editor_mode::selftest_step()
std::getline(words, text);
WriteLog("SELFTEST" + text);
}
else if (command == "signal" || command == "signalmove" || command == "signaldelete" || command == "signalrename" || command == "signalprobe")
{
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);
}
show_track_tab(track_tab::signals);
signal_refresh(true);
auto &tool{m_signal};
auto const standing_named = [&](std::string const &Name) -> signal_standing const * {
auto const found{std::find_if(tool.standing.begin(), tool.standing.end(), [&](signal_standing const &Standing) { return Standing.name == Name; })};
return found != tool.standing.end() ? &*found : nullptr;
};
auto const spot_on = [&](signal_template const &Template, int const Facing, int const Side, signal_spot &Spot) {
auto *track{simulation::Paths.find(values["track"])};
if (track == nullptr)
return false;
auto const path{values.count("path") ? std::stoi(values["path"]) : 0};
auto const at{values.count("at") ? std::stod(values["at"]) : 0.5};
geometry::bezier const curve{track->m_paths[std::clamp(path, 0, static_cast<int>(track->m_paths.size()) - 1)]};
return signal_spot_at(*track, path, curve.point(at), curve.point(at), Facing, Side, Template, Spot);
};
if (command == "signal")
{
auto const *item{signal_template_of(values["file"])};
if (item == nullptr)
{
WriteLog("SELFTEST signal: no signal template " + values["file"]);
continue;
}
for (int k = 0; k < static_cast<int>(tool.templates.size()); ++k)
if (&tool.templates[k] == item)
{
static char const *const kinds[] = {"main", "block", "warning", "shunt", "stop", "substitute", "repeater"};
for (int i = 0; i < 7; ++i)
if (item->kind == kinds[i])
{
tool.kind = i;
tool.chosen[i] = k;
}
}
if (values.count("name"))
std::snprintf(tool.name, sizeof(tool.name), "%s", values["name"].c_str());
if (values.count("plate"))
std::snprintf(tool.plate, sizeof(tool.plate), "%s", values["plate"].c_str());
auto const side{values["side"] == "left" ? -1 : 1};
auto const facing{values["facing"] == "along" ? 1 : values["facing"] == "against" ? -1 : 0};
signal_spot spot;
if (false == spot_on(*signal_armed(), facing, side, spot))
{
WriteLog("SELFTEST signal: no such track " + values["track"]);
continue;
}
auto const placed{signal_place(spot, *signal_armed())};
WriteLog(format("SELFTEST signal %s: %s", placed ? "placed" : "failed", tool.status.c_str()));
}
else if (command == "signalmove")
{
auto const *standing{standing_named(values["name"])};
auto const *item{standing != nullptr ? signal_template_of(standing->include.file) : nullptr};
if (item == nullptr)
{
WriteLog("SELFTEST signalmove: no signal " + values["name"]);
continue;
}
auto const yaw{glm::radians(standing->include.yaw + 180.0)};
glm::dvec2 const travel{std::sin(yaw), std::cos(yaw)};
signal_spot probe;
if (false == spot_on(*item, 1, 1, probe))
{
WriteLog("SELFTEST signalmove: no such track " + values["track"]);
continue;
}
auto const facing{glm::dot(probe.travel, travel) >= 0.0 ? 1 : -1};
auto const sideoftravel{glm::dot(geometry::plan_of(standing->include.location) - geometry::plan_of(standing->spot.axis), glm::dvec2{-travel.y, travel.x}) >= 0.0 ? 1 : -1};
signal_select(standing->include.instance);
tool.moving = true;
tool.moved_valid = spot_on(*item, facing, sideoftravel * facing, tool.moved);
tool.pressed = {-1000.f, -1000.f};
signal_release();
WriteLog("SELFTEST signalmove: " + tool.status);
}
else if (command == "signaldelete")
{
auto const *standing{standing_named(values["name"])};
auto const deleted{standing != nullptr && signal_delete(standing->include.instance)};
WriteLog(std::string{"SELFTEST signaldelete "} + values["name"] + (deleted ? ": removed with its event" : ": not removed by the signal tool"));
}
else if (command == "signalrename")
{
auto const *standing{standing_named(values["name"])};
if (standing == nullptr)
{
WriteLog("SELFTEST signalrename: no signal " + values["name"]);
continue;
}
select_include(standing->include.instance);
m_include.values[0] = values["to"];
apply_include();
m_include_gesture = false;
WriteLog("SELFTEST signalrename " + values["name"] + " -> " + values["to"]);
}
else
{
signal_refresh(true);
WriteLog(format("SELFTEST signalprobe: %zu signals", tool.standing.size()));
for (auto const &standing : tool.standing)
{
std::string where{"not read"};
if (standing.track != nullptr)
{
auto const &curve{standing.track->m_paths.front()};
geometry::bezier const bez{curve};
auto best{std::numeric_limits<double>::max()};
double parameter{0.0};
for (int k = 0; k <= 1024; ++k)
{
auto const d{geometry::plan_distance(bez.point(k / 1024.0), standing.include.location)};
if (d < best)
{
best = d;
parameter = k / 1024.0;
}
}
auto const axis{bez.point(parameter)};
auto tangent{bez.first(parameter)};
glm::dvec2 travel{tangent.x, tangent.z};
travel = glm::normalize(travel) * (standing.list == 1 ? -1.0 : 1.0);
glm::dvec2 const offset{standing.include.location.x - axis.x, standing.include.location.z - axis.z};
auto const cross{travel.x * offset.y - travel.y * offset.x};
auto heading{glm::degrees(std::atan2(travel.x, travel.y))};
auto turn{std::fmod(standing.include.yaw - heading + 720.0, 360.0)};
auto const &list{editor_track::events(*standing.track, standing.list)};
auto const bound{std::any_of(list.begin(), list.end(), [&](auto const &Event) { return Event.second != nullptr && ToLower(Event.first) == ToLower(standing.read); })};
where = std::string{bound ? "bound, " : "unbound, "} + format("%s of %s, %.3f m from the axis, %s of the trains, %.3f m over the axis, model turned %.1f deg from the travel", editor_track::event_keyword(standing.list), standing.track->name().c_str(), glm::length(offset), cross > 0.0 ? "right" : "left",
standing.include.location.y - axis.y, turn);
}
WriteLog(format("SELFTEST signalprobe %s %s at %.3f %.3f %.3f yaw %.2f read %s: %s", standing.name.c_str(), standing.include.file.c_str(), standing.include.location.x, standing.include.location.y, standing.include.location.z, standing.include.yaw,
standing.read.empty() ? "-" : standing.read.c_str(), where.c_str()));
}
}
}
else if (command == "quit")
{
if (false == test.quitting)

File diff suppressed because it is too large Load Diff

View File

@@ -561,15 +561,16 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve
profile_restore();
}
std::array<editor_mode::track_mode, 11> const &editor_mode::track_modes()
std::array<editor_mode::track_mode, 12> const &editor_mode::track_modes()
{
static std::array<track_mode, 11> const modes{{
static std::array<track_mode, 12> const modes{{
{"Select", "Esc", track_tab::path, "LMB on a path selects it: its points, control vectors and parameters"},
{"Lay track", "L", track_tab::lay, "Lays new track through the clicked points, on the ground or from a free end of a track"},
{"Switch", "T", track_tab::turnout, "Puts switches into the selected straight or curve, edits the geometry of the selected switch"},
{"Turntable", "U", track_tab::turntable, "Places a turntable, leads tracks out of it and fits the tracks around to it"},
{"Straight", "G", track_tab::straights, "The whole straight through the selected path: drag its ends or the middle, break it, shift it"},
{"Curve", "C", track_tab::route, "The line through the path, switch to switch: vertices with the radii, transitions and cant of the curves"},
{"Signals", "H", track_tab::signals, "Signals by the track, from the templates of the scenery, with the event the train reads them by"},
{"Objects", "B", track_tab::lineside, "Along the track: hectometre posts, fouling point markers, a parallel track, a vehicle to drive"},
{"Profile", "P", track_tab::profile, "Vertical profile (grade line) along the line"},
{"Speed", "V", track_tab::speed, "Speed limits of the paths against the speed their geometry allows"},
@@ -636,6 +637,10 @@ void editor_mode::show_track_tab(track_tab const Tab)
scan_turntables();
turntable_read(track);
break;
case track_tab::signals:
signal_scan();
signal_refresh(true);
break;
default:
break;
}
@@ -676,7 +681,7 @@ bool editor_mode::track_shortcut(int const Key)
bool editor_mode::track_busy() const
{
auto const &straights{m_straights};
return (m_lay.active && false == m_lay.points.empty()) || m_turntable.placing || straights.dragging || m_extend.active || m_point_drag.active || m_handle_drag.active || m_switch.placing || m_track_point.valid() || m_route.vertex >= 0 || m_route.grip >= 0 || false == straights.detour.empty() || straights.tool != 0 || straights.handle >= 0;
return (m_lay.active && false == m_lay.points.empty()) || m_turntable.placing || m_signal.placing || m_signal.moving || straights.dragging || m_extend.active || m_point_drag.active || m_handle_drag.active || m_switch.placing || m_track_point.valid() || m_route.vertex >= 0 || m_route.grip >= 0 || false == straights.detour.empty() || straights.tool != 0 || straights.handle >= 0;
}
std::string editor_mode::track_mode_name() const
@@ -707,6 +712,12 @@ std::string editor_mode::track_mode_name() const
case track_tab::lineside:
return STR_C("Objects along the track");
case track_tab::infra: return STR_C("Infrastructure along the track");
case track_tab::signals:
if (m_signal.moving)
return STR_C("Signal: drag along the track, release to put it there");
if (m_signal.placing)
return STR_C("Signal: drag along the track sets the direction of the trains, release to place it");
return signal_armed() != nullptr ? STR_C("Signal: LMB by the track places it on that side") : STR_C("Signal: choose its type below");
case track_tab::turntable:
if (m_turntable.placing)
return STR_C("Turntable: drag to turn the bridge, release to place it");
@@ -770,6 +781,7 @@ void editor_mode::render_track_inspector()
case track_tab::speed: render_speed_body(); break;
case track_tab::joints: render_joints_body(); break;
case track_tab::lineside: render_lineside_ui(); break;
case track_tab::signals: render_signal_ui(); break;
case track_tab::infra: render_infra_body(); break;
case track_tab::turntable: render_turntable_ui(); break;
default:
@@ -815,8 +827,8 @@ void editor_mode::render_track_modes(TTrack *Track)
ImGui::SetTooltip("%s [%s]", STR_C(mode.tooltip), mode.key);
}
};
row("Edit", 0, 6);
row("Analysis", 6, static_cast<int>(modes.size()));
row("Edit", 0, 7);
row("Analysis", 7, static_cast<int>(modes.size()));
ImGui::SameLine();
ImGui::Checkbox(STR_C("Structure gauge"), &m_gauge.open);
if (ImGui::IsItemHovered())
@@ -3007,6 +3019,8 @@ void editor_mode::cancel_track_tools()
}
m_extend.active = false;
m_turntable.placing = false;
m_signal.placing = false;
m_signal.moving = false;
m_handle_drag = {};
if (m_point_drag.active)
{
@@ -3468,6 +3482,14 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
hints = {{"LMB on a row", "show the path"}};
else if (m_track_window_open && m_track_tab == track_tab::lineside)
hints = {{"LMB on a track", "the line the objects go along"}};
else if (m_track_window_open && m_track_tab == track_tab::signals && (m_signal.placing || m_signal.moving))
hints = {{"Drag along", m_signal.moving ? "moves the signal along the track" : "the trains it faces run that way"}, {"Release", "done"}, {"Esc", "cancel"}};
else if (m_track_window_open && m_track_tab == track_tab::signals)
{
hints = {{"LMB by a track", "places the signal on that side, facing the trains which have it on their right"}, {"Press+drag along", "the trains it faces run the way of the drag"}, {"LMB on a signal", "selects it"}, {"Drag a signal", "moves it along the track"}};
if (m_signal.selected != 0)
hints.push_back({"Del", "removes the selected signal with its event"});
}
else if (m_track_window_open && m_track_tab == track_tab::joints)
hints = {{"LMB on a row", "show the place"}, {"Fix", "joins the ends, aligns them"}};
else if (m_track_window_open && m_track_tab == track_tab::infra)

View File

@@ -193,7 +193,7 @@ void editor_mode::draw_track_hover() const
bool editor_mode::track_gesture_on() const
{
return mouseHold || m_track_set_using || m_track_box.active || m_input.mouse.button(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS || m_track_gizmo_using || m_route_gizmo_using || m_extend.active || m_switch.placing || m_straights.dragging || m_straights.tool_mouse ||
m_point_drag.active || m_handle_drag.active || m_sweep.dragging || m_turntable.placing || ImGuizmo::IsUsing();
m_point_drag.active || m_handle_drag.active || m_sweep.dragging || m_turntable.placing || m_signal.placing || m_signal.moving || ImGuizmo::IsUsing();
}
editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
@@ -232,6 +232,40 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
}
if (m_track_tab == track_tab::turntable)
return turntable_intent(ground, shift);
if (m_track_tab == track_tab::signals)
{
if (auto const hit{signal_hit()}; hit >= 0)
{
auto const &standing{m_signal.standing[hit]};
intent.index = hit;
intent.position = standing.include.location;
intent.action = format(STR_C("Click: select the signal %s, drag: move it along the track"), standing.name.c_str());
intent.what = kind::signal_move;
return intent;
}
auto const *armed{signal_armed()};
if (m_hover.track != nullptr && armed != nullptr)
{
signal_spot spot;
if (signal_spot_at(*m_hover.track, m_hover.path, m_hover.point, cursor_level(m_hover.point.y), 0, 0, *armed, spot))
{
intent.track = m_hover.track;
intent.path = m_hover.path;
intent.position = spot.position;
intent.action = signal_intent_label(*armed, spot);
intent.what = kind::signal_place;
return intent;
}
}
if (m_hover.track != nullptr)
{
intent.track = m_hover.track;
intent.path = m_hover.path;
intent.position = m_hover.point;
return make(kind::select, "Choose the type of the signal in the Tracks window first");
}
return intent;
}
if (m_track_tab == track_tab::path)
{
if (shift && control && m_hover.track != nullptr && rail(*m_hover.track))
@@ -419,6 +453,8 @@ void editor_mode::track_press(track_intent const &Intent)
case kind::turntable_place:
case kind::turntable_exit:
case kind::turntable_fit: turntable_press(Intent); return;
case kind::signal_place:
case kind::signal_move: signal_press(Intent); return;
case kind::select:
case kind::none:
{